Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 78e7b0f3 authored by Haining Chen's avatar Haining Chen Committed by Android (Google) Code Review
Browse files

Merge "Add config resources for failed auth lock" into main

parents 72b6f5ec 52861c59
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -1548,6 +1548,19 @@
         False means the pattern should not be visible by default. -->
    <bool name="config_lockPatternVisibleDefault">true</bool>

    <!-- If true, then enable the failed authentication lock feature (aka adaptive auth) which
         locks the device after repeated authentication failures. Setting this to false weakens user
         protection and is not recommended for production devices. -->
    <bool name="config_enableFailedAuthLock">true</bool>

    <!-- The maximum number of failed authentication attempts allowed before the device is locked,
         if failed authentication lock is enabled -->
    <integer name="config_maxAllowedFailedAuthAttempts">5</integer>

    <!-- Whether to show a user-facing toggle for failed authentication lock if the feature is
         enabled. -->
    <bool name="config_enableFailedAuthLockToggle">true</bool>

    <!-- Control the behavior when the user long presses the home button.
            0 - Nothing
            1 - Launch all apps intent
+4 −0
Original line number Diff line number Diff line
@@ -4206,6 +4206,10 @@
  <java-symbol type="bool" name="config_lockPinEnhancedPrivacyDefault" />
  <java-symbol type="bool" name="config_lockPatternVisibleDefault" />

  <java-symbol type="bool" name="config_enableFailedAuthLock" />
  <java-symbol type="integer" name="config_maxAllowedFailedAuthAttempts" />
  <java-symbol type="bool" name="config_enableFailedAuthLockToggle" />

  <!-- ETWS primary messages -->
  <java-symbol type="string" name="etws_primary_default_message_earthquake" />
  <java-symbol type="string" name="etws_primary_default_message_tsunami" />
