From e9159cc8c11a140dc4dbf44fa37cf44460982d73 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:38:36 -0500 Subject: [PATCH] [PM-15906] Implement single tap passkey flows (#4547) --- .../java/com/x8bit/bitwarden/MainViewModel.kt | 19 ++-- .../autofill/fido2/di/Fido2ProviderModule.kt | 6 ++ .../model/Fido2CreateCredentialRequest.kt | 1 + .../model/Fido2CredentialAssertionRequest.kt | 1 + .../processor/Fido2ProviderProcessorImpl.kt | 98 +++++++++++++++---- .../autofill/fido2/util/Fido2IntentUtils.kt | 7 +- .../data/platform/manager/model/FlagKey.kt | 20 ++++ .../components/FeatureFlagListItems.kt | 5 + app/src/main/res/values/strings.xml | 4 +- .../com/x8bit/bitwarden/MainViewModelTest.kt | 21 ++-- .../manager/Fido2CredentialManagerTest.kt | 20 ++-- .../Fido2CredentialAssertionRequestUtil.kt | 2 + .../fido2/model/Fido2CredentialRequestUtil.kt | 3 +- .../processor/Fido2ProviderProcessorTest.kt | 94 +++++++++++++----- .../fido2/util/Fido2IntentUtilsTest.kt | 12 ++- .../util/SpecialCircumstanceExtensionsTest.kt | 1 + .../debugmenu/DebugMenuViewModelTest.kt | 4 + .../feature/rootnav/RootNavViewModelTest.kt | 1 + .../addedit/VaultAddEditViewModelTest.kt | 29 +++--- .../Fido2CredentialRequestExtensionsTest.kt | 2 + .../VaultItemListingViewModelTest.kt | 33 ++++--- .../VaultItemListingDataExtensionsTest.kt | 1 + 22 files changed, 286 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt index 2e04819679..bb9658870b 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 @@ -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, 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 c6b52ce30c..aa14691131 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 05f98f756e..14586c3b88 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 3c0e840a7e..1f9b8a5d1f 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 531f1d67f8..0e060bed39 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,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 = 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, 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 459ad49eb9..3549e46e60 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 1ca263ac46..3334c8c087 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 @@ -40,6 +40,8 @@ sealed class FlagKey { NewDeviceTemporaryDismiss, IgnoreEnvironmentCheck, MutualTls, + SingleTapPasskeyCreation, + SingleTapPasskeyAuthentication, ) } } @@ -181,6 +183,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/platform/feature/debugmenu/components/FeatureFlagListItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt index 766c3643af..8c0b067a39 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 @@ -39,6 +39,8 @@ fun FlagKey.ListItemContent( FlagKey.NewDeviceTemporaryDismiss, FlagKey.IgnoreEnvironmentCheck, FlagKey.MutualTls, + FlagKey.SingleTapPasskeyCreation, + FlagKey.SingleTapPasskeyAuthentication, -> BooleanFlagItem( label = flagKey.getDisplayLabel(), key = flagKey as FlagKey, @@ -92,4 +94,7 @@ private fun FlagKey.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) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5a711fb302..99a9d13b15 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1114,11 +1114,13 @@ Do you want to switch to this account? You can change your account email on the Bitwarden web app. Login Credentials Autofill Options - Use this button to generate a new unique password. + Use this button to generate a new unique password. 1 of 3 2 of 3 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. 3 of 3 You must add a web address to use autofill to access this account. Mutual TLS + 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 bf53db8e4d..ef333dacd1 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 @@ -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 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 45c0d49a3c..17d42cb988 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/Fido2CredentialAssertionRequestUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequestUtil.kt index 1cd6cf3dca..10c955ed97 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequestUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CredentialAssertionRequestUtil.kt @@ -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, ) 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 a1833ec3f7..62a7f0c6a6 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 098635ec87..8d081e258d 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,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 = @@ -256,6 +274,11 @@ 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 +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().build() } returns mockPublicKeyCredentialEntry every { Icon.createWithResource(context, any()) } returns mockk() + every { + biometricsEncryptionManager.getOrCreateCipher(any()) + } returns mockk() + 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().setBiometricPromptData(any()) + } } } 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 7f0e708471..49def471da 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 = 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, ) 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 ab26c0582b..a5290f6bfb 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/debugmenu/DebugMenuViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt index 8e3fe1c9d5..20dc421368 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt @@ -121,6 +121,8 @@ private val DEFAULT_MAP_VALUE: Map, 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, Any> = mapOf( @@ -138,6 +140,8 @@ private val UPDATED_MAP_VALUE: Map, 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( 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 730f8da397..b64611114a 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 bc34f2a109..4e6119ec36 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 5e497f431e..b8728ed561 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 60f16c5d86..44592dfb6a 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 e701ae2f0a..ef75c869f3 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,