Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7da93284 authored by Eran Messeri's avatar Eran Messeri
Browse files

DPM: Prevent mixing password quality and complexity

Prevent a mix of password requirements in the form of quality and
complexity, except in one approved scenario (password quality on the
profile DPM instance, complexity on the parent DPM instance).

Bug: 165573442
Test: atest FrameworksServicesTests:DevicePolicyManagerTest
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.ManagedProfilePasswordTest
Test: atest com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testResetPasswordWithToken com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testGetPasswordExpiration
Test: atest com.android.cts.devicepolicy.MixedManagedProfileOwnerTestApi30
Change-Id: I67c15377f12c6fb0c0c5756d279fd44623158781
parent 9868762e
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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) {
+27 −0
Original line number Diff line number Diff line
@@ -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.
+29 −0
Original line number Diff line number Diff line
@@ -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);
    }