HostAuth.java revision 733a11467cc681d956eef9c5e98695f84743c39b
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.emailcommon.provider;
19
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.net.Uri;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.text.TextUtils;
27
28import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
29import com.android.emailcommon.utility.SSLUtils;
30import com.android.emailcommon.utility.Utility;
31
32import java.net.URI;
33import java.net.URISyntaxException;
34
35public final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
36    public static final String TABLE_NAME = "HostAuth";
37    public static Uri CONTENT_URI;
38
39    public static void initHostAuth() {
40        CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
41    }
42
43    // These legacy constants should be used in code created prior to Email2
44    public static final String LEGACY_SCHEME_IMAP = "imap";
45    public static final String LEGACY_SCHEME_POP3 = "pop3";
46    public static final String LEGACY_SCHEME_EAS = "eas";
47    public static final String LEGACY_SCHEME_SMTP = "smtp";
48
49    public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts";
50
51    public static final int PORT_UNKNOWN = -1;
52
53    public static final int FLAG_NONE         = 0x00;    // No flags
54    public static final int FLAG_SSL          = 0x01;    // Use SSL
55    public static final int FLAG_TLS          = 0x02;    // Use TLS
56    public static final int FLAG_AUTHENTICATE = 0x04;    // Use name/password for authentication
57    public static final int FLAG_TRUST_ALL    = 0x08;    // Trust all certificates
58    // Mask of settings directly configurable by the user
59    public static final int USER_CONFIG_MASK  = 0x0b;
60
61    public String mProtocol;
62    public String mAddress;
63    public int mPort;
64    public int mFlags;
65    public String mLogin;
66    public String mPassword;
67    public String mDomain;
68    public String mClientCertAlias = null;
69    // NOTE: The server certificate is NEVER automatically retrieved from EmailProvider
70    public byte[] mServerCert = null;
71
72    public static final int CONTENT_ID_COLUMN = 0;
73    public static final int CONTENT_PROTOCOL_COLUMN = 1;
74    public static final int CONTENT_ADDRESS_COLUMN = 2;
75    public static final int CONTENT_PORT_COLUMN = 3;
76    public static final int CONTENT_FLAGS_COLUMN = 4;
77    public static final int CONTENT_LOGIN_COLUMN = 5;
78    public static final int CONTENT_PASSWORD_COLUMN = 6;
79    public static final int CONTENT_DOMAIN_COLUMN = 7;
80    public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8;
81
82    public static final String[] CONTENT_PROJECTION = new String[] {
83        RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT,
84        HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
85        HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS
86    };
87
88    /**
89     * no public constructor since this is a utility class
90     */
91    public HostAuth() {
92        mBaseUri = CONTENT_URI;
93
94        // other defaults policy)
95        mPort = PORT_UNKNOWN;
96    }
97
98     /**
99     * Restore a HostAuth from the database, given its unique id
100     * @param context
101     * @param id
102     * @return the instantiated HostAuth
103     */
104    public static HostAuth restoreHostAuthWithId(Context context, long id) {
105        return EmailContent.restoreContentWithId(context, HostAuth.class,
106                HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id);
107    }
108
109
110    /**
111     * Returns the scheme for the specified flags.
112     */
113    public static String getSchemeString(String protocol, int flags) {
114        return getSchemeString(protocol, flags, null);
115    }
116
117    /**
118     * Builds a URI scheme name given the parameters for a {@code HostAuth}.
119     * If a {@code clientAlias} is provided, this indicates that a secure connection must be used.
120     */
121    public static String getSchemeString(String protocol, int flags, String clientAlias) {
122        String security = "";
123        switch (flags & USER_CONFIG_MASK) {
124            case FLAG_SSL:
125                security = "+ssl+";
126                break;
127            case FLAG_SSL | FLAG_TRUST_ALL:
128                security = "+ssl+trustallcerts";
129                break;
130            case FLAG_TLS:
131                security = "+tls+";
132                break;
133            case FLAG_TLS | FLAG_TRUST_ALL:
134                security = "+tls+trustallcerts";
135                break;
136        }
137
138        if (!TextUtils.isEmpty(clientAlias)) {
139            if (TextUtils.isEmpty(security)) {
140                throw new IllegalArgumentException(
141                        "Can't specify a certificate alias for a non-secure connection");
142            }
143            if (!security.endsWith("+")) {
144                security += "+";
145            }
146            security += SSLUtils.escapeForSchemeName(clientAlias);
147        }
148
149        return protocol + security;
150    }
151
152    /**
153     * Returns the flags for the specified scheme.
154     */
155    public static int getSchemeFlags(String scheme) {
156        String[] schemeParts = scheme.split("\\+");
157        int flags = HostAuth.FLAG_NONE;
158        if (schemeParts.length >= 2) {
159            String part1 = schemeParts[1];
160            if ("ssl".equals(part1)) {
161                flags |= HostAuth.FLAG_SSL;
162            } else if ("tls".equals(part1)) {
163                flags |= HostAuth.FLAG_TLS;
164            }
165            if (schemeParts.length >= 3) {
166                String part2 = schemeParts[2];
167                if (SCHEME_TRUST_ALL_CERTS.equals(part2)) {
168                    flags |= HostAuth.FLAG_TRUST_ALL;
169                }
170            }
171        }
172        return flags;
173    }
174
175    @Override
176    public void restore(Cursor cursor) {
177        mBaseUri = CONTENT_URI;
178        mId = cursor.getLong(CONTENT_ID_COLUMN);
179        mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN);
180        mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN);
181        mPort = cursor.getInt(CONTENT_PORT_COLUMN);
182        mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
183        mLogin = cursor.getString(CONTENT_LOGIN_COLUMN);
184        mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
185        mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
186        mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN);
187    }
188
189    @Override
190    public ContentValues toContentValues() {
191        ContentValues values = new ContentValues();
192        values.put(HostAuthColumns.PROTOCOL, mProtocol);
193        values.put(HostAuthColumns.ADDRESS, mAddress);
194        values.put(HostAuthColumns.PORT, mPort);
195        values.put(HostAuthColumns.FLAGS, mFlags);
196        values.put(HostAuthColumns.LOGIN, mLogin);
197        values.put(HostAuthColumns.PASSWORD, mPassword);
198        values.put(HostAuthColumns.DOMAIN, mDomain);
199        values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias);
200        values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB
201        return values;
202    }
203
204    /**
205     * Sets the user name and password from URI user info string
206     */
207    public void setLogin(String userInfo) {
208        String userName = null;
209        String userPassword = null;
210        if (!TextUtils.isEmpty(userInfo)) {
211            String[] userInfoParts = userInfo.split(":", 2);
212            userName = userInfoParts[0];
213            if (userInfoParts.length > 1) {
214                userPassword = userInfoParts[1];
215            }
216        }
217        setLogin(userName, userPassword);
218    }
219
220    /**
221     * Sets the user name and password
222     */
223    public void setLogin(String userName, String userPassword) {
224        mLogin = userName;
225        mPassword = userPassword;
226
227        if (mLogin == null) {
228            mFlags &= ~FLAG_AUTHENTICATE;
229        } else {
230            mFlags |= FLAG_AUTHENTICATE;
231        }
232    }
233
234    /**
235     * Returns the login information. [0] is the username and [1] is the password. If
236     * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned.
237     */
238    public String[] getLogin() {
239        if ((mFlags & FLAG_AUTHENTICATE) != 0) {
240            String trimUser = (mLogin != null) ? mLogin.trim() : "";
241            String password = (mPassword != null) ? mPassword : "";
242            return new String[] { trimUser, password };
243        }
244        return null;
245    }
246
247    public void setConnection(String protocol, String address, int port, int flags) {
248        setConnection(protocol, address, port, flags, null);
249    }
250
251    /**
252     * Sets the internal connection parameters based on the specified parameter values.
253     * @param protocol the mail protocol to use (e.g. "eas", "imap").
254     * @param address the address of the server
255     * @param port the port for the connection
256     * @param flags flags indicating the security and type of the connection
257     * @param clientCertAlias an optional alias to use if a client user certificate is to be
258     *     presented during connection establishment. If this is non-empty, it must be the case
259     *     that flags indicates use of a secure connection
260     */
261    public void setConnection(String protocol, String address,
262            int port, int flags, String clientCertAlias) {
263        // Set protocol, security, and additional flags based on uri scheme
264        mProtocol = protocol;
265
266        mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL);
267        mFlags |= (flags & USER_CONFIG_MASK);
268
269        boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0;
270        if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) {
271            throw new IllegalArgumentException("Can't use client alias on non-secure connections");
272        }
273
274        mAddress = address;
275        mPort = port;
276        if (mPort == PORT_UNKNOWN) {
277            boolean useSSL = ((mFlags & FLAG_SSL) != 0);
278            if (LEGACY_SCHEME_SMTP.equals(mProtocol)) {
279                mPort = useSSL ? 465 : 587;
280            }
281        }
282
283        mClientCertAlias = clientCertAlias;
284    }
285
286
287    /** Convenience method to determine if SSL is used. */
288    public boolean shouldUseSsl() {
289        return (mFlags & FLAG_SSL) != 0;
290    }
291
292    /** Convenience method to determine if all server certs should be used. */
293    public boolean shouldTrustAllServerCerts() {
294        return (mFlags & FLAG_TRUST_ALL) != 0;
295    }
296
297    /**
298     * Supports Parcelable
299     */
300    @Override
301    public int describeContents() {
302        return 0;
303    }
304
305    /**
306     * Supports Parcelable
307     */
308    public static final Parcelable.Creator<HostAuth> CREATOR
309            = new Parcelable.Creator<HostAuth>() {
310        @Override
311        public HostAuth createFromParcel(Parcel in) {
312            return new HostAuth(in);
313        }
314
315        @Override
316        public HostAuth[] newArray(int size) {
317            return new HostAuth[size];
318        }
319    };
320
321    /**
322     * Supports Parcelable
323     */
324    @Override
325    public void writeToParcel(Parcel dest, int flags) {
326        // mBaseUri is not parceled
327        dest.writeLong(mId);
328        dest.writeString(mProtocol);
329        dest.writeString(mAddress);
330        dest.writeInt(mPort);
331        dest.writeInt(mFlags);
332        dest.writeString(mLogin);
333        dest.writeString(mPassword);
334        dest.writeString(mDomain);
335        dest.writeString(mClientCertAlias);
336    }
337
338    /**
339     * Supports Parcelable
340     */
341    public HostAuth(Parcel in) {
342        mBaseUri = CONTENT_URI;
343        mId = in.readLong();
344        mProtocol = in.readString();
345        mAddress = in.readString();
346        mPort = in.readInt();
347        mFlags = in.readInt();
348        mLogin = in.readString();
349        mPassword = in.readString();
350        mDomain = in.readString();
351        mClientCertAlias = in.readString();
352    }
353
354    @Override
355    public boolean equals(Object o) {
356        if (!(o instanceof HostAuth)) {
357            return false;
358        }
359        HostAuth that = (HostAuth)o;
360        return mPort == that.mPort
361                && mId == that.mId
362                && mFlags == that.mFlags
363                && Utility.areStringsEqual(mProtocol, that.mProtocol)
364                && Utility.areStringsEqual(mAddress, that.mAddress)
365                && Utility.areStringsEqual(mLogin, that.mLogin)
366                && Utility.areStringsEqual(mPassword, that.mPassword)
367                && Utility.areStringsEqual(mDomain, that.mDomain)
368                && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias);
369                // We don't care about the server certificate for equals
370    }
371
372    /**
373     * The flag, password, and client cert alias are the only items likely to change after a
374     * HostAuth is created
375     */
376    @Override
377    public int hashCode() {
378        int hashCode = 29;
379        if (mPassword != null) {
380            hashCode += mPassword.hashCode();
381        }
382        if (mClientCertAlias != null) {
383            hashCode += (mClientCertAlias.hashCode() << 8);
384        }
385        return (hashCode << 8) + mFlags;
386    }
387
388    /**
389     * Legacy URI parser. Used in parsing template from provider.xml
390     * Example string:
391     *   "eas+ssl+trustallcerts://user:password@server/domain:123"
392     *
393     * Note that the use of client certificate is specified in the URI, a secure connection type
394     * must be used.
395     */
396    public static void setHostAuthFromString(HostAuth auth, String uriString)
397            throws URISyntaxException {
398        URI uri = new URI(uriString);
399        String path = uri.getPath();
400        String domain = null;
401        if (!TextUtils.isEmpty(path)) {
402            // Strip off the leading slash that begins the path.
403            domain = path.substring(1);
404        }
405        auth.mDomain = domain;
406        auth.setLogin(uri.getUserInfo());
407
408        String scheme = uri.getScheme();
409        auth.setConnection(scheme, uri.getHost(), uri.getPort());
410    }
411
412    /**
413     * Legacy code for setting connection values from a "scheme" (see above)
414     */
415    public void setConnection(String scheme, String host, int port) {
416        String[] schemeParts = scheme.split("\\+");
417        String protocol = schemeParts[0];
418        String clientCertAlias = null;
419        int flags = getSchemeFlags(scheme);
420
421        // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias"
422        if (schemeParts.length > 3) {
423            clientCertAlias = schemeParts[3];
424        } else if (schemeParts.length > 2) {
425            if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) {
426                mClientCertAlias = schemeParts[2];
427            }
428        }
429
430        setConnection(protocol, host, port, flags, clientCertAlias);
431    }
432
433}
434