+27 −10
Original line number Diff line number Diff line
@@ -92,9 +92,6 @@ public class AuthenticationPolicyService extends SystemService {
    private static final String TAG = "AuthenticationPolicyService";
    private static final boolean DEBUG = Build.IS_DEBUGGABLE;

    @VisibleForTesting
    static final int MAX_ALLOWED_FAILED_AUTH_ATTEMPTS = 5;
    private static final boolean DEFAULT_DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK = false;
    private static final int MSG_REPORT_PRIMARY_AUTH_ATTEMPT = 1;
    private static final int MSG_REPORT_BIOMETRIC_AUTH_SUCCESS = 2;
    private static final int MSG_REPORT_BIOMETRIC_AUTH_FAILURE = 3;
@@ -110,6 +107,9 @@ public class AuthenticationPolicyService extends SystemService {
    private final KeyguardManager mKeyguardManager;
    private final WindowManagerInternal mWindowManager;
    private final UserManagerInternal mUserManager;
    private final boolean mEnableFailedAuthLock;
    private final int mMaxAllowedFailedAuthAttempts;
    private final boolean mEnableFailedAuthLockToggle;
    private SecureLockDeviceServiceInternal mSecureLockDeviceService;
    private WatchRangingServiceInternal mWatchRangingService;
    @VisibleForTesting
@@ -140,6 +140,12 @@ public class AuthenticationPolicyService extends SystemService {
            mWatchRangingService = Objects.requireNonNull(LocalServices.getService(
                    WatchRangingServiceInternal.class));
        }
        mEnableFailedAuthLock = context.getResources().getBoolean(
                com.android.internal.R.bool.config_enableFailedAuthLock);
        mMaxAllowedFailedAuthAttempts = context.getResources().getInteger(
                com.android.internal.R.integer.config_maxAllowedFailedAuthAttempts);
        mEnableFailedAuthLockToggle = context.getResources().getBoolean(
                com.android.internal.R.bool.config_enableFailedAuthLockToggle);
    }

    @Override
@@ -157,7 +163,7 @@ public class AuthenticationPolicyService extends SystemService {

    @Override
    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
        if (failedAuthLockToggle()) {
        if (failedAuthLockToggle() && mEnableFailedAuthLock && mEnableFailedAuthLockToggle) {
            mayInitiateFailedAuthLockSettings(to.getUserIdentifier());
        }
    }
@@ -167,7 +173,7 @@ public class AuthenticationPolicyService extends SystemService {
        mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener);
        mBiometricManager.registerAuthenticationStateListener(mAuthenticationStateListener);

        if (failedAuthLockToggle()) {
        if (failedAuthLockToggle() && mEnableFailedAuthLock && mEnableFailedAuthLockToggle) {
            final int mainUserId = mUserManager.getMainUserId();
            if (mainUserId != UserHandle.USER_NULL) {
                mayInitiateFailedAuthLockSettings(mainUserId);
@@ -195,7 +201,7 @@ public class AuthenticationPolicyService extends SystemService {
            Settings.Secure.putIntForUser(
                    getContext().getContentResolver(),
                    Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK,
                    DEFAULT_DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK ? 1 : 0,
                    mEnableFailedAuthLock ? 0 : 1,
                    parentUserId);
        }
    }
@@ -338,6 +344,13 @@ public class AuthenticationPolicyService extends SystemService {
    }

    private void reportAuthAttempt(int authType, boolean success, int userId) {
        // Do not report auth attempts and do not proceed to lock the device if the failed auth lock
        // feature (aka adaptive auth) is completely disabled by the device manufacturer
        if (failedAuthLockToggle() && !mEnableFailedAuthLock) {
            Slog.v(TAG, "Failed auth lock is disabled by the device manufacturer");
            return;
        }

        // Disable adaptive auth for automotive devices by default
        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
            return;
@@ -372,14 +385,17 @@ public class AuthenticationPolicyService extends SystemService {
            return;
        }

        if (numFailedAttempts < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS) {
        if (numFailedAttempts < mMaxAllowedFailedAuthAttempts) {
            Slog.d(TAG, "Not locking the device because the number of failed attempts is below"
                    + " the threshold.");
            return;
        }

        //TODO(b/421051706): Remove the condition Build.IS_DEBUGGABLE after flags are ramped up
        if (failedAuthLockToggle() || (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE)) {
        // If a user toggle is enabled by the device manufacturer on 25Q4+ builds, or if it's
        // debuggable 25Q3+ builds, then failed auth lock can be enabled or disabled by
        // users in settings
        if ((failedAuthLockToggle() && mEnableFailedAuthLockToggle)
                || (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE)) {
            // If userId is a profile, use its parent's settings to determine whether failed auth
            // lock is enabled or disabled for the profile, irrespective of the profile's own
            // settings. If userId is a main user (i.e. parentUserId equals to userId), use its own
@@ -388,7 +404,8 @@ public class AuthenticationPolicyService extends SystemService {
            final boolean disabled = Settings.Secure.getIntForUser(
                    getContext().getContentResolver(),
                    Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK,
                    DEFAULT_DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK ? 1 : 0, parentUserId) != 0;
                    mEnableFailedAuthLock ? 0 : 1,
                    parentUserId) != 0;
            if (disabled) {
                Slog.i(TAG, "userId=" + userId + ", parentUserId=" + parentUserId
                        + ", failed auth lock is disabled by user in settings");
+109 −1
Original line number Diff line number Diff line
@@ -24,13 +24,14 @@ import static android.security.authenticationpolicy.AuthenticationPolicyManager.

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
import static com.android.server.security.authenticationpolicy.AuthenticationPolicyService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -40,6 +41,7 @@ import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricRequestConstants;
@@ -91,9 +93,11 @@ public class AuthenticationPolicyServiceTest {

    private static final int PRIMARY_USER_ID = 0;
    private static final int MANAGED_PROFILE_USER_ID = 12;
    private static final int MAX_ALLOWED_FAILED_AUTH_ATTEMPTS = 5;
    private static final int DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS = 0;

    private Context mContext;
    private Resources mResources;
    private AuthenticationPolicyService mAuthenticationPolicyService;

    @Mock
@@ -122,6 +126,16 @@ public class AuthenticationPolicyServiceTest {
    @Before
    public void setUp() {
        mContext = spy(ApplicationProvider.getApplicationContext());
        mResources = spy(mContext.getResources());

        when(mContext.getResources()).thenReturn(mResources);
        when(mResources.getBoolean(
                com.android.internal.R.bool.config_enableFailedAuthLock)).thenReturn(true);
        when(mResources.getInteger(
                com.android.internal.R.integer.config_maxAllowedFailedAuthAttempts))
                .thenReturn(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS);
        when(mResources.getBoolean(
                com.android.internal.R.bool.config_enableFailedAuthLockToggle)).thenReturn(true);

        assumeTrue("Adaptive auth is disabled on device",
                !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
@@ -177,6 +191,100 @@ public class AuthenticationPolicyServiceTest {
        toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */);
    }

    @Test
    @EnableFlags({android.security.Flags.FLAG_FAILED_AUTH_LOCK_TOGGLE})
    public void testConfig_failedAuthLock_whenDisabled() throws RemoteException {
        // The feature is disabled in config. In this case, the toggle config value (true or false)
        // doesn't matter
        clearSettingsAndInitService(false /* featureEnabled */, true /* toggleEnabled */);

        // Verify that the setting was not written
        assertThrows(Settings.SettingNotFoundException.class, () -> {
            Settings.Secure.getIntForUser(mContext.getContentResolver(),
                    Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, PRIMARY_USER_ID);
        });

        // Five failed biometric auth attempts
        for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) {
            mAuthenticationStateListenerCaptor.getValue().onAuthenticationFailed(
                    authFailedInfo(PRIMARY_USER_ID));
        }
        waitForAuthCompletion();

        // Verify that there are no reported failed auth attempts and that the device is never
        // locked, because the failed auth lock feature is completely disabled in config
        verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */,
                PRIMARY_USER_ID);
    }

    @Test
    @EnableFlags({android.security.Flags.FLAG_FAILED_AUTH_LOCK_TOGGLE})
    @DisableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
    public void testConfig_failedAuthLockToggle_whenDisabled() throws RemoteException {
        // The feature is enabled, but the toggle is disabled in config
        clearSettingsAndInitService(true /* featureEnabled */, false /* toggleEnabled */);

        // Verify that the setting was not written because the toggle is disabled
        assertThrows(Settings.SettingNotFoundException.class, () -> {
            Settings.Secure.getIntForUser(mContext.getContentResolver(),
                    Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, PRIMARY_USER_ID);
        });

        // Five failed biometric auth attempts
        for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) {
            mAuthenticationStateListenerCaptor.getValue().onAuthenticationFailed(
                    authFailedInfo(PRIMARY_USER_ID));
        }
        waitForAuthCompletion();

        // Verify that the device is locked, because the toggle is disabled in config, and thus the
        // feature can't be disabled by users in settings on non-debuggable builds
        verifyAdaptiveAuthLocksDevice(PRIMARY_USER_ID);
    }

    @Test
    @EnableFlags({android.security.Flags.FLAG_FAILED_AUTH_LOCK_TOGGLE})
    @DisableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
    public void testConfig_failedAuthLockToggle_whenEnabled() throws RemoteException {
        // The feature and the toggle are enabled in config
        clearSettingsAndInitService(true /* featureEnabled */, true /* toggleEnabled */);

        // Verify that the setting was written with the default value (0 means the feature is
        // enabled)
        assertEquals(0, Settings.Secure.getIntForUser(mContext.getContentResolver(),
                Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, -1, PRIMARY_USER_ID));

        // Five failed biometric auth attempts
        for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) {
            mAuthenticationStateListenerCaptor.getValue().onAuthenticationFailed(
                    authFailedInfo(PRIMARY_USER_ID));
        }
        waitForAuthCompletion();

        // Verify that the device is locked
        verifyAdaptiveAuthLocksDevice(PRIMARY_USER_ID);
    }

    private void clearSettingsAndInitService(boolean featureEnabled, boolean toggleEnabled) {
        // Ensure that the setting does not exist beforehand to test the initialization logic
        Settings.Secure.putStringForUser(mContext.getContentResolver(),
                Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, null, PRIMARY_USER_ID);

        // Change the feature and the toggle configs
        when(mResources.getBoolean(com.android.internal.R.bool.config_enableFailedAuthLock))
                .thenReturn(featureEnabled);
        when(mResources.getBoolean(com.android.internal.R.bool.config_enableFailedAuthLockToggle))
                .thenReturn(toggleEnabled);

        // Re-initialize the service to trigger setting initialization
        mAuthenticationPolicyService = new AuthenticationPolicyService(mContext, mLockPatternUtils);
        mAuthenticationPolicyService.init();

        // Re-capture the listener from the new service instance
        verify(mBiometricManager, atLeastOnce()).registerAuthenticationStateListener(
                mAuthenticationStateListenerCaptor.capture());
    }

    @Test
    public void testReportAuthAttempt_primaryAuthSucceeded()
            throws RemoteException {