Merge "Enforce password complexity in lockscreen setting"

This commit is contained in:
Eran Messeri
2020-12-01 09:53:08 +00:00
committed by Android (Google) Code Review
5 changed files with 147 additions and 9 deletions

View File

@@ -221,9 +221,19 @@ public class ChooseLockGeneric extends SettingsActivity {
mForBiometrics = intent.getBooleanExtra( mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
mRequestedMinComplexity = intent final int complexityFromIntent = intent
.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); .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); intent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME);
mIsCallingAppAdmin = intent mIsCallingAppAdmin = intent
.getBooleanExtra(EXTRA_KEY_IS_CALLING_APP_ADMIN, /* defValue= */ false); .getBooleanExtra(EXTRA_KEY_IS_CALLING_APP_ADMIN, /* defValue= */ false);
@@ -669,8 +679,9 @@ public class ChooseLockGeneric extends SettingsActivity {
final PreferenceScreen entries = getPreferenceScreen(); final PreferenceScreen entries = getPreferenceScreen();
int adminEnforcedQuality = mDpm.getPasswordQuality(null, mUserId); int adminEnforcedQuality = mDpm.getPasswordQuality(null, mUserId);
EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet( EnforcedAdmin enforcedAdmin =
getActivity(), mUserId); RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(getActivity(),
mUserId);
// If we are to unify a work challenge at the end of the credential enrollment, manually // 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 // 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 // password. This is because once unified, the profile's password policy will

View File

@@ -421,6 +421,8 @@ public class ChooseLockPassword extends SettingsActivity {
if (mUnificationProfileId != UserHandle.USER_NULL) { if (mUnificationProfileId != UserHandle.USER_NULL) {
mMinMetrics.maxWith( mMinMetrics.maxWith(
mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId)); mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId));
mMinComplexity = Math.max(mMinComplexity,
mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId));
} }
if (intent.getBooleanExtra( if (intent.getBooleanExtra(

View File

@@ -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_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; 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_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.ChooseLockGeneric.ChooseLockGenericFragment.KEY_LOCK_SETTINGS_FOOTER;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME; 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); Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1);
ShadowStorageManager.reset(); ShadowStorageManager.reset();
ShadowPersistentDataBlockManager.reset(); ShadowPersistentDataBlockManager.reset();
ShadowLockPatternUtils.reset();
} }
@Test @Test
@@ -377,6 +379,64 @@ public class ChooseLockGenericTest {
ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)).isNotNull(); 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) { private void initActivity(@Nullable Intent intent) {
if (intent == null) { if (intent == null) {
intent = new Intent(); intent = new Intent();

View File

@@ -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.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS; 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_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.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
@@ -67,6 +68,7 @@ import org.robolectric.shadows.ShadowDrawable;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = { @Config(shadows = {
SettingsShadowResources.class, SettingsShadowResources.class,
ShadowLockPatternUtils.class,
ShadowUtils.class, ShadowUtils.class,
ShadowDevicePolicyManager.class, ShadowDevicePolicyManager.class,
}) })
@@ -84,6 +86,7 @@ public class ChooseLockPasswordTest {
@After @After
public void tearDown() { public void tearDown() {
SettingsShadowResources.reset(); SettingsShadowResources.reset();
ShadowLockPatternUtils.reset();
} }
@Test @Test
@@ -378,6 +381,29 @@ public class ChooseLockPasswordTest {
assertThat(drawable.getCreatedFromResId()).isNotEqualTo(R.drawable.ic_fingerprint_header); 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) { private ChooseLockPassword buildChooseLockPasswordActivity(Intent intent) {
return Robolectric.buildActivity(ChooseLockPassword.class, intent).setup().get(); return Robolectric.buildActivity(ChooseLockPassword.class, intent).setup().get();
} }
@@ -400,14 +426,27 @@ public class ChooseLockPasswordTest {
private void assertPasswordValidationResult(@PasswordComplexity int minComplexity, private void assertPasswordValidationResult(@PasswordComplexity int minComplexity,
int passwordType, LockscreenCredential userEnteredPassword, int passwordType, LockscreenCredential userEnteredPassword,
String... expectedValidationResult) { String... expectedValidationResult) {
Intent intent = new Intent(); Intent intent = createIntentForPasswordValidation(minComplexity, passwordType);
intent.putExtra(CONFIRM_CREDENTIALS, false); assertPasswordValidationResultForIntent(userEnteredPassword, intent,
intent.putExtra(PASSWORD_TYPE_KEY, passwordType); expectedValidationResult);
intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity); }
private void assertPasswordValidationResultForIntent(LockscreenCredential userEnteredPassword,
Intent intent, String... expectedValidationResult) {
ChooseLockPassword activity = buildChooseLockPasswordActivity(intent); ChooseLockPassword activity = buildChooseLockPasswordActivity(intent);
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity); ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity);
fragment.validatePassword(userEnteredPassword); fragment.validatePassword(userEnteredPassword);
String[] messages = fragment.convertErrorCodeToMessages(); 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;
} }
} }

View File

@@ -18,19 +18,31 @@ package com.android.settings.testutils.shadow;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.os.UserHandle;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.LockscreenCredential;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
@Implements(LockPatternUtils.class) @Implements(LockPatternUtils.class)
public class ShadowLockPatternUtils { public class ShadowLockPatternUtils {
private static boolean sDeviceEncryptionEnabled; private static boolean sDeviceEncryptionEnabled;
private static Map<Integer, Integer> sUserToComplexityMap = new HashMap<>();
@Resetter
public static void reset() {
sUserToComplexityMap.clear();
sDeviceEncryptionEnabled = false;
}
@Implementation @Implementation
protected boolean hasSecureLockScreen() { protected boolean hasSecureLockScreen() {
@@ -76,4 +88,18 @@ public class ShadowLockPatternUtils {
protected boolean checkPasswordHistory(byte[] passwordToCheck, byte[] hashFactor, int userId) { protected boolean checkPasswordHistory(byte[] passwordToCheck, byte[] hashFactor, int userId) {
return false; 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);
}
} }