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

Commit 9e0a3341 authored by Eran Messeri's avatar Eran Messeri Committed by Android (Google) Code Review
Browse files

Merge "Do not apply password quality to the parent"

parents 32fc168f 094c98e2
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -98,6 +98,8 @@ class ActiveAdmin {
    private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
    private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
    private static final String TAG_PASSWORD_QUALITY = "password-quality";
    private static final String TAG_PASSWORD_QUALITY_APPLIES_TO_PARENT =
            "password-quality-applies-parent";
    private static final String TAG_POLICIES = "policies";
    private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
            "cross-profile-widget-providers";
@@ -143,6 +145,7 @@ class ActiveAdmin {

    @NonNull
    PasswordPolicy mPasswordPolicy = new PasswordPolicy();
    boolean mPasswordPolicyAppliesToParent = true;

    @DevicePolicyManager.PasswordComplexity
    int mPasswordComplexity = PASSWORD_COMPLEXITY_NONE;
@@ -333,6 +336,9 @@ class ActiveAdmin {
                writeAttributeValueToXml(
                        out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
            }

            writeAttributeValueToXml(out, TAG_PASSWORD_QUALITY_APPLIES_TO_PARENT,
                    mPasswordPolicyAppliesToParent);
        }
        if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
            writeAttributeValueToXml(
@@ -626,6 +632,9 @@ class ActiveAdmin {
            } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
                mPasswordPolicy.nonLetter = Integer.parseInt(
                        parser.getAttributeValue(null, ATTR_VALUE));
            } else if (TAG_PASSWORD_QUALITY_APPLIES_TO_PARENT.equals(tag)) {
                mPasswordPolicyAppliesToParent = Boolean.parseBoolean(
                        parser.getAttributeValue(null, ATTR_VALUE));
            } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
                maximumTimeToUnlock = Long.parseLong(
                        parser.getAttributeValue(null, ATTR_VALUE));
@@ -995,6 +1004,9 @@ class ActiveAdmin {
        pw.print("minimumPasswordNonLetter=");
        pw.println(mPasswordPolicy.nonLetter);

        pw.print("passwordPolicyAppliesToParent=");
        pw.println(mPasswordPolicyAppliesToParent);

        pw.print("maximumTimeToUnlock=");
        pw.println(maximumTimeToUnlock);

+55 −5
Original line number Diff line number Diff line
@@ -155,10 +155,12 @@ import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.UnsafeStateException;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.EnabledSince;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -527,6 +529,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
    private static final long USE_SET_LOCATION_ENABLED = 117835097L;
    /**
     * Admin apps targeting Android S+ may not use
     * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
     * on the {@code DevicePolicyManager} instance obtained by calling
     * {@link android.app.admin.DevicePolicyManager#getParentProfileInstance}.
     * Instead, they should use
     * {@link android.app.admin.DevicePolicyManager#setRequiredPasswordComplexity} to set
     * coarse-grained password requirements device-wide.
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
    private static final long PREVENT_SETTING_PASSWORD_QUALITY_ON_PARENT = 165573442L;
    final Context mContext;
    final Injector mInjector;
    final IPackageManager mIPackageManager;
@@ -1396,6 +1411,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        public long systemCurrentTimeMillis() {
            return System.currentTimeMillis();
        }
        public boolean isChangeEnabled(long changeId, String packageName, int userId) {
            return CompatChanges.isChangeEnabled(changeId, packageName, UserHandle.of(userId));
        }
    }
    /**
@@ -3315,6 +3334,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                getTargetSdk(profileOwner.getPackageName(), userHandle) > Build.VERSION_CODES.M;
    }
    private boolean canSetPasswordQualityOnParent(String packageName, int userId) {
        return !mInjector.isChangeEnabled(
                PREVENT_SETTING_PASSWORD_QUALITY_ON_PARENT, packageName, userId);
    }
    @Override
    public void setPasswordQuality(ComponentName who, int quality, boolean parent) {
        if (!mHasFeature) {
@@ -3323,7 +3347,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        Objects.requireNonNull(who, "ComponentName is null");
        validateQualityConstant(quality);
        final int userId = mInjector.userHandleGetCallingUserId();
        final CallerIdentity caller = getCallerIdentity(who);
        Preconditions.checkCallAuthorization(
                isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller));
        final boolean qualityMayApplyToParent =
                canSetPasswordQualityOnParent(who.getPackageName(), caller.getUserId());
        if (!qualityMayApplyToParent) {
            Preconditions.checkArgument(!parent,
                    "Profile Owner may not apply password quality requirements device-wide");
        }
        final int userId = caller.getUserId();
        synchronized (getLockObject()) {
            ActiveAdmin ap = getActiveAdminForCallerLocked(
                    who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
@@ -3332,6 +3367,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                if (passwordPolicy.quality != quality) {
                    passwordPolicy.quality = quality;
                    ap.mPasswordComplexity = PASSWORD_COMPLEXITY_NONE;
                    ap.mPasswordPolicyAppliesToParent = qualityMayApplyToParent;
                    resetInactivePasswordRequirementsIfRPlus(userId, ap);
                    updatePasswordValidityCheckpointLocked(userId, parent);
                    updatePasswordQualityCacheForUserGroup(userId);
@@ -4063,9 +4099,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        synchronized (getLockObject()) {
            List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userId);
            for (ActiveAdmin admin : admins) {
                final boolean isAdminOfUser = userId == admin.getUserHandle().getIdentifier();
                // Use the password metrics from the admin in one of three cases:
                // (1) The admin is of the user we're getting the minimum metrics for. The admin
                //     always affects the user it's managing. This applies also to the parent
                //     ActiveAdmin instance: It'd have the same user handle.
                // (2) The mPasswordPolicyAppliesToParent field is true: That indicates the
                //     call to setPasswordQuality was made by an admin that may affect the parent.
                if (isAdminOfUser || admin.mPasswordPolicyAppliesToParent) {
                    adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
                }
            }
        }
        return PasswordMetrics.merge(adminMetrics);
    }
@@ -4155,13 +4200,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    /* shouldIncludeProfileAdmins */ (user) -> user.id == profileUser
                    || !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
            ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>(admins.size());
            int maxRequiredComplexity = PASSWORD_COMPLEXITY_NONE;
            for (ActiveAdmin admin : admins) {
                adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
                if (isDeviceOwner(admin) || isProfileOwnerUncheckedLocked(admin.info.getComponent(),
                        admin.getUserHandle().getIdentifier())) {
                    maxRequiredComplexity = Math.max(maxRequiredComplexity,
                            admin.mPasswordComplexity);
                }
            }
            //TODO: Take complexity into account, would need to take complexity from all admins
            //in the admins list.
            return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics),
                    PASSWORD_COMPLEXITY_NONE, false, metrics).isEmpty();
                    maxRequiredComplexity, false, metrics).isEmpty();
        }
    }
