Merge "Enforce password complexity in lockscreen setting"
This commit is contained in:
@@ -221,9 +221,19 @@ public class ChooseLockGeneric extends SettingsActivity {
|
||||
mForBiometrics = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
|
||||
|
||||
mRequestedMinComplexity = intent
|
||||
final int complexityFromIntent = intent
|
||||
.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
|
||||
mCallerAppName =
|
||||
final int complexityFromAdmin = mLockPatternUtils.getRequestedPasswordComplexity(
|
||||
mUserId);
|
||||
mRequestedMinComplexity = Math.max(complexityFromIntent, complexityFromAdmin);
|
||||
final boolean isComplexityProvidedByAdmin = (complexityFromAdmin > complexityFromIntent)
|
||||
&& mRequestedMinComplexity > PASSWORD_COMPLEXITY_NONE;
|
||||
|
||||
// If the complexity is provided by the admin, do not get the caller app's name.
|
||||
// If the app requires, for example, low complexity, and the admin requires high
|
||||
// complexity, it does not make sense to show a footer telling the user it's the app
|
||||
// requesting a particular complexity because the admin-set complexity will override it.
|
||||
mCallerAppName = isComplexityProvidedByAdmin ? null :
|
||||
intent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME);
|
||||
mIsCallingAppAdmin = intent
|
||||
.getBooleanExtra(EXTRA_KEY_IS_CALLING_APP_ADMIN, /* defValue= */ false);
|
||||
@@ -669,8 +679,9 @@ public class ChooseLockGeneric extends SettingsActivity {
|
||||
final PreferenceScreen entries = getPreferenceScreen();
|
||||
|
||||
int adminEnforcedQuality = mDpm.getPasswordQuality(null, mUserId);
|
||||
EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(
|
||||
getActivity(), mUserId);
|
||||
EnforcedAdmin enforcedAdmin =
|
||||
RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(getActivity(),
|
||||
mUserId);
|
||||
// If we are to unify a work challenge at the end of the credential enrollment, manually
|
||||
// merge any password policy from that profile here, so we are enrolling a compliant
|
||||
// password. This is because once unified, the profile's password policy will
|
||||
|
@@ -421,6 +421,8 @@ public class ChooseLockPassword extends SettingsActivity {
|
||||
if (mUnificationProfileId != UserHandle.USER_NULL) {
|
||||
mMinMetrics.maxWith(
|
||||
mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId));
|
||||
mMinComplexity = Math.max(mMinComplexity,
|
||||
mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId));
|
||||
}
|
||||
|
||||
if (intent.getBooleanExtra(
|
||||
|
@@ -21,6 +21,7 @@ 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_COMPLEX;
|
||||
|
||||
import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.KEY_LOCK_SETTINGS_FOOTER;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
|
||||
@@ -90,6 +91,7 @@ public class ChooseLockGenericTest {
|
||||
Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1);
|
||||
ShadowStorageManager.reset();
|
||||
ShadowPersistentDataBlockManager.reset();
|
||||
ShadowLockPatternUtils.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -377,6 +379,64 @@ public class ChooseLockGenericTest {
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePreferencesOrFinish_ComplexityIsReadFromDPM() {
|
||||
ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
|
||||
ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
|
||||
|
||||
initActivity(null);
|
||||
mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
|
||||
|
||||
FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER);
|
||||
assertThat(footer.getTitle()).isEqualTo(null);
|
||||
|
||||
Intent intent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX);
|
||||
assertThat(intent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
|
||||
PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePreferencesOrFinish_ComplexityIsMergedWithDPM() {
|
||||
ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
|
||||
ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
|
||||
Intent intent = new Intent()
|
||||
.putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
|
||||
.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_LOW);
|
||||
initActivity(intent);
|
||||
|
||||
mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
|
||||
|
||||
// Footer should be null because admin complexity wins.
|
||||
FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER);
|
||||
assertThat(footer.getTitle()).isEqualTo(null);
|
||||
|
||||
Intent passwordIntent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX);
|
||||
assertThat(passwordIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
|
||||
PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePreferencesOrFinish_ComplexityIsMergedWithDPM_AppIsHigher() {
|
||||
ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
|
||||
ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
|
||||
Intent intent = new Intent()
|
||||
.putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
|
||||
.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
|
||||
initActivity(intent);
|
||||
|
||||
mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
|
||||
|
||||
// Footer should include app name because app requirement is higher.
|
||||
CharSequence expectedTitle =
|
||||
mActivity.getString(R.string.unlock_footer_high_complexity_requested, "app name");
|
||||
FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER);
|
||||
assertThat(footer.getTitle()).isEqualTo(expectedTitle);
|
||||
|
||||
Intent passwordIntent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX);
|
||||
assertThat(passwordIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
|
||||
PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
|
||||
}
|
||||
|
||||
private void initActivity(@Nullable Intent intent) {
|
||||
if (intent == null) {
|
||||
intent = new Intent();
|
||||
|
@@ -31,6 +31,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
|
||||
import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
|
||||
import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
@@ -67,6 +68,7 @@ import org.robolectric.shadows.ShadowDrawable;
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {
|
||||
SettingsShadowResources.class,
|
||||
ShadowLockPatternUtils.class,
|
||||
ShadowUtils.class,
|
||||
ShadowDevicePolicyManager.class,
|
||||
})
|
||||
@@ -84,6 +86,7 @@ public class ChooseLockPasswordTest {
|
||||
@After
|
||||
public void tearDown() {
|
||||
SettingsShadowResources.reset();
|
||||
ShadowLockPatternUtils.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -378,6 +381,29 @@ public class ChooseLockPasswordTest {
|
||||
assertThat(drawable.getCreatedFromResId()).isNotEqualTo(R.drawable.ic_fingerprint_header);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateComplexityMergedFromDpmOnCreate() {
|
||||
ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
|
||||
|
||||
assertPasswordValidationResult(
|
||||
/* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
|
||||
/* passwordType= */ PASSWORD_QUALITY_NUMERIC,
|
||||
/* userEnteredPassword= */ LockscreenCredential.createNone(),
|
||||
"PIN must be at least 8 digits");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateComplexityMergedFromUnificationUserOnCreate() {
|
||||
ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
|
||||
ShadowLockPatternUtils.setRequiredPasswordComplexity(123, PASSWORD_COMPLEXITY_HIGH);
|
||||
|
||||
Intent intent = createIntentForPasswordValidation(PASSWORD_COMPLEXITY_NONE,
|
||||
PASSWORD_QUALITY_NUMERIC);
|
||||
intent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID, 123);
|
||||
assertPasswordValidationResultForIntent(LockscreenCredential.createNone(), intent,
|
||||
"PIN must be at least 8 digits");
|
||||
}
|
||||
|
||||
private ChooseLockPassword buildChooseLockPasswordActivity(Intent intent) {
|
||||
return Robolectric.buildActivity(ChooseLockPassword.class, intent).setup().get();
|
||||
}
|
||||
@@ -400,14 +426,27 @@ public class ChooseLockPasswordTest {
|
||||
private void assertPasswordValidationResult(@PasswordComplexity int minComplexity,
|
||||
int passwordType, LockscreenCredential userEnteredPassword,
|
||||
String... expectedValidationResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CONFIRM_CREDENTIALS, false);
|
||||
intent.putExtra(PASSWORD_TYPE_KEY, passwordType);
|
||||
intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity);
|
||||
Intent intent = createIntentForPasswordValidation(minComplexity, passwordType);
|
||||
assertPasswordValidationResultForIntent(userEnteredPassword, intent,
|
||||
expectedValidationResult);
|
||||
}
|
||||
|
||||
private void assertPasswordValidationResultForIntent(LockscreenCredential userEnteredPassword,
|
||||
Intent intent, String... expectedValidationResult) {
|
||||
ChooseLockPassword activity = buildChooseLockPasswordActivity(intent);
|
||||
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity);
|
||||
fragment.validatePassword(userEnteredPassword);
|
||||
String[] messages = fragment.convertErrorCodeToMessages();
|
||||
assertThat(messages).asList().containsExactly((Object[]) expectedValidationResult);
|
||||
assertThat(messages).asList().containsExactly(expectedValidationResult);
|
||||
}
|
||||
|
||||
private Intent createIntentForPasswordValidation(
|
||||
@PasswordComplexity int minComplexity,
|
||||
int passwordType) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CONFIRM_CREDENTIALS, false);
|
||||
intent.putExtra(PASSWORD_TYPE_KEY, passwordType);
|
||||
intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
@@ -18,19 +18,31 @@ package com.android.settings.testutils.shadow;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.annotation.Resetter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Implements(LockPatternUtils.class)
|
||||
public class ShadowLockPatternUtils {
|
||||
|
||||
private static boolean sDeviceEncryptionEnabled;
|
||||
private static Map<Integer, Integer> sUserToComplexityMap = new HashMap<>();
|
||||
|
||||
|
||||
@Resetter
|
||||
public static void reset() {
|
||||
sUserToComplexityMap.clear();
|
||||
sDeviceEncryptionEnabled = false;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
protected boolean hasSecureLockScreen() {
|
||||
@@ -76,4 +88,18 @@ public class ShadowLockPatternUtils {
|
||||
protected boolean checkPasswordHistory(byte[] passwordToCheck, byte[] hashFactor, int userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public @DevicePolicyManager.PasswordComplexity int getRequestedPasswordComplexity(int userId) {
|
||||
return sUserToComplexityMap.getOrDefault(userId,
|
||||
DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
|
||||
}
|
||||
|
||||
public static void setRequiredPasswordComplexity(int userId, int complexity) {
|
||||
sUserToComplexityMap.put(userId, complexity);
|
||||
}
|
||||
|
||||
public static void setRequiredPasswordComplexity(int complexity) {
|
||||
setRequiredPasswordComplexity(UserHandle.myUserId(), complexity);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user