From a0d080558ff06f88f000cf424803c8241dd8d2eb Mon Sep 17 00:00:00 2001 From: Andy Stadler Date: Wed, 19 Jan 2011 11:40:48 -0800 Subject: [PATCH] Properly handle unsupported encryption policy * This fixes the case of: * a device that does *not* support device encryption * connecting to an account that *does* require device encryption * but also supports "non-provisioned devices" (making the encryption requirement optional.) * Added unit test Bug: 3367191 Change-Id: I894e68c4119a102dad02d2e0815fccdae1e87189 --- src/com/android/email/SecurityPolicy.java | 32 ++++++++++++++- src/com/android/exchange/EasSyncService.java | 9 ++++- .../exchange/adapter/ProvisionParser.java | 6 +++ .../android/email/SecurityPolicyTests.java | 40 +++++++++++++++++-- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/com/android/email/SecurityPolicy.java b/src/com/android/email/SecurityPolicy.java index 271ef5bbd..9ad732f89 100644 --- a/src/com/android/email/SecurityPolicy.java +++ b/src/com/android/email/SecurityPolicy.java @@ -187,7 +187,7 @@ public class SecurityPolicy { /** * Get the dpm. This mainly allows us to make some utility calls without it, for testing. */ - private synchronized DevicePolicyManager getDPM() { + /* package */ synchronized DevicePolicyManager getDPM() { if (mDPM == null) { mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); } @@ -216,7 +216,7 @@ public class SecurityPolicy { /** * API: Query if the proposed set of policies are supported on the device. * - * @param policies requested + * @param policies the polices that were requested * @return boolean if supported */ public boolean isSupported(PolicySet policies) { @@ -232,6 +232,34 @@ public class SecurityPolicy { return true; } + /** + * API: Remove any unsupported policies + * + * This is used when we have a set of polices that have been requested, but the server + * is willing to allow unsupported policies to be considered optional. + * + * @param policies the polices that were requested + * @return the same PolicySet if all are supported; A replacement PolicySet if any + * unsupported policies were removed + */ + public PolicySet clearUnsupportedPolicies(PolicySet policies) { + PolicySet result = policies; + // IMPLEMENTATION: At this time, the only policy which might not be supported is + // encryption (which requires low-level systems support). Other policies are fully + // supported by the framework and do not need to be checked. + if (policies.mRequireEncryption) { + int encryptionStatus = getDPM().getStorageEncryptionStatus(); + if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) { + // Make new PolicySet w/o encryption + result = new PolicySet(policies.mMinPasswordLength, policies.mPasswordMode, + policies.mMaxPasswordFails, policies.mMaxScreenLockTime, + policies.mRequireRemoteWipe, policies.mPasswordExpirationDays, + policies.mPasswordHistory, policies.mPasswordComplexChars, false); + } + } + return result; + } + /** * API: Query used to determine if a given policy is "active" (the device is operating at * the required security level). diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index 2a1560d24..98a34d8f2 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -1527,7 +1527,12 @@ public class EasSyncService extends AbstractSyncService { String policyKey = acknowledgeProvision(pp.getPolicyKey(), PROVISION_STATUS_PARTIAL); // Return either the parser (success) or null (failure) - return (policyKey != null) ? pp : null; + if (policyKey != null) { + pp.clearUnsupportedPolicies(); + return pp; + } else { + return null; + } } } } @@ -2190,7 +2195,7 @@ public class EasSyncService extends AbstractSyncService { /** * Common code to sync E+PIM data * - * @param target, an EasMailbox, EasContacts, or EasCalendar object + * @param target an EasMailbox, EasContacts, or EasCalendar object */ public void sync(AbstractSyncAdapter target) throws IOException { Mailbox mailbox = target.mMailbox; diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java index 1744bc0a1..c5ef7ae14 100644 --- a/src/com/android/exchange/adapter/ProvisionParser.java +++ b/src/com/android/exchange/adapter/ProvisionParser.java @@ -60,6 +60,12 @@ public class ProvisionParser extends Parser { return (mPolicySet != null) && mIsSupportable; } + public void clearUnsupportedPolicies() { + mPolicySet = SecurityPolicy.getInstance(mService.mContext) + .clearUnsupportedPolicies(mPolicySet); + mIsSupportable = true; + } + private void parseProvisionDocWbxml() throws IOException { int minPasswordLength = 0; int passwordMode = PolicySet.PASSWORD_MODE_NONE; diff --git a/tests/src/com/android/email/SecurityPolicyTests.java b/tests/src/com/android/email/SecurityPolicyTests.java index b97f04861..5719f884d 100644 --- a/tests/src/com/android/email/SecurityPolicyTests.java +++ b/tests/src/com/android/email/SecurityPolicyTests.java @@ -19,13 +19,14 @@ package com.android.email; import com.android.email.SecurityPolicy.PolicySet; import com.android.email.provider.ContentCache; import com.android.email.provider.EmailContent; -import com.android.email.provider.EmailProvider; -import com.android.email.provider.ProviderTestUtils; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.AccountColumns; import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.EmailContent.Message; +import com.android.email.provider.EmailProvider; +import com.android.email.provider.ProviderTestUtils; +import android.app.admin.DevicePolicyManager; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; @@ -73,7 +74,7 @@ public class SecurityPolicyTests extends ProviderTestCase2 { /** * Private context wrapper used to add back getPackageName() for these tests. * - * This class also implements {@link Context} method(s) that is called during tests. + * This class also implements {@link Context} method(s) that are called during tests. */ private static class MockContext2 extends ContextWrapper { @@ -93,6 +94,11 @@ public class SecurityPolicyTests extends ProviderTestCase2 { public String getPackageName() { return mRealContext.getPackageName(); } + + @Override + public Object getSystemService(String name) { + return mRealContext.getSystemService(name); + } } /** @@ -606,4 +612,32 @@ public class SecurityPolicyTests extends ProviderTestCase2 { account = Account.restoreAccountWithId(mMockContext, account3Id); assertEquals(Account.FLAGS_SECURITY_HOLD, account.mFlags & Account.FLAGS_SECURITY_HOLD); } + + /** + * Test the code that clears unsupported policies + * TODO inject a mock DPM so we can directly control & test all cases, no matter what device + */ + public void testClearUnsupportedPolicies() { + PolicySet p1 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false); + PolicySet p2 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, true); + + SecurityPolicy sp = getSecurityPolicy(); + DevicePolicyManager dpm = sp.getDPM(); + boolean hasEncryption = + dpm.getStorageEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + + PolicySet p1Result = sp.clearUnsupportedPolicies(p1); + PolicySet p2Result = sp.clearUnsupportedPolicies(p2); + + // No changes expected when encryptionRequested was false + assertEquals(p1, p1Result); + if (hasEncryption) { + // No changes expected + assertEquals(p2, p2Result); + } else { + PolicySet p2Expect = + new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false); + assertEquals(p2Expect, p2Result); + } + } }