Merge "Fix resetPasswordWithToken before user unlock" into oc-mr1-dev
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 56123a7..121b58a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2531,7 +2531,7 @@
* @return Returns true if the password meets the current requirements, else false.
* @throws SecurityException if the calling application does not own an active administrator
* that uses {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD}
- * @throws InvalidStateException if the user is not unlocked.
+ * @throws IllegalStateException if the user is not unlocked.
*/
public boolean isActivePasswordSufficient() {
if (mService != null) {
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index ea3f560..4658a47 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -18,13 +18,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.admin.DevicePolicyManager;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.io.IOException;
/**
* A class that represents the metrics of a password that are used to decide whether or not a
@@ -159,6 +157,22 @@
quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter);
}
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof PasswordMetrics)) {
+ return false;
+ }
+ PasswordMetrics o = (PasswordMetrics) other;
+ return this.quality == o.quality
+ && this.length == o.length
+ && this.letters == o.letters
+ && this.upperCase == o.upperCase
+ && this.lowerCase == o.lowerCase
+ && this.numeric == o.numeric
+ && this.symbols == o.symbols
+ && this.nonLetter == o.nonLetter;
+ }
+
/*
* Returns the maximum length of a sequential characters. A sequence is defined as
* monotonically increasing characters with a constant interval or the same character repeated.
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0d15758..f85333eb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -803,12 +803,14 @@
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
}
- final int computedQuality = PasswordMetrics.computeForPassword(password).quality;
- setLong(PASSWORD_TYPE_KEY, Math.max(requestedQuality, computedQuality), userHandle);
+ setLong(PASSWORD_TYPE_KEY,
+ computePasswordQuality(CREDENTIAL_TYPE_PASSWORD, password, requestedQuality),
+ userHandle);
getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
requestedQuality, userHandle);
- updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle);
+ updateEncryptionPasswordIfNeeded(password,
+ PasswordMetrics.computeForPassword(password).quality, userHandle);
updatePasswordHistory(password, userHandle);
} catch (RemoteException re) {
// Cant do much
@@ -898,6 +900,24 @@
}
/**
+ * Returns the password quality of the given credential, promoting it to a higher level
+ * if DevicePolicyManager has a stronger quality requirement. This value will be written
+ * to PASSWORD_TYPE_KEY.
+ */
+ private int computePasswordQuality(int type, String credential, int requestedQuality) {
+ final int quality;
+ if (type == CREDENTIAL_TYPE_PASSWORD) {
+ int computedQuality = PasswordMetrics.computeForPassword(credential).quality;
+ quality = Math.max(requestedQuality, computedQuality);
+ } else if (type == CREDENTIAL_TYPE_PATTERN) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ } else /* if (type == CREDENTIAL_TYPE_NONE) */ {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ }
+ return quality;
+ }
+
+ /**
* Enables/disables the Separate Profile Challenge for this {@param userHandle}. This is a no-op
* for user handles that do not belong to a managed profile.
*
@@ -1505,25 +1525,34 @@
}
}
- public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
- byte[] token, int userId) {
+ /**
+ * Change a user's lock credential with a pre-configured escrow token.
+ *
+ * @param credential The new credential to be set
+ * @param type Credential type: password / pattern / none.
+ * @param requestedQuality the requested password quality by DevicePolicyManager.
+ * See {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ * @param tokenHandle Handle of the escrow token
+ * @param token Escrow token
+ * @param userId The user who's lock credential to be changed
+ * @return {@code true} if the operation is successful.
+ */
+ public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality,
+ long tokenHandle, byte[] token, int userId) {
try {
if (type != CREDENTIAL_TYPE_NONE) {
if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) {
throw new IllegalArgumentException("password must not be null and at least "
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
}
-
- final int computedQuality = PasswordMetrics.computeForPassword(credential).quality;
- int quality = Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
- computedQuality);
+ final int quality = computePasswordQuality(type, credential, requestedQuality);
if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle,
token, quality, userId)) {
return false;
}
setLong(PASSWORD_TYPE_KEY, quality, userId);
- updateEncryptionPasswordIfNeeded(credential, computedQuality, userId);
+ updateEncryptionPasswordIfNeeded(credential, quality, userId);
updatePasswordHistory(credential, userId);
} else {
if (!TextUtils.isEmpty(credential)) {
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 2470e87..d289f1f 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -17,6 +17,7 @@
package android.app.admin;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
@@ -119,4 +120,48 @@
// ordered, but not composed of alphas or digits
assertEquals(1, PasswordMetrics.maxLengthSequence(":;<=>"));
}
+
+ @Test
+ public void testEquals() {
+ PasswordMetrics metrics0 = new PasswordMetrics();
+ PasswordMetrics metrics1 = new PasswordMetrics();
+ assertNotEquals(metrics0, null);
+ assertNotEquals(metrics0, new Object());
+ assertEquals(metrics0, metrics0);
+ assertEquals(metrics0, metrics1);
+
+ assertEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4));
+
+ assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
+
+ assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4));
+
+ metrics0 = PasswordMetrics.computeForPassword("1234abcd,./");
+ metrics1 = PasswordMetrics.computeForPassword("1234abcd,./");
+ assertEquals(metrics0, metrics1);
+ metrics1.letters++;
+ assertNotEquals(metrics0, metrics1);
+ metrics1.letters--;
+ metrics1.upperCase++;
+ assertNotEquals(metrics0, metrics1);
+ metrics1.upperCase--;
+ metrics1.lowerCase++;
+ assertNotEquals(metrics0, metrics1);
+ metrics1.lowerCase--;
+ metrics1.numeric++;
+ assertNotEquals(metrics0, metrics1);
+ metrics1.numeric--;
+ metrics1.symbols++;
+ assertNotEquals(metrics0, metrics1);
+ metrics1.symbols--;
+ metrics1.nonLetter++;
+ assertNotEquals(metrics0, metrics1);
+ metrics1.nonLetter--;
+ assertEquals(metrics0, metrics1);
+
+
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a1b8456..018b5fa4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -154,7 +154,8 @@
private final Injector mInjector;
private final Context mContext;
- private final Handler mHandler;
+ @VisibleForTesting
+ protected final Handler mHandler;
@VisibleForTesting
protected final LockSettingsStorage mStorage;
private final LockSettingsStrongAuth mStrongAuth;
@@ -1736,6 +1737,10 @@
return response;
}
+ /**
+ * Call this method to notify DPMS regarding the latest password metric. This should be called
+ * when the user is authenticating or when a new password is being set.
+ */
private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) {
final PasswordMetrics metrics;
if (password == null) {
@@ -2197,6 +2202,8 @@
}
setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, newHandle, userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+
+ notifyActivePasswordMetricsAvailable(credential, userId);
return newHandle;
}
@@ -2246,13 +2253,13 @@
userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+
+ notifyActivePasswordMetricsAvailable(credential, userId);
} else /* response == null || responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
Slog.w(TAG, "spBasedSetLockCredentialInternalLocked: " +
(response != null ? "rate limit exceeded" : "failed"));
return;
}
- notifyActivePasswordMetricsAvailable(credential, userId);
-
}
@Override
@@ -2358,6 +2365,10 @@
Slog.w(TAG, "Invalid escrow token supplied");
return false;
}
+ // Update PASSWORD_TYPE_KEY since it's needed by notifyActivePasswordMetricsAvailable()
+ // called by setLockCredentialWithAuthTokenLocked().
+ // TODO: refactor usage of PASSWORD_TYPE_KEY b/65239740
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY, requestedQuality, userId);
long oldHandle = getSyntheticPasswordHandleLocked(userId);
setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
requestedQuality, userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 312327e..0b967b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4077,6 +4077,7 @@
return true;
}
enforceFullCrossUsersPermission(userHandle);
+ enforceUserUnlocked(userHandle, parent);
synchronized (this) {
// This API can only be called by an active device admin,
@@ -4096,7 +4097,8 @@
enforceManagedProfile(userHandle, "call APIs refering to the parent profile");
synchronized (this) {
- int targetUser = getProfileParentId(userHandle);
+ final int targetUser = getProfileParentId(userHandle);
+ enforceUserUnlocked(targetUser, false);
DevicePolicyData policy = getUserDataUnchecked(getCredentialOwner(userHandle, false));
return isActivePasswordSufficientForUserLocked(policy, targetUser, false);
}
@@ -4104,8 +4106,6 @@
private boolean isActivePasswordSufficientForUserLocked(
DevicePolicyData policy, int userHandle, boolean parent) {
- enforceUserUnlocked(userHandle, parent);
-
if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()
&& !policy.mPasswordStateHasBeenSetSinceBoot) {
// Before user enters their password for the first time after a reboot, return the
@@ -4438,7 +4438,7 @@
result = mLockPatternUtils.setLockCredentialWithToken(password,
TextUtils.isEmpty(password) ? LockPatternUtils.CREDENTIAL_TYPE_NONE
: LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- tokenHandle, token, userHandle);
+ quality, tokenHandle, token, userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
@@ -5442,6 +5442,11 @@
}
}
+ /**
+ * Notify DPMS regarding the metric of the current password. This happens when the user changes
+ * the password, but also when the user just unlocks the keyguard. In comparison,
+ * reportPasswordChanged() is only called when the user changes the password.
+ */
@Override
public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
if (!mHasFeature) {
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 e3faa52..96d9605 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3653,7 +3653,8 @@
// test reset password with token
when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password),
- eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), eq(handle), eq(token),
+ eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD),
+ eq(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED), eq(handle), eq(token),
eq(UserHandle.USER_SYSTEM)))
.thenReturn(true);
assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 0df834f..0916a33 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -21,6 +21,7 @@
import android.app.IActivityManager;
import android.content.Context;
import android.os.Handler;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.storage.IStorageManager;
@@ -56,7 +57,7 @@
@Override
public Handler getHandler() {
- return mock(Handler.class);
+ return new Handler(Looper.getMainLooper());
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index fd77de3..2c9aa9d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -23,8 +23,9 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+import static org.mockito.Mockito.verify;
-import android.app.admin.DevicePolicyManager;
+import android.app.admin.PasswordMetrics;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -272,14 +273,22 @@
long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ PRIMARY_USER_ID).getResponseCode();
assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+ // Verify DPM gets notified about new device lock
+ mService.mHandler.runWithScissors(() -> {}, 0 /*now*/); // Flush runnables on handler
+ PasswordMetrics metric = PasswordMetrics.computeForPassword(PATTERN);
+ metric.quality = PASSWORD_QUALITY_SOMETHING;
+ verify(mDevicePolicyManager).setActivePasswordState(metric, PRIMARY_USER_ID);
+
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0,
+ PRIMARY_USER_ID).getResponseCode());
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}