Merge "Fix resetPasswordWithToken before user unlock" into oc-mr1-dev

This commit is contained in:
Rubin Xu
2017-09-06 12:12:36 +00:00
committed by Android (Google) Code Review
9 changed files with 141 additions and 26 deletions

View File

@@ -2531,7 +2531,7 @@ public class DevicePolicyManager {
* @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) {

View File

@@ -18,13 +18,11 @@ package android.app.admin;
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 @@ public class PasswordMetrics implements Parcelable {
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.

View File

@@ -803,12 +803,14 @@ public class LockPatternUtils {
+ "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
@@ -897,6 +899,24 @@ public class LockPatternUtils {
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
}
/**
* 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 class LockPatternUtils {
}
}
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)) {

View File

@@ -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 @@ public class PasswordMetricsTest {
// 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);
}
}

View File

@@ -154,7 +154,8 @@ public class LockSettingsService extends ILockSettings.Stub {
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 @@ public class LockSettingsService extends ILockSettings.Stub {
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 @@ public class LockSettingsService extends ILockSettings.Stub {
}
setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, newHandle, userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
notifyActivePasswordMetricsAvailable(credential, userId);
return newHandle;
}
@@ -2246,13 +2253,13 @@ public class LockSettingsService extends ILockSettings.Stub {
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 @@ public class LockSettingsService extends ILockSettings.Stub {
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);

View File

@@ -4077,6 +4077,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
enforceFullCrossUsersPermission(userHandle);
enforceUserUnlocked(userHandle, parent);
synchronized (this) {
// This API can only be called by an active device admin,
@@ -4096,7 +4097,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
/**
* 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) {

View File

@@ -3653,7 +3653,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// 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));

View File

@@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock;
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 @@ public class LockSettingsServiceTestable extends LockSettingsService {
@Override
public Handler getHandler() {
return mock(Handler.class);
return new Handler(Looper.getMainLooper());
}
@Override

View File

@@ -23,8 +23,9 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
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 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
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));
}