summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--core/api/current.txt2
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java72
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java14
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java106
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java135
7 files changed, 323 insertions, 10 deletions
diff --git a/api/current.txt b/api/current.txt
index 344e93baedf0..7df44ab9adef 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6950,6 +6950,7 @@ package android.app.admin {
method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
+ method public int getRequiredPasswordComplexity();
method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
@@ -7081,6 +7082,7 @@ package android.app.admin {
method public void setProfileEnabled(@NonNull android.content.ComponentName);
method public void setProfileName(@NonNull android.content.ComponentName, String);
method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo);
+ method public void setRequiredPasswordComplexity(int);
method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long);
method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName);
diff --git a/core/api/current.txt b/core/api/current.txt
index 9562715b8cff..86de97204afc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6950,6 +6950,7 @@ package android.app.admin {
method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
+ method public int getRequiredPasswordComplexity();
method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
@@ -7081,6 +7082,7 @@ package android.app.admin {
method public void setProfileEnabled(@NonNull android.content.ComponentName);
method public void setProfileName(@NonNull android.content.ComponentName, String);
method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo);
+ method public void setRequiredPasswordComplexity(int);
method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long);
method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1d644c43cd0d..4752160da30a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2729,6 +2729,9 @@ public class DevicePolicyManager {
* {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
* profile.
*
+ * <p><strong>Note:</strong> Specifying password requirements using this method clears the
+ * password complexity requirements set using {@link #setRequiredPasswordComplexity(int)}.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param quality The new desired quality. One of {@link #PASSWORD_QUALITY_UNSPECIFIED},
* {@link #PASSWORD_QUALITY_BIOMETRIC_WEAK},
@@ -3607,13 +3610,18 @@ public class DevicePolicyManager {
* <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.
*
+ * <p>Apps need the {@link permission#REQUEST_PASSWORD_COMPLEXITY} permission to call this
+ * method. On Android {@link android.os.Build.VERSION_CODES#S} and above, the calling
+ * application does not need this permission if it is a device owner or a profile owner.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
*
* @throws IllegalStateException if the user is not unlocked.
* @throws SecurityException if the calling application does not have the permission
- * {@link permission#REQUEST_PASSWORD_COMPLEXITY}
+ * {@link permission#REQUEST_PASSWORD_COMPLEXITY}, and is not a
+ * device owner or a profile owner.
*/
@PasswordComplexity
@RequiresPermission(android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY)
@@ -3630,6 +3638,66 @@ public class DevicePolicyManager {
}
/**
+ * Sets a password complexity requirement for the user's screen lock.
+ * The complexity level is one of the pre-defined levels.
+ *
+ * <p>Note that when called on a profile which uses an unified challenge with its parent, the
+ * complexity would apply to the unified challenge.
+ *
+ * <p>This method can be called on the {@link DevicePolicyManager} instance
+ * returned by {@link #getParentProfileInstance(ComponentName)} in order to set
+ * restrictions on the parent profile.
+ *
+ * <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.
+ *
+ * @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.
+ */
+ public void setRequiredPasswordComplexity(@PasswordComplexity int passwordComplexity) {
+ if (mService == null) {
+ return;
+ }
+
+ try {
+ mService.setRequiredPasswordComplexity(passwordComplexity, mParentInstance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Gets the password complexity requirement set by {@link #setRequiredPasswordComplexity(int)},
+ * for the current user.
+ *
+ * <p>The difference between this method and {@link #getPasswordComplexity()} is that this
+ * method simply returns the value set by {@link #setRequiredPasswordComplexity(int)} while
+ * {@link #getPasswordComplexity()} returns the complexity of the actual password.
+ *
+ * <p>This method can be called on the {@link DevicePolicyManager} instance
+ * returned by {@link #getParentProfileInstance(ComponentName)} in order to get
+ * restrictions on the parent profile.
+ *
+ * @throws SecurityException if the calling application is not a device owner or a profile
+ * owner.
+ */
+ @PasswordComplexity
+ public int getRequiredPasswordComplexity() {
+ if (mService == null) {
+ return PASSWORD_COMPLEXITY_NONE;
+ }
+
+ try {
+ return mService.getRequiredPasswordComplexity(mParentInstance);
+ } 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.
*
@@ -9987,6 +10055,8 @@ public class DevicePolicyManager {
* <li>{@link #getRequiredStrongAuthTimeout}</li>
* <li>{@link #setRequiredStrongAuthTimeout}</li>
* <li>{@link #getAccountTypesWithManagementDisabled}</li>
+ * <li>{@link #setRequiredPasswordComplexity(int)} </li>
+ * <li>{@link #getRequiredPasswordComplexity()}</li>
* </ul>
* <p>
* The following methods are supported for the parent instance but can only be called by the
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f4105e9b0373..58368bc3779a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -87,6 +87,8 @@ interface IDevicePolicyManager {
boolean isProfileActivePasswordSufficientForParent(int userHandle);
boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser);
int getPasswordComplexity(boolean parent);
+ void setRequiredPasswordComplexity(int passwordComplexity, boolean parent);
+ int getRequiredPasswordComplexity(boolean parent);
boolean isUsingUnifiedPassword(in ComponentName admin);
int getCurrentFailedPasswordAttempts(int userHandle, boolean parent);
int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 412f5828828e..d29534e8080e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -130,6 +131,7 @@ class ActiveAdmin {
private static final String TAG_ALWAYS_ON_VPN_PACKAGE = "vpn-package";
private static final String TAG_ALWAYS_ON_VPN_LOCKDOWN = "vpn-lockdown";
private static final String TAG_COMMON_CRITERIA_MODE = "common-criteria-mode";
+ private static final String TAG_PASSWORD_COMPLEXITY = "password-complexity";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -142,6 +144,9 @@ class ActiveAdmin {
@NonNull
PasswordPolicy mPasswordPolicy = new PasswordPolicy();
+ @DevicePolicyManager.PasswordComplexity
+ int mPasswordComplexity = PASSWORD_COMPLEXITY_NONE;
+
@Nullable
FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
@@ -518,6 +523,10 @@ class ActiveAdmin {
if (mCommonCriteriaMode) {
writeAttributeValueToXml(out, TAG_COMMON_CRITERIA_MODE, mCommonCriteriaMode);
}
+
+ if (mPasswordComplexity != PASSWORD_COMPLEXITY_NONE) {
+ writeAttributeValueToXml(out, TAG_PASSWORD_COMPLEXITY, mPasswordComplexity);
+ }
}
void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -777,6 +786,8 @@ class ActiveAdmin {
} else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
mCommonCriteriaMode = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
+ mPasswordComplexity = Integer.parseInt(parser.getAttributeValue(null, ATTR_VALUE));
} else {
Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1112,5 +1123,8 @@ class ActiveAdmin {
pw.print("mCommonCriteriaMode=");
pw.println(mCommonCriteriaMode);
+
+ pw.print("mPasswordComplexity=");
+ pw.println(mPasswordComplexity);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a1167e99201d..757f2486f4ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -60,6 +60,9 @@ 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.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+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_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
@@ -3384,6 +3387,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
if (passwordPolicy.quality != quality) {
passwordPolicy.quality = quality;
+ ap.mPasswordComplexity = PASSWORD_COMPLEXITY_NONE;
resetInactivePasswordRequirementsIfRPlus(userId, ap);
updatePasswordValidityCheckpointLocked(userId, parent);
updatePasswordQualityCacheForUserGroup(userId);
@@ -3612,6 +3616,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private void ensureMinimumQuality(
int userId, ActiveAdmin admin, int minimumQuality, String operation) {
mInjector.binderWithCleanCallingIdentity(() -> {
+ // This check will also take care of the case where the password requirements
+ // are specified as complexity rather than quality: When a password complexity
+ // is set, the quality is reset to "unspecified" which will be below any value
+ // of minimumQuality.
if (admin.mPasswordPolicy.quality < minimumQuality
&& passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
userId)) {
@@ -4209,6 +4217,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
for (ActiveAdmin admin : admins) {
adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
}
+ //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();
}
@@ -4243,31 +4253,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
@NonNull PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
+ final int complexity = getEffectivePasswordComplexityRequirementLocked(userId, parent);
PasswordMetrics minMetrics = getPasswordMinimumMetrics(userId, parent);
final List<PasswordValidationError> passwordValidationErrors =
PasswordMetrics.validatePasswordMetrics(
- minMetrics, PASSWORD_COMPLEXITY_NONE, false, metrics);
+ minMetrics, complexity, false, metrics);
return passwordValidationErrors.isEmpty();
}
@Override
@PasswordComplexity
public int getPasswordComplexity(boolean parent) {
+ final CallerIdentity caller = getNonPrivilegedOrAdminCallerIdentity(null);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.GET_USER_PASSWORD_COMPLEXITY_LEVEL)
.setStrings(parent ? CALLED_FROM_PARENT : NOT_CALLED_FROM_PARENT,
mInjector.getPackageManager().getPackagesForUid(
mInjector.binderGetCallingUid()))
.write();
- final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(!parent || (isDeviceOwner(caller)
|| isProfileOwner(caller) || isSystemUid(caller)),
"Only profile owner, device owner and system may call this method.");
enforceUserUnlocked(caller.getUserId());
- mContext.enforceCallingOrSelfPermission(
- REQUEST_PASSWORD_COMPLEXITY,
- "Must have " + REQUEST_PASSWORD_COMPLEXITY + " permission.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
+ || isDeviceOwner(caller) || isProfileOwner(caller),
+ "Must have " + REQUEST_PASSWORD_COMPLEXITY
+ + " permission, or be a profile owner or device owner.");
synchronized (getLockObject()) {
final int credentialOwner = getCredentialOwner(caller.getUserId(), parent);
@@ -4277,6 +4290,75 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
+ public void setRequiredPasswordComplexity(int passwordComplexity, boolean calledOnParent) {
+ if (!mHasFeature) {
+ return;
+ }
+ final Set<Integer> allowedModes = Set.of(PASSWORD_COMPLEXITY_NONE, PASSWORD_COMPLEXITY_LOW,
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_COMPLEXITY_HIGH);
+ Preconditions.checkArgument(allowedModes.contains(passwordComplexity),
+ "Provided complexity is not one of the allowed values.");
+
+ final CallerIdentity caller = getAdminCallerIdentity(null);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller), calledOnParent);
+ if (admin.mPasswordComplexity != passwordComplexity) {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ admin.mPasswordComplexity = passwordComplexity;
+ // Reset the password policy.
+ admin.mPasswordPolicy = new PasswordPolicy();
+ updatePasswordValidityCheckpointLocked(caller.getUserId(), calledOnParent);
+ updatePasswordQualityCacheForUserGroup(caller.getUserId());
+ saveSettingsLocked(caller.getUserId());
+ //TODO: Log password complexity change if security logging is enabled.
+ });
+ }
+ }
+ //TODO: Log metrics.
+ }
+
+ private int getEffectivePasswordComplexityRequirementLocked(@UserIdInt int userHandle,
+ boolean parent) {
+ ensureLocked();
+ List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(
+ getProfileParentUserIfRequested(userHandle, parent));
+ int maxRequiredComplexity = PASSWORD_COMPLEXITY_NONE;
+ for (ActiveAdmin admin : admins) {
+ final ComponentName adminComponent = admin.info.getComponent();
+ final int adminUser = admin.getUserHandle().getIdentifier();
+ // Password complexity is only taken into account from DO/PO
+ if (isDeviceOwner(adminComponent, adminUser)
+ || isProfileOwner(adminComponent, adminUser)) {
+ maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
+ }
+ }
+ return maxRequiredComplexity;
+ }
+
+ @Override
+ public int getRequiredPasswordComplexity(boolean calledOnParent) {
+ if (!mHasFeature) {
+ return PASSWORD_COMPLEXITY_NONE;
+ }
+
+ final CallerIdentity caller = getAdminCallerIdentity(null);
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwner(caller));
+
+ Preconditions.checkArgument(!calledOnParent || hasProfileOwner(caller.getUserId()));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getParentOfAdminIfRequired(
+ getDeviceOrProfileOwnerAdminLocked(caller.getUserId()), calledOnParent);
+ return requiredAdmin.mPasswordComplexity;
+ }
+ }
+
+ @Override
public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
if (!mLockPatternUtils.hasSecureLockScreen()) {
return 0;
@@ -4466,15 +4548,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
final PasswordMetrics minMetrics = getPasswordMinimumMetrics(userHandle);
final List<PasswordValidationError> validationErrors;
+ final int complexity =
+ getEffectivePasswordComplexityRequirementLocked(userHandle, false);
// TODO: Consider changing validation API to take LockscreenCredential.
if (password.isEmpty()) {
validationErrors = PasswordMetrics.validatePasswordMetrics(
- minMetrics, PASSWORD_COMPLEXITY_NONE, false /* isPin */,
+ minMetrics, complexity, false /* isPin */,
new PasswordMetrics(CREDENTIAL_TYPE_NONE));
} else {
// TODO(b/120484642): remove getBytes() below
validationErrors = PasswordMetrics.validatePassword(
- minMetrics, PASSWORD_COMPLEXITY_NONE, false, password.getBytes());
+ minMetrics, complexity, false, password.getBytes());
}
if (!validationErrors.isEmpty()) {
@@ -7461,6 +7545,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ private int getDeviceOwnerUserIdUncheckedLocked() {
+ return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
+ }
+
@Override
public int getDeviceOwnerUserId() {
if (!mHasFeature) {
@@ -7469,7 +7557,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
synchronized (getLockObject()) {
- return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
+ return getDeviceOwnerUserIdUncheckedLocked();
}
}
@@ -8010,7 +8098,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
private @Nullable ActiveAdmin getDeviceOrProfileOwnerAdminLocked(int userHandle) {
ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
- if (admin == null && getDeviceOwnerUserId() == userHandle) {
+ if (admin == null && getDeviceOwnerUserIdUncheckedLocked() == userHandle) {
admin = getDeviceOwnerAdminLocked();
}
return admin;
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 77e16769d905..c0c82d53b4f0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,6 +25,7 @@ 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_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.WIPE_EUICC;
@@ -6690,6 +6691,140 @@ public class DevicePolicyManagerTest extends DpmTestBase {
.isEqualTo(DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT);
}
+ @Test
+ public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnDO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ // DO must be able to set it.
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+ // But not on the parent DPM.
+ assertExpectException(IllegalArgumentException.class, null,
+ () -> parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW));
+ // Another package must not be allowed to set password complexity.
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW));
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnPO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setupProfileOwner();
+ // PO must be able to set it.
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+ // And on the parent profile DPM instance.
+ parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+ // Another package must not be allowed to set password complexity.
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW));
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexity_validValuesOnly() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setupProfileOwner();
+
+ // Cannot set value other than password_complexity none/low/medium/high
+ assertExpectException(IllegalArgumentException.class, null, () ->
+ dpm.setRequiredPasswordComplexity(-1));
+ assertExpectException(IllegalArgumentException.class, null, () ->
+ dpm.setRequiredPasswordComplexity(7));
+ assertExpectException(IllegalArgumentException.class, null, () ->
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH + 1));
+
+ final Set<Integer> allowedModes = Set.of(PASSWORD_COMPLEXITY_NONE, PASSWORD_COMPLEXITY_LOW,
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_COMPLEXITY_HIGH);
+ for (int complexity : allowedModes) {
+ // Ensure exception is not thrown.
+ dpm.setRequiredPasswordComplexity(complexity);
+ }
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexity_setAndGet() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setupProfileOwner();
+
+ final Set<Integer> allowedModes = Set.of(PASSWORD_COMPLEXITY_NONE, PASSWORD_COMPLEXITY_LOW,
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_COMPLEXITY_HIGH);
+ for (int complexity : allowedModes) {
+ dpm.setRequiredPasswordComplexity(complexity);
+ assertThat(dpm.getRequiredPasswordComplexity()).isEqualTo(complexity);
+ }
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexityOnParent_setAndGet() throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ final Set<Integer> allowedModes = Set.of(PASSWORD_COMPLEXITY_NONE, PASSWORD_COMPLEXITY_LOW,
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_COMPLEXITY_HIGH);
+ for (int complexity : allowedModes) {
+ dpm.getParentProfileInstance(admin1).setRequiredPasswordComplexity(complexity);
+ assertThat(dpm.getParentProfileInstance(admin1).getRequiredPasswordComplexity())
+ .isEqualTo(complexity);
+ assertThat(dpm.getRequiredPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
+ }
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexity_isSufficient() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ mContext.packageName = admin1.getPackageName();
+ setupDeviceOwner();
+
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(dpm.getRequiredPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ when(getServices().packageManager.getPackagesForUid(
+ DpmMockContext.CALLER_SYSTEM_USER_UID)).thenReturn(new String[0]);
+ mServiceContext.permissions.add(permission.REQUEST_PASSWORD_COMPLEXITY);
+ assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
+
+ reset(mContext.spiedContext);
+ PasswordMetrics passwordMetricsNoSymbols = computeForPassword("1234".getBytes());
+ setActivePasswordState(passwordMetricsNoSymbols);
+ assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW);
+ assertThat(dpm.isActivePasswordSufficient()).isFalse();
+
+ reset(mContext.spiedContext);
+ passwordMetricsNoSymbols = computeForPassword("84125312943a".getBytes());
+ setActivePasswordState(passwordMetricsNoSymbols);
+ assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ // using isActivePasswordSufficient
+ assertThat(dpm.isActivePasswordSufficient()).isTrue();
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexity_resetBySettingQuality() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setupProfileOwner();
+
+ // Test that calling setPasswordQuality resets complexity to none.
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(dpm.getRequiredPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ assertThat(dpm.getRequiredPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
+ }
+
+ @Test
+ public void testSetRequiredPasswordComplexity_overridesQuality() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setupProfileOwner();
+
+ // Test that calling setRequiredPasswordComplexity resets password quality.
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+ assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ }
+
private void setUserUnlocked(int userHandle, boolean unlocked) {
when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
}