@@ -4255,6 +4304,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    admin.mPasswordComplexity = passwordComplexity;
                    // Reset the password policy.
                    admin.mPasswordPolicy = new PasswordPolicy();
                    admin.mPasswordPolicyAppliesToParent = true;
                    updatePasswordValidityCheckpointLocked(caller.getUserId(), calledOnParent);
                    updatePasswordQualityCacheForUserGroup(caller.getUserId());
                    saveSettingsLocked(caller.getUserId());
+30 −0
Original line number Diff line number Diff line
@@ -127,6 +127,8 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
        // Used as an override when set to nonzero.
        private long mCurrentTimeMillis = 0;

        private final Map<Long, Pair<String, Integer>> mEnabledChanges = new ArrayMap<>();

        public MockInjector(MockSystemServices services, DpmMockContext context) {
            super(context);
            this.services = services;
@@ -487,5 +489,33 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
        public long systemCurrentTimeMillis() {
            return mCurrentTimeMillis != 0 ? mCurrentTimeMillis : System.currentTimeMillis();
        }

        public void setChangeEnabledForPackage(
                long changeId, boolean enabled, String packageName, int userId) {
            if (enabled) {
                mEnabledChanges.put(changeId, Pair.create(packageName, userId));
            } else {
                mEnabledChanges.remove(changeId);
            }
        }

        public void clearEnabledChanges() {
            mEnabledChanges.clear();
        }

        @Override
        public boolean isChangeEnabled(long changeId, String packageName, int userId) {
            Pair<String, Integer> packageAndUser = mEnabledChanges.get(changeId);
            if (packageAndUser == null) {
                return false;
            }

            if (!packageAndUser.first.equals(packageName)
                    || !packageAndUser.second.equals(userId)) {
                return false;
            }

            return true;
        }
    }
}
+79 −5
Original line number Diff line number Diff line
@@ -5090,11 +5090,11 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        doReturn(true).when(getServices().lockPatternUtils)
                .isSeparateProfileChallengeEnabled(managedProfileUserId);

        dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
        parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
        dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
        parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);

        when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
                .thenReturn(computeForPassword("1234".getBytes()));
                .thenReturn(computeForPassword("184342".getBytes()));

        // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
        // on the parent admin)
