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