diff options
9 files changed, 445 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt index 2c5f8b526db2..7edce0a25c4f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6597,6 +6597,7 @@ package android.app.admin { method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName); method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); + method public int getPasswordComplexity(); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -6853,6 +6854,10 @@ package android.app.admin { field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1 field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2 field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning"; + field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000 + field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000 + field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000 + field public static final int PASSWORD_COMPLEXITY_NONE = 0; // 0x0 field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000 field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000 field public static final int PASSWORD_QUALITY_BIOMETRIC_WEAK = 32768; // 0x8000 diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 76a16cd24fd3..670f8db7c588 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; import android.annotation.IntDef; @@ -1382,6 +1383,73 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PASSWORD"; /** + * Constant for {@link #getPasswordComplexity()}: no password. + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + */ + public static final int PASSWORD_COMPLEXITY_NONE = 0; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>pattern + * <li>PIN with repeating (4444) or ordered (1234, 4321, 2468) sequences + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_SOMETHING + * @see #PASSWORD_QUALITY_NUMERIC + */ + public static final int PASSWORD_COMPLEXITY_LOW = 0x10000; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at + * least 4 + * <li>alphabetic, length at least 4 + * <li>alphanumeric, length at least 4 + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX + * @see #PASSWORD_QUALITY_ALPHABETIC + * @see #PASSWORD_QUALITY_ALPHANUMERIC + */ + public static final int PASSWORD_COMPLEXITY_MEDIUM = 0x30000; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at + * least 4 + * <li>alphabetic, length at least 6 + * <li>alphanumeric, length at least 6 + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX + * @see #PASSWORD_QUALITY_ALPHABETIC + * @see #PASSWORD_QUALITY_ALPHANUMERIC + */ + public static final int PASSWORD_COMPLEXITY_HIGH = 0x50000; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PASSWORD_COMPLEXITY_"}, value = { + PASSWORD_COMPLEXITY_NONE, + PASSWORD_COMPLEXITY_LOW, + PASSWORD_COMPLEXITY_MEDIUM, + PASSWORD_COMPLEXITY_HIGH, + }) + public @interface PasswordComplexity {} + + /** * Activity action: have the user enter a new password for the parent profile. * If the intent is launched from within a managed profile, this will trigger * entering a new password for the parent of the profile. In all other cases @@ -3106,6 +3174,33 @@ public class DevicePolicyManager { } /** + * Returns how complex the current user's screen lock is. + * + * <p>Note that when called from a profile which uses an unified challenge with its parent, the + * screen lock complexity of the parent will be returned. However, this API does not support + * explicitly querying the parent profile screen lock complexity via {@link + * #getParentProfileInstance}. + * + * @throws IllegalStateException if the user is not unlocked. + * @throws SecurityException if the calling application does not have the permission + * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY} + */ + @PasswordComplexity + @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) + public int getPasswordComplexity() { + throwIfParentInstance("getPasswordComplexity"); + if (mService == null) { + return PASSWORD_COMPLEXITY_NONE; + } + + try { + return mService.getPasswordComplexity(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * When called by a profile owner of a managed profile returns true if the profile uses unified * challenge with its parent user. * diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 74cb22c3e645..568becfcdd1a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -82,6 +82,7 @@ interface IDevicePolicyManager { boolean isActivePasswordSufficient(int userHandle, boolean parent); boolean isProfileActivePasswordSufficientForParent(int userHandle); + int getPasswordComplexity(); boolean isUsingUnifiedPassword(in ComponentName admin); int getCurrentFailedPasswordAttempts(int userHandle, boolean parent); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent); diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 5fee853275fb..8b41755f6dec 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -16,8 +16,14 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +41,8 @@ public class PasswordMetrics implements Parcelable { // consider it a complex PIN/password. public static final int MAX_ALLOWED_SEQUENCE = 3; + // TODO(b/120536847): refactor isActivePasswordSufficient logic so that the actual password + // quality is not overwritten public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; public int length = 0; public int letters = 0; @@ -46,6 +54,10 @@ public class PasswordMetrics implements Parcelable { public PasswordMetrics() {} + public PasswordMetrics(int quality) { + this.quality = quality; + } + public PasswordMetrics(int quality, int length) { this.quality = quality; this.length = length; @@ -173,6 +185,15 @@ public class PasswordMetrics implements Parcelable { && this.nonLetter == o.nonLetter; } + private boolean satisfiesBucket(PasswordMetrics... bucket) { + for (PasswordMetrics metrics : bucket) { + if (this.quality == metrics.quality) { + return this.length >= metrics.length; + } + } + return false; + } + /* * Returns the maximum length of a sequential characters. A sequence is defined as * monotonically increasing characters with a constant interval or the same character repeated. @@ -254,4 +275,99 @@ public class PasswordMetrics implements Parcelable { return 0; } } + + /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */ + @PasswordComplexity + public int determineComplexity() { + for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) { + if (satisfiesBucket(bucket.getMetrics())) { + return bucket.mComplexityLevel; + } + } + return PASSWORD_COMPLEXITY_NONE; + } + + /** + * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}. + */ + public static class PasswordComplexityBucket { + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket HIGH = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_HIGH, + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 8)); + + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket MEDIUM = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_MEDIUM, + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 4)); + + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket LOW = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_LOW, + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)); + + /** + * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}. + */ + private static final PasswordComplexityBucket NONE = + new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics()); + + /** Array containing all buckets from high to low. */ + private static final PasswordComplexityBucket[] BUCKETS = + new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW}; + + @PasswordComplexity + private final int mComplexityLevel; + private final PasswordMetrics[] mMetrics; + + private PasswordComplexityBucket(@PasswordComplexity int complexityLevel, + PasswordMetrics... metrics) { + this.mComplexityLevel = complexityLevel; + this.mMetrics = metrics; + } + + /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */ + public PasswordMetrics[] getMetrics() { + return mMetrics; + } + + /** Returns the bucket that {@code complexityLevel} represents. */ + public static PasswordComplexityBucket complexityLevelToBucket( + @PasswordComplexity int complexityLevel) { + for (PasswordComplexityBucket bucket : BUCKETS) { + if (bucket.mComplexityLevel == complexityLevel) { + return bucket; + } + } + return NONE; + } + } } diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index d289f1f5defc..9b5b725a3bed 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -16,9 +16,16 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import android.app.admin.PasswordMetrics.PasswordComplexityBucket; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -164,4 +171,126 @@ public class PasswordMetricsTest { } + + @Test + public void testConstructQuality() { + PasswordMetrics expected = new PasswordMetrics(); + expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; + + PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); + + assertEquals(expected, actual); + } + + @Test + public void testDetermineComplexity_none() { + assertEquals(PASSWORD_COMPLEXITY_NONE, + PasswordMetrics.computeForPassword("").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowSomething() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowNumeric() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("1234").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("124").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("a!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("a!1").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("1238").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("ab!c").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("ab!1").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("12389647!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("alphabetic!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("alphanumeric123!").determineComplexity()); + } + + @Test + public void testComplexityLevelToBucket_none() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_NONE).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_low() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_LOW).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_medium() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_MEDIUM).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_high() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_HIGH).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity()); + } + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index da0a9fbe44f9..d8225b38487c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -99,6 +99,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { } @Override + public int getPasswordComplexity() { + return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + } + + @Override public void installUpdateFromFile(ComponentName admin, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ce540025ca09..ab27d21dc565 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; +import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; @@ -56,6 +57,7 @@ import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLE import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; @@ -116,6 +118,7 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; @@ -4734,6 +4737,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + @PasswordComplexity + public int getPasswordComplexity() { + final int callingUserId = mInjector.userHandleGetCallingUserId(); + enforceUserUnlocked(callingUserId); + mContext.enforceCallingOrSelfPermission( + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY, + "Must have " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY + " permission."); + + synchronized (getLockObject()) { + int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false); + PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId); + return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity(); + } + } + + @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { enforceFullCrossUsersPermission(userHandle); synchronized (getLockObject()) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index c3a0ddaff85f..729fac5b1dff 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -21,6 +21,9 @@ import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY; import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY; @@ -48,6 +51,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; +import static org.testng.Assert.assertThrows; import android.Manifest.permission; import android.annotation.RawRes; @@ -5133,6 +5137,71 @@ public class DevicePolicyManagerTest extends DpmTestBase { }); } + public void testGetPasswordComplexity_securityExceptionIfParentInstance() { + assertThrows(SecurityException.class, + () -> new DevicePolicyManagerTestable( + mServiceContext, + dpms, + /* parentInstance= */ true) + .getPasswordComplexity()); + } + + public void testGetPasswordComplexity_illegalStateExceptionIfLocked() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(false); + assertThrows(IllegalStateException.class, () -> dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_securityExceptionWithoutPermissions() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + assertThrows(SecurityException.class, () -> dpm.getPasswordComplexity()); + } + + + public void testGetPasswordComplexity_currentUserNoPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(DpmMockContext.CALLER_USER_HANDLE); + + assertEquals(PASSWORD_COMPLEXITY_NONE, dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_currentUserHasPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(DpmMockContext.CALLER_USER_HANDLE); + dpms.mUserPasswordMetrics.put( + DpmMockContext.CALLER_USER_HANDLE, + PasswordMetrics.computeForPassword("asdf")); + + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_unifiedChallengeReturnsParentUserPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + + UserInfo parentUser = new UserInfo(); + parentUser.id = DpmMockContext.CALLER_USER_HANDLE + 10; + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(parentUser.id); + + dpms.mUserPasswordMetrics.put( + DpmMockContext.CALLER_USER_HANDLE, + PasswordMetrics.computeForPassword("asdf")); + dpms.mUserPasswordMetrics.put( + parentUser.id, + PasswordMetrics.computeForPassword("parentUser")); + + assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity()); + } + private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) { final long ident = mServiceContext.binder.clearCallingIdentity(); mServiceContext.binder.callingUid = diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java index 3da61d69c742..4982d6e8817f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java @@ -26,7 +26,12 @@ public class DevicePolicyManagerTestable extends DevicePolicyManager { public DevicePolicyManagerTestable(DpmMockContext context, DevicePolicyManagerServiceTestable dpms) { - super(context, dpms, /* parentInstance = */ false); + this(context, dpms, /* parentInstance= */ false); + } + + public DevicePolicyManagerTestable(DpmMockContext context, + DevicePolicyManagerServiceTestable dpms, boolean parentInstance) { + super(context, dpms, parentInstance); this.dpms = dpms; } |