summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java22
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl1
-rw-r--r--core/java/android/app/admin/PasswordMetrics.java2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java22
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java29
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java75
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java20
8 files changed, 166 insertions, 32 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fb9adb730314..41e2dc0de4d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3666,6 +3666,28 @@ public class DevicePolicyManager {
}
/**
+ * Returns whether the given user's credential will be sufficient for all password policy
+ * requirement, once the user's profile has switched to unified challenge.
+ *
+ * <p>This is different from {@link #isActivePasswordSufficient()} since once the profile
+ * switches to unified challenge, policies set explicitly on the profile will start to affect
+ * the parent user.
+ * @param userHandle the user whose password requirement will be checked
+ * @param profileUser the profile user whose lockscreen challenge will be unified.
+ * @hide
+ */
+ public boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser) {
+ if (mService != null) {
+ try {
+ return mService.isPasswordSufficientAfterProfileUnification(userHandle,
+ profileUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+ /**
* Retrieve the number of times the user has failed at entering a password since that last
* successful password entry.
* <p>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 591a3f68eed0..d10153c11723 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -85,6 +85,7 @@ interface IDevicePolicyManager {
boolean isActivePasswordSufficient(int userHandle, boolean parent);
boolean isProfileActivePasswordSufficientForParent(int userHandle);
+ boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser);
int getPasswordComplexity(boolean parent);
boolean isUsingUnifiedPassword(in ComponentName admin);
int getCurrentFailedPasswordAttempts(int userHandle, boolean parent);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 86ebb47400c7..39e1f0dc2d2c 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -350,7 +350,7 @@ public final class PasswordMetrics implements Parcelable {
*
* TODO: move to PasswordPolicy
*/
- private void maxWith(PasswordMetrics other) {
+ public void maxWith(PasswordMetrics other) {
credType = Math.max(credType, other.credType);
if (credType != CREDENTIAL_TYPE_PASSWORD) {
return;
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index fa2ec55bd81a..a77e34b4af1e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -147,6 +147,28 @@ public class RestrictedLockUtils {
public EnforcedAdmin() {
}
+ /**
+ * Combines two {@link EnforcedAdmin} into one: if one of them is null, then just return
+ * the other. If both of them are the same, then return that. Otherwise return the symbolic
+ * {@link #MULTIPLE_ENFORCED_ADMIN}
+ */
+ public static EnforcedAdmin combine(EnforcedAdmin admin1, EnforcedAdmin admin2) {
+ if (admin1 == null) {
+ return admin2;
+ }
+ if (admin2 == null) {
+ return admin1;
+ }
+ if (admin1.equals(admin2)) {
+ return admin1;
+ }
+ if (!admin1.enforcedRestriction.equals(admin2.enforcedRestriction)) {
+ throw new IllegalArgumentException(
+ "Admins with different restriction cannot be combined");
+ }
+ return MULTIPLE_ENFORCED_ADMIN;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 9297a43b04aa..7972f247b46d 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -366,10 +366,15 @@ public class LockSettingsService extends ILockSettings.Stub {
if (mStorage.hasChildProfileLock(managedUserId)) {
return;
}
- // Do not tie it to parent when parent does not have a screen lock
+ // If parent does not have a screen lock, simply clear credential from the managed profile,
+ // to maintain the invariant that unified profile should always have the same secure state
+ // as its parent.
final int parentId = mUserManager.getProfileParent(managedUserId).id;
- if (!isUserSecure(parentId)) {
- if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
+ if (!isUserSecure(parentId) && !managedUserPassword.isNone()) {
+ if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock but profile has one");
+
+ setLockCredentialInternal(LockscreenCredential.createNone(), managedUserPassword,
+ managedUserId, /* isLockTiedToParent= */ true);
return;
}
// Do not tie when the parent has no SID (but does have a screen lock).
@@ -3161,6 +3166,21 @@ public class LockSettingsService extends ILockSettings.Stub {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(timestamp));
}
+ private static String credentialTypeToString(int credentialType) {
+ switch (credentialType) {
+ case CREDENTIAL_TYPE_NONE:
+ return "None";
+ case CREDENTIAL_TYPE_PATTERN:
+ return "Pattern";
+ case CREDENTIAL_TYPE_PIN:
+ return "Pin";
+ case CREDENTIAL_TYPE_PASSWORD:
+ return "Password";
+ default:
+ return "Unknown " + credentialType;
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, printWriter)) return;
@@ -3192,7 +3212,8 @@ public class LockSettingsService extends ILockSettings.Stub {
// It's OK to dump the password type since anyone with physical access can just
// observe it from the keyguard directly.
pw.println("Quality: " + getKeyguardStoredQuality(userId));
- pw.println("CredentialType: " + getCredentialTypeInternal(userId));
+ pw.println("CredentialType: " + credentialTypeToString(
+ getCredentialTypeInternal(userId)));
pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabledInternal(userId));
pw.println(String.format("Metrics: %s",
getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1da074002456..2c0d4c0c9208 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4733,33 +4733,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!parent && isSeparateProfileChallengeEnabled(userHandle)) {
// If this user has a separate challenge, only return its restrictions.
return getUserDataUnchecked(userHandle).mAdminList;
- } else {
- // Return all admins for this user and the profiles that are visible from this
- // user that do not use a separate work challenge.
- ArrayList<ActiveAdmin> admins = new ArrayList<ActiveAdmin>();
- for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
- DevicePolicyData policy = getUserData(userInfo.id);
- if (!userInfo.isManagedProfile()) {
- admins.addAll(policy.mAdminList);
- } else {
- // For managed profiles, we always include the policies set on the parent
- // profile. Additionally, we include the ones set on the managed profile
- // if no separate challenge is in place.
- boolean hasSeparateChallenge = isSeparateProfileChallengeEnabled(userInfo.id);
- final int N = policy.mAdminList.size();
- for (int i = 0; i < N; i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
- if (admin.hasParentActiveAdmin()) {
- admins.add(admin.getParentActiveAdmin());
- }
- if (!hasSeparateChallenge) {
- admins.add(admin);
- }
- }
- }
- }
- return admins;
}
+ // Either parent == true, or isSeparateProfileChallengeEnabled == false
+ // If parent is true, query the parent user of userHandle by definition,
+ // If isSeparateProfileChallengeEnabled is false, userHandle points to a managed profile
+ // with unified challenge so also need to query the parent user who owns the credential.
+ return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
+ (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
}
/**
@@ -4777,6 +4757,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (isManagedProfile(userHandle)) {
return getUserDataUnchecked(userHandle).mAdminList;
}
+ return getActiveAdminsForUserAndItsManagedProfilesLocked(userHandle,
+ /* shouldIncludeProfileAdmins */ (user) -> false);
+ }
+
+ /**
+ * Returns the list of admins on the given user, as well as parent admins for each managed
+ * profile associated with the given user. Optionally also include the admin of each managed
+ * profile.
+ * <p> Should not be called on a profile user.
+ */
+ @GuardedBy("getLockObject()")
+ private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesLocked(int userHandle,
+ Predicate<UserInfo> shouldIncludeProfileAdmins) {
ArrayList<ActiveAdmin> admins = new ArrayList<>();
mInjector.binderWithCleanCallingIdentity(() -> {
for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
@@ -4784,12 +4777,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (userInfo.id == userHandle) {
admins.addAll(policy.mAdminList);
} else if (userInfo.isManagedProfile()) {
- // For managed profiles, policies set on the parent profile will be included
for (int i = 0; i < policy.mAdminList.size(); i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
if (admin.hasParentActiveAdmin()) {
admins.add(admin.getParentActiveAdmin());
}
+ if (shouldIncludeProfileAdmins.test(userInfo)) {
+ admins.add(admin);
+ }
}
} else {
Slog.w(LOG_TAG, "Unknown user type: " + userInfo);
@@ -5366,6 +5361,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ @Override
+ public boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser) {
+ if (!mHasFeature) {
+ return true;
+ }
+ enforceFullCrossUsersPermission(userHandle);
+ enforceNotManagedProfile(userHandle, "check password sufficiency");
+ enforceUserUnlocked(userHandle);
+
+ synchronized (getLockObject()) {
+ PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(userHandle);
+
+ // Combine password policies across the user and its profiles. Profile admins are
+ // included if the profile is to be unified or currently has unified challenge
+ List<ActiveAdmin> admins = getActiveAdminsForUserAndItsManagedProfilesLocked(userHandle,
+ /* shouldIncludeProfileAdmins */ (user) -> user.id == profileUser
+ || !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
+ ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>(admins.size());
+ for (ActiveAdmin admin : admins) {
+ adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
+ }
+ return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics),
+ PASSWORD_COMPLEXITY_NONE, false, metrics).isEmpty();
+ }
+ }
+
private boolean isActivePasswordSufficientForUserLocked(
boolean passwordValidAtLastCheckpoint, @Nullable PasswordMetrics metrics,
int userHandle, boolean parent) {
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 fe224ce058f4..4faed659f5df 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4841,6 +4841,33 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertFalse(dpm.isActivePasswordSufficient());
}
+ public void testIsPasswordSufficientAfterProfileUnification() throws Exception {
+ final int managedProfileUserId = DpmMockContext.CALLER_USER_HANDLE;
+ final int managedProfileAdminUid =
+ UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ doReturn(true).when(getServices().lockPatternUtils)
+ .isSeparateProfileChallengeEnabled(managedProfileUserId);
+
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
+ parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+
+ when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
+ .thenReturn(computeForPassword("1234".getBytes()));
+
+ // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
+ // on the parent admin)
+ assertTrue(dpm.isPasswordSufficientAfterProfileUnification(UserHandle.USER_SYSTEM,
+ UserHandle.USER_NULL));
+ // Numeric password is not compliant if profile is to be unified: the profile has a
+ // QUALITY_ALPHABETIC policy on itself which will be enforced on the password after
+ // unification.
+ assertFalse(dpm.isPasswordSufficientAfterProfileUnification(UserHandle.USER_SYSTEM,
+ managedProfileUserId));
+ }
+
private void setActivePasswordState(PasswordMetrics passwordMetrics)
throws Exception {
final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 07d7830c9b0f..12b144f2b778 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -209,6 +209,26 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
}
@Test
+ public void testManagedProfileChallengeUnification_parentUserNoPassword() throws Exception {
+ // Start with a profile with unified challenge, parent user has not password
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
+
+ // Set a separate challenge on the profile
+ assertTrue(mService.setLockCredential(
+ newPassword("12345678"), nonePassword(), MANAGED_PROFILE_USER_ID));
+ assertNotEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
+
+ // Now unify again, profile should become passwordless again
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false,
+ newPassword("12345678"));
+ assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
+ }
+
+ @Test
public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
assertTrue(mService.setLockCredential(
newPassword("password"),