[PM-15906] Implement single tap passkey flows (#4547)

This commit is contained in:
Patrick Honkonen authored and GitHub committed 2025-01-23 16:38:36 +00:00
1 parent bf60f8f9e3
commit e9159cc8c1
22 files changed
+286 -98

No files matched your search

@@ -13,7 +13,7 @@ import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CreateCredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
@@ -263,7 +263,7 @@ class MainViewModel @Inject constructor(
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
val hasVaultShortcut = intent.isMyVaultShortcut
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
val fido2CreateCredentialRequestData = intent.getFido2CreateCredentialRequestOrNull()
val completeRegistrationData = intent.getCompleteRegistrationDataIntentOrNull()
val fido2CredentialAssertionRequest = intent.getFido2AssertionRequestOrNull()
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
@@ -324,25 +324,30 @@ class MainViewModel @Inject constructor(
)
}
fido2CredentialRequestData != null -> {
fido2CreateCredentialRequestData != null -> {
// Set the user's verification status when a new FIDO 2 request is received to force
// explicit verification if the user's vault is unlocked when the request is
// received.
fido2CredentialManager.isUserVerified = false
fido2CredentialManager.isUserVerified =
fido2CreateCredentialRequestData.isUserVerified
?: fido2CredentialManager.isUserVerified
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CredentialRequestData,
fido2CreateCredentialRequest = fido2CreateCredentialRequestData,
)
// Switch accounts if the selected user is not the active user.
if (authRepository.activeUserId != null &&
authRepository.activeUserId != fido2CredentialRequestData.userId
authRepository.activeUserId != fido2CreateCredentialRequestData.userId
) {
authRepository.switchAccount(fido2CredentialRequestData.userId)
authRepository.switchAccount(fido2CreateCredentialRequestData.userId)
}
}
fido2CredentialAssertionRequest != null -> {
fido2CredentialManager.isUserVerified =
fido2CredentialAssertionRequest.isUserVerified
?: false
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Assertion(
fido2AssertionRequest = fido2CredentialAssertionRequest,
@@ -13,6 +13,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManagerImpl
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessor
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessorImpl
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
@@ -44,6 +46,8 @@ object Fido2ProviderModule {
fido2CredentialManager: Fido2CredentialManager,
dispatcherManager: DispatcherManager,
intentManager: IntentManager,
biometricsEncryptionManager: BiometricsEncryptionManager,
featureFlagManager: FeatureFlagManager,
clock: Clock,
): Fido2ProviderProcessor =
Fido2ProviderProcessorImpl(
@@ -54,6 +58,8 @@ object Fido2ProviderModule {
fido2CredentialManager,
intentManager,
clock,
biometricsEncryptionManager,
featureFlagManager,
dispatcherManager,
)
@@ -20,6 +20,7 @@ data class Fido2CreateCredentialRequest(
val packageName: String,
val signingInfo: SigningInfo,
val origin: String?,
val isUserVerified: Boolean?,
) : Parcelable {
val callingAppInfo: CallingAppInfo
get() = CallingAppInfo(
@@ -18,6 +18,7 @@ data class Fido2CredentialAssertionRequest(
val packageName: String,
val signingInfo: SigningInfo,
val origin: String?,
val isUserVerified: Boolean?,
) : Parcelable {
val callingAppInfo: CallingAppInfo
get() = CallingAppInfo(packageName, signingInfo, origin)
@@ -6,6 +6,8 @@ import android.os.Build
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.ClearCredentialUnsupportedException
import androidx.credentials.exceptions.CreateCredentialCancellationException
@@ -22,6 +24,7 @@ import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
import androidx.credentials.provider.BeginGetCredentialRequest
import androidx.credentials.provider.BeginGetCredentialResponse
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import androidx.credentials.provider.BiometricPromptData
import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.CredentialEntry
import androidx.credentials.provider.ProviderClearCredentialStateRequest
@@ -34,9 +37,13 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
@@ -45,6 +52,7 @@ import kotlinx.coroutines.flow.fold
import kotlinx.coroutines.launch
import java.time.Clock
import java.util.concurrent.atomic.AtomicInteger
import javax.crypto.Cipher
private const val CREATE_PASSKEY_INTENT = "com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY"
const val GET_PASSKEY_INTENT = "com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY"
@@ -54,7 +62,7 @@ const val UNLOCK_ACCOUNT_INTENT = "com.x8bit.bitwarden.fido2.ACTION_UNLOCK_ACCOU
* The default implementation of [Fido2ProviderProcessor]. Its purpose is to handle FIDO2 related
* processing.
*/
@Suppress("LongParameterList")
@Suppress("LongParameterList", "TooManyFunctions")
@RequiresApi(Build.VERSION_CODES.S)
class Fido2ProviderProcessorImpl(
private val context: Context,
@@ -64,6 +72,8 @@ class Fido2ProviderProcessorImpl(
private val fido2CredentialManager: Fido2CredentialManager,
private val intentManager: IntentManager,
private val clock: Clock,
private val biometricsEncryptionManager: BiometricsEncryptionManager,
private val featureFlagManager: FeatureFlagManager,
dispatcherManager: DispatcherManager,
) : Fido2ProviderProcessor {
@@ -127,7 +137,7 @@ class Fido2ProviderProcessorImpl(
private fun UserState.Account.toCreateEntry(isActive: Boolean): CreateEntry {
val accountName = name ?: email
return CreateEntry
val entryBuilder = CreateEntry
.Builder(
accountName = accountName,
pendingIntent = intentManager.createFido2CreationPendingIntent(
@@ -145,7 +155,16 @@ class Fido2ProviderProcessorImpl(
// 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()
.setAutoSelectAllowed(true)
if (isVaultUnlocked &&
featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyCreation)
) {
biometricsEncryptionManager
.getOrCreateCipher(userId)
?.let { entryBuilder.setBiometricPromptDataIfSupported(cipher = it) }
}
return entryBuilder.build()
}
override fun processGetCredentialRequest(
@@ -261,30 +280,73 @@ class Fido2ProviderProcessorImpl(
): List<CredentialEntry> =
this
.map {
PublicKeyCredentialEntry
val publicKeyEntryBuilder = PublicKeyCredentialEntry
.Builder(
context = context,
username = it.userNameForUi ?: context.getString(R.string.no_username),
pendingIntent = intentManager
.createFido2GetCredentialPendingIntent(
action = GET_PASSKEY_INTENT,
userId = userId,
credentialId = it.credentialId.toString(),
cipherId = it.cipherId,
requestCode = requestCode.getAndIncrement(),
),
pendingIntent = intentManager.createFido2GetCredentialPendingIntent(
action = GET_PASSKEY_INTENT,
userId = userId,
credentialId = it.credentialId.toString(),
cipherId = it.cipherId,
requestCode = requestCode.getAndIncrement(),
),
beginGetPublicKeyCredentialOption = option,
)
.setIcon(
Icon
.createWithResource(
context,
R.drawable.ic_bw_passkey,
),
Icon.createWithResource(
context,
R.drawable.ic_bw_passkey,
),
)
.build()
if (featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyAuthentication)) {
biometricsEncryptionManager
.getOrCreateCipher(userId)
?.let {
publicKeyEntryBuilder
.setBiometricPromptDataIfSupported(cipher = it)
}
}
publicKeyEntryBuilder.build()
}
private fun PublicKeyCredentialEntry.Builder.setBiometricPromptDataIfSupported(
cipher: Cipher,
): PublicKeyCredentialEntry.Builder {
return if (isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM)) {
this
} else {
setBiometricPromptData(
biometricPromptData = BiometricPromptData
.Builder()
.buildPromptDataWithCipher(cipher),
)
}
}
private fun CreateEntry.Builder.setBiometricPromptDataIfSupported(
cipher: Cipher,
): CreateEntry.Builder {
return if (isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM)) {
this
} else {
setBiometricPromptData(
biometricPromptData = BiometricPromptData
.Builder()
.buildPromptDataWithCipher(cipher),
)
}
}
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
private fun BiometricPromptData.Builder.buildPromptDataWithCipher(
cipher: Cipher,
): BiometricPromptData = BiometricPromptData.Builder()
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setCryptoObject(BiometricPrompt.CryptoObject(cipher))
.build()
override fun processClearCredentialStateRequest(
request: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
@@ -18,7 +18,7 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_USER_ID
* Checks if this [Intent] contains a [Fido2CreateCredentialRequest] related to an ongoing FIDO 2
* credential creation process.
*/
fun Intent.getFido2CredentialRequestOrNull(): Fido2CreateCredentialRequest? {
fun Intent.getFido2CreateCredentialRequestOrNull(): Fido2CreateCredentialRequest? {
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
val systemRequest = PendingIntentHandler
@@ -39,6 +39,7 @@ fun Intent.getFido2CredentialRequestOrNull(): Fido2CreateCredentialRequest? {
packageName = systemRequest.callingAppInfo.packageName,
signingInfo = systemRequest.callingAppInfo.signingInfo,
origin = systemRequest.callingAppInfo.origin,
isUserVerified = systemRequest.biometricPromptResult?.isSuccessful ?: false,
)
}
@@ -67,6 +68,9 @@ fun Intent.getFido2AssertionRequestOrNull(): Fido2CredentialAssertionRequest? {
val userId: String = getStringExtra(EXTRA_KEY_USER_ID)
?: return null
val isUserVerified = systemRequest.biometricPromptResult?.isSuccessful
?: false
return Fido2CredentialAssertionRequest(
userId = userId,
cipherId = cipherId,
@@ -76,6 +80,7 @@ fun Intent.getFido2AssertionRequestOrNull(): Fido2CredentialAssertionRequest? {
packageName = systemRequest.callingAppInfo.packageName,
signingInfo = systemRequest.callingAppInfo.signingInfo,
origin = systemRequest.callingAppInfo.origin,
isUserVerified = isUserVerified,
)
}
@@ -40,6 +40,8 @@ sealed class FlagKey<out T : Any> {
NewDeviceTemporaryDismiss,
IgnoreEnvironmentCheck,
MutualTls,
SingleTapPasskeyCreation,
SingleTapPasskeyAuthentication,
)
}
}
@@ -181,6 +183,24 @@ sealed class FlagKey<out T : Any> {
override val isRemotelyConfigured: Boolean = false
}
/**
* Data object holding the feature flag key to enable single tap passkey creation.
*/
data object SingleTapPasskeyCreation : FlagKey<Boolean>() {
override val keyName: String = "single-tap-passkey-creation"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = false
}
/**
* Data object holding the feature flag key to enable single tap passkey authentication.
*/
data object SingleTapPasskeyAuthentication : FlagKey<Boolean>() {
override val keyName: String = "single-tap-passkey-authentication"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = false
}
//region Dummy keys for testing
/**
* Data object holding the key for a [Boolean] flag to be used in tests.
@@ -39,6 +39,8 @@ fun <T : Any> FlagKey<T>.ListItemContent(
FlagKey.NewDeviceTemporaryDismiss,
FlagKey.IgnoreEnvironmentCheck,
FlagKey.MutualTls,
FlagKey.SingleTapPasskeyCreation,
FlagKey.SingleTapPasskeyAuthentication,
-> BooleanFlagItem(
label = flagKey.getDisplayLabel(),
key = flagKey as FlagKey<Boolean>,
@@ -92,4 +94,7 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
FlagKey.NewDeviceTemporaryDismiss -> stringResource(R.string.new_device_temporary_dismiss)
FlagKey.IgnoreEnvironmentCheck -> stringResource(R.string.ignore_environment_check)
FlagKey.MutualTls -> stringResource(R.string.mutual_tls)
FlagKey.SingleTapPasskeyCreation -> stringResource(R.string.single_tap_passkey_creation)
FlagKey.SingleTapPasskeyAuthentication ->
stringResource(R.string.single_tap_passkey_authentication)
}
+3 -1
View File
@@ -1114,11 +1114,13 @@ Do you want to switch to this account?</string>
<string name="you_can_change_your_account_email_on_the_bitwarden_web_app">You can change your account email on the Bitwarden web app.</string>
<string name="login_credentials">Login Credentials</string>
<string name="autofill_options">Autofill Options</string>
<string name="use_this_button_to_generate_a_new_unique_password">Use this button to generate a new unique password.</string>
<string name="use_this_button_to_generate_a_new_unique_password">Use this button to generate a new unique password.</string>
<string name="coachmark_1_of_3">1 of 3</string>
<string name="coachmark_2_of_3">2 of 3</string>
<string name="you_ll_only_need_to_set_up_authenticator_key">You’ll only need to set up Authenticator Key for logins that require two-factor authentication with a code. The key will continuously generate six-digit codes you can use to log in.</string>
<string name="coachmark_3_of_3">3 of 3</string>
<string name="you_must_add_a_web_address_to_use_autofill_to_access_this_account">You must add a web address to use autofill to access this account.</string>
<string name="mutual_tls">Mutual TLS</string>
<string name="single_tap_passkey_creation">Single tap passkey creation</string>
<string name="single_tap_passkey_authentication">Single tap passkey sign-on</string>
</resources>
@@ -22,10 +22,10 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionReq
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialAssertionRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2GetCredentialsRequest
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CreateCredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
@@ -139,7 +139,7 @@ class MainViewModelTest : BaseViewModelTest() {
Intent::getAutofillSelectionDataOrNull,
Intent::getCompleteRegistrationDataIntentOrNull,
Intent::getFido2AssertionRequestOrNull,
Intent::getFido2CredentialRequestOrNull,
Intent::getFido2CreateCredentialRequestOrNull,
Intent::getFido2GetCredentialsRequestOrNull,
Intent::isAddTotpLoginItemFromAuthenticator,
)
@@ -159,7 +159,7 @@ class MainViewModelTest : BaseViewModelTest() {
Intent::getAutofillSelectionDataOrNull,
Intent::getCompleteRegistrationDataIntentOrNull,
Intent::getFido2AssertionRequestOrNull,
Intent::getFido2CredentialRequestOrNull,
Intent::getFido2CreateCredentialRequestOrNull,
Intent::getFido2GetCredentialsRequestOrNull,
Intent::isAddTotpLoginItemFromAuthenticator,
)
@@ -647,6 +647,7 @@ class MainViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = "mockOrigin",
isUserVerified = true,
)
val fido2Intent = createMockIntent(
mockFido2CreateCredentialRequest = fido2CreateCredentialRequest,
@@ -673,11 +674,13 @@ class MainViewModelTest : BaseViewModelTest() {
)
}
@Suppress("MaxLineLength")
@Test
fun `on ReceiveFirstIntent with fido2 request data should set the user to unverified`() {
fun `on ReceiveFirstIntent with fido2 create request data should set the user verification based on request`() {
val viewModel = createViewModel()
val createCredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
val fido2Intent = createMockIntent(
mockFido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
mockFido2CreateCredentialRequest = createCredentialRequest,
)
viewModel.trySendAction(
@@ -687,7 +690,7 @@ class MainViewModelTest : BaseViewModelTest() {
)
verify {
fido2CredentialManager.isUserVerified = false
fido2CredentialManager.isUserVerified = createCredentialRequest.isUserVerified ?: false
}
}
@@ -702,6 +705,7 @@ class MainViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = "mockOrigin",
isUserVerified = true,
)
val mockIntent = createMockIntent(
mockFido2CreateCredentialRequest = fido2CreateCredentialRequest,
@@ -732,6 +736,7 @@ class MainViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = "mockOrigin",
isUserVerified = true,
)
val mockIntent = createMockIntent(
mockFido2CreateCredentialRequest = fido2CreateCredentialRequest,
@@ -1139,7 +1144,7 @@ private fun createMockIntent(
every { getAutofillSelectionDataOrNull() } returns mockAutofillSelectionData
every { getCompleteRegistrationDataIntentOrNull() } returns mockCompleteRegistrationData
every { getFido2AssertionRequestOrNull() } returns mockFido2CredentialAssertionRequest
every { getFido2CredentialRequestOrNull() } returns mockFido2CreateCredentialRequest
every { getFido2CreateCredentialRequestOrNull() } returns mockFido2CreateCredentialRequest
every { getFido2GetCredentialsRequestOrNull() } returns mockFido2GetCredentialsRequest
every { isMyVaultShortcut } returns mockIsMyVaultShortcut
every { isPasswordGeneratorShortcut } returns mockIsPasswordGeneratorShortcut
@@ -16,7 +16,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResu
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CreateCredentialRequest
import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@@ -170,7 +170,7 @@ class Fido2CredentialManagerTest {
@Test
fun `registerFido2Credential should construct ClientData DefaultWithCustomHash when callingAppInfo origin is populated`() =
runTest {
val mockFido2CreateCredentialRequest = createMockFido2CredentialRequest(
val mockFido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = "origin",
signingInfo = mockSigningInfo,
@@ -210,7 +210,7 @@ class Fido2CredentialManagerTest {
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
every { hasMultipleSigners() } returns false
}
val mockFido2Request = createMockFido2CredentialRequest(
val mockFido2Request = createMockFido2CreateCredentialRequest(
number = 1,
signingInfo = mockSigningInfo,
)
@@ -243,7 +243,7 @@ class Fido2CredentialManagerTest {
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
every { hasMultipleSigners() } returns false
}
val mockFido2CreateCredentialRequest = createMockFido2CredentialRequest(
val mockFido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = "origin",
signingInfo = mockSigningInfo,
@@ -283,7 +283,7 @@ class Fido2CredentialManagerTest {
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
every { hasMultipleSigners() } returns false
}
val mockFido2CreateCredentialRequest = createMockFido2CredentialRequest(
val mockFido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = "origin",
signingInfo = mockSigningInfo,
@@ -328,7 +328,7 @@ class Fido2CredentialManagerTest {
val mockSigningInfo = mockk<SigningInfo> {
every { hasMultipleSigners() } returns true
}
val mockFido2CredentialRequest = createMockFido2CredentialRequest(
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = "origin",
signingInfo = mockSigningInfo,
@@ -353,7 +353,7 @@ class Fido2CredentialManagerTest {
val mockSigningInfo = mockk<SigningInfo> {
every { hasMultipleSigners() } returns true
}
val mockFido2CredentialRequest = createMockFido2CredentialRequest(
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
signingInfo = mockSigningInfo,
)
@@ -377,7 +377,7 @@ class Fido2CredentialManagerTest {
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
every { hasMultipleSigners() } returns false
}
val mockFido2CredentialRequest = createMockFido2CredentialRequest(
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = "illegal empty spaces",
signingInfo = mockSigningInfo,
@@ -402,7 +402,7 @@ class Fido2CredentialManagerTest {
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
every { hasMultipleSigners() } returns false
}
val mockFido2CredentialRequest = createMockFido2CredentialRequest(
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = "origin",
signingInfo = mockSigningInfo,
@@ -440,7 +440,7 @@ class Fido2CredentialManagerTest {
@Test
fun `registerFido2Credential should return Error when origin is null`() = runTest {
val mockAssertionRequest = createMockFido2CredentialRequest(
val mockAssertionRequest = createMockFido2CreateCredentialRequest(
number = 1,
origin = null,
signingInfo = mockSigningInfo,
@@ -5,6 +5,7 @@ import android.content.pm.SigningInfo
fun createMockFido2CredentialAssertionRequest(
number: Int = 1,
userId: String = "mockUserId-$number",
isUserVerified: Boolean = false,
): Fido2CredentialAssertionRequest =
Fido2CredentialAssertionRequest(
userId = userId,
@@ -15,4 +16,5 @@ fun createMockFido2CredentialAssertionRequest(
packageName = "mockPackageName-$number",
signingInfo = SigningInfo(),
origin = "mockOrigin-$number",
isUserVerified = isUserVerified,
)
@@ -5,7 +5,7 @@ import android.content.pm.SigningInfo
/**
* Creates a mock [Fido2CreateCredentialRequest] with a given [number].
*/
fun createMockFido2CredentialRequest(
fun createMockFido2CreateCredentialRequest(
number: Int,
origin: String? = null,
signingInfo: SigningInfo = SigningInfo(),
@@ -16,4 +16,5 @@ fun createMockFido2CredentialRequest(
packageName = "com.x8bit.bitwarden",
signingInfo = signingInfo,
origin = origin,
isUserVerified = true,
)
@@ -3,9 +3,11 @@ package com.x8bit.bitwarden.data.autofill.fido2.processor
import android.app.PendingIntent
import android.content.Context
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import androidx.biometric.BiometricManager
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.credentials.exceptions.GetCredentialException
@@ -30,12 +32,16 @@ import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFido2CredentialAutofillView
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
@@ -55,14 +61,15 @@ import io.mockk.unmockkConstructor
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.encodeToString
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
import javax.crypto.Cipher
class Fido2ProviderProcessorTest {
@@ -87,6 +94,11 @@ class Fido2ProviderProcessorTest {
private val fido2CredentialStore: Fido2CredentialStore = mockk()
private val intentManager: IntentManager = mockk()
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
private val biometricsEncryptionManager: BiometricsEncryptionManager = mockk()
private val featureFlagManager: FeatureFlagManager = mockk {
every { getFeatureFlag(FlagKey.SingleTapPasskeyCreation) } returns false
every { getFeatureFlag(FlagKey.SingleTapPasskeyAuthentication) } returns false
}
private val cancellationSignal: CancellationSignal = mockk()
private val json = PlatformNetworkModule.providesJson()
@@ -102,15 +114,20 @@ class Fido2ProviderProcessorTest {
fido2CredentialManager,
intentManager,
clock,
biometricsEncryptionManager,
featureFlagManager,
dispatcherManager,
)
mockkStatic(Icon::class)
mockkStatic(::isBuildVersionBelow)
}
@AfterEach
fun tearDown() {
unmockkStatic(Icon::class)
unmockkStatic(::isBuildVersionBelow)
unmockkConstructor(PublicKeyCredentialEntry.Builder::class)
}
@Test
@@ -232,8 +249,9 @@ class Fido2ProviderProcessorTest {
assertEquals(DEFAULT_USER_STATE.accounts[0].email, capturedEntry.accountName)
}
@Suppress("MaxLineLength")
@Test
fun `processCreateCredentialRequest should generate result entries for each user account`() {
fun `processCreateCredentialRequest should generate correct entries based on state`() {
val request: BeginCreatePublicKeyCredentialRequest = mockk()
val candidateQueryData: Bundle = mockk()
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
@@ -256,6 +274,11 @@ class Fido2ProviderProcessorTest {
any(),
)
} returns mockIntent
every {
biometricsEncryptionManager.getOrCreateCipher(any())
} returns mockk<Cipher>()
every { featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyCreation) } returns true
every { isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM) } returns false
fido2Processor.processCreateCredentialRequest(request, cancellationSignal, callback)
@@ -272,6 +295,42 @@ class Fido2ProviderProcessorTest {
DEFAULT_USER_STATE.accounts.forEachIndexed { index, mockAccount ->
assertEquals(mockAccount.email, captureSlot.captured.createEntries[index].accountName)
}
// Verify all entries have biometric prompt data when feature flag is enabled
assertTrue(captureSlot.captured.createEntries.all { it.biometricPromptData != null }) {
"Expected all entries to have biometric prompt data."
}
// Verify entries have the correct authenticators when cipher is not null
assertTrue(
captureSlot
.captured
.createEntries
.all {
it.biometricPromptData
?.allowedAuthenticators == BiometricManager.Authenticators.BIOMETRIC_STRONG
},
) { "Expected all entries to have BIOMETRIC_STRONG authenticators." }
// Verify entries have no biometric prompt data when cipher is null
every { biometricsEncryptionManager.getOrCreateCipher(any()) } returns null
fido2Processor.processCreateCredentialRequest(request, cancellationSignal, callback)
assertTrue(
captureSlot
.captured
.createEntries
.all { it.biometricPromptData == null },
) { "Expected all entries to have null biometric prompt data." }
// Disable single tap feature flag to verify all entries do not have biometric prompt data
every { featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyCreation) } returns false
fido2Processor.processCreateCredentialRequest(request, cancellationSignal, callback)
assertTrue(
captureSlot
.captured
.createEntries
.all { it.biometricPromptData == null },
) { "Expected all entries to not have biometric prompt data." }
}
@Test
@@ -497,32 +556,23 @@ class Fido2ProviderProcessorTest {
anyConstructed<PublicKeyCredentialEntry.Builder>().build()
} returns mockPublicKeyCredentialEntry
every { Icon.createWithResource(context, any()) } returns mockk<Icon>()
every {
biometricsEncryptionManager.getOrCreateCipher(any())
} returns mockk<Cipher>()
every {
featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyAuthentication)
} returns true
every { isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM) } returns false
fido2Processor.processGetCredentialRequest(request, cancellationSignal, callback)
verify(exactly = 0) { callback.onError(any()) }
// TODO: [PM-9515] Uncomment when SDK bug is fixed.
// verify(exactly = 1) {
// callback.onResult(any())
// intentManager.createFido2GetCredentialPendingIntent(
// action = "com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY",
// credentialId = mockFido2CredentialAutofillViews.first().credentialId.toString(),
// cipherId = mockFido2CredentialAutofillViews.first().cipherId,
// requestCode = any(),
// )
// }
// coVerify(exactly = 1) {
// vaultRepository.silentlyDiscoverCredentials(
// userId = DEFAULT_USER_STATE.activeUserId,
// fido2CredentialStore = fido2CredentialStore,
// relyingPartyId = "mockRelyingPartyId-1",
// )
// }
assertEquals(1, captureSlot.captured.credentialEntries.size)
assertEquals(mockPublicKeyCredentialEntry, captureSlot.captured.credentialEntries.first())
unmockkConstructor(PublicKeyCredentialEntry.Builder::class)
// Verify all entries have biometric prompt data when feature flag is enabled
verify(exactly = captureSlot.captured.credentialEntries.size) {
anyConstructed<PublicKeyCredentialEntry.Builder>().setBiometricPromptData(any())
}
}
}
@@ -75,7 +75,7 @@ class Fido2IntentUtilsTest {
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
} returns mockProviderRequest
val createRequest = intent.getFido2CredentialRequestOrNull()
val createRequest = intent.getFido2CreateCredentialRequestOrNull()
assertEquals(
Fido2CreateCredentialRequest(
userId = "mockUserId",
@@ -83,6 +83,7 @@ class Fido2IntentUtilsTest {
packageName = mockCallingAppInfo.packageName,
signingInfo = mockCallingAppInfo.signingInfo,
origin = mockCallingAppInfo.origin,
isUserVerified = false,
),
createRequest,
)
@@ -94,7 +95,7 @@ class Fido2IntentUtilsTest {
every { isBuildVersionBelow(34) } returns true
assertNull(intent.getFido2CredentialRequestOrNull())
assertNull(intent.getFido2CreateCredentialRequestOrNull())
}
@Suppress("MaxLineLength")
@@ -106,7 +107,7 @@ class Fido2IntentUtilsTest {
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
} returns null
assertNull(intent.getFido2CredentialRequestOrNull())
assertNull(intent.getFido2CreateCredentialRequestOrNull())
}
@Suppress("MaxLineLength")
@@ -127,7 +128,7 @@ class Fido2IntentUtilsTest {
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
} returns mockProviderRequest
assertNull(intent.getFido2CredentialRequestOrNull())
assertNull(intent.getFido2CreateCredentialRequestOrNull())
}
@Suppress("MaxLineLength")
@@ -157,7 +158,7 @@ class Fido2IntentUtilsTest {
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
} returns mockProviderRequest
assertNull(intent.getFido2CredentialRequestOrNull())
assertNull(intent.getFido2CreateCredentialRequestOrNull())
}
@Test
@@ -199,6 +200,7 @@ class Fido2IntentUtilsTest {
packageName = mockCallingAppInfo.packageName,
signingInfo = mockCallingAppInfo.signingInfo,
origin = mockCallingAppInfo.origin,
isUserVerified = false,
),
assertionRequest,
)
@@ -152,6 +152,7 @@ class SpecialCircumstanceExtensionsTest {
packageName = "mockPackageName",
signingInfo = SigningInfo(),
origin = "mockOrigin",
isUserVerified = true,
)
assertEquals(
fido2CreateCredentialRequest,
@@ -121,6 +121,8 @@ private val DEFAULT_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
FlagKey.NewDevicePermanentDismiss to true,
FlagKey.IgnoreEnvironmentCheck to true,
FlagKey.MutualTls to true,
FlagKey.SingleTapPasskeyCreation to true,
FlagKey.SingleTapPasskeyAuthentication to true,
)
private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
@@ -138,6 +140,8 @@ private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
FlagKey.NewDevicePermanentDismiss to false,
FlagKey.IgnoreEnvironmentCheck to false,
FlagKey.MutualTls to false,
FlagKey.SingleTapPasskeyCreation to false,
FlagKey.SingleTapPasskeyAuthentication to false,
)
private val DEFAULT_STATE = DebugMenuState(
@@ -670,6 +670,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = "mockOrigin",
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(fido2CreateCredentialRequest)
@@ -21,7 +21,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
@@ -359,6 +359,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
packageName = "mockPackageName-1",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CreateCredentialRequest,
@@ -781,6 +782,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
packageName = "mockPackageName",
signingInfo = mockk<SigningInfo>(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
@@ -860,6 +862,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
packageName = "mockPackageName",
signingInfo = mockk<SigningInfo>(),
origin = null,
isUserVerified = false,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
@@ -940,7 +943,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `in add mode during fido2, SaveClick should skip user verification when user is verified`() =
runTest {
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val fido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CredentialRequest,
@@ -992,7 +995,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `in add mode during fido2, SaveClick should show fido2 error dialog when create options are null`() =
runTest {
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val fido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CredentialRequest,
@@ -1036,7 +1039,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `in add mode during fido2, SaveClick should emit fido user verification as optional when verification is PREFERRED`() =
runTest {
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val fido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CredentialRequest,
@@ -1081,7 +1084,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `in add mode during fido2, SaveClick should emit fido user verification as required when request user verification option is REQUIRED`() =
runTest {
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val fido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CredentialRequest,
@@ -1789,7 +1792,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
),
),
)
val mockFido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockFido2CredentialRequest,
@@ -1840,7 +1843,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
notes = "mockNotes-1",
),
)
val mockFidoRequest = createMockFido2CredentialRequest(number = 1)
val mockFidoRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockFidoRequest,
)
@@ -1911,7 +1914,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
notes = "mockNotes-1",
),
)
val mockFidoRequest = createMockFido2CredentialRequest(number = 1)
val mockFidoRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockFidoRequest,
)
@@ -4083,7 +4086,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
fun `UserVerificationSuccess should display Fido2ErrorDialog when activeUserId is null`() {
every { authRepository.activeUserId } returns null
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(createMockFido2CredentialRequest(number = 1))
SpecialCircumstance.Fido2Save(createMockFido2CreateCredentialRequest(number = 1))
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
@@ -4100,7 +4103,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `UserVerificationSuccess should set isUserVerified to true, and register FIDO 2 credential`() =
runTest {
val mockRequest = createMockFido2CredentialRequest(number = 1)
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
val mockResult = Fido2RegisterCredentialResult.Success(
registrationResponse = "mockResponse",
)
@@ -4144,7 +4147,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
runTest {
val mockRequest = createMockFido2CredentialRequest(number = 1)
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
val mockResult = Fido2RegisterCredentialResult.Error
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockRequest,
@@ -4181,7 +4184,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteFido2Registration result`() =
runTest {
val mockRequest = createMockFido2CredentialRequest(number = 1)
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
val mockResult = Fido2RegisterCredentialResult.Success(
registrationResponse = "mockResponse",
)
@@ -4220,7 +4223,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Test
fun `Fido2RegisterCredentialResult Cancelled should emit CompleteFido2Registration result`() =
runTest {
val mockRequest = createMockFido2CredentialRequest(number = 1)
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
val mockResult = Fido2RegisterCredentialResult.Cancelled
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockRequest,
@@ -53,6 +53,7 @@ class Fido2CredentialRequestExtensionsTest {
packageName = "mockPackageName-1",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
.toDefaultAddTypeContent(
attestationOptions = createMockPasskeyAttestationOptions(1),
@@ -89,6 +90,7 @@ class Fido2CredentialRequestExtensionsTest {
packageName = "mockPackageName-1",
signingInfo = SigningInfo(),
origin = "www.test.com",
isUserVerified = true,
)
.toDefaultAddTypeContent(
attestationOptions = createMockPasskeyAttestationOptions(number = 1),
@@ -26,7 +26,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResu
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialAssertionRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
@@ -204,6 +204,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
"com.x8bit.bitwarden",
SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CreateCredentialRequest,
@@ -452,7 +453,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fun `ItemClick for vault item during FIDO 2 registration should show FIDO 2 error dialog when cipherView is null`() {
val cipherView = createMockCipherView(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
fido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(number = 1),
)
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
@@ -481,7 +482,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
setupMockUri()
val cipherView = createMockCipherView(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
fido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(number = 1),
)
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
@@ -516,7 +517,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
fido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(number = 1),
)
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
@@ -559,7 +560,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
setupMockUri()
val cipherView = createMockCipherView(number = 1, fido2Credentials = null)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
fido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(number = 1),
)
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
@@ -607,7 +608,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
setupMockUri()
val cipherView = createMockCipherView(number = 1)
val mockFido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockFido2CredentialRequest,
)
@@ -650,7 +651,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fun `ItemClick for vault item during FIDO 2 registration should skip user verification when user is verified`() {
setupMockUri()
val cipherView = createMockCipherView(number = 1)
val mockFido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockFido2CredentialRequest,
)
@@ -1576,6 +1577,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = "mockOrigin",
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance =
@@ -2158,6 +2160,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
"com.x8bit.bitwarden",
SigningInfo(),
origin = "com.x8bit.bitwarden",
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest,
@@ -2208,6 +2211,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2239,6 +2243,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2270,6 +2275,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2301,6 +2307,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2332,6 +2339,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2363,6 +2371,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
packageName = "com.x8bit.bitwarden",
signingInfo = SigningInfo(),
origin = null,
isUserVerified = true,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2464,7 +2473,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 registration based on state`() =
runTest {
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
createMockFido2CredentialRequest(number = 1),
createMockFido2CreateCredentialRequest(number = 1),
)
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
@@ -3367,7 +3376,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fun `UserVerificationSuccess should display Fido2ErrorDialog when activeUserId is null`() {
every { authRepository.activeUserId } returns null
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(createMockFido2CredentialRequest(number = 1))
SpecialCircumstance.Fido2Save(createMockFido2CreateCredentialRequest(number = 1))
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(
@@ -3390,7 +3399,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
@Test
fun `UserVerificationSuccess should set isUserVerified to true, and register FIDO 2 credential when verification result is received`() =
runTest {
val mockRequest = createMockFido2CredentialRequest(number = 1)
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = mockRequest,
)
@@ -4050,7 +4059,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
setupMockUri()
val cipherView = createMockCipherView(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
fido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(number = 1),
)
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
@@ -4084,7 +4093,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
setupMockUri()
val cipherView = createMockCipherView(number = 1)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = createMockFido2CredentialRequest(number = 1),
fido2CreateCredentialRequest = createMockFido2CreateCredentialRequest(number = 1),
)
mutableVaultDataStateFlow.value = DataState.Loaded(
data = VaultData(
@@ -828,6 +828,7 @@ class VaultItemListingDataExtensionsTest {
packageName = "",
signingInfo = SigningInfo(),
origin = "https://www.test.com",
isUserVerified = true,
),
fido2CredentialAutofillViews = null,
totpData = null,