HostAuth.java revision 724c3a81cd3649b48ab47c6e49cb42f73f20c815
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 com.android.emailcommon.provider.EmailContent.HostAuthColumns;
21import com.android.emailcommon.utility.SSLUtils;
22import com.android.emailcommon.utility.Utility;
23
24import android.content.ContentValues;
25import android.content.Context;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.text.TextUtils;
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    @SuppressWarnings("hiding")
38    public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
39    // TODO the three following constants duplicate constants in Store.java; remove those and
40    //      just reference these.
41    public static final String SCHEME_IMAP = "imap";
42    public static final String SCHEME_POP3 = "pop3";
43    public static final String SCHEME_EAS = "eas";
44    public static final String SCHEME_SMTP = "smtp";
45    public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts";
46
47    public static final int PORT_UNKNOWN = -1;
48
49    public static final int FLAG_NONE         = 0x00;    // No flags
50    public static final int FLAG_SSL          = 0x01;    // Use SSL
51    public static final int FLAG_TLS          = 0x02;    // Use TLS
52    public static final int FLAG_AUTHENTICATE = 0x04;    // Use name/password for authentication
53    public static final int FLAG_TRUST_ALL    = 0x08;    // Trust all certificates
54    // Mask of settings directly configurable by the user
55    public static final int USER_CONFIG_MASK  = 0x0b;
56
57    public String mProtocol;
58    public String mAddress;
59    public int mPort;
60    public int mFlags;
61    public String mLogin;
62    public String mPassword;
63    public String mDomain;
64    public String mClientCertAlias = null;
65
66    public static final int CONTENT_ID_COLUMN = 0;
67    public static final int CONTENT_PROTOCOL_COLUMN = 1;
68    public static final int CONTENT_ADDRESS_COLUMN = 2;
69    public static final int CONTENT_PORT_COLUMN = 3;
70    public static final int CONTENT_FLAGS_COLUMN = 4;
71    public static final int CONTENT_LOGIN_COLUMN = 5;
72    public static final int CONTENT_PASSWORD_COLUMN = 6;
73    public static final int CONTENT_DOMAIN_COLUMN = 7;
74    public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8;
75
76    public static final String[] CONTENT_PROJECTION = new String[] {
77        RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT,
78        HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
79        HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS
80    };
81
82    /**
83     * no public constructor since this is a utility class
84     */
85    public HostAuth() {
86        mBaseUri = CONTENT_URI;
87
88        // other defaults policy)
89        mPort = PORT_UNKNOWN;
90    }
91
92     /**
93     * Restore a HostAuth from the database, given its unique id
94     * @param context
95     * @param id
96     * @return the instantiated HostAuth
97     */
98    public static HostAuth restoreHostAuthWithId(Context context, long id) {
99        return EmailContent.restoreContentWithId(context, HostAuth.class,
100                HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id);
101    }
102
103
104    /**
105     * Returns the scheme for the specified flags.
106     */
107    public static String getSchemeString(String protocol, int flags) {
108        return getSchemeString(protocol, flags, null);
109    }
110
111    /**
112     * Builds a URI scheme name given the parameters for a {@code HostAuth}.
113     * If a {@code clientAlias} is provided, this indicates that a secure connection must be used.
114     */
115    public static String getSchemeString(String protocol, int flags, String clientAlias) {
116        String security = "";
117        switch (flags & USER_CONFIG_MASK) {
118            case FLAG_SSL:
119                security = "+ssl+";
120                break;
121            case FLAG_SSL | FLAG_TRUST_ALL:
122                security = "+ssl+trustallcerts";
123                break;
124            case FLAG_TLS:
125                security = "+tls+";
126                break;
127            case FLAG_TLS | FLAG_TRUST_ALL:
128                security = "+tls+trustallcerts";
129                break;
130        }
131
132        if (!TextUtils.isEmpty(clientAlias)) {
133            if (TextUtils.isEmpty(security)) {
134                throw new IllegalArgumentException(
135                        "Can't specify a certificate alias for a non-secure connection");
136            }
137            if (!security.endsWith("+")) {
138                security += "+";
139            }
140            security += SSLUtils.escapeForSchemeName(clientAlias);
141        }
142
143        return protocol + security;
144    }
145
146    /**
147     * Returns the flags for the specified scheme.
148     */
149    public static int getSchemeFlags(String scheme) {
150        String[] schemeParts = scheme.split("\\+");
151        int flags = HostAuth.FLAG_NONE;
152        if (schemeParts.length >= 2) {
153            String part1 = schemeParts[1];
154            if ("ssl".equals(part1)) {
155                flags |= HostAuth.FLAG_SSL;
156            } else if ("tls".equals(part1)) {
157                flags |= HostAuth.FLAG_TLS;
158            }
159            if (schemeParts.length >= 3) {
160                String part2 = schemeParts[2];
161                if (SCHEME_TRUST_ALL_CERTS.equals(part2)) {
162                    flags |= HostAuth.FLAG_TRUST_ALL;
163                }
164            }
165        }
166        return flags;
167    }
168
169    @Override
170    public void restore(Cursor cursor) {
171        mBaseUri = CONTENT_URI;
172        mId = cursor.getLong(CONTENT_ID_COLUMN);
173        mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN);
174        mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN);
175        mPort = cursor.getInt(CONTENT_PORT_COLUMN);
176        mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
177        mLogin = cursor.getString(CONTENT_LOGIN_COLUMN);
178        mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
179        mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
180        mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN);
181    }
182
183    @Override
184    public ContentValues toContentValues() {
185        ContentValues values = new ContentValues();
186        values.put(HostAuthColumns.PROTOCOL, mProtocol);
187        values.put(HostAuthColumns.ADDRESS, mAddress);
188        values.put(HostAuthColumns.PORT, mPort);
189        values.put(HostAuthColumns.FLAGS, mFlags);
190        values.put(HostAuthColumns.LOGIN, mLogin);
191        values.put(HostAuthColumns.PASSWORD, mPassword);
192        values.put(HostAuthColumns.DOMAIN, mDomain);
193        values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias);
194        values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB
195        return values;
196    }
197
198    /**
199     * For compatibility while converting to provider model, generate a "store URI"
200     * TODO cache this so we don't rebuild every time
201     *
202     * @return a string in the form of a Uri, as used by the other parts of the email app
203     */
204    public String getStoreUri() {
205        String userInfo = null;
206        if ((mFlags & FLAG_AUTHENTICATE) != 0) {
207            String trimUser = (mLogin != null) ? mLogin.trim() : "";
208            String password = (mPassword != null) ? mPassword : "";
209            userInfo = trimUser + ":" + password;
210        }
211        String scheme = getSchemeString(mProtocol, mFlags);
212        String address = (mAddress != null) ? mAddress.trim() : null;
213        String path = (mDomain != null) ? "/" + mDomain : null;
214
215        URI uri;
216        try {
217            uri = new URI(
218                    scheme,
219                    userInfo,
220                    address,
221                    mPort,
222                    path,
223                    null,
224                    null);
225            return uri.toString();
226        } catch (URISyntaxException e) {
227            return null;
228        }
229    }
230
231    /**
232     * Legacy URI parser. Used in one of three different scenarios:
233     *   1. Backup / Restore of account
234     *   2. Parsing template from provider.xml
235     *   3. Forcefully creating URI for test
236     * Example string:
237     *   "eas+ssl+trustallcerts://user:password@server/domain:123"
238     *
239     * Note that the use of client certificate is specified in the URI, a secure connection type
240     * must be used.
241     */
242    public static void setHostAuthFromString(HostAuth auth, String uriString)
243            throws URISyntaxException {
244        URI uri = new URI(uriString);
245        String path = uri.getPath();
246        String domain = null;
247        if (!TextUtils.isEmpty(path)) {
248            // Strip off the leading slash that begins the path.
249            domain = path.substring(1);
250        }
251        auth.mDomain = domain;
252        auth.setLogin(uri.getUserInfo());
253
254        String scheme = uri.getScheme();
255        auth.setConnection(scheme, uri.getHost(), uri.getPort());
256    }
257
258    /**
259     * Sets the user name and password from URI user info string
260     */
261    public void setLogin(String userInfo) {
262        String userName = null;
263        String userPassword = null;
264        if (!TextUtils.isEmpty(userInfo)) {
265            String[] userInfoParts = userInfo.split(":", 2);
266            userName = userInfoParts[0];
267            if (userInfoParts.length > 1) {
268                userPassword = userInfoParts[1];
269            }
270        }
271        setLogin(userName, userPassword);
272    }
273
274    /**
275     * Sets the user name and password
276     */
277    public void setLogin(String userName, String userPassword) {
278        mLogin = userName;
279        mPassword = userPassword;
280
281        if (mLogin == null) {
282            mFlags &= ~FLAG_AUTHENTICATE;
283        } else {
284            mFlags |= FLAG_AUTHENTICATE;
285        }
286    }
287
288    /**
289     * Returns the login information. [0] is the username and [1] is the password. If
290     * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned.
291     */
292    public String[] getLogin() {
293        if ((mFlags & FLAG_AUTHENTICATE) != 0) {
294            String trimUser = (mLogin != null) ? mLogin.trim() : "";
295            String password = (mPassword != null) ? mPassword : "";
296            return new String[] { trimUser, password };
297        }
298        return null;
299    }
300
301    /**
302     * Sets the connection values of the auth structure per the given scheme, host and port.
303     */
304    public void setConnection(String scheme, String host, int port) {
305        String[] schemeParts = scheme.split("\\+");
306        String protocol = schemeParts[0];
307        String clientCertAlias = null;
308        int flags = getSchemeFlags(scheme);
309
310        // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias"
311        if (schemeParts.length > 3) {
312            clientCertAlias = schemeParts[3];
313        } else if (schemeParts.length > 2) {
314            if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) {
315                mClientCertAlias = schemeParts[2];
316            }
317        }
318
319        setConnection(protocol, host, port, flags, clientCertAlias);
320    }
321
322    public void setConnection(String protocol, String address, int port, int flags) {
323        setConnection(protocol, address, port, flags, null);
324    }
325
326    /**
327     * Sets the internal connection parameters based on the specified parameter values.
328     * @param protocol the mail protocol to use (e.g. "eas", "imap").
329     * @param address the address of the server
330     * @param port the port for the connection
331     * @param flags flags indicating the security and type of the connection
332     * @param clientCertAlias an optional alias to use if a client user certificate is to be
333     *     presented during connection establishment. If this is non-empty, it must be the case
334     *     that flags indicates use of a secure connection
335     */
336    public void setConnection(String protocol, String address,
337            int port, int flags, String clientCertAlias) {
338        // Set protocol, security, and additional flags based on uri scheme
339        mProtocol = protocol;
340
341        mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL);
342        mFlags |= (flags & USER_CONFIG_MASK);
343
344        boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0;
345        if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) {
346            throw new IllegalArgumentException("Can't use client alias on non-secure connections");
347        }
348
349        mAddress = address;
350        mPort = port;
351        if (mPort == PORT_UNKNOWN) {
352            boolean useSSL = ((mFlags & FLAG_SSL) != 0);
353            // infer port# from protocol + security
354            // SSL implies a different port - TLS runs in the "regular" port
355            // NOTE: Although the port should be setup in the various setup screens, this
356            // block cannot easily be moved because we get process URIs from other sources
357            // (e.g. for tests, provider templates and account restore) that may or may not
358            // have a port specified.
359            if (SCHEME_POP3.equals(mProtocol)) {
360                mPort = useSSL ? 995 : 110;
361            } else if (SCHEME_IMAP.equals(mProtocol)) {
362                mPort = useSSL ? 993 : 143;
363            } else if (SCHEME_EAS.equals(mProtocol)) {
364                mPort = useSSL ? 443 : 80;
365            } else if (SCHEME_SMTP.equals(mProtocol)) {
366                mPort = useSSL ? 465 : 587;
367            }
368        }
369
370        mClientCertAlias = clientCertAlias;
371    }
372
373    /** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */
374    public boolean isEasConnection() {
375        return SCHEME_EAS.equals(mProtocol);
376    }
377
378    /**
379     * Supports Parcelable
380     */
381    @Override
382    public int describeContents() {
383        return 0;
384    }
385
386    /**
387     * Supports Parcelable
388     */
389    public static final Parcelable.Creator<HostAuth> CREATOR
390            = new Parcelable.Creator<HostAuth>() {
391        @Override
392        public HostAuth createFromParcel(Parcel in) {
393            return new HostAuth(in);
394        }
395
396        @Override
397        public HostAuth[] newArray(int size) {
398            return new HostAuth[size];
399        }
400    };
401
402    /**
403     * Supports Parcelable
404     */
405    @Override
406    public void writeToParcel(Parcel dest, int flags) {
407        // mBaseUri is not parceled
408        dest.writeLong(mId);
409        dest.writeString(mProtocol);
410        dest.writeString(mAddress);
411        dest.writeInt(mPort);
412        dest.writeInt(mFlags);
413        dest.writeString(mLogin);
414        dest.writeString(mPassword);
415        dest.writeString(mDomain);
416        dest.writeString(mClientCertAlias);
417    }
418
419    /**
420     * Supports Parcelable
421     */
422    public HostAuth(Parcel in) {
423        mBaseUri = CONTENT_URI;
424        mId = in.readLong();
425        mProtocol = in.readString();
426        mAddress = in.readString();
427        mPort = in.readInt();
428        mFlags = in.readInt();
429        mLogin = in.readString();
430        mPassword = in.readString();
431        mDomain = in.readString();
432        mClientCertAlias = in.readString();
433    }
434
435    /**
436     * For debugger support only - DO NOT use for code.
437     */
438    @Override
439    public String toString() {
440        return getStoreUri();
441    }
442
443    @Override
444    public boolean equals(Object o) {
445        if (!(o instanceof HostAuth)) {
446            return false;
447        }
448        HostAuth that = (HostAuth)o;
449        return mPort == that.mPort
450                && mFlags == that.mFlags
451                && Utility.areStringsEqual(mProtocol, that.mProtocol)
452                && Utility.areStringsEqual(mAddress, that.mAddress)
453                && Utility.areStringsEqual(mLogin, that.mLogin)
454                && Utility.areStringsEqual(mPassword, that.mPassword)
455                && Utility.areStringsEqual(mDomain, that.mDomain)
456                && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias);
457    }
458}
459