HostAuth.java revision e714bb9d153cfe13a7f0932e7d67ea08fa5a1d98
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 && mFlags == that.mFlags 362 && Utility.areStringsEqual(mProtocol, that.mProtocol) 363 && Utility.areStringsEqual(mAddress, that.mAddress) 364 && Utility.areStringsEqual(mLogin, that.mLogin) 365 && Utility.areStringsEqual(mPassword, that.mPassword) 366 && Utility.areStringsEqual(mDomain, that.mDomain) 367 && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias); 368 // We don't care about the server certificate for equals 369 } 370 371 /** 372 * The flag, password, and client cert alias are the only items likely to change after a 373 * HostAuth is created 374 */ 375 @Override 376 public int hashCode() { 377 int hashCode = 29; 378 if (mPassword != null) { 379 hashCode += mPassword.hashCode(); 380 } 381 if (mClientCertAlias != null) { 382 hashCode += (mClientCertAlias.hashCode() << 8); 383 } 384 return (hashCode << 8) + mFlags; 385 } 386 387 /** 388 * Legacy URI parser. Used in parsing template from provider.xml 389 * Example string: 390 * "eas+ssl+trustallcerts://user:password@server/domain:123" 391 * 392 * Note that the use of client certificate is specified in the URI, a secure connection type 393 * must be used. 394 */ 395 public static void setHostAuthFromString(HostAuth auth, String uriString) 396 throws URISyntaxException { 397 URI uri = new URI(uriString); 398 String path = uri.getPath(); 399 String domain = null; 400 if (!TextUtils.isEmpty(path)) { 401 // Strip off the leading slash that begins the path. 402 domain = path.substring(1); 403 } 404 auth.mDomain = domain; 405 auth.setLogin(uri.getUserInfo()); 406 407 String scheme = uri.getScheme(); 408 auth.setConnection(scheme, uri.getHost(), uri.getPort()); 409 } 410 411 /** 412 * Legacy code for setting connection values from a "scheme" (see above) 413 */ 414 public void setConnection(String scheme, String host, int port) { 415 String[] schemeParts = scheme.split("\\+"); 416 String protocol = schemeParts[0]; 417 String clientCertAlias = null; 418 int flags = getSchemeFlags(scheme); 419 420 // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias" 421 if (schemeParts.length > 3) { 422 clientCertAlias = schemeParts[3]; 423 } else if (schemeParts.length > 2) { 424 if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) { 425 mClientCertAlias = schemeParts[2]; 426 } 427 } 428 429 setConnection(protocol, host, port, flags, clientCertAlias); 430 } 431 432} 433