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

Commit 105fff47 authored by Grace Cheng's avatar Grace Cheng
Browse files

Delay unlock during secure lock device until two-factor auth complete

Updates AuthenticationRepository and CredentialInteractor to await
two-factor authentication completion when secure lock device is enabled
(for flexiglass-enabled secure lock device implementation)

Bug: 401645997
Flag: android.security.secure_lock_device
Test: atest AuthenticationRepositoryTest
Test: atest CredentialInteractorImplTest
Change-Id: I26396923c9ef5d72f24b056ff1608b2772dbb807
parent 2dfc4d6d
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -19,14 +19,18 @@ package com.android.systemui.authentication.data.repository
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
import android.platform.test.annotations.EnableFlags
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
@@ -46,7 +50,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.never

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -196,6 +202,19 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
            assertThat(hasLockoutOccurred).isFalse()
        }

    @EnableSceneContainer
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun doesNotReportUnlock_afterPrimaryAuthInSecureLockDevice() =
        testScope.runTest {
            whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
                .thenReturn(STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE)

            underTest.reportAuthenticationAttempt(true)
            verify(lockPatternUtils, never()).userPresent(anyInt())
            verify(lockPatternUtils, never()).reportSuccessfulPasswordAttempt(anyInt())
        }

    private fun setSecurityModeAndDispatchBroadcast(
        securityMode: KeyguardSecurityModel.SecurityMode
    ) {
+70 −61
Original line number Diff line number Diff line
@@ -6,9 +6,11 @@ import android.content.pm.UserInfo
import android.hardware.biometrics.Flags
import android.os.UserManager
import android.platform.test.annotations.EnableFlags
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
import com.android.internal.widget.LockscreenCredential
import com.android.internal.widget.VerifyCredentialResponse
import com.android.systemui.SysuiTestCase
@@ -16,6 +18,7 @@ import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.promptInfo
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
@@ -32,6 +35,7 @@ import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.never

private const val USER_ID = 22
private const val OWNER_ID = 10
@@ -113,6 +117,11 @@ class CredentialInteractorImplTest : SysuiTestCase() {

    @Test fun pinCredentialWhenGood() = pinCredential(goodCredential())

    @EnableSceneContainer
    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun pinCredentialWhenGood_duringSecureLockDevice() = pinCredential(goodCredential())

    @Test fun pinCredentialWhenBad() = pinCredential(badCredential())

    @Test fun pinCredentialWhenBadAndThrottled() = pinCredential(badCredential(timeout = 5_000))
@@ -130,22 +139,23 @@ class CredentialInteractorImplTest : SysuiTestCase() {
    fun pinCredentialTiedProfileWhenBadAndThrottled() =
        pinCredential(badCredential(timeout = 5_000), OWNER_ID)

    private fun pinCredential(result: VerifyCredentialResponse, credentialOwner: Int = USER_ID) =
        runTest {
    private fun pinCredential(
        result: VerifyCredentialResponse,
        credentialOwner: Int = USER_ID,
        isSecureLockDeviceEnabled: Boolean = false,
    ) = runTest {
        val usedAttempts = 1
        if (isSecureLockDeviceEnabled) {
            whenever(lockPatternUtils.getStrongAuthForUser(eq(USER_ID)))
                .thenReturn(PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE)
        }
        whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(credentialOwner)))
            .thenReturn(usedAttempts)
        whenever(lockPatternUtils.verifyCredential(any(), eq(credentialOwner), anyInt()))
            .thenReturn(result)
        whenever(lockPatternUtils.verifyTiedProfileChallenge(any(), eq(USER_ID), anyInt()))
            .thenReturn(result)
            whenever(
                    lockPatternUtils.verifyGatekeeperPasswordHandle(
                        anyLong(),
                        anyLong(),
                        eq(USER_ID),
                    )
                )
        whenever(lockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), eq(USER_ID)))
            .thenReturn(result)
        whenever(lockPatternUtils.setLockoutAttemptDeadline(anyInt(), anyInt())).thenAnswer {
            systemClock.elapsedRealtime() + (it.arguments[1] as Int)
@@ -155,10 +165,7 @@ class CredentialInteractorImplTest : SysuiTestCase() {
        // checks prevents the method from returning
        val statusList = mutableListOf<CredentialStatus>()
        interactor
                .verifyCredential(
                    pinRequest(credentialOwner),
                    LockscreenCredential.createPin("1234"),
                )
            .verifyCredential(pinRequest(credentialOwner), LockscreenCredential.createPin("1234"))
            .toList(statusList)

        val last = statusList.removeLastOrNull()
@@ -168,9 +175,13 @@ class CredentialInteractorImplTest : SysuiTestCase() {
            assertThat(successfulResult).isNotNull()
            assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)

            if (isSecureLockDeviceEnabled) {
                verify(lockPatternUtils, never()).userPresent(eq(credentialOwner))
            } else {
                verify(lockPatternUtils).userPresent(eq(credentialOwner))
                verify(lockPatternUtils)
                    .removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
            }
        } else {
            val failedResult = last as? CredentialStatus.Fail.Error
            assertThat(failedResult).isNotNull()
@@ -182,9 +193,7 @@ class CredentialInteractorImplTest : SysuiTestCase() {
                // messages are in the throttled errors, so the final Error.error is empty
                assertThat(failedResult.error).isEmpty()
                assertThat(statusList).isNotEmpty()
                    assertThat(
                            statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java)
                        )
                assertThat(statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java))
                    .hasSize(statusList.size)

                verify(lockPatternUtils)
+23 −2
Original line number Diff line number Diff line
@@ -20,8 +20,11 @@ import android.annotation.UserIdInt
import android.app.admin.DevicePolicyManager
import android.content.IntentFilter
import android.os.UserHandle
import android.security.Flags.secureLockDevice
import android.util.Log
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -295,8 +298,22 @@ constructor(
    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
        withContext(backgroundDispatcher) {
            if (isSuccessful) {
                if (
                    secureLockDevice() &&
                        SceneContainerFlag.isEnabled &&
                        lockPatternUtils
                            .getStrongAuthForUser(selectedUserId)
                            .and(STRONG_BIOMETRIC_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE) != 0
                ) {
                    Log.d(
                        TAG,
                        "Device is in secure lock device mode; awaiting second factor " +
                            "biometric authentication before unlocking.",
                    )
                } else {
                    lockPatternUtils.userPresent(selectedUserId)
                    lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
                }
                _hasLockoutOccurred.value = false
            } else {
                lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
@@ -400,6 +417,10 @@ constructor(
            }
        }
    }

    companion object {
        private const val TAG = "AuthenticationRepository"
    }
}

@Module
+23 −1
Original line number Diff line number Diff line
@@ -5,12 +5,16 @@ import android.app.admin.DevicePolicyResources
import android.content.Context
import android.hardware.biometrics.Flags
import android.os.UserManager
import android.security.Flags.secureLockDevice
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE
import com.android.internal.widget.LockscreenCredential
import com.android.internal.widget.VerifyCredentialResponse
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.delay
@@ -89,7 +93,21 @@ constructor(
            }

        if (response.isMatched) {
            if (
                secureLockDevice() &&
                    SceneContainerFlag.isEnabled &&
                    lockPatternUtils
                        .getStrongAuthForUser(request.userInfo.userId)
                        .and(PRIMARY_AUTH_REQUIRED_FOR_SECURE_LOCK_DEVICE) != 0
            ) {
                Log.i(
                    TAG,
                    "Device is in secure lock device mode; awaiting second factor biometric " +
                        "authentication before unlocking.",
                )
            } else {
                lockPatternUtils.userPresent(effectiveUserId)
            }

            // The response passed into this method contains the Gatekeeper
            // Password. We still have to request Gatekeeper to create a
@@ -166,6 +184,10 @@ constructor(
        } else {
            null
        }

    companion object {
        private val TAG = "CredentialInteractorImpl"
    }
}

private enum class UserType {