Merge "Additional work on EAS security"
This commit is contained in:
commit
f3332ddac8
|
@ -49,7 +49,8 @@ public class SecurityPolicy {
|
|||
|
||||
/** STOPSHIP - ok to check in true for now, but must be false for shipping */
|
||||
/** DO NOT CHECK IN WHILE 'true' */
|
||||
private static final boolean DEBUG_ALWAYS_ACTIVE = false;
|
||||
// Until everything is connected, allow syncs to work
|
||||
private static final boolean DEBUG_ALWAYS_ACTIVE = true;
|
||||
|
||||
private static SecurityPolicy sInstance = null;
|
||||
private Context mContext;
|
||||
|
|
|
@ -31,6 +31,7 @@ public interface EmailServiceStatus {
|
|||
public static final int FOLDER_NOT_CREATED = 0x14;
|
||||
public static final int REMOTE_EXCEPTION = 0x15;
|
||||
public static final int LOGIN_FAILED = 0x16;
|
||||
public static final int SECURITY_FAILURE = 0x17;
|
||||
|
||||
// Maybe we should automatically retry these?
|
||||
public static final int CONNECTION_ERROR = 0x20;
|
||||
|
|
|
@ -62,6 +62,7 @@ public abstract class AbstractSyncService implements Runnable {
|
|||
public static final int EXIT_IO_ERROR = 1;
|
||||
public static final int EXIT_LOGIN_FAILURE = 2;
|
||||
public static final int EXIT_EXCEPTION = 3;
|
||||
public static final int EXIT_SECURITY_FAILURE = 4;
|
||||
|
||||
public Mailbox mMailbox;
|
||||
protected long mMailboxId;
|
||||
|
|
|
@ -228,6 +228,15 @@ public class EasSyncService extends AbstractSyncService {
|
|||
return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an HTTP code represents a provisioning error
|
||||
* @param code the HTTP code returned by the server
|
||||
* @return whether or not the code represents an provisioning error
|
||||
*/
|
||||
protected boolean isProvisionError(int code) {
|
||||
return (code == HTTP_NEED_PROVISIONING) || (code == HttpStatus.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
private void setupProtocolVersion(EasSyncService service, Header versionHeader) {
|
||||
String versions = versionHeader.getValue();
|
||||
if (versions != null) {
|
||||
|
@ -281,7 +290,7 @@ public class EasSyncService extends AbstractSyncService {
|
|||
// We'll get one of the following responses if policies are required by the server
|
||||
if (code == HttpStatus.SC_FORBIDDEN || code == HTTP_NEED_PROVISIONING) {
|
||||
// Get the policies and see if we are able to support them
|
||||
if (svc.canProvision()) {
|
||||
if (svc.canProvision() != null) {
|
||||
// If so, send the advisory Exception (the account may be created later)
|
||||
throw new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
|
||||
} else
|
||||
|
@ -418,7 +427,7 @@ public class EasSyncService extends AbstractSyncService {
|
|||
|
||||
// Try the domain first and see if we can get a response
|
||||
HttpPost post = new HttpPost("https://" + domain + AUTO_DISCOVER_PAGE);
|
||||
setHeaders(post);
|
||||
setHeaders(post, false);
|
||||
post.setHeader("Content-Type", "text/xml");
|
||||
post.setEntity(new StringEntity(req));
|
||||
HttpClient client = getHttpClient(COMMAND_TIMEOUT);
|
||||
|
@ -798,11 +807,23 @@ public class EasSyncService extends AbstractSyncService {
|
|||
return us;
|
||||
}
|
||||
|
||||
private void setHeaders(HttpRequestBase method) {
|
||||
/**
|
||||
* Set standard HTTP headers, using a policy key if required
|
||||
* @param method the method we are going to send
|
||||
* @param usePolicyKey whether or not a policy key should be sent in the headers
|
||||
*/
|
||||
private void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
|
||||
method.setHeader("Authorization", mAuthString);
|
||||
method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
|
||||
method.setHeader("Connection", "keep-alive");
|
||||
method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
|
||||
if (usePolicyKey && (mAccount != null)) {
|
||||
String key = mAccount.mSecuritySyncKey;
|
||||
if (key == null || key.length() == 0) {
|
||||
return;
|
||||
}
|
||||
method.setHeader("X-MS-PolicyKey", key);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientConnectionManager getClientConnectionManager() {
|
||||
|
@ -860,7 +881,7 @@ public class EasSyncService extends AbstractSyncService {
|
|||
} else if (entity != null) {
|
||||
method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
|
||||
}
|
||||
setHeaders(method);
|
||||
setHeaders(method, !cmd.equals(PING_COMMAND));
|
||||
method.setEntity(entity);
|
||||
synchronized(getSynchronizer()) {
|
||||
mPendingPost = method;
|
||||
|
@ -884,7 +905,7 @@ public class EasSyncService extends AbstractSyncService {
|
|||
HttpClient client = getHttpClient(COMMAND_TIMEOUT);
|
||||
String us = makeUriString("OPTIONS", null);
|
||||
HttpOptions method = new HttpOptions(URI.create(us));
|
||||
setHeaders(method);
|
||||
setHeaders(method, false);
|
||||
return client.execute(method);
|
||||
}
|
||||
|
||||
|
@ -899,8 +920,55 @@ public class EasSyncService extends AbstractSyncService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate provisioning with the server. First, get policies form the server and see if
|
||||
* the policies are supported by the device. Then, write the policies to the account and
|
||||
* tell SecurityPolicy that we have policies in effect. Finally, see if those policies are
|
||||
* active; if so, acknowledge the policies to the server and get a final policy key that we
|
||||
* use in future EAS commands and write this key to the account.
|
||||
* @return whether or not provisioning has been successful
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean tryProvision() throws IOException {
|
||||
// First, see if provisioning is even possible, i.e. do we support the policies required
|
||||
// by the server
|
||||
ProvisionParser pp = canProvision();
|
||||
if (pp != null) {
|
||||
SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
|
||||
// Get the policies from ProvisionParser
|
||||
PolicySet ps = pp.getPolicySet();
|
||||
// Update the account with a null policyKey (the key we've gotten is
|
||||
// temporary and cannot be used for syncing)
|
||||
if (ps.writeAccount(mAccount, null, true, mContext)) {
|
||||
sp.updatePolicies(mAccount.mId);
|
||||
}
|
||||
// See if the policies are currently in force
|
||||
if (sp.isActive(ps)) {
|
||||
// If they are, acknowledge the policies to the server and get the final policy
|
||||
// key
|
||||
String policyKey = acknowledgeProvision(pp.getPolicyKey());
|
||||
if (policyKey != null) {
|
||||
// Write the final policy key to the Account and say we've been successful
|
||||
ps.writeAccount(mAccount, policyKey, true, mContext);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Notify that we are blocked because of policies
|
||||
sp.policiesRequired(mAccount.mId);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO This is Exchange 2007 only at this point
|
||||
private boolean canProvision() throws IOException {
|
||||
/**
|
||||
* Obtain a set of policies from the server and determine whether those policies are supported
|
||||
* by the device.
|
||||
* @return the ProvisionParser (holds policies and key) if we receive policies and they are
|
||||
* supported by the device; null otherwise
|
||||
* @throws IOException
|
||||
*/
|
||||
private ProvisionParser canProvision() throws IOException {
|
||||
Serializer s = new Serializer();
|
||||
s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
|
||||
s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, "MS-EAS-Provisioning-WBXML")
|
||||
|
@ -911,15 +979,48 @@ public class EasSyncService extends AbstractSyncService {
|
|||
InputStream is = resp.getEntity().getContent();
|
||||
ProvisionParser pp = new ProvisionParser(is, this);
|
||||
if (pp.parse()) {
|
||||
// If true, we received policies from the server
|
||||
// Retrieve them and write them to the framework
|
||||
// If true, we received policies from the server; see if they are supported by
|
||||
// the framework; if so, return the ProvisionParser containing the policy set and
|
||||
// temporary key
|
||||
PolicySet ps = pp.getPolicySet();
|
||||
if (SecurityPolicy.getInstance(mContext).isSupported(ps)) {
|
||||
return true;
|
||||
return pp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// On failures, simply return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO This is Exchange 2007 only at this point
|
||||
/**
|
||||
* Acknowledge that we support the policies provided by the server, and that these policies
|
||||
* are in force.
|
||||
* @param tempKey the initial (temporary) policy key sent by the server
|
||||
* @return the final policy key, which can be used for syncing
|
||||
* @throws IOException
|
||||
*/
|
||||
private String acknowledgeProvision(String tempKey) throws IOException {
|
||||
Serializer s = new Serializer();
|
||||
s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
|
||||
s.start(Tags.PROVISION_POLICY);
|
||||
s.data(Tags.PROVISION_POLICY_TYPE, "MS-EAS-Provisioning-WBXML");
|
||||
s.data(Tags.PROVISION_POLICY_KEY, tempKey);
|
||||
s.data(Tags.PROVISION_STATUS, "1");
|
||||
s.end();
|
||||
s.end().end().done();
|
||||
HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray());
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
if (code == HttpStatus.SC_OK) {
|
||||
InputStream is = resp.getEntity().getContent();
|
||||
ProvisionParser pp = new ProvisionParser(is, this);
|
||||
if (pp.parse()) {
|
||||
// Return the final polic key from the ProvisionParser
|
||||
return pp.getPolicyKey();
|
||||
}
|
||||
}
|
||||
// On failures, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1009,7 +1110,7 @@ public class EasSyncService extends AbstractSyncService {
|
|||
userLog("Sending Account syncKey: ", mAccount.mSyncKey);
|
||||
Serializer s = new Serializer();
|
||||
s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY)
|
||||
.text(mAccount.mSyncKey).end().end().done();
|
||||
.text(mAccount.mSyncKey).end().end().done();
|
||||
HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
|
||||
if (mStop) break;
|
||||
int code = resp.getStatusLine().getStatusCode();
|
||||
|
@ -1020,27 +1121,21 @@ public class EasSyncService extends AbstractSyncService {
|
|||
InputStream is = entity.getContent();
|
||||
// Returns true if we need to sync again
|
||||
if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
|
||||
.parse()) {
|
||||
.parse()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// // EVERYTHING IN THE code==403 BLOCK IS PLACEHOLDER/SAMPLE CODE
|
||||
} else if (code == 403) { // security error
|
||||
// Reality: Find out from server what policies are required
|
||||
// Fake: Hardcode the policies
|
||||
SecurityPolicy sp = SecurityPolicy.getInstance(mContext);
|
||||
PolicySet ps = new PolicySet(4, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, true);
|
||||
// Update the account
|
||||
if (ps.writeAccount(mAccount, "securitySyncKey", true, mContext)) {
|
||||
sp.updatePolicies(mAccount.mId);
|
||||
} else if (isProvisionError(code)) {
|
||||
// If the sync error is a provisioning failure (perhaps the policies changed),
|
||||
// let's try the provisining procedure
|
||||
if (!tryProvision()) {
|
||||
// Set the appropriate failure status
|
||||
mExitStatus = EXIT_SECURITY_FAILURE;
|
||||
return;
|
||||
}
|
||||
// Notify that we are blocked because of policies
|
||||
sp.policiesRequired(mAccount.mId);
|
||||
// and exit (don't sync in this condition)
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
// END PLACEHOLDER CODE
|
||||
} else if (isAuthError(code)) {
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
return;
|
||||
} else {
|
||||
userLog("FolderSync response error: ", code);
|
||||
}
|
||||
|
@ -1510,7 +1605,12 @@ public class EasSyncService extends AbstractSyncService {
|
|||
}
|
||||
} else {
|
||||
userLog("Sync response error: ", code);
|
||||
if (isAuthError(code)) {
|
||||
if (isProvisionError(code)) {
|
||||
if (!tryProvision()) {
|
||||
mExitStatus = EXIT_SECURITY_FAILURE;
|
||||
return;
|
||||
}
|
||||
} else if (isAuthError(code)) {
|
||||
mExitStatus = EXIT_LOGIN_FAILURE;
|
||||
} else {
|
||||
mExitStatus = EXIT_IO_ERROR;
|
||||
|
@ -1616,6 +1716,9 @@ public class EasSyncService extends AbstractSyncService {
|
|||
case EXIT_LOGIN_FAILURE:
|
||||
status = EmailServiceStatus.LOGIN_FAILED;
|
||||
break;
|
||||
case EXIT_SECURITY_FAILURE:
|
||||
status = EmailServiceStatus.SECURITY_FAILURE;
|
||||
break;
|
||||
default:
|
||||
status = EmailServiceStatus.REMOTE_EXCEPTION;
|
||||
errorLog("Sync ended due to an exception.");
|
||||
|
|
|
@ -477,23 +477,10 @@ public class SyncManager extends Service implements Runnable {
|
|||
return mEasAccountSelector;
|
||||
}
|
||||
|
||||
private boolean syncParametersChanged(Account account) {
|
||||
long accountId = account.mId;
|
||||
// Reload account from database to get its current state
|
||||
account = Account.restoreAccountWithId(getContext(), accountId);
|
||||
for (Account oldAccount: mAccounts) {
|
||||
if (oldAccount.mId == accountId) {
|
||||
return oldAccount.mSyncInterval != account.mSyncInterval ||
|
||||
oldAccount.mSyncLookback != account.mSyncLookback;
|
||||
}
|
||||
}
|
||||
// Really, we can't get here, but we don't want the compiler to complain
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
maybeStartSyncManagerThread();
|
||||
Context context = getContext();
|
||||
|
||||
// A change to the list requires us to scan for deletions (to stop running syncs)
|
||||
// At startup, we want to see what accounts exist and cache them
|
||||
|
@ -518,11 +505,11 @@ public class SyncManager extends Service implements Runnable {
|
|||
mSyncableEasMailboxSelector = null;
|
||||
mEasAccountSelector = null;
|
||||
} else {
|
||||
// See whether any of our accounts has changed sync interval or window
|
||||
if (syncParametersChanged(account)) {
|
||||
// An account has changed
|
||||
Account updatedAccount = Account.restoreAccountWithId(context, account.mId);
|
||||
if (account.mSyncInterval != updatedAccount.mSyncInterval ||
|
||||
account.mSyncLookback != updatedAccount.mSyncLookback) {
|
||||
// Set pushable boxes' sync interval to the sync interval of the Account
|
||||
Account updatedAccount =
|
||||
Account.restoreAccountWithId(getContext(), account.mId);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(MailboxColumns.SYNC_INTERVAL, updatedAccount.mSyncInterval);
|
||||
getContentResolver().update(Mailbox.CONTENT_URI, cv,
|
||||
|
@ -532,6 +519,12 @@ public class SyncManager extends Service implements Runnable {
|
|||
log("Account " + account.mDisplayName + " changed; stop syncs");
|
||||
stopAccountSyncs(account.mId, true);
|
||||
}
|
||||
// TODO Check here for security hold in mFlags
|
||||
|
||||
// Put current values into our cached account
|
||||
account.mSyncInterval = updatedAccount.mSyncInterval;
|
||||
account.mSyncLookback = updatedAccount.mSyncLookback;
|
||||
account.mFlags = updatedAccount.mFlags;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1852,6 +1845,7 @@ public class SyncManager extends Service implements Runnable {
|
|||
}
|
||||
errorMap.remove(mailboxId);
|
||||
break;
|
||||
// I/O errors get retried at increasing intervals
|
||||
case AbstractSyncService.EXIT_IO_ERROR:
|
||||
Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mailboxId);
|
||||
if (m == null) return;
|
||||
|
@ -1863,6 +1857,8 @@ public class SyncManager extends Service implements Runnable {
|
|||
INSTANCE.log(m.mDisplayName + " added to syncErrorMap, hold for 15s");
|
||||
}
|
||||
break;
|
||||
// These errors are not retried automatically
|
||||
case AbstractSyncService.EXIT_SECURITY_FAILURE:
|
||||
case AbstractSyncService.EXIT_LOGIN_FAILURE:
|
||||
case AbstractSyncService.EXIT_EXCEPTION:
|
||||
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, true));
|
||||
|
|
|
@ -51,7 +51,7 @@ public class ProvisionParser extends Parser {
|
|||
int passwordMode = PolicySet.PASSWORD_MODE_NONE;
|
||||
int maxPasswordFails = 0;
|
||||
int maxScreenLockTime = 0;
|
||||
boolean canSupport = false;
|
||||
boolean canSupport = true;
|
||||
|
||||
while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) {
|
||||
switch (tag) {
|
||||
|
@ -122,9 +122,6 @@ public class ProvisionParser extends Parser {
|
|||
public void parsePolicy() throws IOException {
|
||||
while (nextTag(Tags.PROVISION_POLICY) != END) {
|
||||
switch (tag) {
|
||||
case Tags.PROVISION_POLICIES:
|
||||
parsePolicies();
|
||||
break;
|
||||
case Tags.PROVISION_POLICY_TYPE:
|
||||
mService.userLog("Policy type: ", getValue());
|
||||
break;
|
||||
|
@ -167,7 +164,7 @@ public class ProvisionParser extends Parser {
|
|||
break;
|
||||
case Tags.PROVISION_POLICIES:
|
||||
parsePolicies();
|
||||
return (mPolicySet != null);
|
||||
return (mPolicySet != null || mPolicyKey != null);
|
||||
default:
|
||||
skipTag();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue