Merge "Improve work profile unification flow" into rvc-dev am: a042c8afd3 am: 86466e49d8 am: 7a09daec6d
Change-Id: If741970fa48d195ec430e3238b8d6611da83761e
This commit is contained in:
@@ -3665,6 +3665,28 @@ public class DevicePolicyManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -208,6 +208,26 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
|
||||
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
|
||||
}
|
||||
|
||||
@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(
|
||||
|
||||
Reference in New Issue
Block a user