Merge "Delete secured accounts if device admin is disabled"

This commit is contained in:
Marc Blank 2010-10-27 09:03:39 -07:00 committed by Android (Google) Code Review
commit 45b15734d8
4 changed files with 79 additions and 31 deletions

View File

@ -650,6 +650,11 @@ save attachment.</string>
being supported -->
<string name="account_setup_failed_security_policies_unsupported">
This server requires security features your phone does not support.</string>
<!-- Warning given to users when they request disabling device administration (i.e. that their
administered accounts will be deleted) [CHAR LIMIT=none] -->
<string name="disable_admin_warning">WARNING: Deactivating the Email application\'s authority
to administer your device will delete all Email accounts that require it, along with their
email, contacts, calendar events, and other data.</string>
<!-- Notification ticker when device security required -->
<string name="security_notification_ticker_fmt">

View File

@ -942,7 +942,7 @@ public class Controller {
public void deleteAccount(final long accountId) {
Utility.runAsync(new Runnable() {
public void run() {
deleteAccountSync(accountId);
deleteAccountSync(accountId, mContext);
}
});
}
@ -950,19 +950,19 @@ public class Controller {
/**
* Delete an account synchronously. Intended to be used only by unit tests.
*/
public void deleteAccountSync(long accountId) {
public void deleteAccountSync(long accountId, Context context) {
try {
mLegacyControllerMap.remove(accountId);
// Get the account URI.
final Account account = Account.restoreAccountWithId(mContext, accountId);
final Account account = Account.restoreAccountWithId(context, accountId);
if (account == null) {
return; // Already deleted?
}
final String accountUri = account.getStoreUri(mContext);
final String accountUri = account.getStoreUri(context);
// Delete Remote store at first.
if (!TextUtils.isEmpty(accountUri)) {
Store.getInstance(accountUri, mContext, null).delete();
Store.getInstance(accountUri, context, null).delete();
// Remove the Store instance from cache.
Store.removeInstance(accountUri);
}
@ -972,12 +972,12 @@ public class Controller {
mContext.getContentResolver().delete(uri, null, null);
// Update the backup (side copy) of the accounts
AccountBackupRestore.backupAccounts(mContext);
AccountBackupRestore.backupAccounts(context);
// Release or relax device administration, if relevant
SecurityPolicy.getInstance(mContext).reducePolicies();
SecurityPolicy.getInstance(context).reducePolicies();
Email.setServicesEnabled(mContext);
Email.setServicesEnabled(context);
} catch (Exception e) {
Log.w(Email.LOG_TAG, "Exception while deleting account", e);
} finally {

View File

@ -20,7 +20,6 @@ import com.android.email.activity.setup.AccountSecurity;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.service.MailService;
import android.app.Notification;
import android.app.NotificationManager;
@ -28,6 +27,7 @@ import android.app.PendingIntent;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@ -43,7 +43,7 @@ import android.util.Log;
* into and out of various security states.
*/
public class SecurityPolicy {
private static final String TAG = "SecurityPolicy";
private static SecurityPolicy sInstance = null;
private Context mContext;
private DevicePolicyManager mDPM;
@ -703,19 +703,40 @@ public class SecurityPolicy {
}
/**
* Internal handler for enabled->disabled transitions. Resets all security keys
* forcing EAS to resync security state.
* Delete all accounts whose security flags aren't zero (i.e. they have security enabled).
* This method is synchronous, so it should normally be called within a worker thread (the
* exception being for unit tests)
*
* @param context the caller's context
*/
/* package */ void onAdminEnabled(boolean isEnabled) {
/*package*/ void deleteSecuredAccounts(Context context) {
ContentResolver cr = context.getContentResolver();
// Find all accounts with security and delete them
Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
AccountColumns.SECURITY_FLAGS + "!=0", null, null);
try {
Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
" secured account(s)");
while (c.moveToNext()) {
Controller.getInstance(context).deleteAccountSync(
c.getLong(EmailContent.ID_PROJECTION_COLUMN), context);
}
} finally {
c.close();
}
updatePolicies(-1);
}
/**
* Internal handler for enabled->disabled transitions. Deletes all secured accounts.
*/
/*package*/ void onAdminEnabled(boolean isEnabled) {
if (!isEnabled) {
// transition to disabled state
// Response: clear *all* security state information from the accounts, forcing
// them back to the initial configurations requiring policy administration
ContentValues cv = new ContentValues();
cv.put(AccountColumns.SECURITY_FLAGS, 0);
cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
updatePolicies(-1);
Utility.runAsync(new Runnable() {
@Override
public void run() {
deleteSecuredAccounts(mContext);
}});
}
}
@ -743,6 +764,15 @@ public class SecurityPolicy {
SecurityPolicy.getInstance(context).onAdminEnabled(false);
}
/**
* Called when the user asks to disable administration; we return a warning string that
* will be presented to the user
*/
@Override
public CharSequence onDisableRequested(Context context, Intent intent) {
return context.getString(R.string.disable_admin_warning);
}
/**
* Called after the user has changed their password.
*/

View File

@ -379,6 +379,12 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2b.mFlags);
}
private static class MockController extends Controller {
protected MockController(Context context) {
super(context);
}
}
/**
* Test the response to disabling DeviceAdmin status
*/
@ -410,15 +416,22 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a3a = Account.restoreAccountWithId(mMockContext, a3.mId);
assertNull(a3a.mSecuritySyncKey);
// Revoke device admin status. In the accounts we set up, security values should be reset
sp.onAdminEnabled(false); // "disabled" should clear policies
PolicySet after2 = sp.getAggregatePolicy();
assertEquals(SecurityPolicy.NO_POLICY_SET, after2);
Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId);
assertNull(a1b.mSecuritySyncKey);
Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
assertNull(a2b.mSecuritySyncKey);
Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId);
assertNull(a3b.mSecuritySyncKey);
// Simulate revoke of device admin; directly call deleteSecuredAccounts, which is normally
// called from a background thread
MockController mockController = new MockController(mMockContext);
Controller.injectMockControllerForTest(mockController);
try {
sp.deleteSecuredAccounts(mMockContext);
PolicySet after2 = sp.getAggregatePolicy();
assertEquals(SecurityPolicy.NO_POLICY_SET, after2);
Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId);
assertNull(a1b);
Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
assertNull(a2b);
Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId);
assertNull(a3b.mSecuritySyncKey);
} finally {
Controller.injectMockControllerForTest(null);
}
}
}