diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt index 325afe7a45..c407501afd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt @@ -2,8 +2,6 @@ package com.x8bit.bitwarden.data.autofill.fido2.manager import androidx.credentials.provider.CallingAppInfo import com.bitwarden.fido.ClientData -import com.bitwarden.sdk.CheckUserResult -import com.bitwarden.sdk.CipherViewWrapper import com.bitwarden.sdk.Fido2CredentialStore import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson @@ -60,27 +58,25 @@ class Fido2CredentialManagerImpl( val origin = fido2CredentialRequest.origin ?: fido2CredentialRequest.callingAppInfo.getAppOrigin() - return vaultSdkSource.registerFido2Credential( - request = RegisterFido2CredentialRequest( - userId = userId, - origin = origin, - requestJson = """{"publicKey": ${fido2CredentialRequest.requestJson}}""", - clientData = clientData, - selectedCipherView = selectedCipherView, - isUserVerificationSupported = true, - ), - fido2CredentialStore = this, - // TODO: [PM-8137] Determine if user verification is supported - checkUser = { _, _ -> CheckUserResult(true, true) }, - checkUserAndPickCredential = { _, _ -> CipherViewWrapper(selectedCipherView) }, - ) + return vaultSdkSource + .registerFido2Credential( + request = RegisterFido2CredentialRequest( + userId = userId, + origin = origin, + requestJson = """{"publicKey": ${fido2CredentialRequest.requestJson}}""", + clientData = clientData, + selectedCipherView = selectedCipherView, + // User verification is handled prior to engaging the SDK. We always respond + // `true` so that the SDK does not fail if the relying party requests UV. + isUserVerificationSupported = true, + ), + fido2CredentialStore = this, + ) .map { it.toAndroidAttestationResponse() } .mapCatching { json.encodeToString(it) } .fold( onSuccess = { Fido2RegisterCredentialResult.Success(it) }, - onFailure = { - Fido2RegisterCredentialResult.Error - }, + onFailure = { Fido2RegisterCredentialResult.Error }, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt index 179b3a8bc6..8fcd2f3285 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt @@ -8,15 +8,10 @@ import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.UpdatePasswordResponse import com.bitwarden.crypto.TrustDeviceResponse import com.bitwarden.exporters.ExportFormat -import com.bitwarden.fido.CheckUserOptions -import com.bitwarden.fido.ClientData import com.bitwarden.fido.Fido2CredentialAutofillView import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse -import com.bitwarden.sdk.CheckUserResult -import com.bitwarden.sdk.CipherViewWrapper import com.bitwarden.sdk.Fido2CredentialStore -import com.bitwarden.sdk.UiHint import com.bitwarden.send.Send import com.bitwarden.send.SendView import com.bitwarden.vault.Attachment @@ -27,12 +22,12 @@ import com.bitwarden.vault.CipherListView import com.bitwarden.vault.CipherView import com.bitwarden.vault.Collection import com.bitwarden.vault.CollectionView -import com.bitwarden.vault.Fido2CredentialNewView import com.bitwarden.vault.Folder import com.bitwarden.vault.FolderView import com.bitwarden.vault.PasswordHistory import com.bitwarden.vault.PasswordHistoryView import com.bitwarden.vault.TotpResponse +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2CredentialRequest import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest import java.io.File @@ -420,53 +415,23 @@ interface VaultSdkSource { /** * Register a new FIDO 2 credential to a cipher. * - * @param checkUser Receives [CheckUserOptions] and [UiHint] indicating what interactions and - * prompts must be presented to the user during registration. A [CheckUserResult] is expected - * when interactions are completed. - * @param checkUserAndPickCredential Receives [CheckUserOptions] indicating user - * verification requirements and a [Fido2CredentialNewView] representing the newly registered - * credential. A [CipherViewWrapper] containing the selectedCipherView updated with the - * [Fido2CredentialNewView] is expected in response. - * * @return Result of the FIDO 2 credential registration. If successful, a * [PublicKeyCredentialAuthenticatorAttestationResponse] is provided. */ suspend fun registerFido2Credential( request: RegisterFido2CredentialRequest, fido2CredentialStore: Fido2CredentialStore, - checkUser: suspend (CheckUserOptions, UiHint?) -> CheckUserResult, - checkUserAndPickCredential: suspend ( - options: CheckUserOptions, - newCredential: Fido2CredentialNewView, - ) -> CipherViewWrapper, ): Result /** * Authenticate a user with a FIDO 2 credential. * - * @param userId Active user's ID. - * @param origin Origin of the relying party request. - * @param requestJson JSON provided by the relying party. - * @param clientData Client metadata about the relying party or calling application. - * @param isVerificationSupported Whether user verification can be performed on this device. - * @param checkUser Receives [CheckUserOptions] and [UiHint] indicating what interactions and - * prompts must be presented to the user for registration to complete. A [CheckUserResult] is - * expected when interactions are completed. - * @param pickCredentialForAuthentication Receives a collection of [CipherView]s that can be - * chosen to perform authentication with. - * * @return Result of the FIDO 2 credential registration. If successful, a * [PublicKeyCredentialAuthenticatorAttestationResponse] is provided. */ @Suppress("LongParameterList") suspend fun authenticateFido2Credential( - userId: String, - origin: String, - requestJson: String, - clientData: ClientData, - isVerificationSupported: Boolean, - checkUser: suspend (CheckUserOptions, UiHint?) -> CheckUserResult, - pickCredentialForAuthentication: suspend (List) -> CipherViewWrapper, + request: AuthenticateFido2CredentialRequest, fido2CredentialStore: Fido2CredentialStore, ): Result diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt index c743998695..d2cd556e77 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt @@ -7,18 +7,13 @@ import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.UpdatePasswordResponse import com.bitwarden.crypto.TrustDeviceResponse import com.bitwarden.exporters.ExportFormat -import com.bitwarden.fido.CheckUserOptions -import com.bitwarden.fido.ClientData import com.bitwarden.fido.Fido2CredentialAutofillView import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse import com.bitwarden.sdk.BitwardenException -import com.bitwarden.sdk.CheckUserResult -import com.bitwarden.sdk.CipherViewWrapper import com.bitwarden.sdk.Client import com.bitwarden.sdk.ClientVault import com.bitwarden.sdk.Fido2CredentialStore -import com.bitwarden.sdk.UiHint import com.bitwarden.send.Send import com.bitwarden.send.SendView import com.bitwarden.vault.Attachment @@ -28,14 +23,13 @@ import com.bitwarden.vault.CipherListView import com.bitwarden.vault.CipherView import com.bitwarden.vault.Collection import com.bitwarden.vault.CollectionView -import com.bitwarden.vault.Fido2CredentialNewView import com.bitwarden.vault.Folder import com.bitwarden.vault.FolderView import com.bitwarden.vault.PasswordHistory import com.bitwarden.vault.PasswordHistoryView import com.bitwarden.vault.TotpResponse import com.x8bit.bitwarden.data.platform.manager.SdkClientManager -import com.x8bit.bitwarden.data.platform.util.asFailure +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2CredentialRequest import com.x8bit.bitwarden.data.vault.datasource.sdk.model.Fido2CredentialAuthenticationUserInterfaceImpl import com.x8bit.bitwarden.data.vault.datasource.sdk.model.Fido2CredentialRegistrationUserInterfaceImpl import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult @@ -452,11 +446,6 @@ class VaultSdkSourceImpl( override suspend fun registerFido2Credential( request: RegisterFido2CredentialRequest, fido2CredentialStore: Fido2CredentialStore, - checkUser: suspend (CheckUserOptions, UiHint?) -> CheckUserResult, - checkUserAndPickCredential: suspend ( - options: CheckUserOptions, - newCredential: Fido2CredentialNewView, - ) -> CipherViewWrapper, ): Result = runCatching { callbackFlow { try { @@ -465,9 +454,8 @@ class VaultSdkSourceImpl( .fido2() .client( userInterface = Fido2CredentialRegistrationUserInterfaceImpl( + selectedCipherView = request.selectedCipherView, isVerificationSupported = request.isUserVerificationSupported, - checkUser = checkUser, - checkUserAndPickCredentialForCreation = checkUserAndPickCredential, ), credentialStore = fido2CredentialStore, ) @@ -480,10 +468,9 @@ class VaultSdkSourceImpl( ) send(result) - } catch (e: BitwardenException) { - e.asFailure() - } finally { close() + } catch (e: BitwardenException) { + close(e) } awaitClose() } @@ -492,40 +479,32 @@ class VaultSdkSourceImpl( @Suppress("MaxLineLength") override suspend fun authenticateFido2Credential( - userId: String, - origin: String, - requestJson: String, - clientData: ClientData, - isVerificationSupported: Boolean, - checkUser: suspend (CheckUserOptions, UiHint?) -> CheckUserResult, - pickCredentialForAuthentication: suspend (List) -> CipherViewWrapper, + request: AuthenticateFido2CredentialRequest, fido2CredentialStore: Fido2CredentialStore, ): Result = runCatching { callbackFlow { try { - val client = getClient(userId) + val client = getClient(request.userId) .platform() .fido2() .client( userInterface = Fido2CredentialAuthenticationUserInterfaceImpl( - isVerificationSupported = isVerificationSupported, - checkUser = checkUser, - pickCredentialForAuthentication = pickCredentialForAuthentication, + selectedCipherView = request.selectedCipherView, + isVerificationSupported = request.isUserVerificationSupported, ), credentialStore = fido2CredentialStore, ) val result = client.authenticate( - origin = origin, - request = requestJson, - clientData = clientData, + origin = request.origin, + request = request.requestJson, + clientData = request.clientData, ) send(result) - } catch (e: BitwardenException) { - e.asFailure() - } finally { close() + } catch (e: BitwardenException) { + close(e) } awaitClose() diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt new file mode 100644 index 0000000000..ab7e908f2b --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt @@ -0,0 +1,26 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk.model + +import com.bitwarden.fido.ClientData +import com.bitwarden.vault.CipherView + +/** + * Models a FIDO 2 authentication request to the Bitwarden SDK. + * + * @param userId User whom the credential is being authenticated for. + * @param origin Origin of the Relying Party. This can either be a Relying Party's URL or their + * application fingerprint. + * @param requestJson Authentication request JSON received from the OS. + * @param clientData Metadata containing either privileged application certificate hash or Android + * package name of the Relying Party. + * @param selectedCipherView [CipherView] containing the FIDO 2 credential being authenticated. + * @param isUserVerificationSupported Whether the device or application are capable of performing + * user verification. + */ +data class AuthenticateFido2CredentialRequest( + val userId: String, + val origin: String, + val requestJson: String, + val clientData: ClientData, + val selectedCipherView: CipherView, + val isUserVerificationSupported: Boolean, +) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialAuthenticationUserInterfaceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialAuthenticationUserInterfaceImpl.kt index 0cbe02f037..ef52205a96 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialAuthenticationUserInterfaceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialAuthenticationUserInterfaceImpl.kt @@ -15,14 +15,13 @@ import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage */ @OmitFromCoverage class Fido2CredentialAuthenticationUserInterfaceImpl( + private val selectedCipherView: CipherView, private val isVerificationSupported: Boolean, - private val checkUser: suspend (CheckUserOptions, UiHint?) -> CheckUserResult, - private val pickCredentialForAuthentication: suspend (List) -> CipherViewWrapper, ) : Fido2UserInterface { override suspend fun checkUser( options: CheckUserOptions, hint: UiHint, - ): CheckUserResult = checkUser.invoke(options, hint) + ): CheckUserResult = CheckUserResult(true, true) override suspend fun checkUserAndPickCredentialForCreation( options: CheckUserOptions, @@ -33,5 +32,5 @@ class Fido2CredentialAuthenticationUserInterfaceImpl( override suspend fun pickCredentialForAuthentication( availableCredentials: List, - ): CipherViewWrapper = pickCredentialForAuthentication.invoke(availableCredentials) + ): CipherViewWrapper = CipherViewWrapper(selectedCipherView) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialRegistrationUserInterfaceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialRegistrationUserInterfaceImpl.kt index 6a4eef7da0..78b6ce2054 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialRegistrationUserInterfaceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialRegistrationUserInterfaceImpl.kt @@ -15,23 +15,22 @@ import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage */ @OmitFromCoverage class Fido2CredentialRegistrationUserInterfaceImpl( + private val selectedCipherView: CipherView, private val isVerificationSupported: Boolean, - private val checkUser: suspend (CheckUserOptions, UiHint?) -> CheckUserResult, - private val checkUserAndPickCredentialForCreation: suspend ( - options: CheckUserOptions, - newCredential: Fido2CredentialNewView, - ) -> CipherViewWrapper, ) : Fido2UserInterface { override suspend fun checkUser( options: CheckUserOptions, hint: UiHint, - ): CheckUserResult = checkUser.invoke(options, hint) + ): CheckUserResult = CheckUserResult(true, true) override suspend fun checkUserAndPickCredentialForCreation( options: CheckUserOptions, newCredential: Fido2CredentialNewView, - ): CheckUserAndPickCredentialForCreationResult = throw IllegalStateException() + ): CheckUserAndPickCredentialForCreationResult = CheckUserAndPickCredentialForCreationResult( + cipher = CipherViewWrapper(selectedCipherView), + checkUserResult = CheckUserResult(true, true), + ) override suspend fun isVerificationEnabled(): Boolean = isVerificationSupported diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt index e9aab6acbd..3011977d54 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt @@ -1,11 +1,8 @@ -@file:OmitFromCoverage - package com.x8bit.bitwarden.data.vault.datasource.sdk.util import android.util.Base64 import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2AttestationResponse -import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage /** * Converts the SDK attestation response to a [Fido2AttestationResponse] that can be serialized into @@ -28,11 +25,11 @@ fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResp clientExtensionResults = clientExtensionResults .credProps ?.rk - ?.let { + ?.let { residentKey -> Fido2AttestationResponse.ClientExtensionResults( credentialProperties = Fido2AttestationResponse .ClientExtensionResults - .CredentialProperties(residentKey = it), + .CredentialProperties(residentKey = residentKey), ) }, authenticatorAttachment = authenticatorAttachment, 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 188adffbd0..dc1dac7ddf 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 @@ -322,8 +322,6 @@ class Fido2CredentialManagerTest { mockVaultSdkSource.registerFido2Credential( request = capture(requestCaptureSlot), fido2CredentialStore = any(), - checkUser = any(), - checkUserAndPickCredential = any(), ) } coAnswers { mockRegistrationResponse @@ -362,8 +360,6 @@ class Fido2CredentialManagerTest { mockVaultSdkSource.registerFido2Credential( request = capture(requestCaptureSlot), fido2CredentialStore = any(), - checkUser = any(), - checkUserAndPickCredential = any(), ) } coAnswers { mockRegistrationResponse @@ -399,8 +395,6 @@ class Fido2CredentialManagerTest { mockVaultSdkSource.registerFido2Credential( request = capture(requestCaptureSlot), fido2CredentialStore = any(), - checkUser = any(), - checkUserAndPickCredential = any(), ) } coAnswers { mockRegistrationResponse @@ -441,8 +435,6 @@ class Fido2CredentialManagerTest { mockVaultSdkSource.registerFido2Credential( request = capture(requestCaptureSlot), fido2CredentialStore = any(), - checkUser = any(), - checkUserAndPickCredential = any(), ) } coAnswers { mockRegistrationResponse @@ -515,8 +507,6 @@ class Fido2CredentialManagerTest { mockVaultSdkSource.registerFido2Credential( request = any(), fido2CredentialStore = any(), - checkUser = any(), - checkUserAndPickCredential = any(), ) } coAnswers { mockRegistrationResponse diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt index dc684fb6f2..3e7a5d9448 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt @@ -7,14 +7,11 @@ import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.UpdatePasswordResponse import com.bitwarden.crypto.TrustDeviceResponse import com.bitwarden.exporters.ExportFormat -import com.bitwarden.fido.CheckUserOptions import com.bitwarden.fido.ClientData +import com.bitwarden.fido.Fido2CredentialAutofillView import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse -import com.bitwarden.fido.Verification import com.bitwarden.sdk.BitwardenException -import com.bitwarden.sdk.CheckUserResult -import com.bitwarden.sdk.CipherViewWrapper import com.bitwarden.sdk.Client import com.bitwarden.sdk.ClientAuth import com.bitwarden.sdk.ClientCiphers @@ -27,8 +24,6 @@ import com.bitwarden.sdk.ClientPlatform import com.bitwarden.sdk.ClientSends import com.bitwarden.sdk.ClientVault import com.bitwarden.sdk.Fido2CredentialStore -import com.bitwarden.sdk.Fido2UserInterface -import com.bitwarden.sdk.UiHint import com.bitwarden.send.Send import com.bitwarden.send.SendView import com.bitwarden.vault.Attachment @@ -46,6 +41,7 @@ import com.bitwarden.vault.TotpResponse import com.x8bit.bitwarden.data.platform.manager.SdkClientManager import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.util.asSuccess +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2CredentialRequest import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView @@ -58,11 +54,11 @@ import io.mockk.just import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.runs -import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.security.MessageDigest @@ -1016,26 +1012,12 @@ class VaultSdkSourceTest { every { digest(any()) } returns DEFAULT_SIGNATURE.toByteArray() } - val mockCipherView = createMockCipherView(1) val mockAttestation = mockk() coEvery { fido2.register(any(), any(), any()) } returns mockAttestation val result = vaultSdkSource.registerFido2Credential( - request = RegisterFido2CredentialRequest( - userId = "mockUserId", - origin = "www.bitwarden.com", - requestJson = "requestJson", - clientData = ClientData.DefaultWithCustomHash( - hash = DEFAULT_SIGNATURE.toByteArray(), - ), - selectedCipherView = mockCipherView, - isUserVerificationSupported = true, - ), - checkUser = { _, _ -> CheckUserResult(true, true) }, - checkUserAndPickCredential = { _, _ -> - CipherViewWrapper(mockCipherView) - }, + DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST, fido2CredentialStore = mockFido2CredentialStore, ) @@ -1047,58 +1029,23 @@ class VaultSdkSourceTest { } @Test - fun `registerFido2Credential should invoke checkUser when called by the SDK`() = runTest { - val checkUserResult = CheckUserResult(true, true) - - val checkUserOptionsSlot = slot() - val uiHintSlot = slot() - val mockUserInterface = mockk { + fun `registerFido2Credential should return Failure when BitwardenException is thrown`() = + runTest { coEvery { - checkUser( - capture(checkUserOptionsSlot), - capture(uiHintSlot), + fido2.register( + any(), + any(), + any(), ) - } returns checkUserResult - } + } throws BitwardenException.E("mockException") - val mockCipherView = createMockCipherView(number = 1) - val mockAttestation = mockk() - val mockCheckUserOptions = CheckUserOptions(true, Verification.REQUIRED) - coEvery { fido2.register(any(), any(), any()) } coAnswers { - mockUserInterface.checkUser( - mockCheckUserOptions, - UiHint.InformNoCredentialsFound, + val result = vaultSdkSource.registerFido2Credential( + DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST, + fido2CredentialStore = mockFido2CredentialStore, ) - mockAttestation + + assertTrue(result.isFailure) } - vaultSdkSource.registerFido2Credential( - request = RegisterFido2CredentialRequest( - userId = "mockUserId", - origin = "www.bitwarden.com", - requestJson = "requestJson", - clientData = ClientData.DefaultWithCustomHash( - hash = DEFAULT_SIGNATURE.toByteArray(), - ), - selectedCipherView = mockCipherView, - isUserVerificationSupported = true, - ), - checkUser = { _, _ -> checkUserResult }, - checkUserAndPickCredential = { _, _ -> CipherViewWrapper(mockCipherView) }, - fido2CredentialStore = mockFido2CredentialStore, - ) - - coVerify { mockUserInterface.checkUser(any(), any()) } - - assertEquals( - mockCheckUserOptions, - checkUserOptionsSlot.captured, - ) - - assertEquals( - UiHint.InformNoCredentialsFound, - uiHintSlot.captured, - ) - } @Suppress("MaxLineLength") @Test @@ -1109,21 +1056,15 @@ class VaultSdkSourceTest { every { digest(any()) } returns DEFAULT_SIGNATURE.toByteArray() } - val mockCipherView = createMockCipherView(1) val mockAssertion = mockk() coEvery { fido2.authenticate(any(), any(), any()) } returns mockAssertion - val result = vaultSdkSource.authenticateFido2Credential( - userId = "mockUserId", - origin = "www.bitwarden.com", - requestJson = "requestJson", - clientData = ClientData.DefaultWithCustomHash(DEFAULT_SIGNATURE.toByteArray()), - isVerificationSupported = true, - checkUser = { _, _ -> CheckUserResult(true, true) }, - pickCredentialForAuthentication = { CipherViewWrapper(mockCipherView) }, - fido2CredentialStore = mockFido2CredentialStore, - ) + val result = vaultSdkSource + .authenticateFido2Credential( + DEFAULT_FIDO_2_AUTH_REQUEST, + fido2CredentialStore = mockFido2CredentialStore, + ) assertEquals( mockAssertion.asSuccess(), @@ -1133,53 +1074,88 @@ class VaultSdkSourceTest { } @Test - fun `authenticateFido2Credential should invoke checkUser when called by the SDK`() = runTest { - val checkUserResult = CheckUserResult(true, true) - - val checkUserOptionsSlot = slot() - val uiHintSlot = slot() - val mockUserInterface = mockk { + fun `authenticateFido2Credential should return Failure when BitwardenException is thrown`() = + runTest { coEvery { - checkUser( - capture(checkUserOptionsSlot), - capture(uiHintSlot), + fido2.authenticate( + any(), + any(), + any(), ) - } returns checkUserResult + } throws BitwardenException.E("mockException") + + val result = vaultSdkSource + .authenticateFido2Credential( + DEFAULT_FIDO_2_AUTH_REQUEST, + fido2CredentialStore = mockFido2CredentialStore, + ) + + assertTrue(result.isFailure) } + @Test + fun `decryptFido2CredentialAutofillViews should return results when successful`() = runTest { val mockCipherView = createMockCipherView(number = 1) - val mockAssertion = mockk() - val mockCheckUserOptions = CheckUserOptions(true, Verification.REQUIRED) - coEvery { fido2.authenticate(any(), any(), any()) } coAnswers { - mockUserInterface.checkUser( - mockCheckUserOptions, - UiHint.InformNoCredentialsFound, - ) - mockAssertion - } - vaultSdkSource.authenticateFido2Credential( + val mockAutofillView = Fido2CredentialAutofillView( + credentialId = byteArrayOf(0), + cipherId = "mockCipherId", + rpId = "mockRpId", + userNameForUi = "mockUserNameForUi", + userHandle = "mockUserHandle".toByteArray(), + ) + val autofillViews = listOf(mockAutofillView) + + coEvery { + clientFido2.decryptFido2AutofillCredentials(mockCipherView) + } returns autofillViews + + val result = vaultSdkSource.decryptFido2CredentialAutofillViews( userId = "mockUserId", - origin = "www.bitwarden.com", - requestJson = "requestJson", - clientData = ClientData.DefaultWithCustomHash(DEFAULT_SIGNATURE.toByteArray()), - isVerificationSupported = true, - checkUser = { _, _ -> checkUserResult }, - pickCredentialForAuthentication = { CipherViewWrapper(mockCipherView) }, - fido2CredentialStore = mockFido2CredentialStore, - ) - - coVerify { mockUserInterface.checkUser(any(), any()) } - - assertEquals( - mockCheckUserOptions, - checkUserOptionsSlot.captured, + cipherViews = arrayOf(mockCipherView), ) assertEquals( - UiHint.InformNoCredentialsFound, - uiHintSlot.captured, + autofillViews.asSuccess(), + result, ) } + + @Suppress("MaxLineLength") + @Test + fun `decryptFido2CredentialAutofillViews should return Failure when Bitwarden exception is thrown`() = + runTest { + val mockCipherView = createMockCipherView(number = 1) + coEvery { + clientFido2.decryptFido2AutofillCredentials(mockCipherView) + } throws BitwardenException.E("mockException") + + val result = vaultSdkSource.decryptFido2CredentialAutofillViews( + userId = "mockUserId", + cipherViews = arrayOf(mockCipherView), + ) + + assertTrue(result.isFailure) + } } private const val DEFAULT_SIGNATURE = "0987654321ABCDEF" +private val DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST = RegisterFido2CredentialRequest( + userId = "mockUserId", + origin = "www.bitwarden.com", + requestJson = "requestJson", + clientData = ClientData.DefaultWithCustomHash( + DEFAULT_SIGNATURE.toByteArray(), + ), + isUserVerificationSupported = true, + selectedCipherView = createMockCipherView(number = 1), +) +val DEFAULT_FIDO_2_AUTH_REQUEST = AuthenticateFido2CredentialRequest( + userId = "mockUserId", + origin = "www.bitwarden.com", + requestJson = "requestJson", + clientData = ClientData.DefaultWithCustomHash( + DEFAULT_SIGNATURE.toByteArray(), + ), + isUserVerificationSupported = true, + selectedCipherView = createMockCipherView(number = 1), +) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/PublicKeyCredentialCreationOptionsTestHelpers.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/PublicKeyCredentialCreationOptionsTestHelpers.kt index f0c12c809d..68186c3f66 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/PublicKeyCredentialCreationOptionsTestHelpers.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/PublicKeyCredentialCreationOptionsTestHelpers.kt @@ -6,31 +6,34 @@ import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKe * Returns a mock FIDO 2 [PublicKeyCredentialCreationOptions] object to simulate a credential * creation request. */ -fun createMockPublicKeyCredentialCreationOptions(number: Int) = - PublicKeyCredentialCreationOptions( - authenticatorSelection = PublicKeyCredentialCreationOptions - .AuthenticatorSelectionCriteria(), - challenge = "mockPublicKeyCredentialCreationOptionsChallenge-$number", - excludeCredentials = listOf( - PublicKeyCredentialCreationOptions.PublicKeyCredentialDescriptor( - type = "mockPublicKeyCredentialDescriptorType-$number", - id = "mockPublicKeyCredentialDescriptorId-$number", - transports = listOf("mockPublicKeyCredentialDescriptorTransports-$number"), - ), +@Suppress("MaxLineLength") +fun createMockPublicKeyCredentialCreationOptions( + number: Int, + userVerificationRequirement: PublicKeyCredentialCreationOptions.AuthenticatorSelectionCriteria.UserVerificationRequirement? = null, +) = PublicKeyCredentialCreationOptions( + authenticatorSelection = PublicKeyCredentialCreationOptions + .AuthenticatorSelectionCriteria(userVerification = userVerificationRequirement), + challenge = "mockPublicKeyCredentialCreationOptionsChallenge-$number", + excludeCredentials = listOf( + PublicKeyCredentialCreationOptions.PublicKeyCredentialDescriptor( + type = "mockPublicKeyCredentialDescriptorType-$number", + id = "mockPublicKeyCredentialDescriptorId-$number", + transports = listOf("mockPublicKeyCredentialDescriptorTransports-$number"), ), - pubKeyCredParams = listOf( - PublicKeyCredentialCreationOptions.PublicKeyCredentialParameters( - type = "PublicKeyCredentialParametersType-$number", - alg = number.toLong(), - ), + ), + pubKeyCredParams = listOf( + PublicKeyCredentialCreationOptions.PublicKeyCredentialParameters( + type = "PublicKeyCredentialParametersType-$number", + alg = number.toLong(), ), - relyingParty = PublicKeyCredentialCreationOptions.PublicKeyCredentialRpEntity( - name = "mockPublicKeyCredentialRpEntityName-$number", - id = "mockPublicKeyCredentialRpEntity-$number", - ), - user = PublicKeyCredentialCreationOptions.PublicKeyCredentialUserEntity( - name = "mockPublicKeyCredentialUserEntityName-$number", - id = "mockPublicKeyCredentialUserEntityId-$number", - displayName = "mockPublicKeyCredentialUserEntityDisplayName-$number", - ), - ) + ), + relyingParty = PublicKeyCredentialCreationOptions.PublicKeyCredentialRpEntity( + name = "mockPublicKeyCredentialRpEntityName-$number", + id = "mockPublicKeyCredentialRpEntity-$number", + ), + user = PublicKeyCredentialCreationOptions.PublicKeyCredentialUserEntity( + name = "mockPublicKeyCredentialUserEntityName-$number", + id = "mockPublicKeyCredentialUserEntityId-$number", + displayName = "mockPublicKeyCredentialUserEntityDisplayName-$number", + ), +)