diff options
3 files changed, 71 insertions, 0 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 97879b80ea06..1789b635a6fc 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3020,6 +3020,10 @@ public class DevicePolicyManager { * * <p><strong>Note:</strong> Specifying password requirements using this method clears the * password complexity requirements set using {@link #setRequiredPasswordComplexity(int)}. + * If this method is called on the {@link DevicePolicyManager} instance returned by + * {@link #getParentProfileInstance(ComponentName)}, then password complexity requirements + * set on the primary {@link DevicePolicyManager} must be cleared first by calling + * {@link #setRequiredPasswordComplexity} with {@link #PASSWORD_COMPLEXITY_NONE) first. * * @deprecated Prefer using {@link #setRequiredPasswordComplexity(int)}, to require a password * that satisfies a complexity level defined by the platform, rather than specifying custom @@ -3039,6 +3043,9 @@ public class DevicePolicyManager { * calling app is targeting {@link android.os.Build.VERSION_CODES#S} and above, * and is calling the method the {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)}. + * @throws IllegalStateException if the caller is trying to set password quality on the parent + * {@link DevicePolicyManager} instance while password complexity was set on the + * primary {@link DevicePolicyManager} instance. */ @Deprecated public void setPasswordQuality(@NonNull ComponentName admin, int quality) { @@ -4055,10 +4062,18 @@ public class DevicePolicyManager { * <p><strong>Note:</strong> Specifying password requirements using this method clears any * password requirements set using the obsolete {@link #setPasswordQuality(ComponentName, int)} * and any of its associated methods. + * Additionally, if there are password requirements set using the obsolete + * {@link #setPasswordQuality(ComponentName, int)} on the parent {@code DevicePolicyManager} + * instance, they must be cleared by calling {@link #setPasswordQuality(ComponentName, int)} + * with {@link #PASSWORD_QUALITY_UNSPECIFIED} on that instance prior to setting complexity + * requirement for the managed profile. * * @throws SecurityException if the calling application is not a device owner or a profile * owner. * @throws IllegalArgumentException if the complexity level is not one of the four above. + * @throws IllegalStateException if the caller is trying to set password complexity while there + * are password requirements specified using {@link #setPasswordQuality(ComponentName, int)} + * on the parent {@code DevicePolicyManager} instance. */ public void setRequiredPasswordComplexity(@PasswordComplexity int passwordComplexity) { if (mService == null) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 261264648b27..cdd95999ccf1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3413,6 +3413,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); + + // If setPasswordQuality is called on the parent, ensure that + // the primary admin does not have password complexity state (this is an + // unsupported state). + if (parent) { + final ActiveAdmin primaryAdmin = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, false); + final boolean hasComplexitySet = + primaryAdmin.mPasswordComplexity != PASSWORD_COMPLEXITY_NONE; + Preconditions.checkState(!hasComplexitySet, + "Cannot set password quality when complexity is set on the primary admin." + + " Set the primary admin's complexity to NONE first."); + } mInjector.binderWithCleanCallingIdentity(() -> { final PasswordPolicy passwordPolicy = ap.mPasswordPolicy; if (passwordPolicy.quality != quality) { @@ -4378,6 +4391,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final ActiveAdmin admin = getParentOfAdminIfRequired( getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParent); if (admin.mPasswordComplexity != passwordComplexity) { + // We require the caller to explicitly clear any password quality requirements set + // on the parent DPM instance, to avoid the case where password requirements are + // specified in the form of quality on the parent but complexity on the profile + // itself. + if (!calledOnParent) { + final boolean hasQualityRequirementsOnParent = admin.hasParentActiveAdmin() + && admin.getParentActiveAdmin().mPasswordPolicy.quality + != PASSWORD_QUALITY_UNSPECIFIED; + Preconditions.checkState(!hasQualityRequirementsOnParent, + "Password quality is set on the parent when attempting to set password" + + "complexity. Clear the quality by setting the password quality " + + "on the parent to PASSWORD_QUALITY_UNSPECIFIED first"); + } + mInjector.binderWithCleanCallingIdentity(() -> { admin.mPasswordComplexity = passwordComplexity; // Reset the password policy. 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 c1b1133dbb22..ed4472000e87 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -6891,6 +6891,35 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); } + @Test + public void testSetRequiredPasswordComplexityFailsWithQualityOnParent() throws Exception { + final int managedProfileUserId = CALLER_USER_HANDLE; + final int managedProfileAdminUid = + UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); + mContext.binder.callingUid = managedProfileAdminUid; + addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R); + + parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); + + assertThrows(IllegalStateException.class, + () -> dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH)); + } + + @Test + public void testSetQualityOnParentFailsWithComplexityOnProfile() throws Exception { + final int managedProfileUserId = CALLER_USER_HANDLE; + final int managedProfileAdminUid = + UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); + mContext.binder.callingUid = managedProfileAdminUid; + addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.R); + + dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH); + + assertThrows(IllegalStateException.class, + () -> parentDpm.setPasswordQuality(admin1, + DevicePolicyManager.PASSWORD_QUALITY_COMPLEX)); + } + private void setUserUnlocked(int userHandle, boolean unlocked) { when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked); } |