@@ -5107,6 +5107,68 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        managedProfileUserId)).isFalse();
    }

    @Test
    public void testCanSetPasswordRequirementOnParentPreS() 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);
        dpms.mMockInjector.setChangeEnabledForPackage(165573442L, false,
                admin1.getPackageName(), managedProfileUserId);

        parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
        assertThat(parentDpm.getPasswordQuality(admin1))
                .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
    }

    @Test
    public void testCannotSetPasswordRequirementOnParent() 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);
        dpms.mMockInjector.setChangeEnabledForPackage(165573442L, true,
                admin1.getPackageName(), managedProfileUserId);

        try {
            assertExpectException(IllegalArgumentException.class, null, () ->
                    parentDpm.setPasswordQuality(
                            admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX));
        } finally {
            dpms.mMockInjector.clearEnabledChanges();
        }
    }

    @Test
    public void testPasswordQualityAppliesToParentPreS() 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);
        when(getServices().userManager.getProfileParent(CALLER_USER_HANDLE))
                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));

        dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
        assertThat(parentDpm.getPasswordQuality(null))
                .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
    }

    @Test
    public void testPasswordQualityDoesNotApplyToParentPostS() 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.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
        assertThat(parentDpm.getPasswordQuality(admin1))
                .isEqualTo(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
    }

    private void setActivePasswordState(PasswordMetrics passwordMetrics)
            throws Exception {
        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
@@ -7014,19 +7076,31 @@ public class DevicePolicyManagerTest extends DpmTestBase {
     * @param adminUid uid of the admin package.
     * @param copyFromAdmin package information for {@code admin} will be built based on this
     *     component's information.
     * @param appTargetSdk admin's target SDK level
     */
    private void addManagedProfile(
            ComponentName admin, int adminUid, ComponentName copyFromAdmin) throws Exception {
            ComponentName admin, int adminUid, ComponentName copyFromAdmin, int appTargetSdk)
            throws Exception {
        final int userId = UserHandle.getUserId(adminUid);
        getServices().addUser(userId, 0, UserManager.USER_TYPE_PROFILE_MANAGED,
                UserHandle.USER_SYSTEM);
        mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
        setUpPackageManagerForFakeAdmin(admin, adminUid, copyFromAdmin);
        setUpPackageManagerForFakeAdmin(admin, adminUid, /* enabledSetting= */ null,
                appTargetSdk, copyFromAdmin);
        dpm.setActiveAdmin(admin, false, userId);
        assertThat(dpm.setProfileOwner(admin, null, userId)).isTrue();
        mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
    }

    /**
     * Same as {@code addManagedProfile} above, except using development API level as the API
     * level of the admin.
     */
    private void addManagedProfile(
            ComponentName admin, int adminUid, ComponentName copyFromAdmin) throws Exception {
        addManagedProfile(admin, adminUid, copyFromAdmin, VERSION_CODES.CUR_DEVELOPMENT);
    }

    /**
     * Convert String[] to StringParceledListSlice.
     */