diff --git a/src/com/android/email/Account.java b/src/com/android/email/Account.java index aea3467bd..965515698 100644 --- a/src/com/android/email/Account.java +++ b/src/com/android/email/Account.java @@ -76,7 +76,7 @@ public class Account { int mSyncWindow; int mBackupFlags; // for account backups only String mProtocolVersion; // for account backups only - int mSecurityFlags; // for account backups only + long mSecurityFlags; // for account backups only String mSignature; // for account backups only /** @@ -177,7 +177,7 @@ public class Account { mBackupFlags = preferences.mSharedPreferences.getInt(mUuid + KEY_BACKUP_FLAGS, 0); mProtocolVersion = preferences.mSharedPreferences.getString(mUuid + KEY_PROTOCOL_VERSION, null); - mSecurityFlags = preferences.mSharedPreferences.getInt(mUuid + KEY_SECURITY_FLAGS, 0); + mSecurityFlags = preferences.mSharedPreferences.getLong(mUuid + KEY_SECURITY_FLAGS, 0); mSignature = preferences.mSharedPreferences.getString(mUuid + KEY_SIGNATURE, null); } @@ -353,7 +353,7 @@ public class Account { editor.putInt(mUuid + KEY_SYNC_WINDOW, mSyncWindow); editor.putInt(mUuid + KEY_BACKUP_FLAGS, mBackupFlags); editor.putString(mUuid + KEY_PROTOCOL_VERSION, mProtocolVersion); - editor.putInt(mUuid + KEY_SECURITY_FLAGS, mSecurityFlags); + editor.putLong(mUuid + KEY_SECURITY_FLAGS, mSecurityFlags); editor.putString(mUuid + KEY_SIGNATURE, mSignature); // The following fields are *not* written because they need to be more fine-grained diff --git a/src/com/android/email/SecurityPolicy.java b/src/com/android/email/SecurityPolicy.java index 28d7e4e87..8b0c1a209 100644 --- a/src/com/android/email/SecurityPolicy.java +++ b/src/com/android/email/SecurityPolicy.java @@ -51,7 +51,7 @@ public class SecurityPolicy { private PolicySet mAggregatePolicy; /* package */ static final PolicySet NO_POLICY_SET = - new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false); + new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0); /** * This projection on Account is for scanning/reading @@ -110,11 +110,14 @@ public class SecurityPolicy { * max password fails take the min * max screen lock time take the min * require remote wipe take the max (logical or) + * password history take the max (strongest mode) + * password expiration take the max (strongest mode) + * password complex chars take the max (strongest mode) * * @return a policy representing the strongest aggregate. If no policy sets are defined, * a lightweight "nothing required" policy will be returned. Never null. */ - /* package */ PolicySet computeAggregatePolicy() { + /*package*/ PolicySet computeAggregatePolicy() { boolean policiesFound = false; int minPasswordLength = Integer.MIN_VALUE; @@ -122,12 +125,15 @@ public class SecurityPolicy { int maxPasswordFails = Integer.MAX_VALUE; int maxScreenLockTime = Integer.MAX_VALUE; boolean requireRemoteWipe = false; + int passwordHistory = Integer.MIN_VALUE; + int passwordExpiration = Integer.MIN_VALUE; + int passwordComplexChars = Integer.MIN_VALUE; Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI, ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null); try { while (c.moveToNext()) { - int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS); + long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS); if (flags != 0) { PolicySet p = new PolicySet(flags); minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength); @@ -138,6 +144,16 @@ public class SecurityPolicy { if (p.mMaxScreenLockTime > 0) { maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime); } + if (p.mPasswordHistory > 0) { + passwordHistory = Math.max(p.mPasswordHistory, passwordHistory); + } + if (p.mPasswordExpiration > 0) { + passwordExpiration = Math.max(p.mPasswordExpiration, passwordExpiration); + } + if (p.mPasswordComplexChars > 0) { + passwordComplexChars = Math.max(p.mPasswordComplexChars, + passwordComplexChars); + } requireRemoteWipe |= p.mRequireRemoteWipe; policiesFound = true; } @@ -151,9 +167,13 @@ public class SecurityPolicy { if (passwordMode == Integer.MIN_VALUE) passwordMode = 0; if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0; if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0; + if (passwordHistory == Integer.MIN_VALUE) passwordHistory = 0; + if (passwordExpiration == Integer.MIN_VALUE) passwordExpiration = 0; + if (passwordComplexChars == Integer.MIN_VALUE) passwordComplexChars = 0; return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails, - maxScreenLockTime, requireRemoteWipe); + maxScreenLockTime, requireRemoteWipe, passwordExpiration, passwordHistory, + passwordComplexChars); } else { return NO_POLICY_SET; } @@ -242,6 +262,19 @@ public class SecurityPolicy { return false; } } + if (policies.mPasswordExpiration > 0) { + // TODO Complete when DPM supports this + } + if (policies.mPasswordHistory > 0) { + if (dpm.getPasswordHistoryLength(mAdminName) < policies.mPasswordHistory) { + return false; + } + } + if (policies.mPasswordComplexChars > 0) { + if (dpm.getPasswordMinimumNonLetter(mAdminName) < policies.mPasswordComplexChars) { + return false; + } + } // password failures are counted locally - no test required here // no check required for remote wipe (it's supported, if we're the admin) @@ -273,6 +306,12 @@ public class SecurityPolicy { dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000); // local wipe (failed passwords limit) dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails); + // password expiration (days until a password expires) + // TODO set this when DPM allows it + // password history length (number of previous passwords that may not be reused) + dpm.setPasswordHistoryLength(mAdminName, policies.mPasswordHistory); + // password minimum complex characters + dpm.setPasswordMinimumNonLetter(mAdminName, policies.mPasswordComplexChars); } } @@ -415,12 +454,27 @@ public class SecurityPolicy { public static final int SCREEN_LOCK_TIME_MAX = 2047; // bit 25: remote wipe capability required private static final int REQUIRE_REMOTE_WIPE = 1 << 25; + // bit 26..35: password expiration (days; 0=not required) + private static final int PASSWORD_EXPIRATION_SHIFT = 26; + private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT; + public static final int PASSWORD_EXPIRATION_MAX = 1023; + // bit 35..42: password history (length; 0=not required) + private static final int PASSWORD_HISTORY_SHIFT = 36; + private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT; + public static final int PASSWORD_HISTORY_MAX = 255; + // bit 42..46: min complex characters (0=not required) + private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44; + private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT; + public static final int PASSWORD_COMPLEX_CHARS_MAX = 31; /*package*/ final int mMinPasswordLength; /*package*/ final int mPasswordMode; /*package*/ final int mMaxPasswordFails; /*package*/ final int mMaxScreenLockTime; /*package*/ final boolean mRequireRemoteWipe; + /*package*/ final int mPasswordExpiration; + /*package*/ final int mPasswordHistory; + /*package*/ final int mPasswordComplexChars; public int getMinPasswordLength() { return mMinPasswordLength; @@ -452,18 +506,29 @@ public class SecurityPolicy { * @throws IllegalArgumentException for illegal arguments. */ public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, - int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException { - // Check against hard limits - // EAS doesn't generate values outside these limits anyway + int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpiration, + int passwordHistory, int passwordComplexChars) throws IllegalArgumentException { + // This value has a hard limit which cannot be supported if exceeded. Setting the + // exceeded value will force isSupported() to return false. if (minPasswordLength > PASSWORD_LENGTH_MAX) { throw new IllegalArgumentException("password length"); } - if (passwordMode < PASSWORD_MODE_NONE || passwordMode > PASSWORD_MODE_STRONG) { + if ((passwordMode != PASSWORD_MODE_NONE) && (passwordMode != PASSWORD_MODE_SIMPLE) && + (passwordMode != PASSWORD_MODE_STRONG)) { throw new IllegalArgumentException("password mode"); } if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) { throw new IllegalArgumentException("screen lock time"); } + if (passwordExpiration > PASSWORD_EXPIRATION_MAX) { + throw new IllegalArgumentException("password expiration"); + } + if (passwordHistory > PASSWORD_HISTORY_MAX) { + throw new IllegalArgumentException("password history"); + } + if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) { + throw new IllegalArgumentException("complex chars"); + } // This value can be reduced (which actually increases security) if necessary if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) { maxPasswordFails = PASSWORD_MAX_FAILS_MAX; @@ -472,12 +537,14 @@ public class SecurityPolicy { if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) { maxScreenLockTime = SCREEN_LOCK_TIME_MAX; } - mMinPasswordLength = minPasswordLength; mPasswordMode = passwordMode; mMaxPasswordFails = maxPasswordFails; mMaxScreenLockTime = maxScreenLockTime; mRequireRemoteWipe = requireRemoteWipe; + mPasswordExpiration = passwordExpiration; + mPasswordHistory = passwordHistory; + mPasswordComplexChars = passwordComplexChars; } /** @@ -491,16 +558,22 @@ public class SecurityPolicy { /** * Create from values encoded in an account flags int */ - public PolicySet(int flags) { + public PolicySet(long flags) { mMinPasswordLength = - (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT; + (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT); mPasswordMode = - (flags & PASSWORD_MODE_MASK); + (int) (flags & PASSWORD_MODE_MASK); mMaxPasswordFails = - (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT; + (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT); mMaxScreenLockTime = - (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT; + (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT); mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE); + mPasswordExpiration = + (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT); + mPasswordHistory = + (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT); + mPasswordComplexChars = + (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT); } /** @@ -531,7 +604,7 @@ public class SecurityPolicy { */ public boolean writeAccount(Account account, String syncKey, boolean update, Context context) { - int newFlags = hashCode(); + long newFlags = getSecurityCode(); boolean dirty = (newFlags != account.mSecurityFlags); account.mSecurityFlags = newFlags; account.mSecuritySyncKey = syncKey; @@ -548,32 +621,33 @@ public class SecurityPolicy { return dirty; } - @Override + @Override public boolean equals(Object o) { if (o instanceof PolicySet) { PolicySet other = (PolicySet)o; - return (this.mMinPasswordLength == other.mMinPasswordLength) - && (this.mPasswordMode == other.mPasswordMode) - && (this.mMaxPasswordFails == other.mMaxPasswordFails) - && (this.mMaxScreenLockTime == other.mMaxScreenLockTime) - && (this.mRequireRemoteWipe == other.mRequireRemoteWipe); + return (this.getSecurityCode() == other.getSecurityCode()); } return false; } - /** - * Note: the hash code is defined as the encoding used in Account - */ @Override public int hashCode() { - int flags = 0; - flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT; + long code = getSecurityCode(); + return (int) code; + } + + public long getSecurityCode() { + long flags = 0; + flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT; flags |= mPasswordMode; - flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT; - flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT; + flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT; + flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT; if (mRequireRemoteWipe) { flags |= REQUIRE_REMOTE_WIPE; } + flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT; + flags |= (long)mPasswordExpiration << PASSWORD_EXPIRATION_SHIFT; + flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT; return flags; } @@ -581,7 +655,10 @@ public class SecurityPolicy { public String toString() { return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max=" - + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}"; + + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + + " pw-expiration=" + mPasswordExpiration + + " pw-history=" + mPasswordHistory + + " pw-complex-chars=" + mPasswordComplexChars + "}"; } } diff --git a/src/com/android/email/provider/EmailContent.java b/src/com/android/email/provider/EmailContent.java index 890102d82..1741a2c3f 100644 --- a/src/com/android/email/provider/EmailContent.java +++ b/src/com/android/email/provider/EmailContent.java @@ -860,7 +860,7 @@ public abstract class EmailContent { public String mRingtoneUri; public String mProtocolVersion; public int mNewMessageCount; - public int mSecurityFlags; + public long mSecurityFlags; public String mSecuritySyncKey; public String mSignature; @@ -988,7 +988,7 @@ public abstract class EmailContent { mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN); mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN); mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN); - mSecurityFlags = cursor.getInt(CONTENT_SECURITY_FLAGS_COLUMN); + mSecurityFlags = cursor.getLong(CONTENT_SECURITY_FLAGS_COLUMN); mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN); mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN); return this; @@ -1549,7 +1549,7 @@ public abstract class EmailContent { dest.writeString(mRingtoneUri); dest.writeString(mProtocolVersion); dest.writeInt(mNewMessageCount); - dest.writeInt(mSecurityFlags); + dest.writeLong(mSecurityFlags); dest.writeString(mSecuritySyncKey); dest.writeString(mSignature); @@ -1588,7 +1588,7 @@ public abstract class EmailContent { mRingtoneUri = in.readString(); mProtocolVersion = in.readString(); mNewMessageCount = in.readInt(); - mSecurityFlags = in.readInt(); + mSecurityFlags = in.readLong(); mSecuritySyncKey = in.readString(); mSignature = in.readString(); diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java index 6b88c80e3..bbb3464ed 100644 --- a/src/com/android/exchange/adapter/ProvisionParser.java +++ b/src/com/android/exchange/adapter/ProvisionParser.java @@ -65,6 +65,10 @@ public class ProvisionParser extends Parser { int passwordMode = PolicySet.PASSWORD_MODE_NONE; int maxPasswordFails = 0; int maxScreenLockTime = 0; + int passwordExpiration = 0; + int passwordHistory = 0; + int passwordComplexChars = 0; + boolean canSupport = true; while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) { boolean supported = true; @@ -91,6 +95,16 @@ public class ProvisionParser extends Parser { case Tags.PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS: maxPasswordFails = getValueInt(); break; + case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION: + passwordExpiration = getValueInt(); + // We don't yet support this + if (passwordExpiration > 0) { + supported = false; + } + break; + case Tags.PROVISION_DEVICE_PASSWORD_HISTORY: + passwordHistory = getValueInt(); + break; case Tags.PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD: // Ignore this unless there's any MSFT documentation for what this means // Hint: I haven't seen any that's more specific than "simple" @@ -123,8 +137,6 @@ public class ProvisionParser extends Parser { // The following policies, if true, can't be supported at the moment case Tags.PROVISION_DEVICE_ENCRYPTION_ENABLED: case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED: - case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION: - case Tags.PROVISION_DEVICE_PASSWORD_HISTORY: case Tags.PROVISION_REQUIRE_DEVICE_ENCRYPTION: case Tags.PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES: case Tags.PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES: @@ -143,7 +155,9 @@ public class ProvisionParser extends Parser { break; // Complex character setting is only used if we're in "strong" (alphanumeric) mode case Tags.PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS: - if ((passwordMode == PolicySet.PASSWORD_MODE_STRONG) && (getValueInt() > 0)) { + passwordComplexChars = getValueInt(); + if ((passwordMode == PolicySet.PASSWORD_MODE_STRONG) && + (passwordComplexChars > 0)) { supported = false; } break; @@ -190,7 +204,8 @@ public class ProvisionParser extends Parser { } mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode, - maxPasswordFails, maxScreenLockTime, true); + maxPasswordFails, maxScreenLockTime, true, passwordExpiration, passwordHistory, + passwordComplexChars); } /** @@ -219,6 +234,9 @@ public class ProvisionParser extends Parser { int mPasswordMode = PolicySet.PASSWORD_MODE_NONE; int mMaxPasswordFails = 0; int mMaxScreenLockTime = 0; + int mPasswordExpiration = 0; + int mPasswordHistory = 0; + int mPasswordComplexChars = 0; } /*package*/ void parseProvisionDocXml(String doc) throws IOException { @@ -243,7 +261,8 @@ public class ProvisionParser extends Parser { } mPolicySet = new PolicySet(sps.mMinPasswordLength, sps.mPasswordMode, sps.mMaxPasswordFails, - sps.mMaxScreenLockTime, true); + sps.mMaxScreenLockTime, true, sps.mPasswordExpiration, sps.mPasswordHistory, + sps.mPasswordComplexChars); } /** diff --git a/tests/src/com/android/email/SecurityPolicyTests.java b/tests/src/com/android/email/SecurityPolicyTests.java index 105e656ed..986c990b3 100644 --- a/tests/src/com/android/email/SecurityPolicyTests.java +++ b/tests/src/com/android/email/SecurityPolicyTests.java @@ -33,14 +33,17 @@ import android.test.suitebuilder.annotation.SmallTest; /** * This is a series of unit tests for backup/restore of the SecurityPolicy class. + * You can run this entire test case with: + * runtest -c com.android.email.SecurityPolicyTests email */ + @MediumTest public class SecurityPolicyTests extends ProviderTestCase2 { private Context mMockContext; private static final PolicySet EMPTY_POLICY_SET = - new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false); + new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0); public SecurityPolicyTests() { super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY); @@ -99,18 +102,18 @@ public class SecurityPolicyTests extends ProviderTestCase2 { // We know that EMPTY_POLICY_SET doesn't generate an Exception or we wouldn't be here // Try some illegal parameters try { - new PolicySet(100, PolicySet.PASSWORD_MODE_NONE, 0, 0, false); + new PolicySet(100, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0); fail("Too-long password allowed"); } catch (IllegalArgumentException e) { } try { - new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG + 1, 0, 0, false); + new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG + 1, 0, 0, false, 0, 0, 0); fail("Illegal password mode allowed"); } catch (IllegalArgumentException e) { } try { new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, - PolicySet.SCREEN_LOCK_TIME_MAX + 1, false); + PolicySet.SCREEN_LOCK_TIME_MAX + 1, false, 0, 0, 0); fail("Too-long screen lock time allowed"); } catch (IllegalArgumentException e) { } @@ -123,7 +126,7 @@ public class SecurityPolicyTests extends ProviderTestCase2 { SecurityPolicy sp = getSecurityPolicy(); // with no accounts, should return empty set - assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy())); + assertEquals(EMPTY_POLICY_SET, sp.computeAggregatePolicy()); // with accounts having no security, empty set Account a1 = ProviderTestUtils.setupAccount("no-sec-1", false, mMockContext); @@ -132,19 +135,19 @@ public class SecurityPolicyTests extends ProviderTestCase2 { Account a2 = ProviderTestUtils.setupAccount("no-sec-2", false, mMockContext); a2.mSecurityFlags = 0; a2.save(mMockContext); - assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy())); + assertEquals(EMPTY_POLICY_SET, sp.computeAggregatePolicy()); // with a single account in security mode, should return same security as in account // first test with partially-populated policies Account a3 = ProviderTestUtils.setupAccount("sec-3", false, mMockContext); - PolicySet p3ain = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false); + PolicySet p3ain = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0); p3ain.writeAccount(a3, null, true, mMockContext); PolicySet p3aout = sp.computeAggregatePolicy(); assertNotNull(p3aout); assertEquals(p3ain, p3aout); // Repeat that test with fully-populated policies - PolicySet p3bin = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 15, 16, false); + PolicySet p3bin = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 15, 16, false, 1, 2, 3); p3bin.writeAccount(a3, null, true, mMockContext); PolicySet p3bout = sp.computeAggregatePolicy(); assertNotNull(p3bout); @@ -154,7 +157,7 @@ public class SecurityPolicyTests extends ProviderTestCase2 { // pw length and pw mode - max logic - will change because larger #s here // fail count and lock timer - min logic - will *not* change because larger #s here // wipe required - OR logic - will *not* change here because false - PolicySet p4in = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false); + PolicySet p4in = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 0, 5, 7); Account a4 = ProviderTestUtils.setupAccount("sec-4", false, mMockContext); p4in.writeAccount(a4, null, true, mMockContext); PolicySet p4out = sp.computeAggregatePolicy(); @@ -163,13 +166,17 @@ public class SecurityPolicyTests extends ProviderTestCase2 { assertEquals(PolicySet.PASSWORD_MODE_STRONG, p4out.mPasswordMode); assertEquals(15, p4out.mMaxPasswordFails); assertEquals(16, p4out.mMaxScreenLockTime); + assertEquals(1, p4out.mPasswordExpiration); + assertEquals(5, p4out.mPasswordHistory); + assertEquals(7, p4out.mPasswordComplexChars); assertFalse(p4out.mRequireRemoteWipe); // add another account which mixes it up (the remaining fields will change) // pw length and pw mode - max logic - will *not* change because smaller #s here // fail count and lock timer - min logic - will change because smaller #s here + // password exp will change (max logic), but history and complex chars will be as before // wipe required - OR logic - will change here because true - PolicySet p5in = new PolicySet(4, PolicySet.PASSWORD_MODE_NONE, 5, 6, true); + PolicySet p5in = new PolicySet(4, PolicySet.PASSWORD_MODE_NONE, 5, 6, true, 6, 0, 0); Account a5 = ProviderTestUtils.setupAccount("sec-5", false, mMockContext); p5in.writeAccount(a5, null, true, mMockContext); PolicySet p5out = sp.computeAggregatePolicy(); @@ -178,6 +185,9 @@ public class SecurityPolicyTests extends ProviderTestCase2 { assertEquals(PolicySet.PASSWORD_MODE_STRONG, p5out.mPasswordMode); assertEquals(5, p5out.mMaxPasswordFails); assertEquals(6, p5out.mMaxScreenLockTime); + assertEquals(6, p5out.mPasswordExpiration); + assertEquals(5, p4out.mPasswordHistory); + assertEquals(7, p4out.mPasswordComplexChars); assertTrue(p5out.mRequireRemoteWipe); } @@ -197,7 +207,7 @@ public class SecurityPolicyTests extends ProviderTestCase2 { Account a2 = ProviderTestUtils.setupAccount("no-sec-2", false, mMockContext); a2.mSecurityFlags = 0; a2.save(mMockContext); - assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy())); + assertEquals(EMPTY_POLICY_SET, sp.computeAggregatePolicy()); } /** @@ -206,40 +216,85 @@ public class SecurityPolicyTests extends ProviderTestCase2 { */ @SmallTest public void testFieldIsolation() { - PolicySet p = new PolicySet(PolicySet.PASSWORD_LENGTH_MAX, 0, 0, 0, false); + PolicySet p = new PolicySet(PolicySet.PASSWORD_LENGTH_MAX, 0, 0, 0, false, 0, 0 ,0); assertEquals(PolicySet.PASSWORD_LENGTH_MAX, p.mMinPasswordLength); assertEquals(0, p.mPasswordMode); assertEquals(0, p.mMaxPasswordFails); assertEquals(0, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); assertFalse(p.mRequireRemoteWipe); - p = new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG, 0, 0, false); + p = new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG, 0, 0, false, 0, 0, 0); assertEquals(0, p.mMinPasswordLength); assertEquals(PolicySet.PASSWORD_MODE_STRONG, p.mPasswordMode); assertEquals(0, p.mMaxPasswordFails); assertEquals(0, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); assertFalse(p.mRequireRemoteWipe); - p = new PolicySet(0, 0, PolicySet.PASSWORD_MAX_FAILS_MAX, 0, false); + p = new PolicySet(0, 0, PolicySet.PASSWORD_MAX_FAILS_MAX, 0, false, 0, 0, 0); assertEquals(0, p.mMinPasswordLength); assertEquals(0, p.mPasswordMode); assertEquals(PolicySet.PASSWORD_MAX_FAILS_MAX, p.mMaxPasswordFails); assertEquals(0, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); assertFalse(p.mRequireRemoteWipe); - p = new PolicySet(0, 0, 0, PolicySet.SCREEN_LOCK_TIME_MAX, false); + p = new PolicySet(0, 0, 0, PolicySet.SCREEN_LOCK_TIME_MAX, false, 0, 0, 0); assertEquals(0, p.mMinPasswordLength); assertEquals(0, p.mPasswordMode); assertEquals(0, p.mMaxPasswordFails); assertEquals(PolicySet.SCREEN_LOCK_TIME_MAX, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); assertFalse(p.mRequireRemoteWipe); - p = new PolicySet(0, 0, 0, 0, true); + p = new PolicySet(0, 0, 0, 0, true, 0, 0, 0); assertEquals(0, p.mMinPasswordLength); assertEquals(0, p.mPasswordMode); assertEquals(0, p.mMaxPasswordFails); assertEquals(0, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); assertTrue(p.mRequireRemoteWipe); + + p = new PolicySet(0, 0, 0, 0, false, PolicySet.PASSWORD_EXPIRATION_MAX, 0, 0); + assertEquals(0, p.mMinPasswordLength); + assertEquals(0, p.mPasswordMode); + assertEquals(0, p.mMaxPasswordFails); + assertEquals(0, p.mMaxScreenLockTime); + assertEquals(PolicySet.PASSWORD_EXPIRATION_MAX, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); + assertFalse(p.mRequireRemoteWipe); + + p = new PolicySet(0, 0, 0, 0, false, 0, PolicySet.PASSWORD_HISTORY_MAX, 0); + assertEquals(0, p.mMinPasswordLength); + assertEquals(0, p.mPasswordMode); + assertEquals(0, p.mMaxPasswordFails); + assertEquals(0, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(PolicySet.PASSWORD_HISTORY_MAX, p.mPasswordHistory); + assertEquals(0, p.mPasswordComplexChars); + assertFalse(p.mRequireRemoteWipe); + + p = new PolicySet(0, 0, 0, 0, false, 0, 0, PolicySet.PASSWORD_COMPLEX_CHARS_MAX); + assertEquals(0, p.mMinPasswordLength); + assertEquals(0, p.mPasswordMode); + assertEquals(0, p.mMaxPasswordFails); + assertEquals(0, p.mMaxScreenLockTime); + assertEquals(0, p.mPasswordExpiration); + assertEquals(0, p.mPasswordHistory); + assertEquals(PolicySet.PASSWORD_COMPLEX_CHARS_MAX, p.mPasswordComplexChars); + assertFalse(p.mRequireRemoteWipe); } /** @@ -247,7 +302,7 @@ public class SecurityPolicyTests extends ProviderTestCase2 { */ @SmallTest public void testAccountEncoding() { - PolicySet p1 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true); + PolicySet p1 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9); Account a = new Account(); final String SYNC_KEY = "test_sync_key"; p1.writeAccount(a, SYNC_KEY, false, null); @@ -256,18 +311,16 @@ public class SecurityPolicyTests extends ProviderTestCase2 { } /** - * Test equality & hash. Note, the tests for inequality are poor, as each field should + * Test equality. Note, the tests for inequality are poor, as each field should * be tested individually. */ @SmallTest - public void testEqualsAndHash() { - PolicySet p1 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true); - PolicySet p2 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true); - PolicySet p3 = new PolicySet(2, PolicySet.PASSWORD_MODE_SIMPLE, 5, 6, true); + public void testEquals() { + PolicySet p1 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9); + PolicySet p2 = new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9); + PolicySet p3 = new PolicySet(2, PolicySet.PASSWORD_MODE_SIMPLE, 5, 6, true, 7, 8, 9); assertTrue(p1.equals(p2)); assertFalse(p2.equals(p3)); - assertTrue(p1.hashCode() == p2.hashCode()); - assertFalse(p2.hashCode() == p3.hashCode()); } /** @@ -328,11 +381,11 @@ public class SecurityPolicyTests extends ProviderTestCase2 { */ public void testDisableAdmin() { Account a1 = ProviderTestUtils.setupAccount("disable-1", false, mMockContext); - PolicySet p1 = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false); + PolicySet p1 = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0); p1.writeAccount(a1, "sync-key-1", true, mMockContext); Account a2 = ProviderTestUtils.setupAccount("disable-2", false, mMockContext); - PolicySet p2 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false); + PolicySet p2 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0); p2.writeAccount(a2, "sync-key-2", true, mMockContext); Account a3 = ProviderTestUtils.setupAccount("disable-3", false, mMockContext);