[PM-10282] Default to last active account for passkey creation (#3780)

This commit is contained in:
Patrick Honkonen 2024-08-19 15:10:31 -04:00 committed by GitHub
parent 5f46423638
commit a15b84a5bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 28 additions and 3 deletions

View file

@ -21,6 +21,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.Clock
import javax.inject.Singleton import javax.inject.Singleton
/** /**
@ -41,6 +42,7 @@ object Fido2ProviderModule {
fido2CredentialManager: Fido2CredentialManager, fido2CredentialManager: Fido2CredentialManager,
dispatcherManager: DispatcherManager, dispatcherManager: DispatcherManager,
intentManager: IntentManager, intentManager: IntentManager,
clock: Clock,
): Fido2ProviderProcessor = ): Fido2ProviderProcessor =
Fido2ProviderProcessorImpl( Fido2ProviderProcessorImpl(
context, context,
@ -49,6 +51,7 @@ object Fido2ProviderModule {
fido2CredentialStore, fido2CredentialStore,
fido2CredentialManager, fido2CredentialManager,
intentManager, intentManager,
clock,
dispatcherManager, dispatcherManager,
) )

View file

@ -38,6 +38,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAut
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.Clock
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
private const val CREATE_PASSKEY_INTENT = "com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY" private const val CREATE_PASSKEY_INTENT = "com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY"
@ -57,6 +58,7 @@ class Fido2ProviderProcessorImpl(
private val fido2CredentialStore: Fido2CredentialStore, private val fido2CredentialStore: Fido2CredentialStore,
private val fido2CredentialManager: Fido2CredentialManager, private val fido2CredentialManager: Fido2CredentialManager,
private val intentManager: IntentManager, private val intentManager: IntentManager,
private val clock: Clock,
dispatcherManager: DispatcherManager, dispatcherManager: DispatcherManager,
) : Fido2ProviderProcessor { ) : Fido2ProviderProcessor {
@ -111,13 +113,14 @@ class Fido2ProviderProcessorImpl(
val userState = authRepository.userStateFlow.value ?: return null val userState = authRepository.userStateFlow.value ?: return null
return BeginCreateCredentialResponse.Builder() return BeginCreateCredentialResponse.Builder()
.setCreateEntries(userState.accounts.toCreateEntries()) .setCreateEntries(userState.accounts.toCreateEntries(userState.activeUserId))
.build() .build()
} }
private fun List<UserState.Account>.toCreateEntries() = map { it.toCreateEntry() } private fun List<UserState.Account>.toCreateEntries(activeUserId: String) =
map { it.toCreateEntry(isActive = activeUserId == it.userId) }
private fun UserState.Account.toCreateEntry(): CreateEntry { private fun UserState.Account.toCreateEntry(isActive: Boolean): CreateEntry {
val accountName = name ?: email val accountName = name ?: email
return CreateEntry return CreateEntry
.Builder( .Builder(
@ -134,6 +137,9 @@ class Fido2ProviderProcessorImpl(
accountName, accountName,
), ),
) )
// Set the last used time to "now" so the active account is the default option in the
// system prompt.
.setLastUsedTime(if (isActive) clock.instant() else null)
.build() .build()
} }

View file

@ -53,6 +53,9 @@ import kotlinx.serialization.encodeToString
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
class Fido2ProviderProcessorTest { class Fido2ProviderProcessorTest {
@ -80,6 +83,7 @@ class Fido2ProviderProcessorTest {
private val cancellationSignal: CancellationSignal = mockk() private val cancellationSignal: CancellationSignal = mockk()
private val json = PlatformNetworkModule.providesJson() private val json = PlatformNetworkModule.providesJson()
private val clock = FIXED_CLOCK
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
@ -90,6 +94,7 @@ class Fido2ProviderProcessorTest {
fido2CredentialStore, fido2CredentialStore,
fido2CredentialManager, fido2CredentialManager,
intentManager, intentManager,
clock,
dispatcherManager, dispatcherManager,
) )
} }
@ -244,6 +249,12 @@ class Fido2ProviderProcessorTest {
verify(exactly = 0) { callback.onError(any()) } verify(exactly = 0) { callback.onError(any()) }
assertEquals(DEFAULT_USER_STATE.accounts.size, captureSlot.captured.createEntries.size) assertEquals(DEFAULT_USER_STATE.accounts.size, captureSlot.captured.createEntries.size)
// Verify only the active account entry has a lastUsedTime
assertEquals(
1,
captureSlot.captured.createEntries.filter { it.lastUsedTime != null }.size,
)
DEFAULT_USER_STATE.accounts.forEachIndexed { index, mockAccount -> DEFAULT_USER_STATE.accounts.forEachIndexed { index, mockAccount ->
assertEquals(mockAccount.email, captureSlot.captured.createEntries[index].accountName) assertEquals(mockAccount.email, captureSlot.captured.createEntries[index].accountName)
} }
@ -495,6 +506,11 @@ private val DEFAULT_USER_STATE = UserState(
accounts = createMockAccounts(2), accounts = createMockAccounts(2),
) )
private val FIXED_CLOCK: Clock = Clock.fixed(
Instant.parse("2023-10-27T12:00:00Z"),
ZoneOffset.UTC,
)
private fun createMockAccounts(number: Int): List<UserState.Account> { private fun createMockAccounts(number: Int): List<UserState.Account> {
val accounts = mutableListOf<UserState.Account>() val accounts = mutableListOf<UserState.Account>()
repeat(number) { repeat(number) {