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

Commit 38f333a2 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Throttling disables auto-confirm for rest of the session." into main

parents 0cdf58e2 1fc0b095
Loading
Loading
Loading
Loading
+55 −2
Original line number Diff line number Diff line
@@ -312,6 +312,59 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                .isEqualTo(AuthenticationResult.SKIPPED)
        }

    @Test
    fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
        testScope.runTest {
            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
            utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)

            assertThat(isAutoConfirmEnabled).isFalse()
        }

    @Test
    fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
        testScope.runTest {
            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)

            assertThat(isAutoConfirmEnabled).isTrue()
        }

    @Test
    fun isAutoConfirmEnabled_featureEnabledButDisabledByThrottling() =
        testScope.runTest {
            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
            val throttling by collectLastValue(underTest.throttling)
            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)

            // The feature is enabled.
            assertThat(isAutoConfirmEnabled).isTrue()

            // Make many wrong attempts to trigger throttling.
            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
            }
            assertThat(throttling).isNotNull()

            // Throttling disabled auto-confirm.
            assertThat(isAutoConfirmEnabled).isFalse()

            // Move the clock forward one more second, to completely finish the throttling period:
            advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L)
            assertThat(throttling).isNull()

            // Auto-confirm is still disabled, because throttling occurred at least once in this
            // session.
            assertThat(isAutoConfirmEnabled).isFalse()

            // Correct PIN and unlocks successfully, resetting the 'session'.
            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                .isEqualTo(AuthenticationResult.SUCCEEDED)

            // Auto-confirm is re-enabled.
            assertThat(isAutoConfirmEnabled).isTrue()
        }

    @Test
    fun throttling() =
        testScope.runTest {
@@ -350,6 +403,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                )

            // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
            val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS
            repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time ->
                advanceTimeBy(1000)
                assertThat(throttling)
@@ -358,8 +412,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
                            failedAttemptCount =
                                FakeAuthenticationRepository
                                    .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
                            remainingSeconds =
                                FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - (time + 1),
                            remainingSeconds = throttleTimeoutSec - (time + 1),
                        )
                    )
            }
+21 −15
Original line number Diff line number Diff line
@@ -59,14 +59,6 @@ import kotlinx.coroutines.withContext

/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {
    /**
     * Whether the auto confirm feature is enabled for the currently-selected user.
     *
     * Note that the length of the PIN is also important to take into consideration, please see
     * [hintedPinLength].
     */
    val isAutoConfirmFeatureEnabled: StateFlow<Boolean>

    /**
     * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user
     * in order to unlock the device.
@@ -93,6 +85,17 @@ interface AuthenticationRepository {
     */
    val throttling: MutableStateFlow<AuthenticationThrottlingModel?>

    /** Whether throttling has occurred at least once since the last successful authentication. */
    val hasThrottlingOccurred: MutableStateFlow<Boolean>

    /**
     * Whether the auto confirm feature is enabled for the currently-selected user.
     *
     * Note that the length of the PIN is also important to take into consideration, please see
     * [hintedPinLength].
     */
    val isAutoConfirmFeatureEnabled: StateFlow<Boolean>

    /**
     * The currently-configured authentication method. This determines how the authentication
     * challenge needs to be completed in order to unlock an otherwise locked device.
@@ -172,11 +175,6 @@ constructor(
    mobileConnectionsRepository: MobileConnectionsRepository,
) : AuthenticationRepository {

    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
        refreshingFlow(
            initialValue = false,
            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
        )
    override val authenticationChallengeResult = MutableSharedFlow<Boolean>()

    override val hintedPinLength: Int = 6
@@ -190,8 +188,13 @@ constructor(
    override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
        MutableStateFlow(null)

    private val selectedUserId: Int
        get() = userRepository.getSelectedUserInfo().id
    override val hasThrottlingOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)

    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
        refreshingFlow(
            initialValue = false,
            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
        )

    override val authenticationMethod: Flow<AuthenticationMethodModel> =
        combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
@@ -280,6 +283,9 @@ constructor(
        }
    }

    private val selectedUserId: Int
        get() = userRepository.getSelectedUserInfo().id

    /**
     * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is
     * invoked on a background thread every time the selected user is changed and every time a new
+8 −6
Original line number Diff line number Diff line
@@ -95,15 +95,14 @@ constructor(
     *
     * Note that the length of the PIN is also important to take into consideration, please see
     * [hintedPinLength].
     *
     * During throttling, this is always disabled (`false`).
     */
    val isAutoConfirmEnabled: StateFlow<Boolean> =
        combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) {
        combine(repository.isAutoConfirmFeatureEnabled, repository.hasThrottlingOccurred) {
                featureEnabled,
                throttling ->
                // Disable auto-confirm during throttling.
                featureEnabled && throttling == null
                hasThrottlingOccurred ->
                // Disable auto-confirm if throttling occurred since the last successful
                // authentication attempt.
                featureEnabled && !hasThrottlingOccurred
            }
            .stateIn(
                scope = applicationScope,
@@ -221,6 +220,7 @@ constructor(
            repository.setThrottleDuration(
                durationMs = authenticationResult.throttleDurationMs,
            )
            repository.hasThrottlingOccurred.value = true
            startThrottlingCountdown()
        }

@@ -228,6 +228,8 @@ constructor(
            // Since authentication succeeded, we should refresh throttling to make sure that our
            // state is completely reflecting the upstream source of truth.
            refreshThrottling()

            repository.hasThrottlingOccurred.value = false
        }

        return if (authenticationResult.isSuccessful) {
+10 −3
Original line number Diff line number Diff line
@@ -40,9 +40,6 @@ class FakeAuthenticationRepository(
    private val currentTime: () -> Long,
) : AuthenticationRepository {

    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
        _isAutoConfirmFeatureEnabled.asStateFlow()
    override val authenticationChallengeResult = MutableSharedFlow<Boolean>()

    override val hintedPinLength: Int = HINTING_PIN_LENGTH
@@ -53,6 +50,12 @@ class FakeAuthenticationRepository(
    override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
        MutableStateFlow(null)

    override val hasThrottlingOccurred = MutableStateFlow(false)

    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
        _isAutoConfirmFeatureEnabled.asStateFlow()

    private val _authenticationMethod =
        MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
    override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
@@ -107,6 +110,9 @@ class FakeAuthenticationRepository(

    override suspend fun setThrottleDuration(durationMs: Int) {
        throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
        if (durationMs > 0) {
            hasThrottlingOccurred.value = true
        }
    }

    override suspend fun checkCredential(
@@ -128,6 +134,7 @@ class FakeAuthenticationRepository(
        return if (
            isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
        ) {
            hasThrottlingOccurred.value = false
            AuthenticationResultModel(
                isSuccessful = isSuccessful,
                throttleDurationMs = 0,