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:
@@ -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
|
||||||
|
@@ -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(
|
||||||
|
@@ -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();
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user