Home | History | Annotate | Download | only in provider
      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 
     18 package com.android.emailcommon.provider;
     19 
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.net.Uri;
     24 import android.os.Parcel;
     25 import android.os.Parcelable;
     26 import android.text.TextUtils;
     27 
     28 import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
     29 import com.android.emailcommon.utility.SSLUtils;
     30 import com.android.emailcommon.utility.Utility;
     31 
     32 import java.net.URI;
     33 import java.net.URISyntaxException;
     34 
     35 public 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      * Sets the user name and password from URI user info string
    200      */
    201     public void setLogin(String userInfo) {
    202         String userName = null;
    203         String userPassword = null;
    204         if (!TextUtils.isEmpty(userInfo)) {
    205             String[] userInfoParts = userInfo.split(":", 2);
    206             userName = userInfoParts[0];
    207             if (userInfoParts.length > 1) {
    208                 userPassword = userInfoParts[1];
    209             }
    210         }
    211         setLogin(userName, userPassword);
    212     }
    213 
    214     /**
    215      * Sets the user name and password
    216      */
    217     public void setLogin(String userName, String userPassword) {
    218         mLogin = userName;
    219         mPassword = userPassword;
    220 
    221         if (mLogin == null) {
    222             mFlags &= ~FLAG_AUTHENTICATE;
    223         } else {
    224             mFlags |= FLAG_AUTHENTICATE;
    225         }
    226     }
    227 
    228     /**
    229      * Returns the login information. [0] is the username and [1] is the password. If
    230      * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned.
    231      */
    232     public String[] getLogin() {
    233         if ((mFlags & FLAG_AUTHENTICATE) != 0) {
    234             String trimUser = (mLogin != null) ? mLogin.trim() : "";
    235             String password = (mPassword != null) ? mPassword : "";
    236             return new String[] { trimUser, password };
    237         }
    238         return null;
    239     }
    240 
    241     public void setConnection(String protocol, String address, int port, int flags) {
    242         setConnection(protocol, address, port, flags, null);
    243     }
    244 
    245     /**
    246      * Sets the internal connection parameters based on the specified parameter values.
    247      * @param protocol the mail protocol to use (e.g. "eas", "imap").
    248      * @param address the address of the server
    249      * @param port the port for the connection
    250      * @param flags flags indicating the security and type of the connection
    251      * @param clientCertAlias an optional alias to use if a client user certificate is to be
    252      *     presented during connection establishment. If this is non-empty, it must be the case
    253      *     that flags indicates use of a secure connection
    254      */
    255     public void setConnection(String protocol, String address,
    256             int port, int flags, String clientCertAlias) {
    257         // Set protocol, security, and additional flags based on uri scheme
    258         mProtocol = protocol;
    259 
    260         mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL);
    261         mFlags |= (flags & USER_CONFIG_MASK);
    262 
    263         boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0;
    264         if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) {
    265             throw new IllegalArgumentException("Can't use client alias on non-secure connections");
    266         }
    267 
    268         mAddress = address;
    269         mPort = port;
    270         if (mPort == PORT_UNKNOWN) {
    271             boolean useSSL = ((mFlags & FLAG_SSL) != 0);
    272             // infer port# from protocol + security
    273             // SSL implies a different port - TLS runs in the "regular" port
    274             // NOTE: Although the port should be setup in the various setup screens, this
    275             // block cannot easily be moved because we get process URIs from other sources
    276             // (e.g. for tests, provider templates and account restore) that may or may not
    277             // have a port specified.
    278             if (SCHEME_POP3.equals(mProtocol)) {
    279                 mPort = useSSL ? 995 : 110;
    280             } else if (SCHEME_IMAP.equals(mProtocol)) {
    281                 mPort = useSSL ? 993 : 143;
    282             } else if (SCHEME_EAS.equals(mProtocol)) {
    283                 mPort = useSSL ? 443 : 80;
    284             } else if (SCHEME_SMTP.equals(mProtocol)) {
    285                 mPort = useSSL ? 465 : 587;
    286             }
    287         }
    288 
    289         mClientCertAlias = clientCertAlias;
    290     }
    291 
    292     /** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */
    293     public boolean isEasConnection() {
    294         return SCHEME_EAS.equals(mProtocol);
    295     }
    296 
    297     /** Convenience method to determine if SSL is used. */
    298     public boolean shouldUseSsl() {
    299         return (mFlags & FLAG_SSL) != 0;
    300     }
    301 
    302     /** Convenience method to determine if all server certs should be used. */
    303     public boolean shouldTrustAllServerCerts() {
    304         return (mFlags & FLAG_TRUST_ALL) != 0;
    305     }
    306 
    307     /**
    308      * Supports Parcelable
    309      */
    310     @Override
    311     public int describeContents() {
    312         return 0;
    313     }
    314 
    315     /**
    316      * Supports Parcelable
    317      */
    318     public static final Parcelable.Creator<HostAuth> CREATOR
    319             = new Parcelable.Creator<HostAuth>() {
    320         @Override
    321         public HostAuth createFromParcel(Parcel in) {
    322             return new HostAuth(in);
    323         }
    324 
    325         @Override
    326         public HostAuth[] newArray(int size) {
    327             return new HostAuth[size];
    328         }
    329     };
    330 
    331     /**
    332      * Supports Parcelable
    333      */
    334     @Override
    335     public void writeToParcel(Parcel dest, int flags) {
    336         // mBaseUri is not parceled
    337         dest.writeLong(mId);
    338         dest.writeString(mProtocol);
    339         dest.writeString(mAddress);
    340         dest.writeInt(mPort);
    341         dest.writeInt(mFlags);
    342         dest.writeString(mLogin);
    343         dest.writeString(mPassword);
    344         dest.writeString(mDomain);
    345         dest.writeString(mClientCertAlias);
    346     }
    347 
    348     /**
    349      * Supports Parcelable
    350      */
    351     public HostAuth(Parcel in) {
    352         mBaseUri = CONTENT_URI;
    353         mId = in.readLong();
    354         mProtocol = in.readString();
    355         mAddress = in.readString();
    356         mPort = in.readInt();
    357         mFlags = in.readInt();
    358         mLogin = in.readString();
    359         mPassword = in.readString();
    360         mDomain = in.readString();
    361         mClientCertAlias = in.readString();
    362     }
    363 
    364     @Override
    365     public boolean equals(Object o) {
    366         if (!(o instanceof HostAuth)) {
    367             return false;
    368         }
    369         HostAuth that = (HostAuth)o;
    370         return mPort == that.mPort
    371                 && mFlags == that.mFlags
    372                 && Utility.areStringsEqual(mProtocol, that.mProtocol)
    373                 && Utility.areStringsEqual(mAddress, that.mAddress)
    374                 && Utility.areStringsEqual(mLogin, that.mLogin)
    375                 && Utility.areStringsEqual(mPassword, that.mPassword)
    376                 && Utility.areStringsEqual(mDomain, that.mDomain)
    377                 && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias);
    378     }
    379 
    380     /**
    381      * The flag, password, and client cert alias are the only items likely to change after a
    382      * HostAuth is created
    383      */
    384     @Override
    385     public int hashCode() {
    386         int hashCode = 29;
    387         if (mPassword != null) {
    388             hashCode += mPassword.hashCode();
    389         }
    390         if (mClientCertAlias != null) {
    391             hashCode += (mClientCertAlias.hashCode() << 8);
    392         }
    393         return (hashCode << 8) + mFlags;
    394     }
    395 
    396     /**
    397      * Legacy URI parser. Used in parsing template from provider.xml
    398      * Example string:
    399      *   "eas+ssl+trustallcerts://user:password@server/domain:123"
    400      *
    401      * Note that the use of client certificate is specified in the URI, a secure connection type
    402      * must be used.
    403      */
    404     public static void setHostAuthFromString(HostAuth auth, String uriString)
    405             throws URISyntaxException {
    406         URI uri = new URI(uriString);
    407         String path = uri.getPath();
    408         String domain = null;
    409         if (!TextUtils.isEmpty(path)) {
    410             // Strip off the leading slash that begins the path.
    411             domain = path.substring(1);
    412         }
    413         auth.mDomain = domain;
    414         auth.setLogin(uri.getUserInfo());
    415 
    416         String scheme = uri.getScheme();
    417         auth.setConnection(scheme, uri.getHost(), uri.getPort());
    418     }
    419 
    420     /**
    421      * Legacy code for setting connection values from a "scheme" (see above)
    422      */
    423     public void setConnection(String scheme, String host, int port) {
    424         String[] schemeParts = scheme.split("\\+");
    425         String protocol = schemeParts[0];
    426         String clientCertAlias = null;
    427         int flags = getSchemeFlags(scheme);
    428 
    429         // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias"
    430         if (schemeParts.length > 3) {
    431             clientCertAlias = schemeParts[3];
    432         } else if (schemeParts.length > 2) {
    433             if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) {
    434                 mClientCertAlias = schemeParts[2];
    435             }
    436         }
    437 
    438         setConnection(protocol, host, port, flags, clientCertAlias);
    439     }
    440 
    441 }
    442