replicant-packages_apps_Email/emailcommon/src/com/android/emailcommon/provider/HostAuth.java

570 lines
20 KiB
Java

/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.provider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import com.android.emailcommon.utility.SSLUtils;
import com.android.mail.utils.LogUtils;
import com.google.common.annotations.VisibleForTesting;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URI;
import java.net.URISyntaxException;
public class HostAuth extends EmailContent implements Parcelable {
public static final String TABLE_NAME = "HostAuth";
public static Uri CONTENT_URI;
public static void initHostAuth() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
}
// These legacy constants should be used in code created prior to Email2
public static final String LEGACY_SCHEME_SMTP = "smtp";
public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts";
public static final int PORT_UNKNOWN = -1;
public static final int FLAG_NONE = 0x00; // No flags
public static final int FLAG_SSL = 0x01; // Use SSL
public static final int FLAG_TLS = 0x02; // Use TLS
public static final int FLAG_AUTHENTICATE = 0x04; // Use name/password for authentication
public static final int FLAG_TRUST_ALL = 0x08; // Trust all certificates
public static final int FLAG_OAUTH = 0x10; // Use OAuth for authentication
// Mask of settings directly configurable by the user
public static final int USER_CONFIG_MASK = 0x1b;
public static final int FLAG_TRANSPORTSECURITY_MASK = FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL;
public String mProtocol;
public String mAddress;
public int mPort;
public int mFlags;
public String mLogin;
public String mPassword;
public String mDomain;
public String mClientCertAlias = null;
// NOTE: The server certificate is NEVER automatically retrieved from EmailProvider
public byte[] mServerCert = null;
public long mCredentialKey;
@VisibleForTesting
static final String JSON_TAG_CREDENTIAL = "credential";
public transient Credential mCredential;
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_PROTOCOL_COLUMN = 1;
public static final int CONTENT_ADDRESS_COLUMN = 2;
public static final int CONTENT_PORT_COLUMN = 3;
public static final int CONTENT_FLAGS_COLUMN = 4;
public static final int CONTENT_LOGIN_COLUMN = 5;
public static final int CONTENT_PASSWORD_COLUMN = 6;
public static final int CONTENT_DOMAIN_COLUMN = 7;
public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8;
public static final int CONTENT_CREDENTIAL_KEY_COLUMN = 9;
public static final String[] CONTENT_PROJECTION = new String[] {
HostAuthColumns._ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS,
HostAuthColumns.PORT, HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS,
HostAuthColumns.CREDENTIAL_KEY
};
public HostAuth() {
mBaseUri = CONTENT_URI;
mPort = PORT_UNKNOWN;
mCredentialKey = -1;
}
/**
* Restore a HostAuth from the database, given its unique id
* @param context for provider loads
* @param id corresponds to rowid
* @return the instantiated HostAuth
*/
public static HostAuth restoreHostAuthWithId(Context context, long id) {
return EmailContent.restoreContentWithId(context, HostAuth.class,
HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id);
}
/**
* Returns the credential object for this HostAuth. This will load from the
* database if the HosAuth has a valid credential key, or return null if not.
*/
public Credential getCredential(Context context) {
if (mCredential == null) {
if (mCredentialKey >= 0) {
mCredential = Credential.restoreCredentialsWithId(context, mCredentialKey);
}
}
return mCredential;
}
/**
* getOrCreateCredential Return the credential object for this HostAuth,
* creating it if it does not yet exist. This should not be called on the
* main thread.
*
* As a side-effect, it also ensures FLAG_OAUTH is set. Use {@link #removeCredential()} to clear
*
* @param context for provider loads
* @return the credential object for this HostAuth
*/
public Credential getOrCreateCredential(Context context) {
mFlags |= FLAG_OAUTH;
if (mCredential == null) {
if (mCredentialKey >= 0) {
mCredential = Credential.restoreCredentialsWithId(context, mCredentialKey);
} else {
mCredential = new Credential();
}
}
return mCredential;
}
/**
* Clear the credential object.
*/
public void removeCredential() {
mCredential = null;
mCredentialKey = -1;
mFlags &= ~FLAG_OAUTH;
}
/**
* Builds a URI scheme name given the parameters for a {@code HostAuth}. If
* a {@code clientAlias} is provided, this indicates that a secure
* connection must be used.
*
* This is not used in live code, but is kept here for reference when creating providers.xml
* entries
*/
@SuppressWarnings("unused")
public static String getSchemeString(String protocol, int flags, String clientAlias) {
String security = "";
switch (flags & USER_CONFIG_MASK) {
case FLAG_SSL:
security = "+ssl+";
break;
case FLAG_SSL | FLAG_TRUST_ALL:
security = "+ssl+trustallcerts";
break;
case FLAG_TLS:
security = "+tls+";
break;
case FLAG_TLS | FLAG_TRUST_ALL:
security = "+tls+trustallcerts";
break;
}
if (!TextUtils.isEmpty(clientAlias)) {
if (TextUtils.isEmpty(security)) {
throw new IllegalArgumentException(
"Can't specify a certificate alias for a non-secure connection");
}
if (!security.endsWith("+")) {
security += "+";
}
security += SSLUtils.escapeForSchemeName(clientAlias);
}
return protocol + security;
}
/**
* Returns the flags for the specified scheme.
*/
public static int getSchemeFlags(String scheme) {
String[] schemeParts = scheme.split("\\+");
int flags = HostAuth.FLAG_NONE;
if (schemeParts.length >= 2) {
String part1 = schemeParts[1];
if ("ssl".equals(part1)) {
flags |= HostAuth.FLAG_SSL;
} else if ("tls".equals(part1)) {
flags |= HostAuth.FLAG_TLS;
}
if (schemeParts.length >= 3) {
String part2 = schemeParts[2];
if (SCHEME_TRUST_ALL_CERTS.equals(part2)) {
flags |= HostAuth.FLAG_TRUST_ALL;
}
}
}
return flags;
}
@Override
public void restore(Cursor cursor) {
mBaseUri = CONTENT_URI;
mId = cursor.getLong(CONTENT_ID_COLUMN);
mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN);
mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN);
mPort = cursor.getInt(CONTENT_PORT_COLUMN);
mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
mLogin = cursor.getString(CONTENT_LOGIN_COLUMN);
mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN);
mCredentialKey = cursor.getLong(CONTENT_CREDENTIAL_KEY_COLUMN);
if (mCredentialKey != -1) {
mFlags |= FLAG_OAUTH;
}
}
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(HostAuthColumns.PROTOCOL, mProtocol);
values.put(HostAuthColumns.ADDRESS, mAddress);
values.put(HostAuthColumns.PORT, mPort);
values.put(HostAuthColumns.FLAGS, mFlags);
values.put(HostAuthColumns.LOGIN, mLogin);
values.put(HostAuthColumns.PASSWORD, mPassword);
values.put(HostAuthColumns.DOMAIN, mDomain);
values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias);
values.put(HostAuthColumns.CREDENTIAL_KEY, mCredentialKey);
values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB
return values;
}
protected JSONObject toJson() {
try {
final JSONObject json = new JSONObject();
json.put(HostAuthColumns.PROTOCOL, mProtocol);
json.put(HostAuthColumns.ADDRESS, mAddress);
json.put(HostAuthColumns.PORT, mPort);
json.put(HostAuthColumns.FLAGS, mFlags);
json.put(HostAuthColumns.LOGIN, mLogin);
json.putOpt(HostAuthColumns.PASSWORD, mPassword);
json.putOpt(HostAuthColumns.DOMAIN, mDomain);
json.putOpt(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias);
if (mCredential != null) {
json.putOpt(JSON_TAG_CREDENTIAL, mCredential.toJson());
}
return json;
} catch (final JSONException e) {
LogUtils.d(LogUtils.TAG, e, "Exception while serializing HostAuth");
}
return null;
}
protected static HostAuth fromJson(final JSONObject json) {
try {
final HostAuth h = new HostAuth();
h.mProtocol = json.getString(HostAuthColumns.PROTOCOL);
h.mAddress = json.getString(HostAuthColumns.ADDRESS);
h.mPort = json.getInt(HostAuthColumns.PORT);
h.mFlags = json.getInt(HostAuthColumns.FLAGS);
h.mLogin = json.getString(HostAuthColumns.LOGIN);
h.mPassword = json.optString(HostAuthColumns.PASSWORD);
h.mDomain = json.optString(HostAuthColumns.DOMAIN);
h.mClientCertAlias = json.optString(HostAuthColumns.CLIENT_CERT_ALIAS);
final JSONObject credJson = json.optJSONObject(JSON_TAG_CREDENTIAL);
if (credJson != null) {
h.mCredential = Credential.fromJson(credJson);
}
return h;
} catch (final JSONException e) {
LogUtils.d(LogUtils.TAG, e, "Exception while deserializing HostAuth");
}
return null;
}
/**
* Ensure that all optionally-loaded fields are populated from the provider.
* @param context for provider loads
*/
public void ensureLoaded(final Context context) {
getCredential(context);
}
/**
* Sets the user name and password from URI user info string
*/
public void setLogin(String userInfo) {
String userName = null;
String userPassword = null;
if (!TextUtils.isEmpty(userInfo)) {
String[] userInfoParts = userInfo.split(":", 2);
userName = userInfoParts[0];
if (userInfoParts.length > 1) {
userPassword = userInfoParts[1];
}
}
setLogin(userName, userPassword);
}
public void setUserName(final String userName) {
mLogin = userName;
if (TextUtils.isEmpty(mLogin)) {
mFlags &= ~FLAG_AUTHENTICATE;
} else {
mFlags |= FLAG_AUTHENTICATE;
}
}
/**
* Sets the user name and password
*/
public void setLogin(String userName, String userPassword) {
mLogin = userName;
mPassword = userPassword;
if (TextUtils.isEmpty(mLogin)) {
mFlags &= ~FLAG_AUTHENTICATE;
} else {
mFlags |= FLAG_AUTHENTICATE;
}
}
/**
* Returns the login information. [0] is the username and [1] is the password.
*/
public String[] getLogin() {
String trimUser = (mLogin != null) ? mLogin.trim() : null;
return new String[] { trimUser, mPassword };
}
public void setConnection(String protocol, String address, int port, int flags) {
setConnection(protocol, address, port, flags, null);
}
/**
* Sets the internal connection parameters based on the specified parameter values.
* @param protocol the mail protocol to use (e.g. "eas", "imap").
* @param address the address of the server
* @param port the port for the connection
* @param flags flags indicating the security and type of the connection
* @param clientCertAlias an optional alias to use if a client user certificate is to be
* presented during connection establishment. If this is non-empty, it must be the case
* that flags indicates use of a secure connection
*/
public void setConnection(String protocol, String address,
int port, int flags, String clientCertAlias) {
// Set protocol, security, and additional flags based on uri scheme
mProtocol = protocol;
mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL);
mFlags |= (flags & USER_CONFIG_MASK);
boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0;
if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) {
throw new IllegalArgumentException("Can't use client alias on non-secure connections");
}
mAddress = address;
mPort = port;
if (mPort == PORT_UNKNOWN) {
boolean useSSL = ((mFlags & FLAG_SSL) != 0);
if (LEGACY_SCHEME_SMTP.equals(mProtocol)) {
mPort = useSSL ? 465 : 587;
}
}
mClientCertAlias = clientCertAlias;
}
/** Convenience method to determine if SSL is used. */
public boolean shouldUseSsl() {
return (mFlags & FLAG_SSL) != 0;
}
/** Convenience method to determine if all server certs should be used. */
public boolean shouldTrustAllServerCerts() {
return (mFlags & FLAG_TRUST_ALL) != 0;
}
/**
* Supports Parcelable
*/
@Override
public int describeContents() {
return 0;
}
/**
* Supports Parcelable
*/
public static final Parcelable.Creator<HostAuth> CREATOR
= new Parcelable.Creator<HostAuth>() {
@Override
public HostAuth createFromParcel(Parcel in) {
return new HostAuth(in);
}
@Override
public HostAuth[] newArray(int size) {
return new HostAuth[size];
}
};
/**
* Supports Parcelable
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// mBaseUri is not parceled
dest.writeLong(mId);
dest.writeString(mProtocol);
dest.writeString(mAddress);
dest.writeInt(mPort);
dest.writeInt(mFlags);
dest.writeString(mLogin);
dest.writeString(mPassword);
dest.writeString(mDomain);
dest.writeString(mClientCertAlias);
if ((mFlags & FLAG_OAUTH) != 0) {
// TODO: This is nasty, but to be compatible with backward Exchange, we can't make any
// change to the parcelable format. But we need Credential objects to be here.
// So... only parcel or unparcel Credentials if the OAUTH flag is set. This will never
// be set on HostAuth going to or coming from Exchange.
dest.writeLong(mCredentialKey);
if (mCredential == null) {
Credential.EMPTY.writeToParcel(dest, flags);
} else {
mCredential.writeToParcel(dest, flags);
}
}
}
/**
* Supports Parcelable
*/
public HostAuth(Parcel in) {
mBaseUri = CONTENT_URI;
mId = in.readLong();
mProtocol = in.readString();
mAddress = in.readString();
mPort = in.readInt();
mFlags = in.readInt();
mLogin = in.readString();
mPassword = in.readString();
mDomain = in.readString();
mClientCertAlias = in.readString();
if ((mFlags & FLAG_OAUTH) != 0) {
// TODO: This is nasty, but to be compatible with backward Exchange, we can't make any
// change to the parcelable format. But we need Credential objects to be here.
// So... only parcel or unparcel Credentials if the OAUTH flag is set. This will never
// be set on HostAuth going to or coming from Exchange.
mCredentialKey = in.readLong();
mCredential = new Credential(in);
if (mCredential.equals(Credential.EMPTY)) {
mCredential = null;
}
} else {
mCredentialKey = -1;
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof HostAuth)) {
return false;
}
HostAuth that = (HostAuth)o;
return mPort == that.mPort
&& mId == that.mId
&& mFlags == that.mFlags
&& TextUtils.equals(mProtocol, that.mProtocol)
&& TextUtils.equals(mAddress, that.mAddress)
&& TextUtils.equals(mLogin, that.mLogin)
&& TextUtils.equals(mPassword, that.mPassword)
&& TextUtils.equals(mDomain, that.mDomain)
&& TextUtils.equals(mClientCertAlias, that.mClientCertAlias);
// We don't care about the server certificate for equals
}
/**
* The flag, password, and client cert alias are the only items likely to change after a
* HostAuth is created
*/
@Override
public int hashCode() {
int hashCode = 29;
if (mPassword != null) {
hashCode += mPassword.hashCode();
}
if (mClientCertAlias != null) {
hashCode += (mClientCertAlias.hashCode() << 8);
}
return (hashCode << 8) + mFlags;
}
/**
* Legacy URI parser. Used in parsing template from provider.xml
* Example string:
* "eas+ssl+trustallcerts://user:password@server/domain:123"
*
* Note that the use of client certificate is specified in the URI, a secure connection type
* must be used.
*/
public void setHostAuthFromString(String uriString)
throws URISyntaxException {
URI uri = new URI(uriString);
String path = uri.getPath();
String domain = null;
if (!TextUtils.isEmpty(path)) {
// Strip off the leading slash that begins the path.
domain = path.substring(1);
}
mDomain = domain;
setLogin(uri.getUserInfo());
String scheme = uri.getScheme();
setConnection(scheme, uri.getHost(), uri.getPort());
}
/**
* Legacy code for setting connection values from a "scheme" (see above)
*/
public void setConnection(String scheme, String host, int port) {
String[] schemeParts = scheme.split("\\+");
String protocol = schemeParts[0];
String clientCertAlias = null;
int flags = getSchemeFlags(scheme);
// Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias"
if (schemeParts.length > 3) {
clientCertAlias = schemeParts[3];
} else if (schemeParts.length > 2) {
if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) {
mClientCertAlias = schemeParts[2];
}
}
setConnection(protocol, host, port, flags, clientCertAlias);
}
@Override
public String toString() {
return "[protocol " + mProtocol + "]";
}
}