mirror of
https://github.com/bitwarden/android.git
synced 2026-08-29 10:17:56 -05:00
[PM-11886] Update handling of unprivileged apps and improve error messaging (#4694)
This commit is contained in:
+82
-47
@@ -6,6 +6,7 @@ import com.bitwarden.fido.Origin
|
||||
import com.bitwarden.fido.UnverifiedAssetLink
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
@@ -22,9 +23,11 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2Cred
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidAttestationResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.prefixHttpsIfNecessaryOrNull
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Primary implementation of [Fido2CredentialManager].
|
||||
@@ -47,41 +50,45 @@ class Fido2CredentialManagerImpl(
|
||||
fido2CreateCredentialRequest: Fido2CreateCredentialRequest,
|
||||
selectedCipherView: CipherView,
|
||||
): Fido2RegisterCredentialResult {
|
||||
val clientData = if (fido2CreateCredentialRequest.callingAppInfo.isOriginPopulated()) {
|
||||
fido2CreateCredentialRequest
|
||||
.callingAppInfo
|
||||
val callingAppInfo = fido2CreateCredentialRequest.callingAppInfo
|
||||
val clientData = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
|
||||
ClientData.DefaultWithExtraData(androidPackageName = callingAppInfo.packageName)
|
||||
} else {
|
||||
callingAppInfo
|
||||
.getAppSigningSignatureFingerprint()
|
||||
?.let { ClientData.DefaultWithCustomHash(hash = it) }
|
||||
?: return Fido2RegisterCredentialResult.Error
|
||||
?: return Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_app_is_signed_incorrectly.asText(),
|
||||
)
|
||||
}
|
||||
val sdkOrigin = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
|
||||
val host = getOriginUrlFromAttestationOptionsOrNull(
|
||||
requestJson = fido2CreateCredentialRequest.requestJson,
|
||||
)
|
||||
?: return Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_host_url_is_not_present_in_request
|
||||
.asText(),
|
||||
)
|
||||
Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
packageName = callingAppInfo.packageName,
|
||||
sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
|
||||
?: return Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_app_signature_is_invalid
|
||||
.asText(),
|
||||
),
|
||||
host = host,
|
||||
assetLinkUrl = host,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
ClientData.DefaultWithExtraData(
|
||||
androidPackageName = fido2CreateCredentialRequest
|
||||
.callingAppInfo
|
||||
.packageName,
|
||||
)
|
||||
Origin.Web(fido2CreateCredentialRequest.origin)
|
||||
}
|
||||
val assetLinkUrl = fido2CreateCredentialRequest
|
||||
.origin
|
||||
?: getOriginUrlFromAttestationOptionsOrNull(fido2CreateCredentialRequest.requestJson)
|
||||
?: return Fido2RegisterCredentialResult.Error
|
||||
|
||||
val origin = Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
packageName = fido2CreateCredentialRequest.packageName,
|
||||
sha256CertFingerprint = fido2CreateCredentialRequest
|
||||
.callingAppInfo
|
||||
.getSignatureFingerprintAsHexString()
|
||||
?: return Fido2RegisterCredentialResult.Error,
|
||||
host = assetLinkUrl.toHostOrPathOrNull()
|
||||
?: return Fido2RegisterCredentialResult.Error,
|
||||
assetLinkUrl = assetLinkUrl,
|
||||
),
|
||||
)
|
||||
return vaultSdkSource
|
||||
.registerFido2Credential(
|
||||
request = RegisterFido2CredentialRequest(
|
||||
userId = userId,
|
||||
origin = origin,
|
||||
origin = sdkOrigin,
|
||||
requestJson = """{"publicKey": ${fido2CreateCredentialRequest.requestJson}}""",
|
||||
clientData = clientData,
|
||||
selectedCipherView = selectedCipherView,
|
||||
@@ -95,7 +102,11 @@ class Fido2CredentialManagerImpl(
|
||||
.mapCatching { json.encodeToString(it) }
|
||||
.fold(
|
||||
onSuccess = { Fido2RegisterCredentialResult.Success(it) },
|
||||
onFailure = { Fido2RegisterCredentialResult.Error },
|
||||
onFailure = {
|
||||
Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_registration_failed_due_to_an_internal_error.asText(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -114,8 +125,10 @@ class Fido2CredentialManagerImpl(
|
||||
try {
|
||||
json.decodeFromString<PasskeyAttestationOptions>(requestJson)
|
||||
} catch (e: SerializationException) {
|
||||
Timber.e(e, "Failed to decode passkey attestation options.")
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "Failed to decode passkey attestation options.")
|
||||
null
|
||||
}
|
||||
|
||||
@@ -125,11 +138,14 @@ class Fido2CredentialManagerImpl(
|
||||
try {
|
||||
json.decodeFromString<PasskeyAssertionOptions>(requestJson)
|
||||
} catch (e: SerializationException) {
|
||||
Timber.e(e, "Failed to decode passkey assertion options: $e")
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e, "Failed to decode passkey assertion options: $e")
|
||||
null
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun authenticateFido2Credential(
|
||||
userId: String,
|
||||
request: Fido2CredentialAssertionRequest,
|
||||
@@ -139,22 +155,44 @@ class Fido2CredentialManagerImpl(
|
||||
val clientData = request.clientDataHash
|
||||
?.let { ClientData.DefaultWithCustomHash(hash = it) }
|
||||
?: ClientData.DefaultWithExtraData(androidPackageName = callingAppInfo.getAppOrigin())
|
||||
val origin = callingAppInfo.origin
|
||||
?: getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
|
||||
?: return Fido2CredentialAssertionResult.Error
|
||||
val relyingPartyId = json
|
||||
.decodeFromStringOrNull<PasskeyAssertionOptions>(request.requestJson)
|
||||
?.relyingPartyId
|
||||
?: return Fido2CredentialAssertionResult.Error
|
||||
?: return Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_operation_failed_because_relying_party_cannot_be_identified
|
||||
.asText(),
|
||||
)
|
||||
|
||||
val validateOriginResult = validateOrigin(
|
||||
callingAppInfo = callingAppInfo,
|
||||
relyingPartyId = relyingPartyId,
|
||||
)
|
||||
|
||||
val sdkOrigin = if (!request.origin.isNullOrEmpty()) {
|
||||
Origin.Web(request.origin)
|
||||
} else {
|
||||
val hostUrl = getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
|
||||
?: return Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_operation_failed_because_host_url_is_not_present_in_request
|
||||
.asText(),
|
||||
)
|
||||
Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
packageName = callingAppInfo.packageName,
|
||||
sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
|
||||
?: return Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_operation_failed_because_app_signature_is_invalid
|
||||
.asText(),
|
||||
),
|
||||
host = hostUrl,
|
||||
assetLinkUrl = hostUrl,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return when (validateOriginResult) {
|
||||
is Fido2ValidateOriginResult.Error -> {
|
||||
Fido2CredentialAssertionResult.Error
|
||||
Fido2CredentialAssertionResult.Error(validateOriginResult.messageResId.asText())
|
||||
}
|
||||
|
||||
is Fido2ValidateOriginResult.Success -> {
|
||||
@@ -162,16 +200,7 @@ class Fido2CredentialManagerImpl(
|
||||
.authenticateFido2Credential(
|
||||
request = AuthenticateFido2CredentialRequest(
|
||||
userId = userId,
|
||||
origin = Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
callingAppInfo.packageName,
|
||||
callingAppInfo.getSignatureFingerprintAsHexString()
|
||||
?: return Fido2CredentialAssertionResult.Error,
|
||||
origin.toHostOrPathOrNull()
|
||||
?: return Fido2CredentialAssertionResult.Error,
|
||||
origin,
|
||||
),
|
||||
),
|
||||
origin = sdkOrigin,
|
||||
requestJson = """{"publicKey": ${request.requestJson}}""",
|
||||
clientData = clientData,
|
||||
selectedCipherView = selectedCipherView,
|
||||
@@ -183,7 +212,13 @@ class Fido2CredentialManagerImpl(
|
||||
.mapCatching { json.encodeToString(it) }
|
||||
.fold(
|
||||
onSuccess = { Fido2CredentialAssertionResult.Success(it) },
|
||||
onFailure = { Fido2CredentialAssertionResult.Error },
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to authenticate FIDO2 credential.")
|
||||
Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_authentication_failed_due_to_an_internal_error
|
||||
.asText(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -195,13 +230,13 @@ class Fido2CredentialManagerImpl(
|
||||
private fun getOriginUrlFromAssertionOptionsOrNull(requestJson: String) =
|
||||
getPasskeyAssertionOptionsOrNull(requestJson)
|
||||
?.relyingPartyId
|
||||
?.let { "$HTTPS$it" }
|
||||
?.prefixHttpsIfNecessaryOrNull()
|
||||
|
||||
private fun getOriginUrlFromAttestationOptionsOrNull(requestJson: String) =
|
||||
getPasskeyAttestationOptionsOrNull(requestJson)
|
||||
?.relyingParty
|
||||
?.id
|
||||
?.let { "$HTTPS$it" }
|
||||
?.prefixHttpsIfNecessaryOrNull()
|
||||
}
|
||||
|
||||
private const val MAX_AUTHENTICATION_ATTEMPTS = 5
|
||||
|
||||
-9
@@ -20,13 +20,4 @@ interface Fido2OriginManager {
|
||||
callingAppInfo: CallingAppInfo,
|
||||
relyingPartyId: String,
|
||||
): Fido2ValidateOriginResult
|
||||
|
||||
/**
|
||||
* Returns the privileged app origin, or null if the calling app is not allowed.
|
||||
*
|
||||
* @param callingAppInfo The calling app info.
|
||||
*
|
||||
* @return The privileged app origin, or null.
|
||||
*/
|
||||
suspend fun getPrivilegedAppOriginOrNull(callingAppInfo: CallingAppInfo): String?
|
||||
}
|
||||
|
||||
+4
-20
@@ -32,13 +32,6 @@ class Fido2OriginManagerImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPrivilegedAppOriginOrNull(callingAppInfo: CallingAppInfo): String? {
|
||||
if (!callingAppInfo.isOriginPopulated()) return null
|
||||
return callingAppInfo.getOrigin(getGoogleAllowListOrNull().orEmpty())
|
||||
?: callingAppInfo.getOrigin(getCommunityAllowListOrNull().orEmpty())
|
||||
?.takeUnless { !callingAppInfo.isOriginPopulated() }
|
||||
}
|
||||
|
||||
private suspend fun validateCallingApplicationAssetLinks(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
relyingPartyId: String,
|
||||
@@ -123,7 +116,10 @@ class Fido2OriginManagerImpl(
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { Fido2ValidateOriginResult.Error.Unknown },
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to validate privileged app: ${callingAppInfo.packageName}")
|
||||
Fido2ValidateOriginResult.Error.Unknown
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -157,16 +153,4 @@ class Fido2OriginManagerImpl(
|
||||
?: false
|
||||
}
|
||||
.takeUnless { it.isEmpty() }
|
||||
|
||||
private suspend fun getGoogleAllowListOrNull(): String? =
|
||||
assetManager
|
||||
.readAsset(GOOGLE_ALLOW_LIST_FILE_NAME)
|
||||
.onFailure { Timber.e(it, "Failed to read Google allow list.") }
|
||||
.getOrNull()
|
||||
|
||||
private suspend fun getCommunityAllowListOrNull(): String? =
|
||||
assetManager
|
||||
.readAsset(COMMUNITY_ALLOW_LIST_FILE_NAME)
|
||||
.onFailure { Timber.e(it, "Failed to read Community allow list.") }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
||||
/**
|
||||
* Represents possible outcomes of a FIDO 2 credential assertion request.
|
||||
*/
|
||||
@@ -13,5 +15,5 @@ sealed class Fido2CredentialAssertionResult {
|
||||
/**
|
||||
* Indicates there was an error and the assertion was not successful.
|
||||
*/
|
||||
data object Error : Fido2CredentialAssertionResult()
|
||||
data class Error(val message: Text) : Fido2CredentialAssertionResult()
|
||||
}
|
||||
|
||||
+4
-1
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
||||
/**
|
||||
* Represents the result of a FIDO 2 Get Credentials request.
|
||||
@@ -24,5 +25,7 @@ sealed class Fido2GetCredentialsResult {
|
||||
/**
|
||||
* Indicates an error was encountered when querying for matching credentials.
|
||||
*/
|
||||
data object Error : Fido2GetCredentialsResult()
|
||||
data class Error(
|
||||
val message: Text,
|
||||
) : Fido2GetCredentialsResult()
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
||||
/**
|
||||
* Models the data returned from creating a FIDO 2 credential.
|
||||
*/
|
||||
@@ -9,13 +11,13 @@ sealed class Fido2RegisterCredentialResult {
|
||||
* Indicates the credential has been successfully registered.
|
||||
*/
|
||||
data class Success(
|
||||
val registrationResponse: String,
|
||||
val responseJson: String,
|
||||
) : Fido2RegisterCredentialResult()
|
||||
|
||||
/**
|
||||
* Indicates there was an error and the credential was not registered.
|
||||
*/
|
||||
data object Error : Fido2RegisterCredentialResult()
|
||||
data class Error(val message: Text) : Fido2RegisterCredentialResult()
|
||||
|
||||
/**
|
||||
* Indicates the user cancelled the request.
|
||||
|
||||
+1
-1
@@ -13,5 +13,5 @@ data class PublicKeyCredentialDescriptor(
|
||||
@SerialName("id")
|
||||
val id: String,
|
||||
@SerialName("transports")
|
||||
val transports: List<String>,
|
||||
val transports: List<String>?,
|
||||
)
|
||||
|
||||
+63
-63
@@ -104,6 +104,69 @@ class Fido2ProviderProcessorImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun processGetCredentialRequest(
|
||||
request: BeginGetCredentialRequest,
|
||||
cancellationSignal: CancellationSignal,
|
||||
callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
|
||||
) {
|
||||
// If the user is not logged in, return an error.
|
||||
val userState = authRepository.userStateFlow.value
|
||||
if (userState == null) {
|
||||
callback.onError(GetCredentialUnknownException("Active user is required."))
|
||||
return
|
||||
}
|
||||
|
||||
// Return an unlock action if the current account is locked.
|
||||
if (!userState.activeAccount.isVaultUnlocked) {
|
||||
val authenticationAction = AuthenticationAction(
|
||||
title = context.getString(R.string.unlock),
|
||||
pendingIntent = intentManager.createFido2UnlockPendingIntent(
|
||||
action = UNLOCK_ACCOUNT_INTENT,
|
||||
userId = userState.activeUserId,
|
||||
requestCode = requestCode.getAndIncrement(),
|
||||
),
|
||||
)
|
||||
|
||||
callback.onResult(
|
||||
BeginGetCredentialResponse(
|
||||
authenticationActions = listOf(authenticationAction),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, find all matching credentials from the current vault.
|
||||
val getCredentialJob = scope.launch {
|
||||
try {
|
||||
val credentialEntries = getMatchingFido2CredentialEntries(
|
||||
userId = userState.activeUserId,
|
||||
request = request,
|
||||
)
|
||||
|
||||
callback.onResult(
|
||||
BeginGetCredentialResponse(
|
||||
credentialEntries = credentialEntries,
|
||||
),
|
||||
)
|
||||
} catch (e: GetCredentialException) {
|
||||
callback.onError(e)
|
||||
}
|
||||
}
|
||||
cancellationSignal.setOnCancelListener {
|
||||
callback.onError(GetCredentialCancellationException())
|
||||
getCredentialJob.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun processClearCredentialStateRequest(
|
||||
request: ProviderClearCredentialStateRequest,
|
||||
cancellationSignal: CancellationSignal,
|
||||
callback: OutcomeReceiver<Void?, ClearCredentialException>,
|
||||
) {
|
||||
// no-op: RFU
|
||||
callback.onError(ClearCredentialUnsupportedException())
|
||||
}
|
||||
|
||||
private fun processCreateCredentialRequest(
|
||||
request: BeginCreateCredentialRequest,
|
||||
): BeginCreateCredentialResponse? {
|
||||
@@ -167,60 +230,6 @@ class Fido2ProviderProcessorImpl(
|
||||
return entryBuilder.build()
|
||||
}
|
||||
|
||||
override fun processGetCredentialRequest(
|
||||
request: BeginGetCredentialRequest,
|
||||
cancellationSignal: CancellationSignal,
|
||||
callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
|
||||
) {
|
||||
// If the user is not logged in, return an error.
|
||||
val userState = authRepository.userStateFlow.value
|
||||
if (userState == null) {
|
||||
callback.onError(GetCredentialUnknownException("Active user is required."))
|
||||
return
|
||||
}
|
||||
|
||||
// Return an unlock action if the current account is locked.
|
||||
if (!userState.activeAccount.isVaultUnlocked) {
|
||||
val authenticationAction = AuthenticationAction(
|
||||
title = context.getString(R.string.unlock),
|
||||
pendingIntent = intentManager.createFido2UnlockPendingIntent(
|
||||
action = UNLOCK_ACCOUNT_INTENT,
|
||||
userId = userState.activeUserId,
|
||||
requestCode = requestCode.getAndIncrement(),
|
||||
),
|
||||
)
|
||||
|
||||
callback.onResult(
|
||||
BeginGetCredentialResponse(
|
||||
authenticationActions = listOf(authenticationAction),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, find all matching credentials from the current vault.
|
||||
val getCredentialJob = scope.launch {
|
||||
try {
|
||||
val credentialEntries = getMatchingFido2CredentialEntries(
|
||||
userId = userState.activeUserId,
|
||||
request = request,
|
||||
)
|
||||
|
||||
callback.onResult(
|
||||
BeginGetCredentialResponse(
|
||||
credentialEntries = credentialEntries,
|
||||
),
|
||||
)
|
||||
} catch (e: GetCredentialException) {
|
||||
callback.onError(e)
|
||||
}
|
||||
}
|
||||
cancellationSignal.setOnCancelListener {
|
||||
callback.onError(GetCredentialCancellationException())
|
||||
getCredentialJob.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(GetCredentialUnsupportedException::class)
|
||||
private suspend fun getMatchingFido2CredentialEntries(
|
||||
userId: String,
|
||||
@@ -346,13 +355,4 @@ class Fido2ProviderProcessorImpl(
|
||||
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
|
||||
.setCryptoObject(BiometricPrompt.CryptoObject(cipher))
|
||||
.build()
|
||||
|
||||
override fun processClearCredentialStateRequest(
|
||||
request: ProviderClearCredentialStateRequest,
|
||||
cancellationSignal: CancellationSignal,
|
||||
callback: OutcomeReceiver<Void?, ClearCredentialException>,
|
||||
) {
|
||||
// no-op: RFU
|
||||
callback.onError(ClearCredentialUnsupportedException())
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -118,15 +118,15 @@ fun VaultUnlockScreen(
|
||||
)
|
||||
}
|
||||
|
||||
VaultUnlockEvent.Fido2CredentialAssertionError -> {
|
||||
is VaultUnlockEvent.Fido2CredentialAssertionError -> {
|
||||
fido2CompletionManager.completeFido2Assertion(
|
||||
result = Fido2CredentialAssertionResult.Error,
|
||||
result = Fido2CredentialAssertionResult.Error(event.message),
|
||||
)
|
||||
}
|
||||
|
||||
VaultUnlockEvent.Fido2GetCredentialsError -> {
|
||||
is VaultUnlockEvent.Fido2GetCredentialsError -> {
|
||||
fido2CompletionManager.completeFido2GetCredentialRequest(
|
||||
result = Fido2GetCredentialsResult.Error,
|
||||
result = Fido2GetCredentialsResult.Error(message = event.message),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-4
@@ -177,11 +177,21 @@ class VaultUnlockViewModel @Inject constructor(
|
||||
mutableStateFlow.update { it.copy(dialog = null) }
|
||||
when {
|
||||
state.fido2GetCredentialsRequest != null -> {
|
||||
sendEvent(VaultUnlockEvent.Fido2GetCredentialsError)
|
||||
sendEvent(
|
||||
VaultUnlockEvent.Fido2GetCredentialsError(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
state.fido2CredentialAssertionRequest != null -> {
|
||||
sendEvent(VaultUnlockEvent.Fido2CredentialAssertionError)
|
||||
sendEvent(
|
||||
VaultUnlockEvent.Fido2CredentialAssertionError(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
@@ -531,12 +541,12 @@ sealed class VaultUnlockEvent {
|
||||
/**
|
||||
* Completes the FIDO2 get credentials request with an error response.
|
||||
*/
|
||||
data object Fido2GetCredentialsError : VaultUnlockEvent()
|
||||
data class Fido2GetCredentialsError(val message: Text) : VaultUnlockEvent()
|
||||
|
||||
/**
|
||||
* Completes the FIDO2 credential assertion request with an error response.
|
||||
*/
|
||||
data object Fido2CredentialAssertionError : VaultUnlockEvent()
|
||||
data class Fido2CredentialAssertionError(val message: Text) : VaultUnlockEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-5
@@ -49,7 +49,7 @@ class Fido2CompletionManagerImpl(
|
||||
.setCreateCredentialResponse(
|
||||
intent = intent,
|
||||
response = CreatePublicKeyCredentialResponse(
|
||||
registrationResponseJson = result.registrationResponse,
|
||||
registrationResponseJson = result.responseJson,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class Fido2CompletionManagerImpl(
|
||||
activity.also {
|
||||
val intent = Intent()
|
||||
when (result) {
|
||||
Fido2CredentialAssertionResult.Error -> {
|
||||
is Fido2CredentialAssertionResult.Error -> {
|
||||
PendingIntentHandler
|
||||
.setGetCredentialException(
|
||||
intent = intent,
|
||||
@@ -139,11 +139,12 @@ class Fido2CompletionManagerImpl(
|
||||
)
|
||||
}
|
||||
|
||||
Fido2GetCredentialsResult.Error,
|
||||
-> {
|
||||
is Fido2GetCredentialsResult.Error -> {
|
||||
PendingIntentHandler.setGetCredentialException(
|
||||
resultIntent,
|
||||
GetCredentialUnknownException(),
|
||||
GetCredentialUnknownException(
|
||||
errorMessage = result.message.toString(activity.resources),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ fun AboutScreen(
|
||||
AboutEvent.NavigateToRateApp -> {
|
||||
intentManager.launchUri(
|
||||
uri =
|
||||
"https://play.google.com/store/apps/details?id=com.x8bit.bitwarden".toUri(),
|
||||
"https://play.google.com/store/apps/details?id=com.x8bit.bitwarden".toUri(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -25,6 +25,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem
|
||||
@@ -137,7 +138,9 @@ fun VaultAddEditScreen(
|
||||
}
|
||||
|
||||
is VaultAddEditEvent.CompleteFido2Registration -> {
|
||||
fido2CompletionManager.completeFido2Registration(event.result)
|
||||
fido2CompletionManager.completeFido2Registration(
|
||||
result = event.result,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddEditEvent.Fido2UserVerification -> {
|
||||
@@ -193,7 +196,11 @@ fun VaultAddEditScreen(
|
||||
{ viewModel.trySendAction(VaultAddEditAction.Common.InitialAutofillDialogDismissed) }
|
||||
},
|
||||
onFido2ErrorDismiss = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultAddEditAction.Common.Fido2ErrorDialogDismissed) }
|
||||
{ errorMessage ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed(message = errorMessage),
|
||||
)
|
||||
}
|
||||
},
|
||||
onConfirmOverwriteExistingPasskey = remember(viewModel) {
|
||||
{
|
||||
@@ -420,7 +427,7 @@ private fun VaultAddEditItemDialogs(
|
||||
dialogState: VaultAddEditState.DialogState?,
|
||||
onDismissRequest: () -> Unit,
|
||||
onAutofillDismissRequest: () -> Unit,
|
||||
onFido2ErrorDismiss: () -> Unit,
|
||||
onFido2ErrorDismiss: (Text) -> Unit,
|
||||
onConfirmOverwriteExistingPasskey: () -> Unit,
|
||||
onSubmitMasterPasswordFido2Verification: (password: String) -> Unit,
|
||||
onRetryFido2PasswordVerification: () -> Unit,
|
||||
@@ -455,7 +462,7 @@ private fun VaultAddEditItemDialogs(
|
||||
BitwardenBasicDialog(
|
||||
title = stringResource(id = R.string.an_error_has_occurred),
|
||||
message = dialogState.message(),
|
||||
onDismissRequest = onFido2ErrorDismiss,
|
||||
onDismissRequest = { onFido2ErrorDismiss(dialogState.message) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -306,8 +306,8 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
handleUserVerificationCancelled()
|
||||
}
|
||||
|
||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed -> {
|
||||
handleFido2ErrorDialogDismissed()
|
||||
is VaultAddEditAction.Common.Fido2ErrorDialogDismissed -> {
|
||||
handleFido2ErrorDialogDismissed(action)
|
||||
}
|
||||
|
||||
VaultAddEditAction.Common.UserVerificationNotSupported -> {
|
||||
@@ -645,12 +645,16 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
showFido2ErrorDialog()
|
||||
}
|
||||
|
||||
private fun handleFido2ErrorDialogDismissed() {
|
||||
private fun handleFido2ErrorDialogDismissed(
|
||||
action: VaultAddEditAction.Common.Fido2ErrorDialogDismissed,
|
||||
) {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
clearDialogState()
|
||||
sendEvent(
|
||||
VaultAddEditEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
result = Fido2RegisterCredentialResult.Error(
|
||||
message = action.message,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -2837,7 +2841,7 @@ sealed class VaultAddEditAction {
|
||||
/**
|
||||
* The user has dismissed the FIDO 2 credential error dialog.
|
||||
*/
|
||||
data object Fido2ErrorDialogDismissed : Common()
|
||||
data class Fido2ErrorDialogDismissed(val message: Text) : Common()
|
||||
|
||||
/**
|
||||
* User verification cannot be performed with device biometrics or credentials.
|
||||
|
||||
+5
-4
@@ -23,6 +23,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.components.account.BitwardenAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.account.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
@@ -195,9 +196,9 @@ fun VaultItemListingScreen(
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.DismissDialogClick) }
|
||||
},
|
||||
onDismissFido2ErrorDialog = remember(viewModel) {
|
||||
{
|
||||
{ errorMessage ->
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick,
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick(message = errorMessage),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -296,7 +297,7 @@ fun VaultItemListingScreen(
|
||||
private fun VaultItemListingDialogs(
|
||||
dialogState: VaultItemListingState.DialogState?,
|
||||
onDismissRequest: () -> Unit,
|
||||
onDismissFido2ErrorDialog: () -> Unit,
|
||||
onDismissFido2ErrorDialog: (Text) -> Unit,
|
||||
onConfirmOverwriteExistingPasskey: (cipherViewId: String) -> Unit,
|
||||
onSubmitMasterPasswordFido2Verification: (password: String, cipherId: String) -> Unit,
|
||||
onRetryFido2PasswordVerification: (cipherId: String) -> Unit,
|
||||
@@ -321,7 +322,7 @@ private fun VaultItemListingDialogs(
|
||||
is VaultItemListingState.DialogState.Fido2OperationFail -> BitwardenBasicDialog(
|
||||
title = dialogState.title(),
|
||||
message = dialogState.message(),
|
||||
onDismissRequest = onDismissFido2ErrorDialog,
|
||||
onDismissRequest = { onDismissFido2ErrorDialog(dialogState.message) },
|
||||
)
|
||||
|
||||
is VaultItemListingState.DialogState.OverwritePasskeyConfirmationPrompt -> {
|
||||
|
||||
+103
-61
@@ -202,7 +202,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
is VaultItemListingsAction.SwitchAccountClick -> handleSwitchAccountClick(action)
|
||||
is VaultItemListingsAction.DismissDialogClick -> handleDismissDialogClick()
|
||||
is VaultItemListingsAction.DismissFido2ErrorDialogClick -> {
|
||||
handleDismissFido2ErrorDialogClick()
|
||||
handleDismissFido2ErrorDialogClick(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.MasterPasswordFido2VerificationSubmit -> {
|
||||
@@ -316,14 +316,19 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
getCipherViewOrNull(action.cipherViewId)
|
||||
?.let { registerFido2Credential(it) }
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUserVerificationLockOut() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_is_locked_out.asText(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleUserVerificationSuccess(
|
||||
@@ -335,7 +340,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
|
||||
private fun handleUserVerificationFail() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleUserVerificationCancelled() {
|
||||
@@ -356,7 +363,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
val selectedCipherId = action
|
||||
.selectedCipherId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -365,7 +374,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
.value
|
||||
?.activeAccount
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -488,7 +499,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleDismissFido2VerificationDialogClick() {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_verification_was_cancelled.asText(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleCopySendUrlClick(action: ListingItemOverflowAction.SendAction.CopyUrlClick) {
|
||||
@@ -649,7 +662,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
private fun handleFido2RegistrationRequestReceive(action: VaultItemListingsAction.ItemClick) {
|
||||
val cipherView = getCipherViewOrNull(action.id)
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -673,7 +689,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
// This scenario should not occur because `isFido2Creation` is false when
|
||||
// `fido2CredentialRequest` is null. We show the FIDO 2 error dialog to inform
|
||||
// the user and terminate the flow just in case it does occur.
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
mutableStateFlow.update {
|
||||
@@ -702,7 +720,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
val attestationOptions = fido2CredentialManager
|
||||
.getPasskeyAttestationOptionsOrNull(credentialRequest.requestJson)
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
when (attestationOptions.authenticatorSelection.userVerification) {
|
||||
@@ -739,7 +759,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
) {
|
||||
val activeUserId = authRepository.activeUserId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
@@ -762,7 +784,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
) {
|
||||
val activeUserId = authRepository.activeUserId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
@@ -881,13 +905,17 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
clearDialogState()
|
||||
}
|
||||
|
||||
private fun handleDismissFido2ErrorDialogClick() {
|
||||
private fun handleDismissFido2ErrorDialogClick(
|
||||
action: VaultItemListingsAction.DismissFido2ErrorDialogClick,
|
||||
) {
|
||||
clearDialogState()
|
||||
when {
|
||||
state.fido2CreateCredentialRequest != null -> {
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
result = Fido2RegisterCredentialResult.Error(
|
||||
action.message,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -895,7 +923,19 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
state.fido2CredentialAssertionRequest != null -> {
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2Assertion(
|
||||
result = Fido2CredentialAssertionResult.Error,
|
||||
result = Fido2CredentialAssertionResult.Error(
|
||||
message = action.message,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
state.fido2GetCredentialsRequest != null -> {
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2GetCredentialsRequest(
|
||||
result = Fido2GetCredentialsResult.Error(
|
||||
message = action.message,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -905,7 +945,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
message = action.message,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1242,7 +1282,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
|
||||
when (action.result) {
|
||||
ValidatePasswordResult.Error -> {
|
||||
showFido2ErrorDialog()
|
||||
showFido2UserVerificationErrorDialog()
|
||||
}
|
||||
|
||||
is ValidatePasswordResult.Success -> {
|
||||
@@ -1270,7 +1310,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
|
||||
when (action.result) {
|
||||
ValidatePinResult.Error -> {
|
||||
showFido2ErrorDialog()
|
||||
showFido2UserVerificationErrorDialog()
|
||||
}
|
||||
|
||||
is ValidatePinResult.Success -> {
|
||||
@@ -1300,7 +1340,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
it.copy(dialogState = errorDialogState)
|
||||
}
|
||||
} else {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_verification_attempts_exceeded
|
||||
.asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1310,7 +1353,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
|
||||
val cipherView = getCipherViewOrNull(cipherId = selectedCipherId)
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1342,7 +1388,11 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
?: showFido2ErrorDialog()
|
||||
?: run {
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
//endregion VaultItemListing Handlers
|
||||
|
||||
@@ -1372,7 +1422,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
)
|
||||
?.relyingPartyId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid
|
||||
.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
sendEvent(
|
||||
@@ -1446,7 +1499,9 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
val options = fido2CredentialManager
|
||||
.getPasskeyAttestationOptionsOrNull(requestJson = action.request.requestJson)
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
val validateOriginResult = fido2OriginManager
|
||||
@@ -1487,40 +1542,11 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleFido2OriginValidationFail(error: Fido2ValidateOriginResult.Error) {
|
||||
val messageResId = when (error) {
|
||||
Fido2ValidateOriginResult.Error.ApplicationNotFound -> {
|
||||
R.string.passkey_operation_failed_because_app_not_found_in_asset_links
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Error.ApplicationFingerprintNotVerified -> {
|
||||
R.string.passkey_operation_failed_because_app_could_not_be_verified
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Error.AssetLinkNotFound -> {
|
||||
R.string.passkey_operation_failed_because_of_missing_asset_links
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Error.PrivilegedAppNotAllowed -> {
|
||||
R.string.passkey_operation_failed_because_browser_is_not_privileged
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Error.PasskeyNotSupportedForApp -> {
|
||||
R.string.passkeys_not_supported_for_this_app
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Error.PrivilegedAppSignatureNotFound -> {
|
||||
R.string.passkey_operation_failed_because_browser_signature_does_not_match
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Error.Unknown -> {
|
||||
R.string.generic_error_message
|
||||
}
|
||||
}
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = messageResId.asText(),
|
||||
message = error.messageResId.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1544,12 +1570,17 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
.orEmpty()
|
||||
.filter { it.isActiveWithFido2Credentials }
|
||||
if (request.cipherId.isNullOrEmpty()) {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_no_item_was_selected.asText(),
|
||||
)
|
||||
} else {
|
||||
val selectedCipher = ciphers
|
||||
.find { it.id == request.cipherId }
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1580,13 +1611,17 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
val assertionOptions = fido2CredentialManager
|
||||
.getPasskeyAssertionOptionsOrNull(request.requestJson)
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val relyingPartyId = assertionOptions.relyingPartyId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1773,14 +1808,19 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
as? DecryptFido2CredentialAutofillViewResult.Success)
|
||||
?.fido2CredentialAutofillViews
|
||||
|
||||
private fun showFido2ErrorDialog() {
|
||||
private fun showFido2UserVerificationErrorDialog() {
|
||||
showFido2ErrorDialog(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun showFido2ErrorDialog(message: Text) {
|
||||
fido2CredentialManager.authenticationAttempts = 0
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
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(),
|
||||
message = message,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -2393,7 +2433,9 @@ sealed class VaultItemListingsAction {
|
||||
/**
|
||||
* Click to dismiss the FIDO 2 creation error dialog.
|
||||
*/
|
||||
data object DismissFido2ErrorDialogClick : VaultItemListingsAction()
|
||||
data class DismissFido2ErrorDialogClick(
|
||||
val message: Text,
|
||||
) : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click to submit the master password for FIDO 2 verification.
|
||||
|
||||
@@ -1171,7 +1171,6 @@ Do you want to switch to this account?</string>
|
||||
<string name="coachmark_6_of_6">6 OF 6</string>
|
||||
<string name="use_these_options_to_adjust_your_password_to_your_account_requirements">Use these options to adjust your password to meet your account website\'s requirements.</string>
|
||||
<string name="after_you_save_your_new_password_to_bitwarden_don_t_forget_to_update_it_on_your_account_website">"After you save your new password to Bitwarden, don’t forget to update it on your account website. "</string>
|
||||
<string name="link">Link</string>
|
||||
<string name="error_loading_certificate">Error loading certificate</string>
|
||||
<string name="certificate_alias">Certificate alias</string>
|
||||
<string name="import_certificate">Import certificate</string>
|
||||
@@ -1184,4 +1183,17 @@ Do you want to switch to this account?</string>
|
||||
<string name="invalid_certificate_chain">Invalid certificate chain</string>
|
||||
<string name="system_certificates_are_not_as_secure_as_importing_certificates_to_bitwarden">Using a system certificate is less secure than storing the certificate with Bitwarden. Continuing will display a list of available system certificates if one is already installed.</string>
|
||||
<string name="certificate_alias_already_exists">Certificate alias already exists</string>
|
||||
<string name="link">Link</string>
|
||||
<string name="passkey_operation_failed_because_app_is_signed_incorrectly">Passkey operation failed because app is signed incorrectly.</string>
|
||||
<string name="passkey_operation_failed_because_host_url_is_not_present_in_request">Passkey operation failed because host URL is not present in request.</string>
|
||||
<string name="passkey_operation_failed_because_app_signature_is_invalid">Passkey operation failed because app signature is invalid.</string>
|
||||
<string name="passkey_registration_failed_due_to_an_internal_error">Passkey registration failed due to an internal error.</string>
|
||||
<string name="passkey_operation_failed_because_relying_party_cannot_be_identified">Passkey operation failed because relying party cannot be identified.</string>
|
||||
<string name="passkey_authentication_failed_due_to_an_internal_error">Passkey authentication failed due to an internal error.</string>
|
||||
<string name="passkey_operation_failed_because_the_selected_item_does_not_exist">Passkey operation failed because the selected item does not exist.</string>
|
||||
<string name="passkey_operation_failed_because_user_is_locked_out">Passkey operation failed because user is locked out.</string>
|
||||
<string name="passkey_operation_failed_because_user_verification_was_cancelled">Passkey operation failed because user verification was cancelled.</string>
|
||||
<string name="passkey_operation_failed_because_the_request_is_invalid">Passkey operation failed because the request is invalid.</string>
|
||||
<string name="passkey_operation_failed_because_user_verification_attempts_exceeded">Passkey operation failed because user verification attempts exceeded.</string>
|
||||
<string name="passkey_operation_failed_because_no_item_was_selected">Passkey operation failed because no item was selected.</string>
|
||||
</resources>
|
||||
|
||||
+36
-44
@@ -8,6 +8,7 @@ import com.bitwarden.fido.Origin
|
||||
import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse
|
||||
import com.bitwarden.fido.UnverifiedAssetLink
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2AttestationResponse
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
@@ -26,6 +27,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockPublicKeyAssertionResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockPublicKeyAttestationResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAssertionOptions
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAttestationOptions
|
||||
import io.mockk.coEvery
|
||||
@@ -340,7 +342,10 @@ class Fido2CredentialManagerTest {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
Fido2RegisterCredentialResult.Error,
|
||||
Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_app_is_signed_incorrectly
|
||||
.asText(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -364,32 +369,10 @@ class Fido2CredentialManagerTest {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
Fido2RegisterCredentialResult.Error,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerFido2Credential should return Error when toHostOrPathOrNull is null`() =
|
||||
runTest {
|
||||
val mockSigningInfo = mockk<SigningInfo> {
|
||||
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
|
||||
every { hasMultipleSigners() } returns false
|
||||
}
|
||||
val mockFido2CredentialRequest = createMockFido2CreateCredentialRequest(
|
||||
number = 1,
|
||||
origin = "illegal empty spaces",
|
||||
signingInfo = mockSigningInfo,
|
||||
)
|
||||
|
||||
val result = fido2CredentialManager.registerFido2Credential(
|
||||
userId = "mockUserId",
|
||||
fido2CreateCredentialRequest = mockFido2CredentialRequest,
|
||||
selectedCipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
Fido2RegisterCredentialResult.Error,
|
||||
Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_app_signature_is_invalid
|
||||
.asText(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -432,7 +415,10 @@ class Fido2CredentialManagerTest {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
Fido2RegisterCredentialResult.Error,
|
||||
Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_registration_failed_due_to_an_internal_error
|
||||
.asText(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -465,7 +451,10 @@ class Fido2CredentialManagerTest {
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
Fido2RegisterCredentialResult.Error,
|
||||
Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_host_url_is_not_present_in_request
|
||||
.asText(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -518,7 +507,7 @@ class Fido2CredentialManagerTest {
|
||||
assertEquals(
|
||||
AuthenticateFido2CredentialRequest(
|
||||
userId = "activeUserId",
|
||||
origin = DEFAULT_ORIGIN,
|
||||
origin = DEFAULT_WEB_ORIGIN,
|
||||
requestJson = """{"publicKey": ${mockRequest.requestJson}}""",
|
||||
clientData = ClientData.DefaultWithCustomHash(mockRequest.clientDataHash!!),
|
||||
selectedCipherView = mockCipherView,
|
||||
@@ -568,14 +557,7 @@ class Fido2CredentialManagerTest {
|
||||
assertEquals(
|
||||
AuthenticateFido2CredentialRequest(
|
||||
userId = "activeUserId",
|
||||
origin = Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
packageName = DEFAULT_PACKAGE_NAME,
|
||||
sha256CertFingerprint = DEFAULT_CERT_FINGERPRINT,
|
||||
host = DEFAULT_HOST,
|
||||
assetLinkUrl = mockRequest.origin!!,
|
||||
),
|
||||
),
|
||||
origin = DEFAULT_WEB_ORIGIN,
|
||||
requestJson = """{"publicKey": ${mockRequest.requestJson}}""",
|
||||
clientData = ClientData.DefaultWithExtraData(
|
||||
androidPackageName = "android:apk-key-hash:$DEFAULT_APP_SIGNATURE",
|
||||
@@ -626,7 +608,7 @@ class Fido2CredentialManagerTest {
|
||||
UnverifiedAssetLink(
|
||||
DEFAULT_PACKAGE_NAME,
|
||||
DEFAULT_CERT_FINGERPRINT,
|
||||
mockAssertionOptions.relyingPartyId!!,
|
||||
"https://${mockAssertionOptions.relyingPartyId!!}",
|
||||
"https://${mockAssertionOptions.relyingPartyId}",
|
||||
),
|
||||
),
|
||||
@@ -664,7 +646,10 @@ class Fido2CredentialManagerTest {
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
Fido2CredentialAssertionResult.Error,
|
||||
Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_operation_failed_because_host_url_is_not_present_in_request
|
||||
.asText(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -749,7 +734,10 @@ class Fido2CredentialManagerTest {
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
Fido2CredentialAssertionResult.Error,
|
||||
Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_authentication_failed_due_to_an_internal_error
|
||||
.asText(),
|
||||
),
|
||||
authResult,
|
||||
)
|
||||
}
|
||||
@@ -779,7 +767,10 @@ class Fido2CredentialManagerTest {
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
Fido2CredentialAssertionResult.Error,
|
||||
Fido2CredentialAssertionResult.Error(
|
||||
R.string.passkey_operation_failed_because_relying_party_cannot_be_identified
|
||||
.asText(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
@@ -803,7 +794,7 @@ class Fido2CredentialManagerTest {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
Fido2CredentialAssertionResult.Error,
|
||||
Fido2CredentialAssertionResult.Error(R.string.generic_error_message.asText()),
|
||||
result,
|
||||
)
|
||||
|
||||
@@ -817,7 +808,7 @@ private const val DEFAULT_PACKAGE_NAME = "com.x8bit.bitwarden"
|
||||
private const val DEFAULT_APP_SIGNATURE = "0987654321ABCDEF"
|
||||
private const val DEFAULT_CERT_FINGERPRINT = "30:39:38:37:36:35:34:33:32:31:41:42:43:44:45:46"
|
||||
private const val DEFAULT_HOST = "bitwarden.com"
|
||||
private val DEFAULT_ORIGIN = Origin.Android(
|
||||
private val DEFAULT_ANDROID_ORIGIN = Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
packageName = DEFAULT_PACKAGE_NAME,
|
||||
sha256CertFingerprint = DEFAULT_CERT_FINGERPRINT,
|
||||
@@ -825,6 +816,7 @@ private val DEFAULT_ORIGIN = Origin.Android(
|
||||
assetLinkUrl = "bitwarden.com",
|
||||
),
|
||||
)
|
||||
private val DEFAULT_WEB_ORIGIN = Origin.Web("bitwarden.com")
|
||||
private const val DEFAULT_ALLOW_LIST = """
|
||||
{
|
||||
"apps": [
|
||||
|
||||
+96
-9
@@ -22,6 +22,7 @@ import androidx.credentials.provider.BeginGetCredentialRequest
|
||||
import androidx.credentials.provider.BeginGetCredentialResponse
|
||||
import androidx.credentials.provider.BeginGetPasswordOption
|
||||
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.bitwarden.vault.CipherView
|
||||
@@ -132,7 +133,14 @@ class Fido2ProviderProcessorTest {
|
||||
|
||||
@Test
|
||||
fun `processCreateCredentialRequest should invoke callback with error when user id is null`() {
|
||||
val request: BeginCreateCredentialRequest = mockk()
|
||||
val request: BeginCreateCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
every { candidateQueryData } returns Bundle()
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||
mockk()
|
||||
val captureSlot = slot<CreateCredentialException>()
|
||||
@@ -153,7 +161,14 @@ class Fido2ProviderProcessorTest {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `processCreateCredentialRequest should invoke callback with error on password create request`() {
|
||||
val request: BeginCreatePasswordCredentialRequest = mockk()
|
||||
val request: BeginCreatePasswordCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
every { candidateQueryData } returns Bundle()
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||
mockk()
|
||||
val captureSlot = slot<CreateCredentialException>()
|
||||
@@ -171,7 +186,13 @@ class Fido2ProviderProcessorTest {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `processCreateCredentialRequest should invoke callback with error when json is null or empty`() {
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk()
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val candidateQueryData: Bundle = mockk()
|
||||
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||
mockk()
|
||||
@@ -193,7 +214,13 @@ class Fido2ProviderProcessorTest {
|
||||
|
||||
@Test
|
||||
fun `processCreateCredentialRequest should invoke callback with error when user state null`() {
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk()
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val candidateQueryData: Bundle = mockk()
|
||||
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||
mockk()
|
||||
@@ -216,7 +243,13 @@ class Fido2ProviderProcessorTest {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `processCreateCredentialRequest should invoke callback with result when user state is valid`() {
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk()
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val candidateQueryData: Bundle = mockk()
|
||||
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||
mockk()
|
||||
@@ -252,7 +285,13 @@ class Fido2ProviderProcessorTest {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `processCreateCredentialRequest should generate correct entries based on state`() {
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk()
|
||||
val request: BeginCreatePublicKeyCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val candidateQueryData: Bundle = mockk()
|
||||
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||
mockk()
|
||||
@@ -335,7 +374,20 @@ class Fido2ProviderProcessorTest {
|
||||
|
||||
@Test
|
||||
fun `processGetCredentialRequest should invoke callback with error when user state is null`() {
|
||||
val request: BeginGetCredentialRequest = mockk()
|
||||
val request: BeginGetCredentialRequest = mockk {
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
every {
|
||||
beginGetCredentialOptions
|
||||
} returns listOf(
|
||||
mockk<BeginGetPasswordOption> {
|
||||
every { candidateQueryData } returns Bundle()
|
||||
},
|
||||
)
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException> = mockk()
|
||||
val captureSlot = slot<GetCredentialException>()
|
||||
every { cancellationSignal.setOnCancelListener(any()) } just runs
|
||||
@@ -354,7 +406,18 @@ class Fido2ProviderProcessorTest {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `processGetCredentialRequest should invoke callback with authentication action when vault is locked`() {
|
||||
val request: BeginGetCredentialRequest = mockk()
|
||||
val request: BeginGetCredentialRequest = mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(
|
||||
mockk<BeginGetPasswordOption> {
|
||||
every { candidateQueryData } returns Bundle()
|
||||
},
|
||||
)
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException> = mockk()
|
||||
val captureSlot = slot<BeginGetCredentialResponse>()
|
||||
val mockIntent: PendingIntent = mockk()
|
||||
@@ -409,7 +472,16 @@ class Fido2ProviderProcessorTest {
|
||||
@Test
|
||||
fun `processGetCredentialRequest should invoke callback with error when option is not BeginGetPublicKeyCredentialOption`() {
|
||||
val request: BeginGetCredentialRequest = mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockk<BeginGetPasswordOption>())
|
||||
every { beginGetCredentialOptions } returns listOf(
|
||||
mockk<BeginGetPasswordOption> {
|
||||
every { candidateQueryData } returns Bundle()
|
||||
},
|
||||
)
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException> = mockk()
|
||||
val captureSlot = slot<GetCredentialException>()
|
||||
@@ -436,6 +508,11 @@ class Fido2ProviderProcessorTest {
|
||||
)
|
||||
val request: BeginGetCredentialRequest = mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockOption)
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
|
||||
@@ -465,6 +542,11 @@ class Fido2ProviderProcessorTest {
|
||||
)
|
||||
val request: BeginGetCredentialRequest = mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockOption)
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException> = mockk()
|
||||
val captureSlot = slot<GetCredentialException>()
|
||||
@@ -511,6 +593,11 @@ class Fido2ProviderProcessorTest {
|
||||
)
|
||||
val request: BeginGetCredentialRequest = mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockOption)
|
||||
every { callingAppInfo } returns CallingAppInfo(
|
||||
packageName = "com.x8bit.bitwarden.dev",
|
||||
signingInfo = mockk(),
|
||||
origin = null,
|
||||
)
|
||||
}
|
||||
val callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException> = mockk()
|
||||
val captureSlot = slot<BeginGetCredentialResponse>()
|
||||
|
||||
+12
-4
@@ -18,12 +18,14 @@ import androidx.compose.ui.test.performKeyPress
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.requestFocus
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||
@@ -125,10 +127,16 @@ class VaultUnlockScreenTest : BaseComposeTest() {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on Fido2GetCredentialsError should call completeFido2GetCredentialRequest on fido2CompletionManager`() {
|
||||
mutableEventFlow.tryEmit(VaultUnlockEvent.Fido2GetCredentialsError)
|
||||
mutableEventFlow.tryEmit(
|
||||
VaultUnlockEvent.Fido2GetCredentialsError(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
fido2CompletionManager.completeFido2GetCredentialRequest(
|
||||
result = Fido2GetCredentialsResult.Error,
|
||||
result = Fido2GetCredentialsResult.Error(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,10 +144,10 @@ class VaultUnlockScreenTest : BaseComposeTest() {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on Fido2AssertCredentialError should call completeFido2AssertCredential on fido2CompletionManager`() {
|
||||
mutableEventFlow.tryEmit(VaultUnlockEvent.Fido2CredentialAssertionError)
|
||||
mutableEventFlow.tryEmit(VaultUnlockEvent.Fido2CredentialAssertionError("".asText()))
|
||||
verify(exactly = 1) {
|
||||
fido2CompletionManager.completeFido2Assertion(
|
||||
result = Fido2CredentialAssertionResult.Error,
|
||||
result = Fido2CredentialAssertionResult.Error("".asText()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -570,7 +570,12 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||
val viewModel = createViewModel(state = initialState)
|
||||
viewModel.trySendAction(VaultUnlockAction.DismissDialog)
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(VaultUnlockEvent.Fido2GetCredentialsError, awaitItem())
|
||||
assertEquals(
|
||||
VaultUnlockEvent.Fido2GetCredentialsError(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,7 +591,13 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||
val viewModel = createViewModel(state = initialState)
|
||||
viewModel.trySendAction(VaultUnlockAction.DismissDialog)
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(VaultUnlockEvent.Fido2CredentialAssertionError, awaitItem())
|
||||
assertEquals(
|
||||
VaultUnlockEvent.Fido2CredentialAssertionError(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.processor.GET_PASSKEY_INTENT
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFido2CredentialAutofillView
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import io.mockk.Called
|
||||
import io.mockk.MockKVerificationScope
|
||||
@@ -40,6 +41,7 @@ class Fido2CompletionManagerTest {
|
||||
|
||||
private val mockActivity = mockk<Activity> {
|
||||
every { packageName } returns "packageName"
|
||||
every { resources } returns mockk(relaxed = true)
|
||||
every { setResult(Activity.RESULT_OK, any()) } just runs
|
||||
every { finish() } just runs
|
||||
}
|
||||
@@ -116,7 +118,7 @@ class Fido2CompletionManagerTest {
|
||||
fido2CompletionManager
|
||||
.completeFido2Registration(
|
||||
Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "registrationResponse",
|
||||
responseJson = "registrationResponse",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -129,7 +131,7 @@ class Fido2CompletionManagerTest {
|
||||
@Test
|
||||
fun `completeFido2Registration should set CreateCredentialException, set activity result, then finish activity when result is Error`() {
|
||||
fido2CompletionManager
|
||||
.completeFido2Registration(Fido2RegisterCredentialResult.Error)
|
||||
.completeFido2Registration(Fido2RegisterCredentialResult.Error("".asText()))
|
||||
|
||||
verifyActivityResultIsSetAndFinishedAfter {
|
||||
PendingIntentHandler.setCreateCredentialException(any(), any())
|
||||
@@ -162,7 +164,7 @@ class Fido2CompletionManagerTest {
|
||||
@Test
|
||||
fun `completeFido2Assertion should set GetCredentialException, set activity result, then finish activity when result is Error`() {
|
||||
fido2CompletionManager
|
||||
.completeFido2Assertion(Fido2CredentialAssertionResult.Error)
|
||||
.completeFido2Assertion(Fido2CredentialAssertionResult.Error("".asText()))
|
||||
|
||||
verifyActivityResultIsSetAndFinishedAfter {
|
||||
PendingIntentHandler.setGetCredentialException(any(), any())
|
||||
@@ -291,8 +293,13 @@ class Fido2CompletionManagerTest {
|
||||
@Test
|
||||
fun `completeFido2GetCredentials should set GetCredentialException, set activity result, then finish activity when result is Error`() {
|
||||
fido2CompletionManager
|
||||
.completeFido2GetCredentialRequest(Fido2GetCredentialsResult.Error)
|
||||
.completeFido2GetCredentialRequest(
|
||||
Fido2GetCredentialsResult.Error(
|
||||
"".asText(),
|
||||
),
|
||||
)
|
||||
verifyActivityResultIsSetAndFinishedAfter {
|
||||
mockActivity.resources
|
||||
PendingIntentHandler.setGetCredentialException(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -217,7 +217,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
@Test
|
||||
fun `on CompleteFido2Create event should invoke Fido2CompletionManager`() {
|
||||
val result = Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockRegistrationResponse",
|
||||
responseJson = "mockRegistrationResponse",
|
||||
)
|
||||
mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteFido2Registration(result = result))
|
||||
verify { fido2CompletionManager.completeFido2Registration(result) }
|
||||
@@ -454,7 +454,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify { viewModel.trySendAction(VaultAddEditAction.Common.Fido2ErrorDialogDismissed) }
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed("mockMessage".asText()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+16
-9
@@ -833,7 +833,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
val mockCreateResult = Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockRegistrationResponse",
|
||||
responseJson = "mockRegistrationResponse",
|
||||
)
|
||||
val mockAttestationOptions = createMockPasskeyAttestationOptions(
|
||||
number = 1,
|
||||
@@ -989,7 +989,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
fido2CreateCredentialRequest = fido2CredentialRequest,
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(registrationResponse = "mockResponse")
|
||||
} returns Fido2RegisterCredentialResult.Success(responseJson = "mockResponse")
|
||||
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
|
||||
|
||||
@@ -2045,12 +2045,19 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||
),
|
||||
)
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.Fido2ErrorDialogDismissed)
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
),
|
||||
)
|
||||
viewModel.eventFlow.test {
|
||||
assertNull(viewModel.stateFlow.value.dialog)
|
||||
assertEquals(
|
||||
VaultAddEditEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
result = Fido2RegisterCredentialResult.Error(
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@@ -4149,7 +4156,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
||||
@@ -4181,7 +4188,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
||||
@@ -4218,7 +4225,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
runTest {
|
||||
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
|
||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
|
||||
fido2CreateCredentialRequest = mockRequest,
|
||||
@@ -4261,7 +4268,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
||||
runTest {
|
||||
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
|
||||
val mockResult = Fido2RegisterCredentialResult.Error
|
||||
val mockResult = Fido2RegisterCredentialResult.Error("".asText())
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
|
||||
fido2CreateCredentialRequest = mockRequest,
|
||||
)
|
||||
@@ -4299,7 +4306,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
runTest {
|
||||
val mockRequest = createMockFido2CreateCredentialRequest(number = 1)
|
||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
|
||||
fido2CreateCredentialRequest = mockRequest,
|
||||
|
||||
+5
-1
@@ -1873,7 +1873,11 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick(
|
||||
message = dialogMessage.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-32
@@ -471,7 +471,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
message = R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
@@ -502,8 +502,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message = R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -2639,7 +2638,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
@Test
|
||||
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
||||
runTest {
|
||||
val mockResult = Fido2RegisterCredentialResult.Error
|
||||
val mockResult = Fido2RegisterCredentialResult.Error("".asText())
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(
|
||||
@@ -2666,7 +2665,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteFido2Registration result`() =
|
||||
runTest {
|
||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
@@ -2718,12 +2717,18 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
createMockFido2CreateCredentialRequest(number = 1),
|
||||
)
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick(
|
||||
"".asText(),
|
||||
),
|
||||
)
|
||||
viewModel.eventFlow.test {
|
||||
assertNull(viewModel.stateFlow.value.dialogState)
|
||||
assertEquals(
|
||||
VaultItemListingEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
result = Fido2RegisterCredentialResult.Error(
|
||||
"".asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@@ -2750,11 +2755,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick("".asText()),
|
||||
)
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
VaultItemListingEvent.CompleteFido2Assertion(
|
||||
result = Fido2CredentialAssertionResult.Error,
|
||||
result = Fido2CredentialAssertionResult.Error("".asText()),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@@ -2768,11 +2775,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
runTest {
|
||||
specialCircumstanceManager.specialCircumstance = null
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.trySendAction(VaultItemListingsAction.DismissFido2ErrorDialogClick)
|
||||
viewModel.trySendAction(
|
||||
VaultItemListingsAction.DismissFido2ErrorDialogClick("".asText()),
|
||||
)
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
message = "".asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3007,7 +3016,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
message = R.string.passkey_operation_failed_because_the_request_is_invalid
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
@@ -3058,8 +3067,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message = R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3181,8 +3189,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message =
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3223,8 +3232,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message =
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3503,7 +3513,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message = R.string.passkey_operation_failed_because_user_is_locked_out.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3556,7 +3566,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
@@ -3569,7 +3579,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message = R.string.passkey_operation_failed_because_the_request_is_invalid
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3594,7 +3605,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
@@ -3607,7 +3618,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message = R.string.passkey_operation_failed_because_the_request_is_invalid
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3652,7 +3664,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(
|
||||
registrationResponse = "mockResponse",
|
||||
responseJson = "mockResponse",
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
@@ -3939,8 +3951,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message =
|
||||
R.string.passkey_operation_failed_because_user_verification_attempts_exceeded
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -3969,7 +3982,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
message = R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
@@ -4110,8 +4123,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
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(),
|
||||
message =
|
||||
R.string.passkey_operation_failed_because_user_verification_attempts_exceeded
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
@@ -4139,7 +4153,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
message = R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
@@ -4288,7 +4302,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
message = R.string.passkey_operation_failed_because_user_verification_was_cancelled
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
@@ -4355,7 +4369,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2OperationFail(
|
||||
R.string.an_error_has_occurred.asText(),
|
||||
R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
R.string.passkey_operation_failed_because_the_selected_item_does_not_exist
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user