Enforce password complexity in lockscreen setting

Enforce a lock screen that adheres with the required complexity set by
the admin.

This is done by querying the DevicePolicyManager for the complexity set
for the given user, and merging it with the complexity from the "change
lock screen" intent (if any).

If the admin sets a higher complexity requirement than the app
triggering the lock screen change request, then the admin-set complexity
is enforced and the user is not shown information about the requesting
app.

Bug: 165573442
Test: Manually, set complexity using TestDPC and see it applies.
Test: m RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.password.ChooseLockGenericTest
Test: m RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.password.ChooseLockPasswordTest
Change-Id: If3f24f7430bdcbcd34265339f7d2a1ff82a44fc1
This commit is contained in:
Eran Messeri
2020-11-13 15:35:46 +00:00
parent 61464f21a5
commit d8e49b45b1
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);
}
} }