mirror of
https://github.com/bitwarden/android.git
synced 2024-11-25 10:56:03 +03:00
[PM-9409] Authenticate selected FIDO 2 credential (#3630)
Some checks failed
Scan / Check PR run (push) Failing after 0s
Test / Check PR run (push) Failing after 0s
Scan / SAST scan (push) Has been skipped
Scan / Quality scan (push) Has been skipped
Test / Test (push) Has been skipped
Crowdin Push / Crowdin Push (push) Has been cancelled
Some checks failed
Scan / Check PR run (push) Failing after 0s
Test / Check PR run (push) Failing after 0s
Scan / SAST scan (push) Has been skipped
Scan / Quality scan (push) Has been skipped
Test / Test (push) Has been skipped
Crowdin Push / Crowdin Push (push) Has been cancelled
This commit is contained in:
parent
a6bbde2bed
commit
74132de8ed
9 changed files with 950 additions and 127 deletions
|
@ -31,8 +31,10 @@ import com.x8bit.bitwarden.R
|
|||
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.dispatcher.DispatcherManager
|
||||
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
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -188,7 +190,7 @@ class Fido2ProviderProcessorImpl(
|
|||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
@Throws(GetCredentialUnsupportedException::class)
|
||||
private suspend fun getMatchingFido2CredentialEntries(
|
||||
userId: String,
|
||||
request: BeginGetCredentialRequest,
|
||||
|
@ -201,24 +203,37 @@ class Fido2ProviderProcessorImpl(
|
|||
.getPasskeyAssertionOptionsOrNull(requestJson = option.requestJson)
|
||||
?.relyingPartyId
|
||||
?: throw GetCredentialUnknownException("Invalid data.")
|
||||
|
||||
vaultRepository
|
||||
.silentlyDiscoverCredentials(
|
||||
userId = userId,
|
||||
fido2CredentialStore = fido2CredentialStore,
|
||||
relyingPartyId = relyingPartyId,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { it.toCredentialEntries(option) },
|
||||
onFailure = {
|
||||
throw GetCredentialUnknownException("Error decrypting credentials.")
|
||||
},
|
||||
)
|
||||
buildCredentialEntries(relyingPartyId, option)
|
||||
} else {
|
||||
throw GetCredentialUnsupportedException("Unsupported option.")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildCredentialEntries(
|
||||
relyingPartyId: String,
|
||||
option: BeginGetPublicKeyCredentialOption,
|
||||
): List<CredentialEntry> {
|
||||
val cipherViews = vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
?.filter { it.isActiveWithFido2Credentials }
|
||||
?: emptyList()
|
||||
val result = vaultRepository
|
||||
.getDecryptedFido2CredentialAutofillViews(cipherViews)
|
||||
return when (result) {
|
||||
DecryptFido2CredentialAutofillViewResult.Error -> {
|
||||
throw GetCredentialUnknownException("Error decrypting credentials.")
|
||||
}
|
||||
is DecryptFido2CredentialAutofillViewResult.Success -> {
|
||||
result
|
||||
.fido2CredentialAutofillViews
|
||||
.filter { it.rpId == relyingPartyId }
|
||||
.toCredentialEntries(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Fido2CredentialAutofillView>.toCredentialEntries(
|
||||
option: BeginGetPublicKeyCredentialOption,
|
||||
): List<CredentialEntry> =
|
||||
|
|
|
@ -165,6 +165,10 @@ fun VaultItemListingScreen(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
is VaultItemListingEvent.CompleteFido2Assertion -> {
|
||||
fido2CompletionManager.completeFido2Assertion(event.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +180,7 @@ fun VaultItemListingScreen(
|
|||
onDismissFido2ErrorDialog = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.DismissFido2CreationErrorDialogClick,
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -286,7 +290,7 @@ private fun VaultItemListingDialogs(
|
|||
visibilityState = LoadingDialogState.Shown(dialogState.message),
|
||||
)
|
||||
|
||||
is VaultItemListingState.DialogState.Fido2CreationFail -> BitwardenBasicDialog(
|
||||
is VaultItemListingState.DialogState.Fido2OperationFail -> BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = dialogState.title,
|
||||
message = dialogState.message,
|
||||
|
|
|
@ -11,6 +11,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||
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.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
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
|
||||
|
@ -25,6 +27,7 @@ import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardMan
|
|||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2AssertionRequestOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -100,9 +103,10 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||
val autofillSelectionData = specialCircumstance as? SpecialCircumstance.AutofillSelection
|
||||
val fido2CreationData = specialCircumstance as? SpecialCircumstance.Fido2Save
|
||||
val fido2AssertionData = specialCircumstance as? SpecialCircumstance.Fido2Assertion
|
||||
val shouldFinishOnComplete = autofillSelectionData
|
||||
?.shouldFinishWhenComplete
|
||||
?: (fido2CreationData != null)
|
||||
?: (fido2CreationData != null || fido2AssertionData != null)
|
||||
val dialogState = fido2CreationData
|
||||
?.let { VaultItemListingState.DialogState.Loading(R.string.loading.asText()) }
|
||||
VaultItemListingState(
|
||||
|
@ -125,6 +129,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
shouldFinishOnComplete = shouldFinishOnComplete,
|
||||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||
fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest,
|
||||
fido2CredentialAssertionRequest = fido2AssertionData?.fido2AssertionRequest,
|
||||
isPremium = userState.activeAccount.isPremium,
|
||||
)
|
||||
},
|
||||
|
@ -152,6 +157,14 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
?: state.fido2CredentialAssertionRequest
|
||||
?.let { request ->
|
||||
sendAction(
|
||||
VaultItemListingsAction.Internal.Fido2AssertionDataReceive(
|
||||
data = request,
|
||||
),
|
||||
)
|
||||
}
|
||||
?: observeVaultData()
|
||||
}
|
||||
|
||||
|
@ -183,7 +196,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
is VaultItemListingsAction.LogoutAccountClick -> handleLogoutAccountClick(action)
|
||||
is VaultItemListingsAction.SwitchAccountClick -> handleSwitchAccountClick(action)
|
||||
is VaultItemListingsAction.DismissDialogClick -> handleDismissDialogClick()
|
||||
is VaultItemListingsAction.DismissFido2CreationErrorDialogClick -> {
|
||||
is VaultItemListingsAction.DismissFido2ErrorDialogClick -> {
|
||||
handleDismissFido2ErrorDialogClick()
|
||||
}
|
||||
|
||||
|
@ -308,21 +321,9 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
action: VaultItemListingsAction.UserVerificationSuccess,
|
||||
) {
|
||||
fido2CredentialManager.isUserVerified = true
|
||||
getRequestAndRegisterCredential(cipherView = action.selectedCipherView)
|
||||
continueFido2Operation(action.selectedCipherView)
|
||||
}
|
||||
|
||||
private fun getRequestAndRegisterCredential(cipherView: CipherView) =
|
||||
specialCircumstanceManager
|
||||
.specialCircumstance
|
||||
?.toFido2RequestOrNull()
|
||||
?.let { request ->
|
||||
registerFido2CredentialToCipher(
|
||||
request = request,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
}
|
||||
?: showFido2ErrorDialog()
|
||||
|
||||
private fun handleUserVerificationFail() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
showFido2ErrorDialog()
|
||||
|
@ -679,6 +680,36 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun authenticateFido2Credential(
|
||||
request: Fido2CredentialAssertionRequest,
|
||||
cipherView: CipherView,
|
||||
) {
|
||||
val activeUserId = authRepository.activeUserId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = fido2CredentialManager
|
||||
.authenticateFido2Credential(
|
||||
userId = activeUserId,
|
||||
selectedCipherView = cipherView,
|
||||
request = Fido2CredentialAssertionRequest(
|
||||
cipherId = request.cipherId,
|
||||
credentialId = request.credentialId,
|
||||
requestJson = request.requestJson,
|
||||
clientDataHash = request.clientDataHash,
|
||||
packageName = request.packageName,
|
||||
signingInfo = request.signingInfo,
|
||||
origin = request.origin,
|
||||
),
|
||||
)
|
||||
sendAction(
|
||||
VaultItemListingsAction.Internal.Fido2AssertionResultReceive(result),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMasterPasswordRepromptSubmit(
|
||||
action: VaultItemListingsAction.MasterPasswordRepromptSubmit,
|
||||
) {
|
||||
|
@ -756,6 +787,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
|
||||
private fun handleDismissFido2ErrorDialogClick() {
|
||||
clearDialogState()
|
||||
when {
|
||||
state.fido2CredentialRequest != null -> {
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
|
@ -763,6 +796,27 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
state.fido2CredentialAssertionRequest != null -> {
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2Assertion(
|
||||
result = Fido2CredentialAssertionResult.Error,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackClick() {
|
||||
sendEvent(
|
||||
event = VaultItemListingEvent.NavigateBack,
|
||||
|
@ -898,6 +952,14 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
is VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive -> {
|
||||
handleFido2RegisterCredentialResultReceive(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.Internal.Fido2AssertionDataReceive -> {
|
||||
handleFido2AssertionDataReceive(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.Internal.Fido2AssertionResultReceive -> {
|
||||
handleFido2AssertionResultReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1134,9 +1196,30 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
getRequestAndRegisterCredential(cipherView = cipherView)
|
||||
continueFido2Operation(cipherView)
|
||||
}
|
||||
|
||||
private fun continueFido2Operation(cipherView: CipherView) {
|
||||
specialCircumstanceManager
|
||||
.specialCircumstance
|
||||
?.toFido2RequestOrNull()
|
||||
?.let { request ->
|
||||
registerFido2CredentialToCipher(
|
||||
request = request,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
}
|
||||
?: specialCircumstanceManager
|
||||
.specialCircumstance
|
||||
?.toFido2AssertionRequestOrNull()
|
||||
?.let { request ->
|
||||
authenticateFido2Credential(
|
||||
request = request,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
}
|
||||
?: showFido2ErrorDialog()
|
||||
}
|
||||
//endregion VaultItemListing Handlers
|
||||
|
||||
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
||||
|
@ -1265,7 +1348,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
}
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = messageResId.asText(),
|
||||
),
|
||||
|
@ -1277,6 +1360,81 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
observeVaultData()
|
||||
}
|
||||
|
||||
private fun handleFido2AssertionDataReceive(
|
||||
action: VaultItemListingsAction.Internal.Fido2AssertionDataReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
val request = action.data
|
||||
val ciphers = vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
.orEmpty()
|
||||
.filter { it.isActiveWithFido2Credentials }
|
||||
if (request.cipherId.isNullOrEmpty()) {
|
||||
showFido2ErrorDialog()
|
||||
} else {
|
||||
val selectedCipher = ciphers
|
||||
.find { it.id == request.cipherId }
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
return
|
||||
}
|
||||
verifyUserAndAuthenticateCredential(request, selectedCipher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyUserAndAuthenticateCredential(
|
||||
request: Fido2CredentialAssertionRequest,
|
||||
selectedCipher: CipherView,
|
||||
) {
|
||||
val assertionOptions = fido2CredentialManager
|
||||
.getPasskeyAssertionOptionsOrNull(request.requestJson)
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
return
|
||||
}
|
||||
|
||||
if (fido2CredentialManager.isUserVerified) {
|
||||
authenticateFido2Credential(request, selectedCipher)
|
||||
return
|
||||
}
|
||||
|
||||
when (assertionOptions.userVerification) {
|
||||
UserVerificationRequirement.DISCOURAGED -> {
|
||||
authenticateFido2Credential(request, selectedCipher)
|
||||
}
|
||||
|
||||
UserVerificationRequirement.PREFERRED -> {
|
||||
sendUserVerificationEvent(isRequired = false, selectedCipher = selectedCipher)
|
||||
}
|
||||
|
||||
UserVerificationRequirement.REQUIRED -> {
|
||||
sendUserVerificationEvent(isRequired = true, selectedCipher = selectedCipher)
|
||||
}
|
||||
|
||||
null -> {
|
||||
showFido2ErrorDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFido2AssertionResultReceive(
|
||||
action: VaultItemListingsAction.Internal.Fido2AssertionResultReceive,
|
||||
) {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
clearDialogState()
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2Assertion(action.result),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateStateWithVaultData(vaultData: VaultData, clearDialogState: Boolean) {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
|
@ -1291,8 +1449,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
viewState = when (val listingType = currentState.itemListingType) {
|
||||
is VaultItemListingState.ItemListingType.Vault -> {
|
||||
vaultData.toViewState(
|
||||
vaultFilterType = state.vaultFilterType,
|
||||
itemListingType = listingType,
|
||||
vaultFilterType = state.vaultFilterType,
|
||||
hasMasterPassword = state.hasMasterPassword,
|
||||
baseIconUrl = state.baseIconUrl,
|
||||
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||
|
@ -1329,6 +1487,10 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
?.cipherViewList
|
||||
?.firstOrNull { it.id == cipherId }
|
||||
|
||||
private fun sendUserVerificationEvent(isRequired: Boolean, selectedCipher: CipherView) {
|
||||
sendEvent(VaultItemListingEvent.Fido2UserVerification(isRequired, selectedCipher))
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the given vault data and filters it for autofill if necessary.
|
||||
*/
|
||||
|
@ -1387,7 +1549,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
fido2CredentialManager.authenticationAttempts = 0
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -1419,6 +1581,7 @@ data class VaultItemListingState(
|
|||
private val isPullToRefreshSettingEnabled: Boolean,
|
||||
val autofillSelectionData: AutofillSelectionData? = null,
|
||||
val fido2CredentialRequest: Fido2CredentialRequest? = null,
|
||||
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
||||
val shouldFinishOnComplete: Boolean = false,
|
||||
val hasMasterPassword: Boolean,
|
||||
val isPremium: Boolean,
|
||||
|
@ -1485,11 +1648,10 @@ data class VaultItemListingState(
|
|||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Represents a dialog indicating that the FIDO 2 credential creation flow was not
|
||||
* successful.
|
||||
* Represents a dialog indicating that a FIDO 2 credential operation encountered an error.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Fido2CreationFail(
|
||||
data class Fido2OperationFail(
|
||||
val title: Text,
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
|
@ -1892,7 +2054,7 @@ sealed class VaultItemListingEvent {
|
|||
/**
|
||||
* Complete the current FIDO 2 credential registration process.
|
||||
*
|
||||
* @property result the result of FIDO 2 credential registration.
|
||||
* @property result The result of FIDO 2 credential registration.
|
||||
*/
|
||||
data class CompleteFido2Registration(
|
||||
val result: Fido2RegisterCredentialResult,
|
||||
|
@ -1905,6 +2067,16 @@ sealed class VaultItemListingEvent {
|
|||
val isRequired: Boolean,
|
||||
val selectedCipherView: CipherView,
|
||||
) : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* FIDO 2 credential assertion result has been received and the process is ready to be
|
||||
* completed.
|
||||
*
|
||||
* @property result The result of the FIDO 2 credential assertion.
|
||||
*/
|
||||
data class CompleteFido2Assertion(
|
||||
val result: Fido2CredentialAssertionResult,
|
||||
) : VaultItemListingEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1942,7 +2114,7 @@ sealed class VaultItemListingsAction {
|
|||
/**
|
||||
* Click to dismiss the FIDO 2 creation error dialog.
|
||||
*/
|
||||
data object DismissFido2CreationErrorDialogClick : VaultItemListingsAction()
|
||||
data object DismissFido2ErrorDialogClick : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click to submit the master password for FIDO 2 verification.
|
||||
|
@ -2192,6 +2364,20 @@ sealed class VaultItemListingsAction {
|
|||
data class Fido2RegisterCredentialResultReceive(
|
||||
val result: Fido2RegisterCredentialResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that FIDO 2 assertion request data has been received.
|
||||
*/
|
||||
data class Fido2AssertionDataReceive(
|
||||
val data: Fido2CredentialAssertionRequest,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a result of a FIDO 2 credential assertion has been received.
|
||||
*/
|
||||
data class Fido2AssertionResultReceive(
|
||||
val result: Fido2CredentialAssertionResult,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ import com.x8bit.bitwarden.data.platform.util.asSuccess
|
|||
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
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAssertionOptions
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
|
@ -387,6 +387,9 @@ class Fido2ProviderProcessorTest {
|
|||
mutableCiphersStateFlow.value = DataState.Loaded(mockCipherViews)
|
||||
every { cancellationSignal.setOnCancelListener(any()) } just runs
|
||||
every { callback.onError(capture(captureSlot)) } just runs
|
||||
coEvery {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(any())
|
||||
} returns DecryptFido2CredentialAutofillViewResult.Error
|
||||
coEvery {
|
||||
vaultRepository.silentlyDiscoverCredentials(
|
||||
userId = DEFAULT_USER_STATE.activeUserId,
|
||||
|
@ -399,13 +402,14 @@ class Fido2ProviderProcessorTest {
|
|||
|
||||
verify(exactly = 1) { callback.onError(any()) }
|
||||
verify(exactly = 0) { callback.onResult(any()) }
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.silentlyDiscoverCredentials(
|
||||
userId = DEFAULT_USER_STATE.activeUserId,
|
||||
fido2CredentialStore = fido2CredentialStore,
|
||||
relyingPartyId = "mockRelyingPartyId-1",
|
||||
)
|
||||
}
|
||||
// TODO: [PM-9515] Uncomment when SDK bug is fixed.
|
||||
// coVerify(exactly = 1) {
|
||||
// vaultRepository.silentlyDiscoverCredentials(
|
||||
// userId = DEFAULT_USER_STATE.activeUserId,
|
||||
// fido2CredentialStore = fido2CredentialStore,
|
||||
// relyingPartyId = "mockRelyingPartyId-1",
|
||||
// )
|
||||
// }
|
||||
|
||||
assert(captureSlot.captured is GetCredentialUnknownException)
|
||||
assertEquals("Error decrypting credentials.", captureSlot.captured.errorMessage)
|
||||
|
@ -441,6 +445,9 @@ class Fido2ProviderProcessorTest {
|
|||
relyingPartyId = "mockRelyingPartyId-1",
|
||||
)
|
||||
} returns mockFido2CredentialAutofillViews.asSuccess()
|
||||
coEvery {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(any())
|
||||
} returns DecryptFido2CredentialAutofillViewResult.Success(mockFido2CredentialAutofillViews)
|
||||
every {
|
||||
intentManager.createFido2GetCredentialPendingIntent(
|
||||
action = "com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY",
|
||||
|
@ -458,22 +465,23 @@ class Fido2ProviderProcessorTest {
|
|||
fido2Processor.processGetCredentialRequest(request, cancellationSignal, callback)
|
||||
|
||||
verify(exactly = 0) { callback.onError(any()) }
|
||||
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",
|
||||
)
|
||||
}
|
||||
// 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())
|
||||
|
|
|
@ -31,7 +31,7 @@ fun createMockFido2CredentialAutofillView(
|
|||
): Fido2CredentialAutofillView = Fido2CredentialAutofillView(
|
||||
credentialId = "mockCredentialId-$number".toByteArray(),
|
||||
cipherId = "mockCipherId-$number",
|
||||
rpId = "mockRpId-$number",
|
||||
rpId = "mockRelyingPartyId-$number",
|
||||
userNameForUi = "mockUserNameForUi-$number",
|
||||
userHandle = "mockUserHandle-$number".toByteArray(),
|
||||
)
|
||||
|
|
|
@ -10,6 +10,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
|
|||
*/
|
||||
fun createMockPasskeyAssertionOptions(
|
||||
number: Int,
|
||||
userVerificationRequirement: UserVerificationRequirement? =
|
||||
UserVerificationRequirement.PREFERRED,
|
||||
) = PasskeyAssertionOptions(
|
||||
challenge = "mockChallenge-$number",
|
||||
allowCredentials = listOf(
|
||||
|
@ -20,5 +22,5 @@ fun createMockPasskeyAssertionOptions(
|
|||
),
|
||||
),
|
||||
relyingPartyId = "mockRelyingPartyId-$number",
|
||||
userVerification = UserVerificationRequirement.PREFERRED,
|
||||
userVerification = userVerificationRequirement,
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.test.performClick
|
|||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -87,6 +88,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
}
|
||||
private val fido2CompletionManager: Fido2CompletionManager = mockk {
|
||||
every { completeFido2Registration(any()) } just runs
|
||||
every { completeFido2Assertion(any()) } just runs
|
||||
}
|
||||
private val biometricsManager: BiometricsManager = mockk()
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<VaultItemListingEvent>()
|
||||
|
@ -1746,6 +1748,31 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fido2 error dialog should display and function according to state`() {
|
||||
val dialogMessage = "Passkey error message"
|
||||
composeTestRule.onNode(isDialog()).assertDoesNotExist()
|
||||
composeTestRule.onNodeWithText(dialogMessage).assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = dialogMessage.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Ok")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CompleteFido2Registration event should call Fido2CompletionManager with result`() {
|
||||
val result = Fido2RegisterCredentialResult.Success("mockResponse")
|
||||
|
@ -1755,6 +1782,15 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CompleteFido2Assertion event should call Fido2CompletionManager with result`() {
|
||||
val result = Fido2CredentialAssertionResult.Success("mockResponse")
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.CompleteFido2Assertion(result))
|
||||
verify {
|
||||
fido2CompletionManager.completeFido2Assertion(result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Fido2UserVerification event should perform user verification when it is supported`() {
|
||||
every {
|
||||
|
|
|
@ -13,10 +13,12 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||
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.Fido2CredentialAssertionResult
|
||||
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.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.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
|
@ -53,6 +55,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.concat
|
|||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAssertionOptions
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAttestationOptions
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayItemForCipher
|
||||
|
@ -360,7 +363,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewModel.trySendAction(VaultItemListingsAction.ItemClick(cipherView.id.orEmpty()))
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -391,7 +394,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewModel.trySendAction(VaultItemListingsAction.ItemClick(cipherView.id.orEmpty()))
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -1877,7 +1880,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.generic_error_message.asText(),
|
||||
),
|
||||
|
@ -1908,7 +1911,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_browser_is_not_privileged.asText(),
|
||||
),
|
||||
|
@ -1939,7 +1942,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_browser_signature_does_not_match.asText(),
|
||||
),
|
||||
|
@ -1970,7 +1973,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkeys_not_supported_for_this_app.asText(),
|
||||
),
|
||||
|
@ -2001,7 +2004,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_app_not_found_in_asset_links.asText(),
|
||||
),
|
||||
|
@ -2032,7 +2035,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_of_missing_asset_links.asText(),
|
||||
),
|
||||
|
@ -2063,7 +2066,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_app_could_not_be_verified.asText(),
|
||||
),
|
||||
|
@ -2148,10 +2151,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 create`() =
|
||||
fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 registration based on state`() =
|
||||
runTest {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
|
||||
createMockFido2CredentialRequest(number = 1),
|
||||
)
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2CreationErrorDialogClick)
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
viewModel.eventFlow.test {
|
||||
assertNull(viewModel.stateFlow.value.dialogState)
|
||||
assertEquals(
|
||||
|
@ -2163,6 +2169,515 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `DismissFido2ErrorDialogClick should clear dialog state then complete FIDO 2 assertion with error when assertion request is not null`() =
|
||||
runTest {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
createMockFido2CredentialAssertionRequest(number = 1),
|
||||
)
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
VaultItemListingEvent.CompleteFido2Assertion(
|
||||
result = Fido2CredentialAssertionResult.Error,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertNull(viewModel.stateFlow.value.dialogState)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `DismissFido2ErrorDialogClick should show general error dialog when no FIDO 2 request is present`() =
|
||||
runTest {
|
||||
specialCircumstanceManager.specialCircumstance = null
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should display loading dialog then request user verification when user is not verified and verification is REQUIRED`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
val mockCipherView = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.REQUIRED,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Loading(R.string.loading.asText()),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
verify { fido2CredentialManager.isUserVerified }
|
||||
assertEquals(
|
||||
VaultItemListingEvent.Fido2UserVerification(
|
||||
isRequired = true,
|
||||
selectedCipherView = mockCipherView,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should display loading dialog then request user verification when user is not verified and verification is PREFERED`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
val mockCipherView = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.PREFERRED,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Loading(R.string.loading.asText()),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
verify { fido2CredentialManager.isUserVerified }
|
||||
assertEquals(
|
||||
VaultItemListingEvent.Fido2UserVerification(
|
||||
isRequired = false,
|
||||
selectedCipherView = mockCipherView,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should skip user verification when user is not verified and verification is DISCOURAGED`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
val mockCipherView = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every { authRepository.activeUserId } returns "activeUserId"
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.DISCOURAGED,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
coEvery {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = "activeUserId",
|
||||
request = mockAssertionRequest,
|
||||
selectedCipherView = mockCipherView,
|
||||
)
|
||||
} returns Fido2CredentialAssertionResult.Success(responseJson = "responseJson")
|
||||
|
||||
createVaultItemListingViewModel()
|
||||
|
||||
coVerify {
|
||||
fido2CredentialManager.isUserVerified
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = "activeUserId",
|
||||
request = mockAssertionRequest,
|
||||
selectedCipherView = mockCipherView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should show error dialog when user is not verified and verification is null`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
val mockCipherView = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every { authRepository.activeUserId } returns "activeUserId"
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = null,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
coEvery {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = "activeUserId",
|
||||
request = mockAssertionRequest,
|
||||
selectedCipherView = mockCipherView,
|
||||
)
|
||||
} returns Fido2CredentialAssertionResult.Success(responseJson = "responseJson")
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
verify { fido2CredentialManager.isUserVerified }
|
||||
coVerify(exactly = 0) {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = "activeUserId",
|
||||
request = mockAssertionRequest,
|
||||
selectedCipherView = mockCipherView,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should show error dialog when assertion options are null`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns null
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
verify(exactly = 0) { fido2CredentialManager.isUserVerified }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should observe vault data when request does not contain a cipherId`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = null)
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
|
||||
createVaultItemListingViewModel()
|
||||
|
||||
verify { vaultRepository.vaultDataStateFlow }
|
||||
verify(exactly = 0) {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
requestJson = mockAssertionRequest.requestJson,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should show error dialog when cipher state flow data is null`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns null
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should show error dialog when cipher state flow data has no matching cipher`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should skip user verification when user is verified`() = runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
val mockCipherView = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.PREFERRED,
|
||||
)
|
||||
coEvery {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = "activeUserId",
|
||||
request = mockAssertionRequest,
|
||||
selectedCipherView = mockCipherView,
|
||||
)
|
||||
} returns Fido2CredentialAssertionResult.Success(responseJson = "responseJson")
|
||||
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(number = 1)
|
||||
every { authRepository.activeUserId } returns "activeUserId"
|
||||
|
||||
createVaultItemListingViewModel()
|
||||
|
||||
coVerify {
|
||||
fido2CredentialManager.isUserVerified
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = any(),
|
||||
request = any(),
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should show error dialog when active user id is null`() = runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.PREFERRED,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(number = 1)
|
||||
every { authRepository.activeUserId } returns null
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
coVerify(exactly = 0) {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = any(),
|
||||
request = any(),
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationLockout should display Fido2ErrorDialog and set isUserVerified to false`() {
|
||||
|
@ -2171,7 +2686,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
|
@ -2205,7 +2720,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2237,7 +2752,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
|
@ -2275,7 +2790,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
|
@ -2298,7 +2813,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2309,7 +2824,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationSuccess should set isUserVerified to true, and register FIDO 2 credential when registration result is received`() =
|
||||
fun `UserVerificationSuccess should set isUserVerified to true, and register FIDO 2 credential when verification result is received`() =
|
||||
runTest {
|
||||
val mockRequest = createMockFido2CredentialRequest(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
|
||||
|
@ -2342,6 +2857,63 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationSuccess should set isUserVerified to true, and authenticate FIDO 2 credential when verification result is received`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
fido2AssertionRequest = mockAssertionRequest,
|
||||
)
|
||||
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
),
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.PREFERRED,
|
||||
)
|
||||
coEvery {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
any(),
|
||||
any(),
|
||||
any(),
|
||||
)
|
||||
} returns Fido2CredentialAssertionResult.Success(
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.UserVerificationSuccess(
|
||||
selectedCipherView = createMockCipherView(number = 1),
|
||||
),
|
||||
)
|
||||
|
||||
coVerify {
|
||||
fido2CredentialManager.isUserVerified = true
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = DEFAULT_ACCOUNT.userId,
|
||||
request = mockAssertionRequest,
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UserVerificationNotSupported should display Fido2CreationFail when no cipher id found`() {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
@ -2354,7 +2926,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2378,7 +2950,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2488,7 +3060,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2549,7 +3121,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2579,7 +3151,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2659,7 +3231,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2720,7 +3292,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2749,7 +3321,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2898,7 +3470,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
|
@ -2965,7 +3537,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2CreationFail(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
|
|
|
@ -393,13 +393,13 @@ class VaultItemListingDataExtensionsTest {
|
|||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
).toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Folder("mockId-1"),
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
)
|
||||
|
@ -481,16 +481,16 @@ class VaultItemListingDataExtensionsTest {
|
|||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
).toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Folder("mockId-1"),
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
uri = null,
|
||||
),
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = fido2CredentialAutofillViews,
|
||||
isPremiumUser = true,
|
||||
)
|
||||
|
@ -550,13 +550,13 @@ class VaultItemListingDataExtensionsTest {
|
|||
buttonText = R.string.add_an_item.asText(),
|
||||
),
|
||||
vaultData.toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Trash,
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
),
|
||||
|
@ -570,15 +570,15 @@ class VaultItemListingDataExtensionsTest {
|
|||
buttonText = R.string.add_an_item.asText(),
|
||||
),
|
||||
vaultData.toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Folder(
|
||||
folderId = "folderId",
|
||||
),
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
),
|
||||
|
@ -592,13 +592,13 @@ class VaultItemListingDataExtensionsTest {
|
|||
buttonText = R.string.add_an_item.asText(),
|
||||
),
|
||||
vaultData.toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
),
|
||||
|
@ -612,16 +612,16 @@ class VaultItemListingDataExtensionsTest {
|
|||
buttonText = R.string.add_an_item.asText(),
|
||||
),
|
||||
vaultData.toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
uri = "https://www.test.com",
|
||||
),
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
),
|
||||
|
@ -635,10 +635,11 @@ class VaultItemListingDataExtensionsTest {
|
|||
buttonText = R.string.save_passkey_as_new_login.asText(),
|
||||
),
|
||||
vaultData.toViewState(
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
||||
isIconLoadingDisabled = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = Fido2CredentialRequest(
|
||||
userId = "",
|
||||
|
@ -647,7 +648,6 @@ class VaultItemListingDataExtensionsTest {
|
|||
signingInfo = SigningInfo(),
|
||||
origin = "https://www.test.com",
|
||||
),
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
),
|
||||
|
@ -783,13 +783,13 @@ class VaultItemListingDataExtensionsTest {
|
|||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
autofillSelectionData = null,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Folder("1"),
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
)
|
||||
|
@ -826,13 +826,13 @@ class VaultItemListingDataExtensionsTest {
|
|||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
autofillSelectionData = null,
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Collection("mockId-1"),
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
fido2CredentialAutofillViews = null,
|
||||
isPremiumUser = true,
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue