diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManager.kt index 1609bd1b8..e87418488 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManager.kt @@ -2,12 +2,12 @@ package com.x8bit.bitwarden.data.autofill.fido2.manager import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult /** - * Responsible for managing FIDO 2 credential creation and authentication. + * Responsible for managing FIDO 2 credential registration and authentication. */ interface Fido2CredentialManager { @@ -32,5 +32,5 @@ interface Fido2CredentialManager { userId: String, fido2CredentialRequest: Fido2CredentialRequest, selectedCipherView: CipherView, - ): Fido2CreateCredentialResult + ): Fido2RegisterCredentialResult } 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 49fffee89..325afe7a4 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 @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.data.autofill.fido2.manager -import androidx.credentials.exceptions.CreateCredentialUnknownException import androidx.credentials.provider.CallingAppInfo import com.bitwarden.fido.ClientData import com.bitwarden.sdk.CheckUserResult @@ -10,8 +9,8 @@ import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult import com.x8bit.bitwarden.data.platform.manager.AssetManager import com.x8bit.bitwarden.data.platform.util.asFailure @@ -46,15 +45,11 @@ class Fido2CredentialManagerImpl( userId: String, fido2CredentialRequest: Fido2CredentialRequest, selectedCipherView: CipherView, - ): Fido2CreateCredentialResult { + ): Fido2RegisterCredentialResult { val clientData = if (fido2CredentialRequest.callingAppInfo.isOriginPopulated()) { fido2CredentialRequest.callingAppInfo.getAppSigningSignatureFingerprint() ?.let { ClientData.DefaultWithCustomHash(hash = it) } - ?: return Fido2CreateCredentialResult.Error( - exception = CreateCredentialUnknownException( - errorMessage = "Application contains multiple signing certificates.", - ), - ) + ?: return Fido2RegisterCredentialResult.Error } else { ClientData.DefaultWithExtraData( androidPackageName = fido2CredentialRequest @@ -82,9 +77,9 @@ class Fido2CredentialManagerImpl( .map { it.toAndroidAttestationResponse() } .mapCatching { json.encodeToString(it) } .fold( - onSuccess = { Fido2CreateCredentialResult.Success(it) }, + onSuccess = { Fido2RegisterCredentialResult.Success(it) }, onFailure = { - Fido2CreateCredentialResult.Error(CreateCredentialUnknownException()) + Fido2RegisterCredentialResult.Error }, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialResult.kt deleted file mode 100644 index 00795c02b..000000000 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2CreateCredentialResult.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.x8bit.bitwarden.data.autofill.fido2.model - -import androidx.credentials.exceptions.CreateCredentialException - -/** - * Models the data returned from creating a FIDO 2 credential. - */ -sealed class Fido2CreateCredentialResult { - - /** - * Models a successful response for creating a credential. - */ - data class Success( - val registrationResponse: String, - ) : Fido2CreateCredentialResult() - - /** - * Models an error response for creating a credential. - */ - data class Error( - val exception: CreateCredentialException, - ) : Fido2CreateCredentialResult() -} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2RegisterCredentialResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2RegisterCredentialResult.kt new file mode 100644 index 000000000..ce42a1490 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/model/Fido2RegisterCredentialResult.kt @@ -0,0 +1,24 @@ +package com.x8bit.bitwarden.data.autofill.fido2.model + +/** + * Models the data returned from creating a FIDO 2 credential. + */ +sealed class Fido2RegisterCredentialResult { + + /** + * Indicates the credential has been successfully registered. + */ + data class Success( + val registrationResponse: String, + ) : Fido2RegisterCredentialResult() + + /** + * Indicates there was an error and the credential was not registered. + */ + data object Error : Fido2RegisterCredentialResult() + + /** + * Indicates the user cancelled the request. + */ + data object Cancelled : Fido2RegisterCredentialResult() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManager.kt index a9c767d0b..3fa548a44 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManager.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.ui.autofill.fido2.manager -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult /** * A manager for completing the FIDO 2 creation process. @@ -8,7 +8,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult interface Fido2CompletionManager { /** - * Completes the FIDO 2 creation process with the provided [result]. + * Completes the FIDO 2 registration process with the provided [result]. */ - fun completeFido2Create(result: Fido2CreateCredentialResult) + fun completeFido2Registration(result: Fido2RegisterCredentialResult) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt index 7156f622e..5cc0d8cac 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/fido2/manager/Fido2CompletionManagerImpl.kt @@ -5,8 +5,10 @@ import android.content.Intent import android.os.Build import androidx.annotation.RequiresApi import androidx.credentials.CreatePublicKeyCredentialResponse +import androidx.credentials.exceptions.CreateCredentialCancellationException +import androidx.credentials.exceptions.CreateCredentialUnknownException import androidx.credentials.provider.PendingIntentHandler -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage /** @@ -18,19 +20,19 @@ class Fido2CompletionManagerImpl( ) : Fido2CompletionManager { @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - override fun completeFido2Create(result: Fido2CreateCredentialResult) { + override fun completeFido2Registration(result: Fido2RegisterCredentialResult) { activity.also { val intent = Intent() when (result) { - is Fido2CreateCredentialResult.Error -> { + is Fido2RegisterCredentialResult.Error -> { PendingIntentHandler .setCreateCredentialException( intent = intent, - exception = result.exception, + exception = CreateCredentialUnknownException(), ) } - is Fido2CreateCredentialResult.Success -> { + is Fido2RegisterCredentialResult.Success -> { PendingIntentHandler .setCreateCredentialResponse( intent = intent, @@ -39,6 +41,14 @@ class Fido2CompletionManagerImpl( ), ) } + + is Fido2RegisterCredentialResult.Cancelled -> { + PendingIntentHandler + .setCreateCredentialException( + intent = intent, + exception = CreateCredentialCancellationException(), + ) + } } it.setResult(Activity.RESULT_OK, intent) it.finish() diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index 8b73e87b5..2b481a94c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -112,8 +112,8 @@ fun VaultAddEditScreen( ) } - is VaultAddEditEvent.CompleteFido2Create -> { - fido2CompletionManager.completeFido2Create(event.result) + is VaultAddEditEvent.CompleteFido2Registration -> { + fido2CompletionManager.completeFido2Registration(event.result) } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index aaf341cbb..011641787 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -1,7 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit import android.os.Parcelable -import androidx.credentials.exceptions.CreateCredentialUnknownException import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.bitwarden.vault.CipherView @@ -10,8 +9,8 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager @@ -379,14 +378,12 @@ class VaultAddEditViewModel @Inject constructor( ?: run { sendAction( VaultAddEditAction.Internal.Fido2RegisterCredentialResultReceive( - result = Fido2CreateCredentialResult.Error( - exception = CreateCredentialUnknownException(), - ), + result = Fido2RegisterCredentialResult.Error, ), ) return@launch } - val result: Fido2CreateCredentialResult = + val result: Fido2RegisterCredentialResult = fido2CredentialManager.registerFido2Credential( userId = activeUserId, fido2CredentialRequest = request, @@ -1356,15 +1353,19 @@ class VaultAddEditViewModel @Inject constructor( ) { clearDialogState() when (action.result) { - is Fido2CreateCredentialResult.Error -> { + is Fido2RegisterCredentialResult.Error -> { sendEvent(VaultAddEditEvent.ShowToast(R.string.an_error_has_occurred.asText())) } - is Fido2CreateCredentialResult.Success -> { + is Fido2RegisterCredentialResult.Success -> { sendEvent(VaultAddEditEvent.ShowToast(R.string.item_updated.asText())) } + + Fido2RegisterCredentialResult.Cancelled -> { + // no-op: The OS will handle re-displaying the system prompt. + } } - sendEvent(VaultAddEditEvent.CompleteFido2Create(action.result)) + sendEvent(VaultAddEditEvent.CompleteFido2Registration(action.result)) } //endregion Internal Type Handlers @@ -1989,12 +1990,12 @@ sealed class VaultAddEditEvent { ) : VaultAddEditEvent() /** - * Complete the current FIDO 2 credential creation process. + * Complete the current FIDO 2 credential registration process. * * @property result the result of FIDO 2 credential creation. */ - data class CompleteFido2Create( - val result: Fido2CreateCredentialResult, + data class CompleteFido2Registration( + val result: Fido2RegisterCredentialResult, ) : VaultAddEditEvent() } @@ -2484,7 +2485,7 @@ sealed class VaultAddEditAction { * Indicates that the FIDO 2 registration result has been received. */ data class Fido2RegisterCredentialResultReceive( - val result: Fido2CreateCredentialResult, + val result: Fido2RegisterCredentialResult, ) : Internal() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt index f8886f44f..559271430 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt @@ -134,8 +134,8 @@ fun VaultItemListingScreen( onNavigateToVaultItemListing(VaultItemListingType.Collection(event.collectionId)) } - is VaultItemListingEvent.CompleteFido2Create -> { - fido2CompletionManager.completeFido2Create(event.result) + is VaultItemListingEvent.CompleteFido2Registration -> { + fido2CompletionManager.completeFido2Registration(event.result) } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index f891159ba..5858bb2a5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -1,15 +1,14 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting import android.os.Parcelable -import androidx.credentials.exceptions.CreateCredentialUnknownException import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData @@ -408,10 +407,8 @@ class VaultItemListingViewModel @Inject constructor( private fun handleDismissFido2ErrorDialogClick() { sendEvent( - VaultItemListingEvent.CompleteFido2Create( - result = Fido2CreateCredentialResult.Error( - exception = CreateCredentialUnknownException(), - ), + VaultItemListingEvent.CompleteFido2Registration( + result = Fido2RegisterCredentialResult.Error, ), ) } @@ -766,15 +763,19 @@ class VaultItemListingViewModel @Inject constructor( ) { mutableStateFlow.update { it.copy(dialogState = null) } when (action.result) { - is Fido2CreateCredentialResult.Error -> { + is Fido2RegisterCredentialResult.Error -> { sendEvent(VaultItemListingEvent.ShowToast(R.string.an_error_has_occurred.asText())) } - is Fido2CreateCredentialResult.Success -> { + is Fido2RegisterCredentialResult.Success -> { sendEvent(VaultItemListingEvent.ShowToast(R.string.item_updated.asText())) } + + Fido2RegisterCredentialResult.Cancelled -> { + // no-op: The OS will handle re-displaying the system prompt. + } } - sendEvent(VaultItemListingEvent.CompleteFido2Create(action.result)) + sendEvent(VaultItemListingEvent.CompleteFido2Registration(action.result)) } private fun handleValidateFido2OriginResultReceive( @@ -1346,8 +1347,8 @@ sealed class VaultItemListingEvent { * * @property result the result of FIDO 2 credential creation. */ - data class CompleteFido2Create( - val result: Fido2CreateCredentialResult, + data class CompleteFido2Registration( + val result: Fido2RegisterCredentialResult, ) : VaultItemListingEvent() } @@ -1529,7 +1530,7 @@ sealed class VaultItemListingsAction { * Indicates that a result for FIDO 2 credential registration has been received. */ data class Fido2RegisterCredentialResultReceive( - val result: Fido2CreateCredentialResult, + val result: Fido2RegisterCredentialResult, ) : Internal() } } 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 308533a58..188adffbd 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 @@ -10,8 +10,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalA import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2AttestationResponse -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest import com.x8bit.bitwarden.data.platform.manager.AssetManager @@ -486,7 +486,7 @@ class Fido2CredentialManagerTest { ) assertTrue( - result is Fido2CreateCredentialResult.Error, + result is Fido2RegisterCredentialResult.Error, ) } @@ -530,7 +530,7 @@ class Fido2CredentialManagerTest { ) assertTrue( - result is Fido2CreateCredentialResult.Error, + result is Fido2RegisterCredentialResult.Error, ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index f6283b21a..a5936b81b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTouchInput import androidx.core.net.toUri import com.bitwarden.vault.UriMatchType -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager @@ -96,7 +96,7 @@ class VaultAddEditScreenTest : BaseComposeTest() { every { launchUri(any()) } just runs } private val fido2CompletionManager: Fido2CompletionManager = mockk { - every { completeFido2Create(any()) } just runs + every { completeFido2Registration(any()) } just runs } @Before @@ -194,11 +194,11 @@ class VaultAddEditScreenTest : BaseComposeTest() { @Test fun `on CompleteFido2Create even should invoke Fido2CompletionManager`() { - val result = Fido2CreateCredentialResult.Success( + val result = Fido2RegisterCredentialResult.Success( registrationResponse = "mockRegistrationResponse", ) - mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteFido2Create(result = result)) - verify { fido2CompletionManager.completeFido2Create(result) } + mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteFido2Registration(result = result)) + verify { fido2CompletionManager.completeFido2Registration(result) } } @Test 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 e816828be..f9c1f327b 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 @@ -1,7 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit import android.content.pm.SigningInfo -import androidx.credentials.exceptions.CreateCredentialUnknownException import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import app.cash.turbine.turbineScope @@ -17,8 +16,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.Organization import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager -import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.platform.manager.PolicyManager @@ -725,7 +724,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN), ), ) - val mockCreateResult = Fido2CreateCredentialResult.Success( + val mockCreateResult = Fido2RegisterCredentialResult.Success( registrationResponse = "mockRegistrationResponse", ) coEvery { @@ -750,7 +749,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { eventTurbine.awaitItem(), ) assertEquals( - VaultAddEditEvent.CompleteFido2Create(result = mockCreateResult), + VaultAddEditEvent.CompleteFido2Registration(result = mockCreateResult), eventTurbine.awaitItem(), ) assertEquals(stateWithName, stateTurbine.awaitItem()) @@ -767,7 +766,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test - fun `in add mode during fido2, SaveClick should show dialog, register credential, show toast on error, and emit ExitApp`() = + fun `in add mode during fido2, SaveClick should show dialog, register credential, show toast on error, and emit ExitApp when result is Error`() = runTest { val fido2CredentialRequest = Fido2CredentialRequest( userId = "mockUserId", @@ -807,9 +806,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN), ), ) - val mockCreateResult = Fido2CreateCredentialResult.Error( - exception = CreateCredentialUnknownException(), - ) + val mockCreateResult = Fido2RegisterCredentialResult.Error coEvery { fido2CredentialManager.registerFido2Credential( userId = "mockUserId", @@ -832,7 +829,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { eventTurbine.awaitItem(), ) assertEquals( - VaultAddEditEvent.CompleteFido2Create(result = mockCreateResult), + VaultAddEditEvent.CompleteFido2Registration(result = mockCreateResult), eventTurbine.awaitItem(), ) assertEquals(stateWithName, stateTurbine.awaitItem()) @@ -847,6 +844,86 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { } } + @Suppress("MaxLineLength") + @Test + fun `in add mode during fido2, SaveClick should show saving dialog, register credential, dismiss dialog, show toast on error, and emit ExitApp when activeUserId is null`() = + runTest { + val fido2CredentialRequest = Fido2CredentialRequest( + userId = "mockUserId", + requestJson = "mockRequestJson", + packageName = "mockPackageName", + signingInfo = mockk(), + origin = null, + ) + specialCircumstanceManager.specialCircumstance = + SpecialCircumstance.Fido2Save( + fido2CredentialRequest = fido2CredentialRequest, + ) + val stateWithDialog = createVaultAddItemState( + vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN), + dialogState = VaultAddEditState.DialogState.Loading( + R.string.saving.asText(), + ), + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + ), + ) + .copy(shouldExitOnSave = true) + + val stateWithName = createVaultAddItemState( + vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN), + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + ), + ) + .copy(shouldExitOnSave = true) + mutableVaultDataFlow.value = DataState.Loaded( + createVaultData(), + ) + val viewModel = createAddVaultItemViewModel( + createSavedStateHandleWithState( + state = stateWithName, + vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN), + ), + ) + val mockCreateResult = Fido2RegisterCredentialResult.Error + coEvery { + fido2CredentialManager.registerFido2Credential( + userId = "mockUserId", + selectedCipherView = any(), + fido2CredentialRequest = fido2CredentialRequest, + ) + } returns mockCreateResult + every { authRepository.activeUserId } returns null + + turbineScope { + val stateTurbine = viewModel.stateFlow.testIn(backgroundScope) + val eventTurbine = viewModel.eventFlow.testIn(backgroundScope) + + viewModel.trySendAction(VaultAddEditAction.Common.SaveClick) + + assertEquals(stateWithName, stateTurbine.awaitItem()) + assertEquals(stateWithDialog, stateTurbine.awaitItem()) + assertEquals( + VaultAddEditEvent.ShowToast(R.string.an_error_has_occurred.asText()), + eventTurbine.awaitItem(), + ) + assertEquals( + VaultAddEditEvent.CompleteFido2Registration(result = mockCreateResult), + eventTurbine.awaitItem(), + ) + assertEquals(stateWithName, stateTurbine.awaitItem()) + + coVerify(exactly = 0) { + fido2CredentialManager.registerFido2Credential( + userId = "mockUserId", + selectedCipherView = any(), + fido2CredentialRequest = fido2CredentialRequest, + ) + } + } + } + @Test fun `in add mode, createCipherInOrganization success should ShowToast and NavigateBack`() = runTest { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt index 5e726eee0..feb32028c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt @@ -82,7 +82,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { every { launchUri(any()) } just runs } private val fido2CompletionManager: Fido2CompletionManager = mockk { - every { completeFido2Create(any()) } just runs + every { completeFido2Registration(any()) } just runs } private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) 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 7964bfa4d..080748419 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 @@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest +import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl @@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.time.Clock @@ -1736,6 +1738,98 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() = + runTest { + val mockResult = Fido2RegisterCredentialResult.Error + + val viewModel = createVaultItemListingViewModel() + viewModel.trySendAction( + VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive( + mockResult, + ), + ) + + viewModel.eventFlow.test { + assertEquals( + VaultItemListingEvent.ShowToast(R.string.an_error_has_occurred.asText()), + awaitItem(), + ) + + assertEquals( + VaultItemListingEvent.CompleteFido2Registration(mockResult), + awaitItem(), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteFido2Registration result`() = + runTest { + val mockResult = Fido2RegisterCredentialResult.Success( + registrationResponse = "mockResponse", + ) + + val viewModel = createVaultItemListingViewModel() + viewModel.trySendAction( + VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive( + mockResult, + ), + ) + + viewModel.eventFlow.test { + assertEquals( + VaultItemListingEvent.ShowToast(R.string.item_updated.asText()), + awaitItem(), + ) + + assertEquals( + VaultItemListingEvent.CompleteFido2Registration(mockResult), + awaitItem(), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `Fido2RegisterCredentialResult Cancelled should emit CompleteFido2Registration result`() = + runTest { + val mockResult = Fido2RegisterCredentialResult.Cancelled + val viewModel = createVaultItemListingViewModel() + + viewModel.trySendAction( + VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive( + mockResult, + ), + ) + + viewModel.eventFlow.test { + assertEquals( + VaultItemListingEvent.CompleteFido2Registration(mockResult), + awaitItem(), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 create`() = + runTest { + val viewModel = createVaultItemListingViewModel() + viewModel.trySendAction(VaultItemListingsAction.DismissFido2CreationErrorDialogClick) + viewModel.eventFlow.test { + assertNull(viewModel.stateFlow.value.dialogState) + assertEquals( + VaultItemListingEvent.CompleteFido2Registration( + result = Fido2RegisterCredentialResult.Error, + ), + awaitItem(), + ) + } + } + @Suppress("CyclomaticComplexMethod") private fun createSavedStateHandleWithVaultItemListingType( vaultItemListingType: VaultItemListingType,