From 4680813f26f8333409ac704022ccfc14f017f2a8 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Thu, 9 Jan 2025 11:13:43 -0500 Subject: [PATCH] [PM-15906] Implement single tap passkey flows This change adds a biometric prompt to the passkey autofill flow. The biometric prompt will be shown when creating or authenticating with a passkey if the user has supported device biometrics enabled. This change also adds an isUserVerified flag to the Fido2 requests to determine if the user has verified their identity using the single tap flow. --- .../java/com/x8bit/bitwarden/MainViewModel.kt | 19 +-- .../autofill/fido2/di/Fido2ProviderModule.kt | 6 + .../model/Fido2CreateCredentialRequest.kt | 1 + .../model/Fido2CredentialAssertionRequest.kt | 1 + .../processor/Fido2ProviderProcessorImpl.kt | 112 ++++++++++++++---- .../autofill/fido2/util/Fido2IntentUtils.kt | 7 +- .../data/platform/manager/model/FlagKey.kt | 20 ++++ .../manager/Fido2CompletionManagerImpl.kt | 3 +- .../components/FeatureFlagListItems.kt | 5 + app/src/main/res/values/strings.xml | 2 + .../com/x8bit/bitwarden/MainViewModelTest.kt | 21 ++-- .../manager/Fido2CredentialManagerTest.kt | 20 ++-- .../fido2/model/Fido2CredentialRequestUtil.kt | 3 +- .../processor/Fido2ProviderProcessorTest.kt | 65 +++++++++- .../fido2/util/Fido2IntentUtilsTest.kt | 11 +- .../util/SpecialCircumstanceExtensionsTest.kt | 1 + .../feature/rootnav/RootNavViewModelTest.kt | 1 + .../addedit/VaultAddEditViewModelTest.kt | 29 +++-- .../Fido2CredentialRequestExtensionsTest.kt | 2 + .../VaultItemListingViewModelTest.kt | 33 ++++-- .../VaultItemListingDataExtensionsTest.kt | 1 + 21 files changed, 282 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt index 36855feb2c5..e8516ef3558 100644 --- a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt @@ -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 @@ -257,7 +257,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() @@ -318,25 +318,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, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/di/Fido2ProviderModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/di/Fido2ProviderModule.kt index c6b52ce30cb..aa146911316 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/di/Fido2ProviderModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/di/Fido2ProviderModule.kt @@ -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, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialRequest.kt index 05f98f756eb..14586c3b887 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialRequest.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialRequest.kt @@ -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( diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequest.kt index 3c0e840a7ed..1f9b8a5d1f5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequest.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequest.kt @@ -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) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorImpl.kt index 531f1d67f8c..4ab3657e6e0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorImpl.kt @@ -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,20 @@ 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) + + return if ( + !isVaultUnlocked || + featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyCreation) == false + ) { + entryBuilder.build() + } else { + entryBuilder + .setBiometricPromptDataIfSupported( + cipher = biometricsEncryptionManager.getOrCreateCipher(userId), + ) + .build() + } } override fun processGetCredentialRequest( @@ -258,32 +281,81 @@ class Fido2ProviderProcessorImpl( private fun List.toCredentialEntries( userId: String, option: BeginGetPublicKeyCredentialOption, - ): List = - this + ): List { + return 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, + ), + ) + + if (!featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyAuthentication)) { + publicKeyEntryBuilder.build() + } else { + publicKeyEntryBuilder + .setBiometricPromptDataIfSupported( + cipher = biometricsEncryptionManager.getOrCreateCipher(userId), + ) + .build() + } + } + } + + private fun PublicKeyCredentialEntry.Builder.setBiometricPromptDataIfSupported( + cipher: Cipher?, + ): PublicKeyCredentialEntry.Builder { + if (isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM)) return this + return BiometricPromptData.Builder().buildPromptDataOrNull(cipher) + ?.let { setBiometricPromptData(it) } + ?: this + } + + private fun CreateEntry.Builder.setBiometricPromptDataIfSupported( + cipher: Cipher?, + ): CreateEntry.Builder { + if (isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM)) return this + return BiometricPromptData.Builder().buildPromptDataOrNull(cipher) + ?.let { setBiometricPromptData(it) } + ?: this + } + + @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + private fun BiometricPromptData.Builder.buildPromptDataOrNull( + cipher: Cipher?, + ): BiometricPromptData? { + val promptBuilder = BiometricPromptData.Builder() + when { + cipher == null -> { + promptBuilder.setAllowedAuthenticators( + BiometricManager.Authenticators.BIOMETRIC_WEAK, + ) + } + + else -> { + promptBuilder + .setAllowedAuthenticators( + BiometricManager.Authenticators.BIOMETRIC_STRONG, ) - .build() + .setCryptoObject(BiometricPrompt.CryptoObject(cipher)) } + } + return promptBuilder.build() + } override fun processClearCredentialStateRequest( request: ProviderClearCredentialStateRequest, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtils.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtils.kt index 459ad49eb98..3549e46e605 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtils.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtils.kt @@ -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, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt index 0315863d95a..17821c25535 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt @@ -39,6 +39,8 @@ sealed class FlagKey { NewDevicePermanentDismiss, NewDeviceTemporaryDismiss, IgnoreEnvironmentCheck, + SingleTapPasskeyCreation, + SingleTapPasskeyAuthentication, ) } } @@ -171,6 +173,24 @@ sealed class FlagKey { override val isRemotelyConfigured: Boolean = false } + /** + * Data object holding the feature flag key to enable single tap passkey creation. + */ + data object SingleTapPasskeyCreation : FlagKey() { + 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() { + 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. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt index 4f4a5d76466..3e61f631d23 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt @@ -139,8 +139,7 @@ class Fido2CompletionManagerImpl( ) } - Fido2GetCredentialsResult.Error, - -> { + Fido2GetCredentialsResult.Error -> { PendingIntentHandler.setGetCredentialException( resultIntent, GetCredentialUnknownException(), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt index 8e8f75ed672..42f68704f7a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt @@ -36,6 +36,8 @@ fun FlagKey.ListItemContent( FlagKey.NewDevicePermanentDismiss, FlagKey.NewDeviceTemporaryDismiss, FlagKey.IgnoreEnvironmentCheck, + FlagKey.SingleTapPasskeyCreation, + FlagKey.SingleTapPasskeyAuthentication, -> BooleanFlagItem( label = flagKey.getDisplayLabel(), key = flagKey as FlagKey, @@ -87,4 +89,7 @@ private fun FlagKey.getDisplayLabel(): String = when (this) { FlagKey.NewDevicePermanentDismiss -> stringResource(R.string.new_device_permanent_dismiss) FlagKey.NewDeviceTemporaryDismiss -> stringResource(R.string.new_device_temporary_dismiss) FlagKey.IgnoreEnvironmentCheck -> stringResource(R.string.ignore_environment_check) + FlagKey.SingleTapPasskeyCreation -> stringResource(R.string.single_tap_passkey_creation) + FlagKey.SingleTapPasskeyAuthentication -> + stringResource(R.string.single_tap_passkey_authentication) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bb7c068a93d..2fd4848ffe7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1112,4 +1112,6 @@ Do you want to switch to this account? Review flow launched! Copy private key You can change your account email on the Bitwarden web app. + Single tap passkey creation + Single tap passkey sign-on diff --git a/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt index ce5b5181060..1b0be1dd183 100644 --- a/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt @@ -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 @@ -136,7 +136,7 @@ class MainViewModelTest : BaseViewModelTest() { Intent::getAutofillSelectionDataOrNull, Intent::getCompleteRegistrationDataIntentOrNull, Intent::getFido2AssertionRequestOrNull, - Intent::getFido2CredentialRequestOrNull, + Intent::getFido2CreateCredentialRequestOrNull, Intent::getFido2GetCredentialsRequestOrNull, Intent::isAddTotpLoginItemFromAuthenticator, ) @@ -156,7 +156,7 @@ class MainViewModelTest : BaseViewModelTest() { Intent::getAutofillSelectionDataOrNull, Intent::getCompleteRegistrationDataIntentOrNull, Intent::getFido2AssertionRequestOrNull, - Intent::getFido2CredentialRequestOrNull, + Intent::getFido2CreateCredentialRequestOrNull, Intent::getFido2GetCredentialsRequestOrNull, Intent::isAddTotpLoginItemFromAuthenticator, ) @@ -612,6 +612,7 @@ class MainViewModelTest : BaseViewModelTest() { packageName = "com.x8bit.bitwarden", signingInfo = SigningInfo(), origin = "mockOrigin", + isUserVerified = true, ) val fido2Intent = createMockIntent( mockFido2CreateCredentialRequest = fido2CreateCredentialRequest, @@ -638,11 +639,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( @@ -652,7 +655,7 @@ class MainViewModelTest : BaseViewModelTest() { ) verify { - fido2CredentialManager.isUserVerified = false + fido2CredentialManager.isUserVerified = createCredentialRequest.isUserVerified ?: false } } @@ -667,6 +670,7 @@ class MainViewModelTest : BaseViewModelTest() { packageName = "com.x8bit.bitwarden", signingInfo = SigningInfo(), origin = "mockOrigin", + isUserVerified = true, ) val mockIntent = createMockIntent( mockFido2CreateCredentialRequest = fido2CreateCredentialRequest, @@ -697,6 +701,7 @@ class MainViewModelTest : BaseViewModelTest() { packageName = "com.x8bit.bitwarden", signingInfo = SigningInfo(), origin = "mockOrigin", + isUserVerified = true, ) val mockIntent = createMockIntent( mockFido2CreateCredentialRequest = fido2CreateCredentialRequest, @@ -1101,7 +1106,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 diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt index 45c0d49a3cf..17d42cb9881 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt @@ -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 { 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 { 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, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialRequestUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialRequestUtil.kt index a1833ec3f75..62a7f0c6a63 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialRequestUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialRequestUtil.kt @@ -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, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorTest.kt index 098635ec87c..30a5d1b4b97 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/processor/Fido2ProviderProcessorTest.kt @@ -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,10 @@ 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 + } private val cancellationSignal: CancellationSignal = mockk() private val json = PlatformNetworkModule.providesJson() @@ -102,15 +113,19 @@ class Fido2ProviderProcessorTest { fido2CredentialManager, intentManager, clock, + biometricsEncryptionManager, + featureFlagManager, dispatcherManager, ) mockkStatic(Icon::class) + mockkStatic(::isBuildVersionBelow) } @AfterEach fun tearDown() { unmockkStatic(Icon::class) + unmockkStatic(::isBuildVersionBelow) } @Test @@ -232,8 +247,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 = @@ -256,6 +272,12 @@ class Fido2ProviderProcessorTest { any(), ) } returns mockIntent + every { + biometricsEncryptionManager.getOrCreateCipher(any()) + } returns mockk() + + every { featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyCreation) } returns true + every { isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM) } returns false fido2Processor.processCreateCredentialRequest(request, cancellationSignal, callback) @@ -272,6 +294,45 @@ 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 the correct authenticators when cipher is null + every { biometricsEncryptionManager.getOrCreateCipher(any()) } returns null + fido2Processor.processCreateCredentialRequest(request, cancellationSignal, callback) + assertTrue( + captureSlot + .captured + .createEntries + .all { + it.biometricPromptData + ?.allowedAuthenticators == BiometricManager.Authenticators.BIOMETRIC_WEAK + }, + ) { "Expected all entries to have BIOMETRIC_WEAK authenticators." } + + // 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 diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtilsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtilsTest.kt index 7f0e708471d..226b8245a2e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtilsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/util/Fido2IntentUtilsTest.kt @@ -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 = true, ), 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 diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt index ab26c0582bc..a5290f6bfbd 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt @@ -152,6 +152,7 @@ class SpecialCircumstanceExtensionsTest { packageName = "mockPackageName", signingInfo = SigningInfo(), origin = "mockOrigin", + isUserVerified = true, ) assertEquals( fido2CreateCredentialRequest, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt index 730f8da3975..b64611114a5 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt @@ -670,6 +670,7 @@ class RootNavViewModelTest : BaseViewModelTest() { packageName = "com.x8bit.bitwarden", signingInfo = SigningInfo(), origin = "mockOrigin", + isUserVerified = true, ) specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(fido2CreateCredentialRequest) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index c152bd8d34b..f364b57e3eb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -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(), origin = null, + isUserVerified = true, ) specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save( @@ -860,6 +862,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { packageName = "mockPackageName", signingInfo = mockk(), 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, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/Fido2CredentialRequestExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/Fido2CredentialRequestExtensionsTest.kt index 5e497f431e8..b8728ed561c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/Fido2CredentialRequestExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/Fido2CredentialRequestExtensionsTest.kt @@ -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), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 60f16c5d862..44592dfb6ae 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -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( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt index e701ae2f0a3..ef75c869f32 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt @@ -828,6 +828,7 @@ class VaultItemListingDataExtensionsTest { packageName = "", signingInfo = SigningInfo(), origin = "https://www.test.com", + isUserVerified = true, ), fido2CredentialAutofillViews = null, totpData = null,