Merge "Rework account security bootstrap procedure" into honeycomb

This commit is contained in:
Andy Stadler 2011-01-24 11:18:12 -08:00 committed by Android (Google) Code Review
commit 3dc2fcaac3

View File

@ -18,6 +18,7 @@ package com.android.email.activity.setup;
import com.android.email.R; import com.android.email.R;
import com.android.email.SecurityPolicy; import com.android.email.SecurityPolicy;
import com.android.email.Utility;
import com.android.email.activity.ActivityHelper; import com.android.email.activity.ActivityHelper;
import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.HostAuth; import com.android.email.provider.EmailContent.HostAuth;
@ -26,6 +27,7 @@ import android.app.Activity;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
/** /**
@ -47,6 +49,11 @@ public class AccountSecurity extends Activity {
private static final int REQUEST_PASSWORD = 2; private static final int REQUEST_PASSWORD = 2;
private static final int REQUEST_ENCRYPTION = 3; private static final int REQUEST_ENCRYPTION = 3;
private boolean mTriedAddAdministrator = false;
private boolean mTriedSetPassword = false;
private boolean mTriedSetEncryption = false;
private Account mAccount;
/** /**
* Used for generating intent for this activity (which is intended to be launched * Used for generating intent for this activity (which is intended to be launched
* from a notification.) * from a notification.)
@ -67,114 +74,144 @@ public class AccountSecurity extends Activity {
ActivityHelper.debugSetWindowFlags(this); ActivityHelper.debugSetWindowFlags(this);
Intent i = getIntent(); Intent i = getIntent();
long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1); final long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
SecurityPolicy security = SecurityPolicy.getInstance(this); SecurityPolicy security = SecurityPolicy.getInstance(this);
security.clearNotification(accountId); security.clearNotification(accountId);
if (accountId != -1) { if (accountId == -1) {
// TODO: spin up a thread to do this in the background, because of DB ops finish();
Account account = Account.restoreAccountWithId(this, accountId); return;
if (account != null) {
if (account.mSecurityFlags != 0) {
// This account wants to control security
if (!security.isActiveAdmin()) {
// retrieve name of server for the format string
HostAuth hostAuth =
HostAuth.restoreHostAuthWithId(this, account.mHostAuthKeyRecv);
if (hostAuth != null) {
// try to become active - must happen here in activity, to get result
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
security.getAdminComponent());
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
this.getString(R.string.account_security_policy_explanation_fmt,
hostAuth.mAddress));
startActivityForResult(intent, REQUEST_ENABLE);
// keep this activity on stack to process result
return;
}
} else {
// already active - try to set actual policies, finish, and return
boolean startedActivity = setActivePolicies();
if (startedActivity) {
// keep this activity on stack to process result
return;
}
}
}
}
} }
finish();
// Let onCreate exit, while background thread retrieves account.
// Then start the security check/bootstrap process.
new AsyncTask<Void, Void, Account>() {
@Override
protected Account doInBackground(Void... params) {
return Account.restoreAccountWithId(AccountSecurity.this, accountId);
}
@Override
protected void onPostExecute(Account result) {
mAccount = result;
if (mAccount != null && mAccount.mSecurityFlags != 0) {
// This account wants to control security
tryAdvanceSecurity(mAccount);
return;
}
finish();
}
}.execute();
} }
/** /**
* Handle the eventual result of the user allowing us to become an active device admin * After any of the activities return, try to advance to the "next step"
*/ */
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
boolean startedActivity = false; tryAdvanceSecurity(mAccount);
switch (requestCode) {
case REQUEST_PASSWORD:
case REQUEST_ENCRYPTION:
// Force the result code and just check the DPM to check for actual success
resultCode = Activity.RESULT_OK;
//$FALL-THROUGH$
case REQUEST_ENABLE:
if (resultCode == Activity.RESULT_OK) {
// now active - try to set actual policies
startedActivity = setActivePolicies();
} else {
// failed - repost notification, and exit
final long accountId = getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1);
if (accountId != -1) {
new Thread() {
@Override
public void run() {
SecurityPolicy.getInstance(AccountSecurity.this)
.policiesRequired(accountId);
}
}.start();
}
}
}
if (!startedActivity) {
finish();
}
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }
/** /**
* Now that we are connected as an active device admin, try to set the device to the * Walk the user through the required steps to become an active administrator and with
* correct security level, and ask for a password if necessary. * the requisite security settings for the given account.
* @return true if we started another activity (and should not finish(), as we're waiting for *
* their result.) * These steps will be repeated each time we return from a given attempt (e.g. asking the
* user to choose a device pin/password). In a typical activation, we may repeat these
* steps a few times. It may go as far as step 5 (password) or step 6 (encryption), but it
* will terminate when step 2 (isActive()) succeeds.
*
* If at any point we do not advance beyond a given user step, (e.g. the user cancels
* instead of setting a password) we simply repost the security notification, and exit.
* We never want to loop here.
*/ */
private boolean setActivePolicies() { private void tryAdvanceSecurity(Account account) {
SecurityPolicy sp = SecurityPolicy.getInstance(this); SecurityPolicy security = SecurityPolicy.getInstance(this);
// check current security level - if sufficient, we're done!
if (sp.isActive(null)) { // Step 1. Check if we are an active device administrator, and stop here to activate
Account.clearSecurityHoldOnAllAccounts(this); if (!security.isActiveAdmin()) {
return false; if (mTriedAddAdministrator) {
repostNotification(account, security);
finish();
} else {
mTriedAddAdministrator = true;
// retrieve name of server for the format string
HostAuth hostAuth = HostAuth.restoreHostAuthWithId(this, account.mHostAuthKeyRecv);
if (hostAuth == null) {
repostNotification(account, security);
finish();
} else {
// try to become active - must happen here in activity, to get result
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
security.getAdminComponent());
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
this.getString(R.string.account_security_policy_explanation_fmt,
hostAuth.mAddress));
startActivityForResult(intent, REQUEST_ENABLE);
}
}
return;
} }
// set current security level
sp.setActivePolicies(); // Step 2. Check if the current aggregate security policy is being satisfied by the
// check current security level - if sufficient, we're done! // DevicePolicyManager (the current system security level).
int inactiveReasons = sp.getInactiveReasons(null); if (security.isActive(null)) {
if (inactiveReasons == 0) {
Account.clearSecurityHoldOnAllAccounts(this); Account.clearSecurityHoldOnAllAccounts(this);
return false; finish();
return;
} }
// If password or encryption required, launch relevant intent
// Step 3. Try to assert the current aggregate security requirements with the system.
security.setActivePolicies();
// Step 4. Recheck the security policy, and determine what changes are needed (if any)
// to satisfy the requirements.
int inactiveReasons = security.getInactiveReasons(null);
// Step 5. If password is needed, try to have the user set it
if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_PASSWORD) != 0) { if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_PASSWORD) != 0) {
// launch the activity to have the user set a new password. if (mTriedSetPassword) {
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); repostNotification(account, security);
startActivityForResult(intent, REQUEST_PASSWORD); finish();
return true; } else {
} else if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_ENCRYPTION) != 0) { mTriedSetPassword = true;
// launch the activity to start up encryption. // launch the activity to have the user set a new password.
Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION); Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
startActivityForResult(intent, REQUEST_ENCRYPTION); startActivityForResult(intent, REQUEST_PASSWORD);
return true; }
return;
} }
return false;
// Step 6. If encryption is needed, try to have the user set it
if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_ENCRYPTION) != 0) {
if (mTriedSetEncryption) {
repostNotification(account, security);
finish();
} else {
mTriedSetEncryption = true;
// launch the activity to start up encryption.
Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION);
startActivityForResult(intent, REQUEST_ENCRYPTION);
}
return;
}
// Step 7. No problems were found, so clear holds and exit
Account.clearSecurityHoldOnAllAccounts(this);
finish();
}
/**
* Mark an account as not-ready-for-sync and post a notification to bring the user back here
* eventually.
*/
private void repostNotification(final Account account, final SecurityPolicy security) {
Utility.runAsync(new Runnable() {
@Override
public void run() {
security.policiesRequired(account.mId);
}
});
} }
} }