mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
[PM-24148] add credential manager provider for create passwords (#5579)
Co-authored-by: Patrick Honkonen <phonkonen@bitwarden.com> Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
This commit is contained in:
parent
839e9e8a1a
commit
5ec0a1986d
@ -86,6 +86,7 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY" />
|
<action android:name="com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY" />
|
||||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY" />
|
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY" />
|
||||||
|
<action android:name="com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSWORD" />
|
||||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSWORD" />
|
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSWORD" />
|
||||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_UNLOCK_ACCOUNT" />
|
<action android:name="com.x8bit.bitwarden.credentials.ACTION_UNLOCK_ACCOUNT" />
|
||||||
|
|
||||||
|
|||||||
@ -58,6 +58,13 @@ interface CredentialManagerPendingIntentManager {
|
|||||||
userId: String,
|
userId: String,
|
||||||
): PendingIntent
|
): PendingIntent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a pending intent to use when providing options for Password credential creation.
|
||||||
|
*/
|
||||||
|
fun createPasswordCreationPendingIntent(
|
||||||
|
userId: String,
|
||||||
|
): PendingIntent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a pending intent to use when providing options for Password credential filling.
|
* Creates a pending intent to use when providing options for Password credential filling.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -75,6 +75,24 @@ class CredentialManagerPendingIntentManagerImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a pending intent to use when providing options for FIDO 2 credential creation.
|
||||||
|
*/
|
||||||
|
override fun createPasswordCreationPendingIntent(
|
||||||
|
userId: String,
|
||||||
|
): PendingIntent {
|
||||||
|
val intent = Intent(CREATE_PASSWORD_ACTION)
|
||||||
|
.setPackage(context.packageName)
|
||||||
|
.putExtra(EXTRA_KEY_USER_ID, userId)
|
||||||
|
|
||||||
|
return PendingIntent.getActivity(
|
||||||
|
/* context = */ context,
|
||||||
|
/* requestCode = */ Random.nextInt(),
|
||||||
|
/* intent = */ intent,
|
||||||
|
/* flags = */ PendingIntent.FLAG_UPDATE_CURRENT.toPendingIntentMutabilityFlag(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a pending intent to use when providing options for Password credential filling.
|
* Creates a pending intent to use when providing options for Password credential filling.
|
||||||
*/
|
*/
|
||||||
@ -101,4 +119,5 @@ class CredentialManagerPendingIntentManagerImpl(
|
|||||||
private const val CREATE_PASSKEY_ACTION = "com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY"
|
private const val CREATE_PASSKEY_ACTION = "com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY"
|
||||||
private const val UNLOCK_ACCOUNT_ACTION = "com.x8bit.bitwarden.credentials.ACTION_UNLOCK_ACCOUNT"
|
private const val UNLOCK_ACCOUNT_ACTION = "com.x8bit.bitwarden.credentials.ACTION_UNLOCK_ACCOUNT"
|
||||||
private const val GET_PASSKEY_ACTION = "com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY"
|
private const val GET_PASSKEY_ACTION = "com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY"
|
||||||
|
private const val CREATE_PASSWORD_ACTION = "com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSWORD"
|
||||||
private const val GET_PASSWORD_ACTION = "com.x8bit.bitwarden.credentials.ACTION_GET_PASSWORD"
|
private const val GET_PASSWORD_ACTION = "com.x8bit.bitwarden.credentials.ACTION_GET_PASSWORD"
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.credentials.model
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.credentials.CreatePasswordRequest
|
||||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||||
import androidx.credentials.provider.CallingAppInfo
|
import androidx.credentials.provider.CallingAppInfo
|
||||||
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||||
@ -48,6 +49,15 @@ data class CreateCredentialRequest(
|
|||||||
providerRequest.callingRequest as? CreatePublicKeyCredentialRequest
|
providerRequest.callingRequest as? CreatePublicKeyCredentialRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [CreatePasswordRequest] of the [providerRequest], or null if the calling
|
||||||
|
* request is not a [CreatePasswordRequest].
|
||||||
|
*/
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val createPasswordCredentialRequest: CreatePasswordRequest? by lazy {
|
||||||
|
providerRequest.callingRequest as? CreatePasswordRequest
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [requestJson] of the [createPublicKeyCredentialRequest], or null if the calling request
|
* The [requestJson] of the [createPublicKeyCredentialRequest], or null if the calling request
|
||||||
* is not a [CreatePublicKeyCredentialRequest].
|
* is not a [CreatePublicKeyCredentialRequest].
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import androidx.credentials.exceptions.GetCredentialUnknownException
|
|||||||
import androidx.credentials.provider.AuthenticationAction
|
import androidx.credentials.provider.AuthenticationAction
|
||||||
import androidx.credentials.provider.BeginCreateCredentialRequest
|
import androidx.credentials.provider.BeginCreateCredentialRequest
|
||||||
import androidx.credentials.provider.BeginCreateCredentialResponse
|
import androidx.credentials.provider.BeginCreateCredentialResponse
|
||||||
|
import androidx.credentials.provider.BeginCreatePasswordCredentialRequest
|
||||||
import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
|
import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
|
||||||
import androidx.credentials.provider.BeginGetCredentialRequest
|
import androidx.credentials.provider.BeginGetCredentialRequest
|
||||||
import androidx.credentials.provider.BeginGetCredentialResponse
|
import androidx.credentials.provider.BeginGetCredentialResponse
|
||||||
@ -69,7 +70,7 @@ class CredentialProviderProcessorImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val createCredentialJob = ioScope.launch {
|
val createCredentialJob = ioScope.launch {
|
||||||
processCreateCredentialRequest(request = request)
|
(handleCreatePasskeyQuery(request) ?: handleCreatePasswordQuery(request))
|
||||||
?.let { callback.onResult(it) }
|
?.let { callback.onResult(it) }
|
||||||
?: callback.onError(CreateCredentialUnknownException())
|
?: callback.onError(CreateCredentialUnknownException())
|
||||||
}
|
}
|
||||||
@ -137,21 +138,11 @@ class CredentialProviderProcessorImpl(
|
|||||||
callback.onError(ClearCredentialUnsupportedException())
|
callback.onError(ClearCredentialUnsupportedException())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processCreateCredentialRequest(
|
private fun handleCreatePasskeyQuery(
|
||||||
request: BeginCreateCredentialRequest,
|
request: BeginCreateCredentialRequest,
|
||||||
): BeginCreateCredentialResponse? {
|
): BeginCreateCredentialResponse? {
|
||||||
return when (request) {
|
if (request !is BeginCreatePublicKeyCredentialRequest) return null
|
||||||
is BeginCreatePublicKeyCredentialRequest -> {
|
|
||||||
handleCreatePasskeyQuery(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleCreatePasskeyQuery(
|
|
||||||
request: BeginCreatePublicKeyCredentialRequest,
|
|
||||||
): BeginCreateCredentialResponse? {
|
|
||||||
val requestJson = request
|
val requestJson = request
|
||||||
.candidateQueryData
|
.candidateQueryData
|
||||||
.getString("androidx.credentials.BUNDLE_KEY_REQUEST_JSON")
|
.getString("androidx.credentials.BUNDLE_KEY_REQUEST_JSON")
|
||||||
@ -161,14 +152,19 @@ class CredentialProviderProcessorImpl(
|
|||||||
val userState = authRepository.userStateFlow.value ?: return null
|
val userState = authRepository.userStateFlow.value ?: return null
|
||||||
|
|
||||||
return BeginCreateCredentialResponse.Builder()
|
return BeginCreateCredentialResponse.Builder()
|
||||||
.setCreateEntries(userState.accounts.toCreateEntries(userState.activeUserId))
|
.setCreateEntries(
|
||||||
|
userState.accounts.toCreatePasskeyEntry(userState.activeUserId),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<UserState.Account>.toCreateEntries(activeUserId: String) =
|
private fun List<UserState.Account>.toCreatePasskeyEntry(
|
||||||
map { it.toCreateEntry(isActive = activeUserId == it.userId) }
|
activeUserId: String,
|
||||||
|
): List<CreateEntry> = map { it.toCreatePasskeyEntry(isActive = activeUserId == it.userId) }
|
||||||
|
|
||||||
private fun UserState.Account.toCreateEntry(isActive: Boolean): CreateEntry {
|
private fun UserState.Account.toCreatePasskeyEntry(
|
||||||
|
isActive: Boolean,
|
||||||
|
): CreateEntry {
|
||||||
val accountName = name ?: email
|
val accountName = name ?: email
|
||||||
val entryBuilder = CreateEntry
|
val entryBuilder = CreateEntry
|
||||||
.Builder(
|
.Builder(
|
||||||
@ -196,6 +192,54 @@ class CredentialProviderProcessorImpl(
|
|||||||
return entryBuilder.build()
|
return entryBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCreatePasswordQuery(
|
||||||
|
request: BeginCreateCredentialRequest,
|
||||||
|
): BeginCreateCredentialResponse? {
|
||||||
|
if (request !is BeginCreatePasswordCredentialRequest) return null
|
||||||
|
|
||||||
|
val userState = authRepository.userStateFlow.value ?: return null
|
||||||
|
|
||||||
|
return BeginCreateCredentialResponse.Builder()
|
||||||
|
.setCreateEntries(
|
||||||
|
userState.accounts.toCreatePasswordEntry(userState.activeUserId),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<UserState.Account>.toCreatePasswordEntry(
|
||||||
|
activeUserId: String,
|
||||||
|
) = map { it.toCreatePasswordEntry(isActive = activeUserId == it.userId) }
|
||||||
|
|
||||||
|
private fun UserState.Account.toCreatePasswordEntry(
|
||||||
|
isActive: Boolean,
|
||||||
|
): CreateEntry {
|
||||||
|
val accountName = name ?: email
|
||||||
|
val entryBuilder = CreateEntry
|
||||||
|
.Builder(
|
||||||
|
accountName = accountName,
|
||||||
|
pendingIntent = pendingIntentManager.createPasswordCreationPendingIntent(
|
||||||
|
userId = userId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
context.getString(
|
||||||
|
BitwardenString.your_password_will_be_saved_to_your_bitwarden_vault_for_x,
|
||||||
|
accountName,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Set the last used time to "now" so the active account is the default option in the
|
||||||
|
// system prompt.
|
||||||
|
.setLastUsedTime(if (isActive) clock.instant() else null)
|
||||||
|
.setAutoSelectAllowed(true)
|
||||||
|
|
||||||
|
if (isVaultUnlocked) {
|
||||||
|
biometricsEncryptionManager
|
||||||
|
.getOrCreateCipher(userId)
|
||||||
|
?.let { entryBuilder.setBiometricPromptDataIfSupported(cipher = it) }
|
||||||
|
}
|
||||||
|
return entryBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
private fun CreateEntry.Builder.setBiometricPromptDataIfSupported(
|
private fun CreateEntry.Builder.setBiometricPromptDataIfSupported(
|
||||||
cipher: Cipher,
|
cipher: Cipher,
|
||||||
): CreateEntry.Builder {
|
): CreateEntry.Builder {
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
package com.x8bit.bitwarden.ui.credentials.manager
|
package com.x8bit.bitwarden.ui.credentials.manager
|
||||||
|
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A manager for completing the FIDO 2 creation process.
|
* A manager for completing the credential creation process.
|
||||||
*/
|
*/
|
||||||
interface CredentialProviderCompletionManager {
|
interface CredentialProviderCompletionManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completes the FIDO 2 registration process with the provided [result].
|
* Completes the credential registration process with the provided [result].
|
||||||
*/
|
*/
|
||||||
fun completeFido2Registration(result: RegisterFido2CredentialResult)
|
fun completeCredentialRegistration(result: CreateCredentialResult)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete the FIDO 2 credential assertion process with the provided [result].
|
* Complete the FIDO 2 credential assertion process with the provided [result].
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.credentials.CreatePasswordResponse
|
||||||
import androidx.credentials.CreatePublicKeyCredentialResponse
|
import androidx.credentials.CreatePublicKeyCredentialResponse
|
||||||
import androidx.credentials.GetCredentialResponse
|
import androidx.credentials.GetCredentialResponse
|
||||||
import androidx.credentials.PasswordCredential
|
import androidx.credentials.PasswordCredential
|
||||||
@ -15,9 +16,9 @@ import androidx.credentials.exceptions.GetCredentialUnknownException
|
|||||||
import androidx.credentials.provider.BeginGetCredentialResponse
|
import androidx.credentials.provider.BeginGetCredentialResponse
|
||||||
import androidx.credentials.provider.PendingIntentHandler
|
import androidx.credentials.provider.PendingIntentHandler
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary implementation of [CredentialProviderCompletionManager] when the build version is
|
* Primary implementation of [CredentialProviderCompletionManager] when the build version is
|
||||||
@ -28,11 +29,11 @@ class CredentialProviderCompletionManagerImpl(
|
|||||||
private val activity: Activity,
|
private val activity: Activity,
|
||||||
) : CredentialProviderCompletionManager {
|
) : CredentialProviderCompletionManager {
|
||||||
|
|
||||||
override fun completeFido2Registration(result: RegisterFido2CredentialResult) {
|
override fun completeCredentialRegistration(result: CreateCredentialResult) {
|
||||||
activity.also {
|
activity.also {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
when (result) {
|
when (result) {
|
||||||
is RegisterFido2CredentialResult.Error -> {
|
is CreateCredentialResult.Error -> {
|
||||||
PendingIntentHandler
|
PendingIntentHandler
|
||||||
.setCreateCredentialException(
|
.setCreateCredentialException(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
@ -42,7 +43,7 @@ class CredentialProviderCompletionManagerImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is RegisterFido2CredentialResult.Success -> {
|
is CreateCredentialResult.Success.Fido2CredentialRegistered -> {
|
||||||
PendingIntentHandler
|
PendingIntentHandler
|
||||||
.setCreateCredentialResponse(
|
.setCreateCredentialResponse(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
@ -52,7 +53,15 @@ class CredentialProviderCompletionManagerImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is RegisterFido2CredentialResult.Cancelled -> {
|
is CreateCredentialResult.Success.PasswordCreated -> {
|
||||||
|
PendingIntentHandler
|
||||||
|
.setCreateCredentialResponse(
|
||||||
|
intent = intent,
|
||||||
|
response = CreatePasswordResponse(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is CreateCredentialResult.Cancelled -> {
|
||||||
PendingIntentHandler
|
PendingIntentHandler
|
||||||
.setCreateCredentialException(
|
.setCreateCredentialException(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
|
|||||||
@ -3,9 +3,9 @@ package com.x8bit.bitwarden.ui.credentials.manager
|
|||||||
import androidx.credentials.CredentialProvider
|
import androidx.credentials.CredentialProvider
|
||||||
import com.bitwarden.annotation.OmitFromCoverage
|
import com.bitwarden.annotation.OmitFromCoverage
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A no-op implementation of [CredentialProviderCompletionManagerImpl] provided when the build
|
* A no-op implementation of [CredentialProviderCompletionManagerImpl] provided when the build
|
||||||
@ -13,7 +13,7 @@ import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialR
|
|||||||
*/
|
*/
|
||||||
@OmitFromCoverage
|
@OmitFromCoverage
|
||||||
object CredentialProviderCompletionManagerUnsupportedApiImpl : CredentialProviderCompletionManager {
|
object CredentialProviderCompletionManagerUnsupportedApiImpl : CredentialProviderCompletionManager {
|
||||||
override fun completeFido2Registration(result: RegisterFido2CredentialResult) = Unit
|
override fun completeCredentialRegistration(result: CreateCredentialResult) = Unit
|
||||||
|
|
||||||
override fun completeFido2Assertion(result: AssertFido2CredentialResult) = Unit
|
override fun completeFido2Assertion(result: AssertFido2CredentialResult) = Unit
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
package com.x8bit.bitwarden.ui.credentials.manager.model
|
||||||
|
|
||||||
|
import com.bitwarden.ui.util.Text
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of a credential creation attempt.
|
||||||
|
*/
|
||||||
|
sealed class CreateCredentialResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a successful credential creation attempt.
|
||||||
|
*/
|
||||||
|
sealed class Success : CreateCredentialResult() {
|
||||||
|
/**
|
||||||
|
* Indicates that the FIDO2 registration was successful.
|
||||||
|
*/
|
||||||
|
data class Fido2CredentialRegistered(val responseJson: String) : Success()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the Password creation was successful.
|
||||||
|
*/
|
||||||
|
data object PasswordCreated : Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that an error occurred during credential creation.
|
||||||
|
*/
|
||||||
|
data class Error(val message: Text) : CreateCredentialResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that credential creation was cancelled by the user.
|
||||||
|
*/
|
||||||
|
data object Cancelled : CreateCredentialResult()
|
||||||
|
}
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package com.x8bit.bitwarden.ui.credentials.manager.model
|
|
||||||
|
|
||||||
import com.bitwarden.ui.util.Text
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the result of a FIDO2 credential registration attempt.
|
|
||||||
*/
|
|
||||||
sealed class RegisterFido2CredentialResult {
|
|
||||||
/**
|
|
||||||
* Indicates that the registration was successful.
|
|
||||||
*/
|
|
||||||
data class Success(val responseJson: String) : RegisterFido2CredentialResult()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that an error occurred during registration.
|
|
||||||
*/
|
|
||||||
data class Error(val message: Text) : RegisterFido2CredentialResult()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the registration was cancelled by the user.
|
|
||||||
*/
|
|
||||||
data object Cancelled : RegisterFido2CredentialResult()
|
|
||||||
}
|
|
||||||
@ -6,21 +6,22 @@ import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
|
|||||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reusable dialog for confirming whether or not the user wants to overwrite an existing FIDO 2
|
* A reusable dialog for confirming whether or not the user wants to overwrite an existing credential.
|
||||||
* credential.
|
|
||||||
*
|
*
|
||||||
* @param onConfirmClick A callback for when the overwrite confirmation button is clicked.
|
* @param onConfirmClick A callback for when the overwrite confirmation button is clicked.
|
||||||
* @param onDismissRequest A callback for when the dialog is requesting dismissal.
|
* @param onDismissRequest A callback for when the dialog is requesting dismissal.
|
||||||
*/
|
*/
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Composable
|
@Composable
|
||||||
fun BitwardenOverwritePasskeyConfirmationDialog(
|
fun BitwardenOverwriteCredentialConfirmationDialog(
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
onConfirmClick: () -> Unit,
|
onConfirmClick: () -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
BitwardenTwoButtonDialog(
|
BitwardenTwoButtonDialog(
|
||||||
title = stringResource(id = BitwardenString.overwrite_passkey),
|
title = title,
|
||||||
message = stringResource(id = BitwardenString.this_item_already_contains_a_passkey_are_you_sure_you_want_to_overwrite_the_current_passkey),
|
message = message,
|
||||||
confirmButtonText = stringResource(id = BitwardenString.okay),
|
confirmButtonText = stringResource(id = BitwardenString.okay),
|
||||||
dismissButtonText = stringResource(id = BitwardenString.cancel),
|
dismissButtonText = stringResource(id = BitwardenString.cancel),
|
||||||
onConfirmClick = onConfirmClick,
|
onConfirmClick = onConfirmClick,
|
||||||
@ -71,6 +71,7 @@ import com.x8bit.bitwarden.ui.vault.feature.exportitems.navigateToExportItemsGra
|
|||||||
import com.x8bit.bitwarden.ui.vault.feature.exportitems.verifypassword.navigateToVerifyPassword
|
import com.x8bit.bitwarden.ui.vault.feature.exportitems.verifypassword.navigateToVerifyPassword
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListingAsRoot
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListingAsRoot
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
@ -141,6 +142,7 @@ fun RootNavScreen(
|
|||||||
is RootNavState.VaultUnlockedForFido2Assertion,
|
is RootNavState.VaultUnlockedForFido2Assertion,
|
||||||
is RootNavState.VaultUnlockedForPasswordGet,
|
is RootNavState.VaultUnlockedForPasswordGet,
|
||||||
is RootNavState.VaultUnlockedForProviderGetCredentials,
|
is RootNavState.VaultUnlockedForProviderGetCredentials,
|
||||||
|
is RootNavState.VaultUnlockedForCreatePasswordRequest,
|
||||||
-> VaultUnlockedGraphRoute
|
-> VaultUnlockedGraphRoute
|
||||||
|
|
||||||
is RootNavState.CredentialExchangeExport,
|
is RootNavState.CredentialExchangeExport,
|
||||||
@ -245,6 +247,17 @@ fun RootNavScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is RootNavState.VaultUnlockedForCreatePasswordRequest -> {
|
||||||
|
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||||
|
navController.navigateToVaultAddEdit(
|
||||||
|
args = VaultAddEditArgs(
|
||||||
|
vaultAddEditType = VaultAddEditType.AddItem,
|
||||||
|
vaultItemCipherType = VaultItemCipherType.LOGIN,
|
||||||
|
),
|
||||||
|
navOptions = rootNavOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is RootNavState.VaultUnlockedForAutofillSelection -> {
|
is RootNavState.VaultUnlockedForAutofillSelection -> {
|
||||||
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||||
navController.navigateToVaultItemListingAsRoot(
|
navController.navigateToVaultItemListingAsRoot(
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.bitwarden.network.model.OrganizationType
|
import com.bitwarden.network.model.OrganizationType
|
||||||
import com.bitwarden.network.util.parseJwtTokenDataOrNull
|
import com.bitwarden.network.util.parseJwtTokenDataOrNull
|
||||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import com.bitwarden.ui.platform.base.util.toAndroidAppUriString
|
||||||
import com.bitwarden.ui.platform.manager.share.model.ShareData
|
import com.bitwarden.ui.platform.manager.share.model.ShareData
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
@ -144,11 +145,31 @@ class RootNavViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is SpecialCircumstance.ProviderCreateCredential -> {
|
is SpecialCircumstance.ProviderCreateCredential -> {
|
||||||
RootNavState.VaultUnlockedForFido2Save(
|
val request = specialCircumstance.createCredentialRequest
|
||||||
activeUserId = userState.activeUserId,
|
val publicKeyRequest = request.createPublicKeyCredentialRequest
|
||||||
createCredentialRequest =
|
val passwordRequest = request.createPasswordCredentialRequest
|
||||||
specialCircumstance.createCredentialRequest,
|
|
||||||
)
|
when {
|
||||||
|
publicKeyRequest != null -> {
|
||||||
|
RootNavState.VaultUnlockedForFido2Save(
|
||||||
|
activeUserId = userState.activeUserId,
|
||||||
|
createCredentialRequest = request,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordRequest != null -> {
|
||||||
|
RootNavState.VaultUnlockedForCreatePasswordRequest(
|
||||||
|
username = passwordRequest.id,
|
||||||
|
password = passwordRequest.password,
|
||||||
|
uri = request
|
||||||
|
.callingAppInfo
|
||||||
|
.packageName
|
||||||
|
.toAndroidAppUriString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalStateException("Should not have entered here.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is SpecialCircumstance.Fido2Assertion -> {
|
is SpecialCircumstance.Fido2Assertion -> {
|
||||||
@ -336,6 +357,21 @@ sealed class RootNavState : Parcelable {
|
|||||||
val createCredentialRequest: CreateCredentialRequest,
|
val createCredentialRequest: CreateCredentialRequest,
|
||||||
) : RootNavState()
|
) : RootNavState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App should show an add item screen for a user to complete the saving of data collected by
|
||||||
|
* the credential manager framework.
|
||||||
|
*
|
||||||
|
* @param username The username of the user.
|
||||||
|
* @param password The password of the user.
|
||||||
|
* @param uri The URI to associate this credential with.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class VaultUnlockedForCreatePasswordRequest(
|
||||||
|
val username: String,
|
||||||
|
val password: String,
|
||||||
|
val uri: String,
|
||||||
|
) : RootNavState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App should perform FIDO 2 credential assertion for the user.
|
* App should perform FIDO 2 credential assertion for the user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -71,7 +71,7 @@ import com.bitwarden.ui.platform.theme.BitwardenTheme
|
|||||||
import com.bitwarden.ui.util.Text
|
import com.bitwarden.ui.util.Text
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenOverwritePasskeyConfirmationDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenOverwriteCredentialConfirmationDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenPinDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenPinDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalCredentialProviderCompletionManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalCredentialProviderCompletionManager
|
||||||
@ -161,8 +161,8 @@ fun VaultAddEditScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultAddEditEvent.CompleteFido2Registration -> {
|
is VaultAddEditEvent.CompleteCredentialRegistration -> {
|
||||||
credentialProviderCompletionManager.completeFido2Registration(
|
credentialProviderCompletionManager.completeCredentialRegistration(
|
||||||
result = event.result,
|
result = event.result,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -227,10 +227,14 @@ fun VaultAddEditScreen(
|
|||||||
onAutofillDismissRequest = remember(viewModel) {
|
onAutofillDismissRequest = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(VaultAddEditAction.Common.InitialAutofillDialogDismissed) }
|
{ viewModel.trySendAction(VaultAddEditAction.Common.InitialAutofillDialogDismissed) }
|
||||||
},
|
},
|
||||||
onFido2ErrorDismiss = remember(viewModel) {
|
onCredentialErrorDismiss = remember(viewModel) {
|
||||||
{ errorMessage ->
|
{ errorMessage ->
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed(message = errorMessage),
|
VaultAddEditAction
|
||||||
|
.Common
|
||||||
|
.CredentialErrorDialogDismissed(
|
||||||
|
message = errorMessage,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -463,7 +467,7 @@ private fun VaultAddEditItemDialogs(
|
|||||||
dialogState: VaultAddEditState.DialogState?,
|
dialogState: VaultAddEditState.DialogState?,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onAutofillDismissRequest: () -> Unit,
|
onAutofillDismissRequest: () -> Unit,
|
||||||
onFido2ErrorDismiss: (Text) -> Unit,
|
onCredentialErrorDismiss: (Text) -> Unit,
|
||||||
onConfirmOverwriteExistingPasskey: () -> Unit,
|
onConfirmOverwriteExistingPasskey: () -> Unit,
|
||||||
onSubmitMasterPasswordFido2Verification: (password: String) -> Unit,
|
onSubmitMasterPasswordFido2Verification: (password: String) -> Unit,
|
||||||
onRetryFido2PasswordVerification: () -> Unit,
|
onRetryFido2PasswordVerification: () -> Unit,
|
||||||
@ -495,16 +499,22 @@ private fun VaultAddEditItemDialogs(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultAddEditState.DialogState.Fido2Error -> {
|
is VaultAddEditState.DialogState.CredentialError -> {
|
||||||
BitwardenBasicDialog(
|
BitwardenBasicDialog(
|
||||||
title = stringResource(id = BitwardenString.an_error_has_occurred),
|
title = stringResource(id = BitwardenString.an_error_has_occurred),
|
||||||
message = dialogState.message(),
|
message = dialogState.message(),
|
||||||
onDismissRequest = { onFido2ErrorDismiss(dialogState.message) },
|
onDismissRequest = { onCredentialErrorDismiss(dialogState.message) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultAddEditState.DialogState.OverwritePasskeyConfirmationPrompt -> {
|
is VaultAddEditState.DialogState.OverwritePasskeyConfirmationPrompt -> {
|
||||||
BitwardenOverwritePasskeyConfirmationDialog(
|
@Suppress("MaxLineLength")
|
||||||
|
BitwardenOverwriteCredentialConfirmationDialog(
|
||||||
|
title = stringResource(id = BitwardenString.overwrite_passkey),
|
||||||
|
message = stringResource(
|
||||||
|
id = BitwardenString
|
||||||
|
.this_item_already_contains_a_passkey_are_you_sure_you_want_to_overwrite_the_current_passkey,
|
||||||
|
),
|
||||||
onConfirmClick = onConfirmOverwriteExistingPasskey,
|
onConfirmClick = onConfirmOverwriteExistingPasskey,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||||
import androidx.credentials.provider.CallingAppInfo
|
import androidx.credentials.provider.CallingAppInfo
|
||||||
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.bitwarden.core.DateTime
|
import com.bitwarden.core.DateTime
|
||||||
@ -34,7 +33,6 @@ import com.x8bit.bitwarden.data.credentials.manager.BitwardenCredentialManager
|
|||||||
import com.x8bit.bitwarden.data.credentials.model.CreateCredentialRequest
|
import com.x8bit.bitwarden.data.credentials.model.CreateCredentialRequest
|
||||||
import com.x8bit.bitwarden.data.credentials.model.Fido2RegisterCredentialResult
|
import com.x8bit.bitwarden.data.credentials.model.Fido2RegisterCredentialResult
|
||||||
import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement
|
import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement
|
||||||
import com.x8bit.bitwarden.data.credentials.util.getCreatePasskeyCredentialRequestOrNull
|
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
@ -59,7 +57,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
|||||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||||
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||||
@ -150,7 +148,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
val autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull()
|
val autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull()
|
||||||
// Check for totp data to pre-populate
|
// Check for totp data to pre-populate
|
||||||
val totpData = specialCircumstance?.toTotpDataOrNull()
|
val totpData = specialCircumstance?.toTotpDataOrNull()
|
||||||
// Check for Fido2 data to pre-populate
|
// Check for Fido2 or Password credential data to pre-populate
|
||||||
val providerCreateCredentialRequest =
|
val providerCreateCredentialRequest =
|
||||||
specialCircumstance?.toCreateCredentialRequestOrNull()
|
specialCircumstance?.toCreateCredentialRequestOrNull()
|
||||||
val fido2AttestationOptions = providerCreateCredentialRequest?.requestJson
|
val fido2AttestationOptions = providerCreateCredentialRequest?.requestJson
|
||||||
@ -348,8 +346,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
handleUserVerificationCancelled()
|
handleUserVerificationCancelled()
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultAddEditAction.Common.Fido2ErrorDialogDismissed -> {
|
is VaultAddEditAction.Common.CredentialErrorDialogDismissed -> {
|
||||||
handleFido2ErrorDialogDismissed(action)
|
handleCredentialErrorDialogDismissed(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
VaultAddEditAction.Common.UserVerificationNotSupported -> {
|
VaultAddEditAction.Common.UserVerificationNotSupported -> {
|
||||||
@ -401,31 +399,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
private fun handleSaveClick() = onContent { content ->
|
private fun handleSaveClick() = onContent { content ->
|
||||||
if (content.common.name.isBlank()) {
|
if (hasValidationErrors(content)) return@onContent
|
||||||
showGenericErrorDialog(
|
|
||||||
message = BitwardenString.validation_field_required
|
|
||||||
.asText(BitwardenString.name.asText()),
|
|
||||||
)
|
|
||||||
return@onContent
|
|
||||||
} else if (
|
|
||||||
content.common.selectedOwnerId != null &&
|
|
||||||
content.common.selectedOwner?.collections?.all { !it.isSelected } == true
|
|
||||||
) {
|
|
||||||
showGenericErrorDialog(
|
|
||||||
message = BitwardenString.select_one_collection.asText(),
|
|
||||||
)
|
|
||||||
return@onContent
|
|
||||||
} else if (
|
|
||||||
!networkConnectionManager.isNetworkConnected
|
|
||||||
) {
|
|
||||||
showDialog(
|
|
||||||
dialogState = VaultAddEditState.DialogState.Generic(
|
|
||||||
title = BitwardenString.internet_connection_required_title.asText(),
|
|
||||||
message = BitwardenString.internet_connection_required_message.asText(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return@onContent
|
|
||||||
}
|
|
||||||
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@ -435,14 +409,17 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.createCredentialRequest
|
state.createCredentialRequest?.run {
|
||||||
?.let { request ->
|
createPublicKeyCredentialRequest
|
||||||
handleProviderCreateCredentialRequest(
|
?.let { createPublicKeyCredentialRequest ->
|
||||||
request.providerRequest,
|
handleCreatePublicKeyCredentialRequest(
|
||||||
content.toCipherView(),
|
request = createPublicKeyCredentialRequest,
|
||||||
)
|
callingAppInfo = this.callingAppInfo,
|
||||||
return@onContent
|
cipherView = content.toCipherView(),
|
||||||
}
|
)
|
||||||
|
return@onContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (val vaultAddEditType = state.vaultAddEditType) {
|
when (val vaultAddEditType = state.vaultAddEditType) {
|
||||||
@ -467,15 +444,34 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleProviderCreateCredentialRequest(
|
private fun hasValidationErrors(content: VaultAddEditState.ViewState.Content): Boolean =
|
||||||
request: ProviderCreateCredentialRequest,
|
if (content.common.name.isBlank()) {
|
||||||
cipherView: CipherView,
|
showGenericErrorDialog(
|
||||||
) {
|
message = BitwardenString.validation_field_required
|
||||||
request
|
.asText(BitwardenString.name.asText()),
|
||||||
.getCreatePasskeyCredentialRequestOrNull()
|
)
|
||||||
?.let { handleCreatePublicKeyCredentialRequest(request.callingAppInfo, it, cipherView) }
|
true
|
||||||
?: run { handleUnsupportedProviderCreateCredentialRequest() }
|
} else if (
|
||||||
}
|
content.common.selectedOwnerId != null &&
|
||||||
|
content.common.selectedOwner?.collections?.all { !it.isSelected } == true
|
||||||
|
) {
|
||||||
|
showGenericErrorDialog(
|
||||||
|
message = BitwardenString.select_one_collection.asText(),
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else if (
|
||||||
|
!networkConnectionManager.isNetworkConnected
|
||||||
|
) {
|
||||||
|
showDialog(
|
||||||
|
dialogState = VaultAddEditState.DialogState.Generic(
|
||||||
|
title = BitwardenString.internet_connection_required_title.asText(),
|
||||||
|
message = BitwardenString.internet_connection_required_message.asText(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCreatePublicKeyCredentialRequest(
|
private fun handleCreatePublicKeyCredentialRequest(
|
||||||
callingAppInfo: CallingAppInfo,
|
callingAppInfo: CallingAppInfo,
|
||||||
@ -520,7 +516,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val userId = authRepository.activeUserId
|
val userId = authRepository.activeUserId
|
||||||
?: run {
|
?: run {
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
@ -539,12 +535,6 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnsupportedProviderCreateCredentialRequest() {
|
|
||||||
showFido2ErrorDialog(
|
|
||||||
BitwardenString.passkey_operation_failed_because_the_request_is_unsupported.asText(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAttachmentsClick() {
|
private fun handleAttachmentsClick() {
|
||||||
onEdit { sendEvent(VaultAddEditEvent.NavigateToAttachments(it.vaultItemId)) }
|
onEdit { sendEvent(VaultAddEditEvent.NavigateToAttachments(it.vaultItemId)) }
|
||||||
}
|
}
|
||||||
@ -613,63 +603,69 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
private fun handleConfirmOverwriteExistingPasskeyClick() {
|
private fun handleConfirmOverwriteExistingPasskeyClick() {
|
||||||
state
|
state
|
||||||
.createCredentialRequest
|
.createCredentialRequest
|
||||||
?.providerRequest
|
|
||||||
?.let { request ->
|
?.let { request ->
|
||||||
onContent { content ->
|
request.createPublicKeyCredentialRequest
|
||||||
handleProviderCreateCredentialRequest(
|
?.let { createPublicKeyCredentialRequest ->
|
||||||
request,
|
onContent { content ->
|
||||||
content.toCipherView(),
|
handleCreatePublicKeyCredentialRequest(
|
||||||
)
|
request = createPublicKeyCredentialRequest,
|
||||||
}
|
callingAppInfo = request.callingAppInfo,
|
||||||
|
cipherView = content.toCipherView(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?: showFido2ErrorDialog(
|
?: showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
BitwardenString.passkey_operation_failed_because_the_request_is_invalid.asText(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationLockOut() {
|
private fun handleUserVerificationLockOut() {
|
||||||
bitwardenCredentialManager.isUserVerified = false
|
bitwardenCredentialManager.isUserVerified = false
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationSuccess() {
|
private fun handleUserVerificationSuccess() {
|
||||||
bitwardenCredentialManager.isUserVerified = true
|
bitwardenCredentialManager.isUserVerified = true
|
||||||
getRequestAndRegisterCredential()
|
getRequestAndRegisterFido2Credential()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRequestAndRegisterCredential() =
|
private fun getRequestAndRegisterFido2Credential() =
|
||||||
state.createCredentialRequest
|
state.createCredentialRequest
|
||||||
?.providerRequest
|
|
||||||
?.let { request ->
|
?.let { request ->
|
||||||
onContent { content ->
|
request.createPublicKeyCredentialRequest
|
||||||
handleProviderCreateCredentialRequest(
|
?.let { createPublicKeyCredentialRequest ->
|
||||||
request = request,
|
onContent { content ->
|
||||||
cipherView = content.toCipherView(),
|
handleCreatePublicKeyCredentialRequest(
|
||||||
)
|
request = createPublicKeyCredentialRequest,
|
||||||
}
|
callingAppInfo = request.callingAppInfo,
|
||||||
|
cipherView = content.toCipherView(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?: showFido2ErrorDialog(
|
?: showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_the_request_is_unsupported
|
BitwardenString.passkey_operation_failed_because_the_request_is_unsupported
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun handleUserVerificationFail() {
|
private fun handleUserVerificationFail() {
|
||||||
bitwardenCredentialManager.isUserVerified = false
|
bitwardenCredentialManager.isUserVerified = false
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFido2ErrorDialogDismissed(
|
private fun handleCredentialErrorDialogDismissed(
|
||||||
action: VaultAddEditAction.Common.Fido2ErrorDialogDismissed,
|
action: VaultAddEditAction.Common.CredentialErrorDialogDismissed,
|
||||||
) {
|
) {
|
||||||
bitwardenCredentialManager.isUserVerified = false
|
bitwardenCredentialManager.isUserVerified = false
|
||||||
clearDialogState()
|
clearDialogState()
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Error(action.message),
|
result = CreateCredentialResult.Error(action.message),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -678,8 +674,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
bitwardenCredentialManager.isUserVerified = false
|
bitwardenCredentialManager.isUserVerified = false
|
||||||
clearDialogState()
|
clearDialogState()
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Cancelled,
|
result = CreateCredentialResult.Cancelled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -692,7 +688,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
.value
|
.value
|
||||||
?.activeAccount
|
?.activeAccount
|
||||||
?: run {
|
?: run {
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
@ -780,7 +776,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDismissFido2VerificationDialogClick() {
|
private fun handleDismissFido2VerificationDialogClick() {
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
@ -1657,7 +1653,13 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
if (state.shouldClearSpecialCircumstance) {
|
if (state.shouldClearSpecialCircumstance) {
|
||||||
specialCircumstanceManager.specialCircumstance = null
|
specialCircumstanceManager.specialCircumstance = null
|
||||||
}
|
}
|
||||||
if (state.shouldExitOnSave) {
|
if (state.createCredentialRequest?.createPasswordCredentialRequest != null) {
|
||||||
|
sendEvent(
|
||||||
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
|
CreateCredentialResult.Success.PasswordCreated,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else if (state.shouldExitOnSave) {
|
||||||
sendEvent(event = VaultAddEditEvent.ExitApp)
|
sendEvent(event = VaultAddEditEvent.ExitApp)
|
||||||
} else {
|
} else {
|
||||||
snackbarRelayManager.sendSnackbarData(
|
snackbarRelayManager.sendSnackbarData(
|
||||||
@ -1970,8 +1972,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
// Use toast here because we are closing the activity.
|
// Use toast here because we are closing the activity.
|
||||||
toastManager.show(BitwardenString.an_error_has_occurred)
|
toastManager.show(BitwardenString.an_error_has_occurred)
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Error(
|
CreateCredentialResult.Error(
|
||||||
action.result.messageResourceId.asText(),
|
action.result.messageResourceId.asText(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1982,8 +1984,10 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
// Use toast here because we are closing the activity.
|
// Use toast here because we are closing the activity.
|
||||||
toastManager.show(BitwardenString.item_updated)
|
toastManager.show(BitwardenString.item_updated)
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Success(action.result.responseJson),
|
CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
|
responseJson = action.result.responseJson,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1997,7 +2001,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
|
|
||||||
when (action.result) {
|
when (action.result) {
|
||||||
is ValidatePasswordResult.Error -> {
|
is ValidatePasswordResult.Error -> {
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
@ -2022,7 +2026,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
|
|
||||||
when (action.result) {
|
when (action.result) {
|
||||||
is ValidatePinResult.Error -> {
|
is ValidatePinResult.Error -> {
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
@ -2047,7 +2051,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
it.copy(dialog = errorDialogState)
|
it.copy(dialog = errorDialogState)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showFido2ErrorDialog(
|
showCredentialErrorDialog(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
)
|
)
|
||||||
@ -2058,7 +2062,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
bitwardenCredentialManager.isUserVerified = true
|
bitwardenCredentialManager.isUserVerified = true
|
||||||
bitwardenCredentialManager.authenticationAttempts = 0
|
bitwardenCredentialManager.authenticationAttempts = 0
|
||||||
|
|
||||||
getRequestAndRegisterCredential()
|
getRequestAndRegisterFido2Credential()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAuthenticatorHelpToolTipClick() {
|
private fun handleAuthenticatorHelpToolTipClick() {
|
||||||
@ -2072,10 +2076,10 @@ class VaultAddEditViewModel @Inject constructor(
|
|||||||
mutableStateFlow.update { it.copy(dialog = null) }
|
mutableStateFlow.update { it.copy(dialog = null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showFido2ErrorDialog(message: Text) {
|
private fun showCredentialErrorDialog(message: Text) {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialog = VaultAddEditState.DialogState.Fido2Error(message),
|
dialog = VaultAddEditState.DialogState.CredentialError(message),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2754,10 +2758,10 @@ data class VaultAddEditState(
|
|||||||
data object InitialAutofillPrompt : DialogState()
|
data object InitialAutofillPrompt : DialogState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a FIDO 2 operation error dialog to the user.
|
* Displays a credential operation error dialog to the user.
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Fido2Error(val message: Text) : DialogState()
|
data class CredentialError(val message: Text) : DialogState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the overwrite passkey confirmation prompt to the user.
|
* Displays the overwrite passkey confirmation prompt to the user.
|
||||||
@ -2888,12 +2892,12 @@ sealed class VaultAddEditEvent {
|
|||||||
) : VaultAddEditEvent()
|
) : VaultAddEditEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete the current FIDO 2 credential registration process.
|
* Complete the current credential registration process.
|
||||||
*
|
*
|
||||||
* @property result the result of FIDO 2 credential registration.
|
* @property result the result of FIDO 2 credential registration.
|
||||||
*/
|
*/
|
||||||
data class CompleteFido2Registration(
|
data class CompleteCredentialRegistration(
|
||||||
val result: RegisterFido2CredentialResult,
|
val result: CreateCredentialResult,
|
||||||
) : BackgroundEvent, VaultAddEditEvent()
|
) : BackgroundEvent, VaultAddEditEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3083,9 +3087,9 @@ sealed class VaultAddEditAction {
|
|||||||
data object UserVerificationCancelled : Common()
|
data object UserVerificationCancelled : Common()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has dismissed the FIDO 2 credential error dialog.
|
* The user has dismissed the credential error dialog.
|
||||||
*/
|
*/
|
||||||
data class Fido2ErrorDialogDismissed(val message: Text) : Common()
|
data class CredentialErrorDialogDismissed(val message: Text) : Common()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User verification cannot be performed with device biometrics or credentials.
|
* User verification cannot be performed with device biometrics or credentials.
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import java.util.UUID
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns pre-filled content that may be used for an "add" type
|
* Returns pre-filled content that may be used for an "add" type
|
||||||
* [VaultAddEditState.ViewState.Content] during FIDO 2 credential creation.
|
* [VaultAddEditState.ViewState.Content] during FIDO 2 or Password credential creation.
|
||||||
*/
|
*/
|
||||||
fun CreateCredentialRequest.toDefaultAddTypeContent(
|
fun CreateCredentialRequest.toDefaultAddTypeContent(
|
||||||
attestationOptions: PasskeyAttestationOptions?,
|
attestationOptions: PasskeyAttestationOptions?,
|
||||||
@ -26,11 +26,17 @@ fun CreateCredentialRequest.toDefaultAddTypeContent(
|
|||||||
val rpName = attestationOptions
|
val rpName = attestationOptions
|
||||||
?.relyingParty
|
?.relyingParty
|
||||||
?.name
|
?.name
|
||||||
.orEmpty()
|
?: callingAppInfo.packageName
|
||||||
|
|
||||||
val username = attestationOptions
|
val username = attestationOptions
|
||||||
?.user
|
?.user
|
||||||
?.name
|
?.name
|
||||||
|
?: createPasswordCredentialRequest
|
||||||
|
?.id
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
|
val password = createPasswordCredentialRequest
|
||||||
|
?.password
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
return VaultAddEditState.ViewState.Content(
|
return VaultAddEditState.ViewState.Content(
|
||||||
@ -40,6 +46,7 @@ fun CreateCredentialRequest.toDefaultAddTypeContent(
|
|||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||||
username = username,
|
username = username,
|
||||||
|
password = password,
|
||||||
uriList = listOf(
|
uriList = listOf(
|
||||||
UriItem(
|
UriItem(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
|
|||||||
@ -50,7 +50,7 @@ import com.bitwarden.ui.util.Text
|
|||||||
import com.bitwarden.ui.util.asText
|
import com.bitwarden.ui.util.asText
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenOverwritePasskeyConfirmationDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenOverwriteCredentialConfirmationDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenPinDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenPinDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalCredentialProviderCompletionManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalCredentialProviderCompletionManager
|
||||||
@ -179,8 +179,8 @@ fun VaultItemListingScreen(
|
|||||||
onNavigateToVaultItemListing(VaultItemListingType.Collection(event.collectionId))
|
onNavigateToVaultItemListing(VaultItemListingType.Collection(event.collectionId))
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemListingEvent.CompleteFido2Registration -> {
|
is VaultItemListingEvent.CompleteCredentialRegistration -> {
|
||||||
credentialProviderCompletionManager.completeFido2Registration(event.result)
|
credentialProviderCompletionManager.completeCredentialRegistration(event.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemListingEvent.CredentialManagerUserVerification -> {
|
is VaultItemListingEvent.CredentialManagerUserVerification -> {
|
||||||
@ -392,7 +392,13 @@ private fun VaultItemListingDialogs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
is VaultItemListingState.DialogState.OverwritePasskeyConfirmationPrompt -> {
|
is VaultItemListingState.DialogState.OverwritePasskeyConfirmationPrompt -> {
|
||||||
BitwardenOverwritePasskeyConfirmationDialog(
|
@Suppress("MaxLineLength")
|
||||||
|
BitwardenOverwriteCredentialConfirmationDialog(
|
||||||
|
title = stringResource(id = BitwardenString.overwrite_passkey),
|
||||||
|
message = stringResource(
|
||||||
|
id = BitwardenString
|
||||||
|
.this_item_already_contains_a_passkey_are_you_sure_you_want_to_overwrite_the_current_passkey,
|
||||||
|
),
|
||||||
onConfirmClick = { onConfirmOverwriteExistingPasskey(dialogState.cipherViewId) },
|
onConfirmClick = { onConfirmOverwriteExistingPasskey(dialogState.cipherViewId) },
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.credentials.CreatePasswordRequest
|
||||||
|
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||||
import androidx.credentials.GetPublicKeyCredentialOption
|
import androidx.credentials.GetPublicKeyCredentialOption
|
||||||
import androidx.credentials.provider.CallingAppInfo
|
import androidx.credentials.provider.CallingAppInfo
|
||||||
import androidx.credentials.provider.CredentialEntry
|
import androidx.credentials.provider.CredentialEntry
|
||||||
@ -77,9 +79,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
|||||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.SearchTypeData
|
import com.x8bit.bitwarden.ui.platform.feature.search.SearchTypeData
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.util.filterAndOrganize
|
import com.x8bit.bitwarden.ui.platform.feature.search.util.filterAndOrganize
|
||||||
@ -168,9 +170,11 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||||
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
||||||
dialogState = providerCreateCredentialRequest?.let {
|
dialogState = providerCreateCredentialRequest
|
||||||
VaultItemListingState.DialogState.Loading(BitwardenString.loading.asText())
|
?.createPublicKeyCredentialRequest
|
||||||
},
|
?.let {
|
||||||
|
VaultItemListingState.DialogState.Loading(BitwardenString.loading.asText())
|
||||||
|
},
|
||||||
policyDisablesSend = policyManager
|
policyDisablesSend = policyManager
|
||||||
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
||||||
.any(),
|
.any(),
|
||||||
@ -426,8 +430,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
state.createCredentialRequest
|
state.createCredentialRequest
|
||||||
?.let {
|
?.let {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Cancelled,
|
result = CreateCredentialResult.Cancelled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -973,7 +977,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
createCredentialRequest
|
createCredentialRequest
|
||||||
.providerRequest
|
.providerRequest
|
||||||
.getCreatePasskeyCredentialRequestOrNull()
|
.getCreatePasskeyCredentialRequestOrNull()
|
||||||
?.let { createPasskeyCredentialRequest ->
|
?.let {
|
||||||
handleItemClickForCreatePublicKeyCredentialRequest(
|
handleItemClickForCreatePublicKeyCredentialRequest(
|
||||||
cipherId = action.id,
|
cipherId = action.id,
|
||||||
cipherView = cipherView,
|
cipherView = cipherView,
|
||||||
@ -984,7 +988,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
VaultItemListingsAction.Internal.CredentialOperationFailureReceive(
|
VaultItemListingsAction.Internal.CredentialOperationFailureReceive(
|
||||||
title = BitwardenString.an_error_has_occurred.asText(),
|
title = BitwardenString.an_error_has_occurred.asText(),
|
||||||
message = BitwardenString
|
message = BitwardenString
|
||||||
.passkey_operation_failed_because_the_request_is_unsupported
|
.credential_operation_failed_because_the_request_is_unsupported
|
||||||
.asText(),
|
.asText(),
|
||||||
error = null,
|
error = null,
|
||||||
),
|
),
|
||||||
@ -1085,6 +1089,27 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun registerCredentialToCipher(
|
||||||
|
cipherView: CipherView,
|
||||||
|
providerRequest: ProviderCreateCredentialRequest,
|
||||||
|
) {
|
||||||
|
when (providerRequest.callingRequest) {
|
||||||
|
is CreatePublicKeyCredentialRequest -> {
|
||||||
|
registerFido2CredentialToCipher(
|
||||||
|
cipherView = cipherView,
|
||||||
|
providerRequest = providerRequest,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
showCredentialManagerErrorDialog(
|
||||||
|
BitwardenString.credential_operation_failed_because_the_request_is_invalid
|
||||||
|
.asText(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun registerFido2CredentialToCipher(
|
private fun registerFido2CredentialToCipher(
|
||||||
cipherView: CipherView,
|
cipherView: CipherView,
|
||||||
providerRequest: ProviderCreateCredentialRequest,
|
providerRequest: ProviderCreateCredentialRequest,
|
||||||
@ -1326,8 +1351,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
when {
|
when {
|
||||||
state.createCredentialRequest != null -> {
|
state.createCredentialRequest != null -> {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Error(action.message),
|
result = CreateCredentialResult.Error(action.message),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1530,7 +1555,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemListingsAction.Internal.CreateCredentialRequestReceive -> {
|
is VaultItemListingsAction.Internal.CreateCredentialRequestReceive -> {
|
||||||
handleRegisterFido2CredentialRequestReceive(action)
|
handleRegisterCredentialRequestReceive(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive -> {
|
is VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive -> {
|
||||||
@ -1900,7 +1925,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
state.createCredentialRequest
|
state.createCredentialRequest
|
||||||
?.providerRequest
|
?.providerRequest
|
||||||
?.let { request ->
|
?.let { request ->
|
||||||
registerFido2CredentialToCipher(
|
registerCredentialToCipher(
|
||||||
cipherView = cipherView,
|
cipherView = cipherView,
|
||||||
providerRequest = request,
|
providerRequest = request,
|
||||||
)
|
)
|
||||||
@ -2012,6 +2037,32 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleRegisterCredentialRequestReceive(
|
||||||
|
action: VaultItemListingsAction.Internal.CreateCredentialRequestReceive,
|
||||||
|
) {
|
||||||
|
when (action.request.providerRequest.callingRequest) {
|
||||||
|
is CreatePublicKeyCredentialRequest -> {
|
||||||
|
handleRegisterFido2CredentialRequestReceive(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
is CreatePasswordRequest -> {
|
||||||
|
observeVaultData()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState =
|
||||||
|
VaultItemListingState.DialogState.CredentialManagerOperationFail(
|
||||||
|
title = BitwardenString.an_error_has_occurred.asText(),
|
||||||
|
message = BitwardenString.generic_error_message.asText(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleRegisterFido2CredentialRequestReceive(
|
private fun handleRegisterFido2CredentialRequestReceive(
|
||||||
action: VaultItemListingsAction.Internal.CreateCredentialRequestReceive,
|
action: VaultItemListingsAction.Internal.CreateCredentialRequestReceive,
|
||||||
) {
|
) {
|
||||||
@ -2062,8 +2113,10 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
// user to have time to see the message.
|
// user to have time to see the message.
|
||||||
toastManager.show(messageId = BitwardenString.item_updated)
|
toastManager.show(messageId = BitwardenString.item_updated)
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Success(action.result.responseJson),
|
CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
|
responseJson = action.result.responseJson,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2077,8 +2130,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
// user to have time to see the message.
|
// user to have time to see the message.
|
||||||
toastManager.show(messageId = BitwardenString.an_error_has_occurred)
|
toastManager.show(messageId = BitwardenString.an_error_has_occurred)
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Error(
|
CreateCredentialResult.Error(
|
||||||
message = error.messageResourceId.asText(),
|
message = error.messageResourceId.asText(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -3240,12 +3293,12 @@ sealed class VaultItemListingEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete the current FIDO 2 credential registration process.
|
* Complete the current credential registration process.
|
||||||
*
|
*
|
||||||
* @property result The result of FIDO 2 credential registration.
|
* @property result The result of the credential registration.
|
||||||
*/
|
*/
|
||||||
data class CompleteFido2Registration(
|
data class CompleteCredentialRegistration(
|
||||||
val result: RegisterFido2CredentialResult,
|
val result: CreateCredentialResult,
|
||||||
) : BackgroundEvent, VaultItemListingEvent()
|
) : BackgroundEvent, VaultItemListingEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -11,7 +11,7 @@ fun createMockCreateCredentialRequest(
|
|||||||
isUserPreVerified: Boolean = false,
|
isUserPreVerified: Boolean = false,
|
||||||
requestData: Bundle = bundleOf(),
|
requestData: Bundle = bundleOf(),
|
||||||
): CreateCredentialRequest = CreateCredentialRequest(
|
): CreateCredentialRequest = CreateCredentialRequest(
|
||||||
userId = "mockUserId-$number",
|
userId = "mockUserIdx-$number",
|
||||||
isUserPreVerified = isUserPreVerified,
|
isUserPreVerified = isUserPreVerified,
|
||||||
requestData = requestData,
|
requestData = requestData,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -125,7 +125,7 @@ class CredentialProviderProcessorTest {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `processCreateCredentialRequest should invoke callback with error on password create request`() {
|
fun `processCreateCredentialRequest should invoke callback with error on password create request when userState is null`() {
|
||||||
val request: BeginCreatePasswordCredentialRequest = mockk {
|
val request: BeginCreatePasswordCredentialRequest = mockk {
|
||||||
every { callingAppInfo } returns mockk(relaxed = true)
|
every { callingAppInfo } returns mockk(relaxed = true)
|
||||||
every { candidateQueryData } returns Bundle()
|
every { candidateQueryData } returns Bundle()
|
||||||
@ -148,6 +148,198 @@ class CredentialProviderProcessorTest {
|
|||||||
assert(captureSlot.captured is CreateCredentialUnknownException)
|
assert(captureSlot.captured is CreateCredentialUnknownException)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `processCreateCredentialRequest should invoke callback with result on password create request with valid userState`() {
|
||||||
|
val request: BeginCreatePasswordCredentialRequest = mockk {
|
||||||
|
every { callingAppInfo } returns mockk(relaxed = true)
|
||||||
|
every { candidateQueryData } returns Bundle()
|
||||||
|
}
|
||||||
|
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||||
|
mockk()
|
||||||
|
val captureSlot = slot<BeginCreateCredentialResponse>()
|
||||||
|
val mockIntent: PendingIntent = mockk()
|
||||||
|
mutableUserStateFlow.value = DEFAULT_USER_STATE
|
||||||
|
every { context.packageName } returns "com.x8bit.bitwarden"
|
||||||
|
every { context.getString(any(), any()) } returns "mockDescription"
|
||||||
|
every {
|
||||||
|
pendingIntentManager.createPasswordCreationPendingIntent(
|
||||||
|
userId = any(),
|
||||||
|
)
|
||||||
|
} returns mockIntent
|
||||||
|
every {
|
||||||
|
biometricsEncryptionManager.getOrCreateCipher(userId = any())
|
||||||
|
} returns mockk<Cipher>()
|
||||||
|
every { cancellationSignal.setOnCancelListener(any()) } just runs
|
||||||
|
every { callback.onResult(capture(captureSlot)) } just runs
|
||||||
|
|
||||||
|
credentialProviderProcessor.processCreateCredentialRequest(
|
||||||
|
request = request,
|
||||||
|
cancellationSignal = cancellationSignal,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) { callback.onResult(any()) }
|
||||||
|
verify(exactly = 0) { callback.onError(any()) }
|
||||||
|
|
||||||
|
assertEquals(DEFAULT_USER_STATE.accounts.size, captureSlot.captured.createEntries.size)
|
||||||
|
val capturedEntry = captureSlot.captured.createEntries[0]
|
||||||
|
assertEquals(DEFAULT_USER_STATE.accounts[0].email, capturedEntry.accountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `processCreateCredentialRequest should generate correct password entries based on state`() {
|
||||||
|
val request: BeginCreatePasswordCredentialRequest = mockk {
|
||||||
|
every { callingAppInfo } returns mockk(relaxed = true)
|
||||||
|
every { candidateQueryData } returns Bundle()
|
||||||
|
}
|
||||||
|
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||||
|
mockk()
|
||||||
|
mutableUserStateFlow.value = DEFAULT_USER_STATE
|
||||||
|
val captureSlot = slot<BeginCreateCredentialResponse>()
|
||||||
|
val mockIntent: PendingIntent = mockk()
|
||||||
|
every { context.packageName } returns "com.x8bit.bitwarden.dev"
|
||||||
|
every { context.getString(any(), any()) } returns "mockDescription"
|
||||||
|
every { cancellationSignal.setOnCancelListener(any()) } just runs
|
||||||
|
every { callback.onResult(capture(captureSlot)) } just runs
|
||||||
|
every {
|
||||||
|
pendingIntentManager.createPasswordCreationPendingIntent(
|
||||||
|
userId = any(),
|
||||||
|
)
|
||||||
|
} returns mockIntent
|
||||||
|
every {
|
||||||
|
biometricsEncryptionManager.getOrCreateCipher(any())
|
||||||
|
} returns mockk<Cipher>()
|
||||||
|
every { isBuildVersionAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) } returns true
|
||||||
|
|
||||||
|
credentialProviderProcessor.processCreateCredentialRequest(
|
||||||
|
request = request,
|
||||||
|
cancellationSignal = cancellationSignal,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) { callback.onResult(any()) }
|
||||||
|
verify(exactly = 0) { callback.onError(any()) }
|
||||||
|
|
||||||
|
assertEquals(DEFAULT_USER_STATE.accounts.size, captureSlot.captured.createEntries.size)
|
||||||
|
|
||||||
|
// Verify only the active account entry has a lastUsedTime
|
||||||
|
assertEquals(
|
||||||
|
1,
|
||||||
|
captureSlot.captured.createEntries.filter { it.lastUsedTime != null }.size,
|
||||||
|
)
|
||||||
|
DEFAULT_USER_STATE.accounts.forEachIndexed { index, mockAccount ->
|
||||||
|
assertEquals(mockAccount.email, captureSlot.captured.createEntries[index].accountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all entries have biometric prompt data when feature flag is enabled
|
||||||
|
assertTrue(captureSlot.captured.createEntries.all { it.biometricPromptData != null }) {
|
||||||
|
"Expected all entries to have biometric prompt data."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify entries have the correct authenticators when cipher is not null
|
||||||
|
assertTrue(
|
||||||
|
captureSlot.captured
|
||||||
|
.createEntries
|
||||||
|
.all {
|
||||||
|
it.biometricPromptData?.allowedAuthenticators ==
|
||||||
|
BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
},
|
||||||
|
) { "Expected all entries to have BIOMETRIC_STRONG authenticators." }
|
||||||
|
|
||||||
|
// Verify entries have no biometric prompt data when cipher is null
|
||||||
|
every { biometricsEncryptionManager.getOrCreateCipher(any()) } returns null
|
||||||
|
credentialProviderProcessor.processCreateCredentialRequest(
|
||||||
|
request = request,
|
||||||
|
cancellationSignal = cancellationSignal,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
captureSlot.captured.createEntries.all { it.biometricPromptData == null },
|
||||||
|
) { "Expected all entries to have null biometric prompt data." }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `processCreateCredentialRequest should not add biometric data to password entries on pre-V devices`() {
|
||||||
|
val request: BeginCreatePasswordCredentialRequest = mockk {
|
||||||
|
every { callingAppInfo } returns mockk(relaxed = true)
|
||||||
|
every { candidateQueryData } returns Bundle()
|
||||||
|
}
|
||||||
|
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||||
|
mockk()
|
||||||
|
val captureSlot = slot<BeginCreateCredentialResponse>()
|
||||||
|
val mockIntent: PendingIntent = mockk()
|
||||||
|
mutableUserStateFlow.value = DEFAULT_USER_STATE
|
||||||
|
every { context.packageName } returns "com.x8bit.bitwarden"
|
||||||
|
every { context.getString(any(), any()) } returns "mockDescription"
|
||||||
|
every {
|
||||||
|
pendingIntentManager.createPasswordCreationPendingIntent(
|
||||||
|
userId = any(),
|
||||||
|
)
|
||||||
|
} returns mockIntent
|
||||||
|
every {
|
||||||
|
biometricsEncryptionManager.getOrCreateCipher(userId = any())
|
||||||
|
} returns mockk<Cipher>()
|
||||||
|
every { cancellationSignal.setOnCancelListener(any()) } just runs
|
||||||
|
every { callback.onResult(capture(captureSlot)) } just runs
|
||||||
|
every { isBuildVersionAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) } returns false
|
||||||
|
|
||||||
|
credentialProviderProcessor.processCreateCredentialRequest(
|
||||||
|
request = request,
|
||||||
|
cancellationSignal = cancellationSignal,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) { callback.onResult(any()) }
|
||||||
|
|
||||||
|
// Verify entries have no biometric prompt data on older devices
|
||||||
|
assertTrue(captureSlot.captured.createEntries.all { it.biometricPromptData == null }) {
|
||||||
|
"Expected all entries to have null biometric prompt data on pre-V devices."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `processCreateCredentialRequest should not add biometric data to password entries when vault is locked`() {
|
||||||
|
val request: BeginCreatePasswordCredentialRequest = mockk {
|
||||||
|
every { callingAppInfo } returns mockk(relaxed = true)
|
||||||
|
every { candidateQueryData } returns Bundle()
|
||||||
|
}
|
||||||
|
val callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> =
|
||||||
|
mockk()
|
||||||
|
val captureSlot = slot<BeginCreateCredentialResponse>()
|
||||||
|
val mockIntent: PendingIntent = mockk()
|
||||||
|
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||||
|
accounts = DEFAULT_USER_STATE.accounts.map { it.copy(isVaultUnlocked = false) },
|
||||||
|
)
|
||||||
|
every { context.packageName } returns "com.x8bit.bitwarden"
|
||||||
|
every { context.getString(any(), any()) } returns "mockDescription"
|
||||||
|
every {
|
||||||
|
pendingIntentManager.createPasswordCreationPendingIntent(
|
||||||
|
userId = any(),
|
||||||
|
)
|
||||||
|
} returns mockIntent
|
||||||
|
every { cancellationSignal.setOnCancelListener(any()) } just runs
|
||||||
|
every { callback.onResult(capture(captureSlot)) } just runs
|
||||||
|
every { isBuildVersionAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) } returns true
|
||||||
|
|
||||||
|
credentialProviderProcessor.processCreateCredentialRequest(
|
||||||
|
request = request,
|
||||||
|
cancellationSignal = cancellationSignal,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) { callback.onResult(any()) }
|
||||||
|
verify(exactly = 0) { biometricsEncryptionManager.getOrCreateCipher(any()) }
|
||||||
|
|
||||||
|
// Verify entries have no biometric prompt data when vault is locked
|
||||||
|
assertTrue(captureSlot.captured.createEntries.all { it.biometricPromptData == null }) {
|
||||||
|
"Expected all entries to have null biometric prompt data when vault is locked."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `processCreateCredentialRequest should invoke callback with error when json is null or empty`() {
|
fun `processCreateCredentialRequest should invoke callback with error when json is null or empty`() {
|
||||||
|
|||||||
@ -48,7 +48,7 @@ fun createMockCipherView(
|
|||||||
organizationId: String? = "mockOrganizationId-$number",
|
organizationId: String? = "mockOrganizationId-$number",
|
||||||
folderId: String? = "mockId-$number",
|
folderId: String? = "mockId-$number",
|
||||||
notes: String? = "mockNotes-$number",
|
notes: String? = "mockNotes-$number",
|
||||||
password: String = "mockPassword-$number",
|
password: String? = "mockPassword-$number",
|
||||||
clock: Clock = FIXED_CLOCK,
|
clock: Clock = FIXED_CLOCK,
|
||||||
fido2Credentials: List<Fido2Credential>? = null,
|
fido2Credentials: List<Fido2Credential>? = null,
|
||||||
sshKey: SshKeyView? = createMockSshKeyView(number = number),
|
sshKey: SshKeyView? = createMockSshKeyView(number = number),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
|
import androidx.credentials.CreatePasswordResponse
|
||||||
import androidx.credentials.exceptions.GetCredentialCancellationException
|
import androidx.credentials.exceptions.GetCredentialCancellationException
|
||||||
import androidx.credentials.exceptions.GetCredentialUnknownException
|
import androidx.credentials.exceptions.GetCredentialUnknownException
|
||||||
import androidx.credentials.provider.BeginGetCredentialResponse
|
import androidx.credentials.provider.BeginGetCredentialResponse
|
||||||
@ -16,9 +17,9 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFido2Creden
|
|||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockPasswordCredentialAutofillCipherLogin
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockPasswordCredentialAutofillCipherLogin
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
import io.mockk.Called
|
import io.mockk.Called
|
||||||
import io.mockk.MockKVerificationScope
|
import io.mockk.MockKVerificationScope
|
||||||
import io.mockk.Ordering
|
import io.mockk.Ordering
|
||||||
@ -60,9 +61,11 @@ class CredentialProviderCompletionManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `completeFido2Registration should perform no operations`() {
|
fun `completeCredentialRegistration should perform no operations`() {
|
||||||
val mockRegistrationResult = mockk<RegisterFido2CredentialResult>()
|
val mockRegistrationResult = mockk<CreateCredentialResult>()
|
||||||
credentialProviderCompletionManager.completeFido2Registration(mockRegistrationResult)
|
credentialProviderCompletionManager.completeCredentialRegistration(
|
||||||
|
mockRegistrationResult,
|
||||||
|
)
|
||||||
verify {
|
verify {
|
||||||
mockRegistrationResult wasNot Called
|
mockRegistrationResult wasNot Called
|
||||||
mockActivity wasNot Called
|
mockActivity wasNot Called
|
||||||
@ -132,10 +135,10 @@ class CredentialProviderCompletionManagerTest {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `completeFido2Registration should set CreateCredentialResponse, set activity result, then finish activity when result is Success`() {
|
fun `completeCredentialRegistration should set CreateCredentialResponse, set activity result, then finish activity when result is SuccessFido2`() {
|
||||||
credentialProviderCompletionManager
|
credentialProviderCompletionManager
|
||||||
.completeFido2Registration(
|
.completeCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Success(
|
CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
responseJson = "registrationResponse",
|
responseJson = "registrationResponse",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -147,9 +150,24 @@ class CredentialProviderCompletionManagerTest {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `completeFido2Registration should set CreateCredentialException, set activity result, then finish activity when result is Error`() {
|
fun `completeCredentialRegistration should set CreateCredentialResponse, set activity result, then finish activity when result is SuccessPassword`() {
|
||||||
|
credentialProviderCompletionManager.completeCredentialRegistration(
|
||||||
|
CreateCredentialResult.Success.PasswordCreated,
|
||||||
|
)
|
||||||
|
|
||||||
|
verifyActivityResultIsSetAndFinishedAfter {
|
||||||
|
PendingIntentHandler.setCreateCredentialResponse(
|
||||||
|
intent = any(),
|
||||||
|
response = any<CreatePasswordResponse>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `completeCredentialRegistration should set CreateCredentialException, set activity result, then finish activity when result is Error`() {
|
||||||
credentialProviderCompletionManager
|
credentialProviderCompletionManager
|
||||||
.completeFido2Registration(RegisterFido2CredentialResult.Error("".asText()))
|
.completeCredentialRegistration(CreateCredentialResult.Error("".asText()))
|
||||||
|
|
||||||
verifyActivityResultIsSetAndFinishedAfter {
|
verifyActivityResultIsSetAndFinishedAfter {
|
||||||
mockActivity.resources
|
mockActivity.resources
|
||||||
@ -159,9 +177,9 @@ class CredentialProviderCompletionManagerTest {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `completeFido2Registration should set CreateCredentialException, set activity result, then finish activity when result is Cancelled`() {
|
fun `completeCredentialRegistration should set CreateCredentialException, set activity result, then finish activity when result is Cancelled`() {
|
||||||
credentialProviderCompletionManager
|
credentialProviderCompletionManager
|
||||||
.completeFido2Registration(RegisterFido2CredentialResult.Cancelled)
|
.completeCredentialRegistration(CreateCredentialResult.Cancelled)
|
||||||
|
|
||||||
verifyActivityResultIsSetAndFinishedAfter {
|
verifyActivityResultIsSetAndFinishedAfter {
|
||||||
PendingIntentHandler.setCreateCredentialException(any(), any())
|
PendingIntentHandler.setCreateCredentialException(any(), any())
|
||||||
|
|||||||
@ -313,6 +313,31 @@ class RootNavScreenTest : BitwardenComposeTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure navigating to vault unlocked for create password request works as expected:
|
||||||
|
rootNavStateFlow.value = RootNavState.VaultUnlockedForCreatePasswordRequest(
|
||||||
|
username = "activeUserId",
|
||||||
|
password = "mockPassword",
|
||||||
|
uri = "mockUri",
|
||||||
|
)
|
||||||
|
composeTestRule.runOnIdle {
|
||||||
|
verify {
|
||||||
|
mockNavHostController.navigate(
|
||||||
|
route = VaultUnlockedGraphRoute,
|
||||||
|
navOptions = expectedNavOptions,
|
||||||
|
)
|
||||||
|
mockNavHostController.navigate(
|
||||||
|
route = VaultAddEditRoute(
|
||||||
|
vaultAddEditMode = VaultAddEditMode.ADD,
|
||||||
|
vaultItemId = null,
|
||||||
|
vaultItemCipherType = VaultItemCipherType.LOGIN,
|
||||||
|
selectedFolderId = null,
|
||||||
|
selectedCollectionId = null,
|
||||||
|
),
|
||||||
|
navOptions = expectedNavOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure navigating to vault unlocked for CreateCredentialRequest works as expected:
|
// Make sure navigating to vault unlocked for CreateCredentialRequest works as expected:
|
||||||
rootNavStateFlow.value = RootNavState.VaultUnlockedForFido2Save(
|
rootNavStateFlow.value = RootNavState.VaultUnlockedForFido2Save(
|
||||||
activeUserId = "activeUserId",
|
activeUserId = "activeUserId",
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
||||||
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.credentials.CreatePasswordRequest
|
||||||
|
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||||
|
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||||
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
||||||
import com.bitwarden.cxf.model.ImportCredentialsRequestData
|
import com.bitwarden.cxf.model.ImportCredentialsRequestData
|
||||||
import com.bitwarden.data.repository.model.Environment
|
import com.bitwarden.data.repository.model.Environment
|
||||||
@ -28,7 +31,9 @@ import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
|||||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkObject
|
||||||
import io.mockk.unmockkStatic
|
import io.mockk.unmockkStatic
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
@ -64,6 +69,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||||||
@AfterEach
|
@AfterEach
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
unmockkStatic(::parseJwtTokenDataOrNull)
|
unmockkStatic(::parseJwtTokenDataOrNull)
|
||||||
|
unmockkObject(ProviderCreateCredentialRequest.Companion)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -682,11 +688,18 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `when the active user has an unlocked vault but there is a Fido2Save special circumstance the nav state should be VaultUnlockedForFido2Save`() {
|
fun `when the active user has an unlocked vault but there is a Fido2Save special circumstance the nav state should be VaultUnlockedForFido2Save`() {
|
||||||
|
mockkObject(ProviderCreateCredentialRequest.Companion)
|
||||||
|
|
||||||
val createCredentialRequest = CreateCredentialRequest(
|
val createCredentialRequest = CreateCredentialRequest(
|
||||||
userId = "activeUserId",
|
userId = "activeUserId",
|
||||||
isUserPreVerified = false,
|
isUserPreVerified = false,
|
||||||
requestData = bundleOf(),
|
requestData = bundleOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
every { ProviderCreateCredentialRequest.fromBundle(any()) } returns mockk {
|
||||||
|
every { callingRequest } returns mockk<CreatePublicKeyCredentialRequest>()
|
||||||
|
}
|
||||||
|
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.ProviderCreateCredential(createCredentialRequest)
|
SpecialCircumstance.ProviderCreateCredential(createCredentialRequest)
|
||||||
mutableUserStateFlow.tryEmit(
|
mutableUserStateFlow.tryEmit(
|
||||||
@ -728,6 +741,73 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `when the active user has an unlocked vault but there is a ProviderCreateCredential with password request the nav state should be VaultUnlockedForCreatePasswordRequest`() {
|
||||||
|
mockkObject(ProviderCreateCredentialRequest.Companion)
|
||||||
|
|
||||||
|
val mockUsername = "testUser"
|
||||||
|
val mockPassword = "testPassword123"
|
||||||
|
val mockPackageName = "com.example.app"
|
||||||
|
|
||||||
|
val createCredentialRequest = CreateCredentialRequest(
|
||||||
|
userId = "activeUserId",
|
||||||
|
isUserPreVerified = false,
|
||||||
|
requestData = bundleOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
every { ProviderCreateCredentialRequest.fromBundle(any()) } returns mockk {
|
||||||
|
every { callingRequest } returns mockk<CreatePasswordRequest> {
|
||||||
|
every { id } returns mockUsername
|
||||||
|
every { password } returns mockPassword
|
||||||
|
}
|
||||||
|
every { callingAppInfo } returns mockk {
|
||||||
|
every { packageName } returns mockPackageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
SpecialCircumstance.ProviderCreateCredential(createCredentialRequest)
|
||||||
|
mutableUserStateFlow.tryEmit(
|
||||||
|
UserState(
|
||||||
|
activeUserId = "activeUserId",
|
||||||
|
accounts = listOf(
|
||||||
|
UserState.Account(
|
||||||
|
userId = "activeUserId",
|
||||||
|
name = "name",
|
||||||
|
email = "email",
|
||||||
|
avatarColorHex = "avatarHexColor",
|
||||||
|
environment = Environment.Us,
|
||||||
|
isPremium = true,
|
||||||
|
isLoggedIn = true,
|
||||||
|
isVaultUnlocked = true,
|
||||||
|
needsPasswordReset = false,
|
||||||
|
isBiometricsEnabled = false,
|
||||||
|
organizations = emptyList(),
|
||||||
|
needsMasterPassword = false,
|
||||||
|
trustedDevice = null,
|
||||||
|
hasMasterPassword = true,
|
||||||
|
isUsingKeyConnector = false,
|
||||||
|
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||||
|
firstTimeState = FirstTimeState(
|
||||||
|
showImportLoginsCard = true,
|
||||||
|
),
|
||||||
|
isExportable = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
assertEquals(
|
||||||
|
RootNavState.VaultUnlockedForCreatePasswordRequest(
|
||||||
|
username = mockUsername,
|
||||||
|
password = mockPassword,
|
||||||
|
uri = "androidapp://$mockPackageName",
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `when the active user has an unlocked vault but there is a Fido2Assertion special circumstance the nav state should be VaultUnlockedForFido2Assertion`() {
|
fun `when the active user has an unlocked vault but there is a Fido2Assertion special circumstance the nav state should be VaultUnlockedForFido2Assertion`() {
|
||||||
|
|||||||
@ -57,7 +57,7 @@ import com.bitwarden.vault.UriMatchType
|
|||||||
import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent
|
import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
|
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
|
||||||
@ -112,7 +112,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
|
|||||||
every { launchUri(any()) } just runs
|
every { launchUri(any()) } just runs
|
||||||
}
|
}
|
||||||
private val credentialProviderCompletionManager: CredentialProviderCompletionManager = mockk {
|
private val credentialProviderCompletionManager: CredentialProviderCompletionManager = mockk {
|
||||||
every { completeFido2Registration(any()) } just runs
|
every { completeCredentialRegistration(any()) } just runs
|
||||||
}
|
}
|
||||||
private val biometricsManager: BiometricsManager = mockk {
|
private val biometricsManager: BiometricsManager = mockk {
|
||||||
every { isUserVerificationSupported } returns true
|
every { isUserVerificationSupported } returns true
|
||||||
@ -233,18 +233,18 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on CompleteFido2Create event should invoke Fido2CompletionManager`() {
|
fun `on CompleteCredentialCreate event should invoke CredentialProviderCompletionManager`() {
|
||||||
val result = RegisterFido2CredentialResult.Success(
|
val result = CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
responseJson = "mockRegistrationResponse",
|
responseJson = "mockRegistrationResponse",
|
||||||
)
|
)
|
||||||
mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteFido2Registration(result = result))
|
mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteCredentialRegistration(result = result))
|
||||||
verify { credentialProviderCompletionManager.completeFido2Registration(result) }
|
verify { credentialProviderCompletionManager.completeCredentialRegistration(result) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Fido2Error dialog should display based on state`() {
|
fun `CredentialError dialog should display based on state`() {
|
||||||
mutableStateFlow.value = DEFAULT_STATE_LOGIN.copy(
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN.copy(
|
||||||
dialog = VaultAddEditState.DialogState.Fido2Error("mockMessage".asText()),
|
dialog = VaultAddEditState.DialogState.CredentialError("mockMessage".asText()),
|
||||||
)
|
)
|
||||||
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
@ -464,7 +464,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `clicking dismiss dialog on Fido2Error dialog should send Fido2ErrorDialogDismissed action`() {
|
fun `clicking dismiss dialog on Fido2Error dialog should send Fido2ErrorDialogDismissed action`() {
|
||||||
mutableStateFlow.value = DEFAULT_STATE_LOGIN.copy(
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN.copy(
|
||||||
dialog = VaultAddEditState.DialogState.Fido2Error("mockMessage".asText()),
|
dialog = VaultAddEditState.DialogState.CredentialError("mockMessage".asText()),
|
||||||
)
|
)
|
||||||
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
@ -474,7 +474,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
|
|||||||
|
|
||||||
verify {
|
verify {
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed("mockMessage".asText()),
|
VaultAddEditAction.Common.CredentialErrorDialogDismissed("mockMessage".asText()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||||
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.credentials.CreatePasswordRequest
|
|
||||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||||
import androidx.credentials.provider.CallingAppInfo
|
import androidx.credentials.provider.CallingAppInfo
|
||||||
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||||
@ -78,7 +77,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
|||||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||||
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||||
@ -1085,8 +1084,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
assertEquals(stateWithSavingDialog, stateFlow.awaitItem())
|
assertEquals(stateWithSavingDialog, stateFlow.awaitItem())
|
||||||
assertEquals(stateWithName, stateFlow.awaitItem())
|
assertEquals(stateWithName, stateFlow.awaitItem())
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Success(
|
CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
responseJson = "mockResponse",
|
responseJson = "mockResponse",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1183,55 +1182,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `in add mode during fido2, SaveClick should show fido2 error dialog when request type is not supported`() =
|
|
||||||
runTest {
|
|
||||||
val fido2CredentialRequest = createMockCreateCredentialRequest(number = 1)
|
|
||||||
specialCircumstanceManager.specialCircumstance =
|
|
||||||
SpecialCircumstance.ProviderCreateCredential(
|
|
||||||
createCredentialRequest = fido2CredentialRequest,
|
|
||||||
)
|
|
||||||
val stateWithName = createVaultAddItemState(
|
|
||||||
commonContentViewState = createCommonContentViewState(
|
|
||||||
name = "mockName-1",
|
|
||||||
),
|
|
||||||
createCredentialRequest = fido2CredentialRequest,
|
|
||||||
)
|
|
||||||
.copy(shouldExitOnSave = true)
|
|
||||||
|
|
||||||
val mockProviderCreateCredentialRequest: ProviderCreateCredentialRequest =
|
|
||||||
mockk<ProviderCreateCredentialRequest>(relaxed = true) {
|
|
||||||
every { callingAppInfo } returns mockk(relaxed = true)
|
|
||||||
every { callingRequest } returns mockk<CreatePasswordRequest>(relaxed = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
every {
|
|
||||||
ProviderCreateCredentialRequest.fromBundle(any())
|
|
||||||
} returns mockProviderCreateCredentialRequest
|
|
||||||
|
|
||||||
mutableVaultDataFlow.value = DataState.Loaded(
|
|
||||||
createVaultData(),
|
|
||||||
)
|
|
||||||
val viewModel = createAddVaultItemViewModel(
|
|
||||||
createSavedStateHandleWithState(
|
|
||||||
state = stateWithName,
|
|
||||||
vaultAddEditType = VaultAddEditType.AddItem,
|
|
||||||
vaultItemCipherType = VaultItemCipherType.LOGIN,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
|
||||||
message = BitwardenString.passkey_operation_failed_because_the_request_is_unsupported
|
|
||||||
.asText(),
|
|
||||||
),
|
|
||||||
viewModel.stateFlow.value.dialog,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `in add mode during fido2, SaveClick should emit fido user verification as optional when verification is PREFERRED`() =
|
fun `in add mode during fido2, SaveClick should emit fido user verification as optional when verification is PREFERRED`() =
|
||||||
@ -2276,7 +2226,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 create`() =
|
fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 create`() =
|
||||||
runTest {
|
runTest {
|
||||||
val errorState = createVaultAddItemState(
|
val errorState = createVaultAddItemState(
|
||||||
dialogState = VaultAddEditState.DialogState.Fido2Error(
|
dialogState = VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -2288,15 +2238,15 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultAddEditAction.Common.Fido2ErrorDialogDismissed(
|
VaultAddEditAction.Common.CredentialErrorDialogDismissed(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertNull(viewModel.stateFlow.value.dialog)
|
assertNull(viewModel.stateFlow.value.dialog)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Error(
|
result = CreateCredentialResult.Error(
|
||||||
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4034,12 +3984,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationLockout should set isUserVerified to false and display Fido2ErrorDialog`() {
|
fun `UserVerificationLockout should set isUserVerified to false and display CredentialErrorDialog`() {
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationLockOut)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationLockOut)
|
||||||
|
|
||||||
verify { bitwardenCredentialManager.isUserVerified = false }
|
verify { bitwardenCredentialManager.isUserVerified = false }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value.dialog,
|
viewModel.stateFlow.value.dialog,
|
||||||
@ -4048,7 +3998,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationCancelled should clear dialog state, set isUserVerified to false, and emit CompleteFido2Create with cancelled result`() =
|
fun `UserVerificationCancelled should clear dialog state, set isUserVerified to false, and emit CompleteCredentialRegistration with cancelled result`() =
|
||||||
runTest {
|
runTest {
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationCancelled)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationCancelled)
|
||||||
|
|
||||||
@ -4056,8 +4006,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
assertNull(viewModel.stateFlow.value.dialog)
|
assertNull(viewModel.stateFlow.value.dialog)
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Cancelled,
|
result = CreateCredentialResult.Cancelled,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
@ -4066,12 +4016,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationFail should set isUserVerified to false, and display Fido2ErrorDialog`() {
|
fun `UserVerificationFail should set isUserVerified to false, and display CredentialErrorDialog`() {
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationFail)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationFail)
|
||||||
|
|
||||||
verify { bitwardenCredentialManager.isUserVerified = false }
|
verify { bitwardenCredentialManager.isUserVerified = false }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value.dialog,
|
viewModel.stateFlow.value.dialog,
|
||||||
@ -4080,12 +4030,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationNotSupported should display Fido2ErrorDialog when active account not found`() {
|
fun `UserVerificationNotSupported should display CredentialErrorDialog when active account not found`() {
|
||||||
mutableUserStateFlow.value = null
|
mutableUserStateFlow.value = null
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationNotSupported)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationNotSupported)
|
||||||
verify { bitwardenCredentialManager.isUserVerified = false }
|
verify { bitwardenCredentialManager.isUserVerified = false }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value.dialog,
|
viewModel.stateFlow.value.dialog,
|
||||||
@ -4181,7 +4131,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `MasterPasswordFido2VerificationSubmit should display Fido2Error when password verification fails`() {
|
fun `MasterPasswordFido2VerificationSubmit should display CredentialError when password verification fails`() {
|
||||||
val password = "password"
|
val password = "password"
|
||||||
coEvery {
|
coEvery {
|
||||||
authRepository.validatePassword(password = password)
|
authRepository.validatePassword(password = password)
|
||||||
@ -4194,7 +4144,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4230,7 +4180,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `MasterPasswordFido2VerificationSubmit should display Fido2Error when user has no retries remaining`() {
|
fun `MasterPasswordFido2VerificationSubmit should display CredentialError when user has no retries remaining`() {
|
||||||
val password = "password"
|
val password = "password"
|
||||||
every { bitwardenCredentialManager.hasAuthenticationAttemptsRemaining() } returns false
|
every { bitwardenCredentialManager.hasAuthenticationAttemptsRemaining() } returns false
|
||||||
coEvery {
|
coEvery {
|
||||||
@ -4244,7 +4194,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4286,7 +4236,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `PinFido2VerificationSubmit should display Fido2Error when Pin verification fails`() {
|
fun `PinFido2VerificationSubmit should display CredentialError when Pin verification fails`() {
|
||||||
val pin = "PIN"
|
val pin = "PIN"
|
||||||
coEvery {
|
coEvery {
|
||||||
authRepository.validatePin(pin = pin)
|
authRepository.validatePin(pin = pin)
|
||||||
@ -4299,7 +4249,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4335,7 +4285,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `PinFido2VerificationSubmit should display Fido2Error when user has no retries remaining`() {
|
fun `PinFido2VerificationSubmit should display CredentialError when user has no retries remaining`() {
|
||||||
val pin = "PIN"
|
val pin = "PIN"
|
||||||
every { bitwardenCredentialManager.hasAuthenticationAttemptsRemaining() } returns false
|
every { bitwardenCredentialManager.hasAuthenticationAttemptsRemaining() } returns false
|
||||||
coEvery {
|
coEvery {
|
||||||
@ -4349,7 +4299,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
message = BitwardenString.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4430,13 +4380,13 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `DismissFido2VerificationDialogClick should display Fido2ErrorDialog`() {
|
fun `DismissFido2VerificationDialogClick should display CredentialErrorDialog`() {
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultAddEditAction.Common.DismissFido2VerificationDialogClick,
|
VaultAddEditAction.Common.DismissFido2VerificationDialogClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString
|
message = BitwardenString
|
||||||
.passkey_operation_failed_because_user_could_not_be_verified
|
.passkey_operation_failed_because_user_could_not_be_verified
|
||||||
.asText(),
|
.asText(),
|
||||||
@ -4447,7 +4397,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationSuccess should display Fido2ErrorDialog when request is invalid`() {
|
fun `UserVerificationSuccess should display CredentialErrorDialog when request is invalid`() {
|
||||||
every { authRepository.activeUserId } returns null
|
every { authRepository.activeUserId } returns null
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.ProviderCreateCredential(
|
SpecialCircumstance.ProviderCreateCredential(
|
||||||
@ -4459,7 +4409,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.CredentialError(
|
||||||
message = BitwardenString.passkey_operation_failed_because_the_request_is_unsupported
|
message = BitwardenString.passkey_operation_failed_because_the_request_is_unsupported
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4499,7 +4449,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteCredentialRegistration result`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockRequest = createMockCreateCredentialRequest(number = 1)
|
val mockRequest = createMockCreateCredentialRequest(number = 1)
|
||||||
val mockResult = Fido2RegisterCredentialResult.Error.InternalError
|
val mockResult = Fido2RegisterCredentialResult.Error.InternalError
|
||||||
@ -4527,8 +4477,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Error(
|
CreateCredentialResult.Error(
|
||||||
BitwardenString.passkey_registration_failed_due_to_an_internal_error
|
BitwardenString.passkey_registration_failed_due_to_an_internal_error
|
||||||
.asText(),
|
.asText(),
|
||||||
),
|
),
|
||||||
@ -4543,7 +4493,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteFido2Registration result`() =
|
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteCredentialRegistration result`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockRequest = createMockCreateCredentialRequest(number = 1)
|
val mockRequest = createMockCreateCredentialRequest(number = 1)
|
||||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||||
@ -4572,8 +4522,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Success(
|
CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
responseJson = "mockResponse",
|
responseJson = "mockResponse",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -49,9 +49,9 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
|||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
||||||
@ -106,7 +106,7 @@ class VaultItemListingScreenTest : BitwardenComposeTest() {
|
|||||||
every { launchUri(any()) } just runs
|
every { launchUri(any()) } just runs
|
||||||
}
|
}
|
||||||
private val credentialProviderCompletionManager: CredentialProviderCompletionManager = mockk {
|
private val credentialProviderCompletionManager: CredentialProviderCompletionManager = mockk {
|
||||||
every { completeFido2Registration(any()) } just runs
|
every { completeCredentialRegistration(any()) } just runs
|
||||||
every { completeFido2Assertion(any()) } just runs
|
every { completeFido2Assertion(any()) } just runs
|
||||||
every { completePasswordGet(any()) } just runs
|
every { completePasswordGet(any()) } just runs
|
||||||
every { completeProviderGetCredentialsRequest(any()) } just runs
|
every { completeProviderGetCredentialsRequest(any()) } just runs
|
||||||
@ -1966,11 +1966,11 @@ class VaultItemListingScreenTest : BitwardenComposeTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `CompleteFido2Registration event should call CredentialProviderCompletionManager with result`() {
|
fun `CompleteCredentialRegistration event should call CredentialProviderCompletionManager with result`() {
|
||||||
val result = RegisterFido2CredentialResult.Success("mockResponse")
|
val result = CreateCredentialResult.Success.Fido2CredentialRegistered("mockResponse")
|
||||||
mutableEventFlow.tryEmit(VaultItemListingEvent.CompleteFido2Registration(result))
|
mutableEventFlow.tryEmit(VaultItemListingEvent.CompleteCredentialRegistration(result))
|
||||||
verify {
|
verify {
|
||||||
credentialProviderCompletionManager.completeFido2Registration(result)
|
credentialProviderCompletionManager.completeCredentialRegistration(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -99,9 +99,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
|||||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.AssertFido2CredentialResult
|
||||||
|
import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetCredentialsResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
import com.x8bit.bitwarden.ui.credentials.manager.model.GetPasswordCredentialResult
|
||||||
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
|
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
|
||||||
@ -261,10 +261,18 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
private val mockGetPublicKeyCredentialOption = mockk<GetPublicKeyCredentialOption> {
|
private val mockGetPublicKeyCredentialOption = mockk<GetPublicKeyCredentialOption> {
|
||||||
every { requestJson } returns "mockRequestJson"
|
every { requestJson } returns "mockRequestJson"
|
||||||
}
|
}
|
||||||
|
private val mockCreatePublicKeyCredentialOption = mockk<CreatePublicKeyCredentialRequest> {
|
||||||
|
every { requestJson } returns "mockRequestJson"
|
||||||
|
every { origin } returns "mockOrigin"
|
||||||
|
}
|
||||||
private val mockProviderGetCredentialRequest = mockk<ProviderGetCredentialRequest> {
|
private val mockProviderGetCredentialRequest = mockk<ProviderGetCredentialRequest> {
|
||||||
every { credentialOptions } returns listOf(mockGetPublicKeyCredentialOption)
|
every { credentialOptions } returns listOf(mockGetPublicKeyCredentialOption)
|
||||||
every { callingAppInfo } returns mockCallingAppInfo
|
every { callingAppInfo } returns mockCallingAppInfo
|
||||||
}
|
}
|
||||||
|
private val mockProviderCreateCredentialRequest = mockk<ProviderCreateCredentialRequest> {
|
||||||
|
every { callingRequest } returns mockCreatePublicKeyCredentialOption
|
||||||
|
every { callingAppInfo } returns mockCallingAppInfo
|
||||||
|
}
|
||||||
private val mockBeginGetPublicKeyCredentialOption = mockk<BeginGetPublicKeyCredentialOption> {
|
private val mockBeginGetPublicKeyCredentialOption = mockk<BeginGetPublicKeyCredentialOption> {
|
||||||
every { requestJson } returns "mockRequestJson"
|
every { requestJson } returns "mockRequestJson"
|
||||||
}
|
}
|
||||||
@ -272,14 +280,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
every { beginGetCredentialOptions } returns listOf(mockBeginGetPublicKeyCredentialOption)
|
every { beginGetCredentialOptions } returns listOf(mockBeginGetPublicKeyCredentialOption)
|
||||||
every { callingAppInfo } returns mockCallingAppInfo
|
every { callingAppInfo } returns mockCallingAppInfo
|
||||||
}
|
}
|
||||||
private val mockCreatePublicKeyCredentialRequest = mockk<CreatePublicKeyCredentialRequest> {
|
|
||||||
every { requestJson } returns "mockRequestJson"
|
|
||||||
every { origin } returns "mockOrigin"
|
|
||||||
}
|
|
||||||
private val mockProviderCreateCredentialRequest = mockk<ProviderCreateCredentialRequest> {
|
|
||||||
every { callingRequest } returns mockCreatePublicKeyCredentialRequest
|
|
||||||
every { callingAppInfo } returns mockCallingAppInfo
|
|
||||||
}
|
|
||||||
private val mutableSnackbarDataFlow: MutableSharedFlow<BitwardenSnackbarData> =
|
private val mutableSnackbarDataFlow: MutableSharedFlow<BitwardenSnackbarData> =
|
||||||
bufferedMutableSharedFlow()
|
bufferedMutableSharedFlow()
|
||||||
private val snackbarRelayManager: SnackbarRelayManager<SnackbarRelay> = mockk {
|
private val snackbarRelayManager: SnackbarRelayManager<SnackbarRelay> = mockk {
|
||||||
@ -303,12 +304,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
ProviderGetCredentialRequest.Companion,
|
ProviderGetCredentialRequest.Companion,
|
||||||
BeginGetCredentialRequest.Companion,
|
BeginGetCredentialRequest.Companion,
|
||||||
)
|
)
|
||||||
every {
|
|
||||||
ProviderCreateCredentialRequest.fromBundle(any())
|
|
||||||
} returns mockProviderCreateCredentialRequest
|
|
||||||
every {
|
every {
|
||||||
ProviderGetCredentialRequest.fromBundle(any())
|
ProviderGetCredentialRequest.fromBundle(any())
|
||||||
} returns mockProviderGetCredentialRequest
|
} returns mockProviderGetCredentialRequest
|
||||||
|
every {
|
||||||
|
ProviderCreateCredentialRequest.fromBundle(any())
|
||||||
|
} returns mockProviderCreateCredentialRequest
|
||||||
every {
|
every {
|
||||||
BeginGetCredentialRequest.fromBundle(any())
|
BeginGetCredentialRequest.fromBundle(any())
|
||||||
} returns mockBeginGetCredentialRequest
|
} returns mockBeginGetCredentialRequest
|
||||||
@ -348,6 +350,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
coEvery {
|
coEvery {
|
||||||
originManager.validateOrigin(any(), any())
|
originManager.validateOrigin(any(), any())
|
||||||
} returns ValidateOriginResult.Success(null)
|
} returns ValidateOriginResult.Success(null)
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
viewModel.stateFlow.test {
|
viewModel.stateFlow.test {
|
||||||
@ -511,6 +515,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
shouldFinishWhenComplete = false,
|
shouldFinishWhenComplete = false,
|
||||||
)
|
)
|
||||||
val searchType = SearchType.Vault.All
|
val searchType = SearchType.Vault.All
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
viewModel.trySendAction(VaultItemListingsAction.SearchIconClick)
|
viewModel.trySendAction(VaultItemListingsAction.SearchIconClick)
|
||||||
@ -677,7 +682,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `ItemClick for vault item during FIDO 2 registration should show FIDO 2 error dialog when cipherView is null`() {
|
fun `ItemClick for vault item during credential registration should show credential error dialog when cipherView is null`() {
|
||||||
val cipherView = createMockCipherView(number = 1)
|
val cipherView = createMockCipherView(number = 1)
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.ProviderCreateCredential(
|
SpecialCircumstance.ProviderCreateCredential(
|
||||||
@ -755,6 +760,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.ItemClick(
|
VaultItemListingsAction.ItemClick(
|
||||||
@ -810,6 +816,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.ItemClick(
|
VaultItemListingsAction.ItemClick(
|
||||||
@ -881,6 +888,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.ItemClick(
|
VaultItemListingsAction.ItemClick(
|
||||||
@ -934,6 +942,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
} returns Fido2RegisterCredentialResult.Success("mockResponse")
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.ItemClick(
|
VaultItemListingsAction.ItemClick(
|
||||||
@ -2313,6 +2322,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
mutableVaultDataStateFlow.value = dataState
|
mutableVaultDataStateFlow.value = dataState
|
||||||
@ -2990,6 +3000,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `icon loading state updates should update isIconLoadingDisabled`() = runTest {
|
fun `icon loading state updates should update isIconLoadingDisabled`() = runTest {
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
assertFalse(viewModel.stateFlow.value.isIconLoadingDisabled)
|
assertFalse(viewModel.stateFlow.value.isIconLoadingDisabled)
|
||||||
@ -3065,6 +3076,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns ValidateOriginResult.Success("mockOrigin")
|
} returns ValidateOriginResult.Success("mockOrigin")
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
createVaultItemListingViewModel()
|
createVaultItemListingViewModel()
|
||||||
|
|
||||||
coVerify(ordering = Ordering.ORDERED) {
|
coVerify(ordering = Ordering.ORDERED) {
|
||||||
@ -3075,9 +3087,10 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ValidateOriginResult should update dialog state on Unknown error`() = runTest {
|
fun `ValidateOriginResult should update dialog state on Unknown error`() = runTest {
|
||||||
|
val mockCredentialsRequest = createMockCreateCredentialRequest(number = 1)
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.ProviderCreateCredential(
|
SpecialCircumstance.ProviderCreateCredential(
|
||||||
createMockCreateCredentialRequest(number = 1),
|
mockCredentialsRequest,
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
originManager.validateOrigin(
|
originManager.validateOrigin(
|
||||||
@ -3086,6 +3099,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns ValidateOriginResult.Error.Unknown
|
} returns ValidateOriginResult.Error.Unknown
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -3111,6 +3125,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns ValidateOriginResult.Error.PrivilegedAppNotAllowed
|
} returns ValidateOriginResult.Error.PrivilegedAppNotAllowed
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -3139,6 +3154,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns ValidateOriginResult.Error.PrivilegedAppSignatureNotFound
|
} returns ValidateOriginResult.Error.PrivilegedAppSignatureNotFound
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -3165,6 +3181,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns ValidateOriginResult.Error.PasskeyNotSupportedForApp
|
} returns ValidateOriginResult.Error.PasskeyNotSupportedForApp
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -3191,6 +3208,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns ValidateOriginResult.Error.AssetLinkNotFound
|
} returns ValidateOriginResult.Error.AssetLinkNotFound
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -3204,7 +3222,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteCredentialRegistration result`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockResult = Fido2RegisterCredentialResult.Error.InternalError
|
val mockResult = Fido2RegisterCredentialResult.Error.InternalError
|
||||||
|
|
||||||
@ -3217,8 +3235,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Error(
|
CreateCredentialResult.Error(
|
||||||
BitwardenString.passkey_registration_failed_due_to_an_internal_error.asText(),
|
BitwardenString.passkey_registration_failed_due_to_an_internal_error.asText(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -3232,7 +3250,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteFido2Registration result`() =
|
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteCredentialRegistration result`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||||
responseJson = "mockResponse",
|
responseJson = "mockResponse",
|
||||||
@ -3247,8 +3265,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
RegisterFido2CredentialResult.Success(
|
CreateCredentialResult.Success.Fido2CredentialRegistered(
|
||||||
responseJson = "mockResponse",
|
responseJson = "mockResponse",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -3268,6 +3286,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
SpecialCircumstance.ProviderCreateCredential(
|
SpecialCircumstance.ProviderCreateCredential(
|
||||||
createMockCreateCredentialRequest(number = 1),
|
createMockCreateCredentialRequest(number = 1),
|
||||||
)
|
)
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.DismissCredentialManagerErrorDialogClick(
|
VaultItemListingsAction.DismissCredentialManagerErrorDialogClick(
|
||||||
@ -3277,8 +3296,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertNull(viewModel.stateFlow.value.dialogState)
|
assertNull(viewModel.stateFlow.value.dialogState)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Error(
|
result = CreateCredentialResult.Error(
|
||||||
"".asText(),
|
"".asText(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -4130,12 +4149,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationCancelled should clear dialog state, set isUserVerified to false, and emit CompleteFido2Registration with cancelled result`() =
|
fun `UserVerificationCancelled should clear dialog state, set isUserVerified to false, and emit CompleteCredentialRegistration with cancelled result`() =
|
||||||
runTest {
|
runTest {
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.ProviderCreateCredential(
|
SpecialCircumstance.ProviderCreateCredential(
|
||||||
createMockCreateCredentialRequest(number = 1),
|
createMockCreateCredentialRequest(number = 1),
|
||||||
)
|
)
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(VaultItemListingsAction.UserVerificationCancelled)
|
viewModel.trySendAction(VaultItemListingsAction.UserVerificationCancelled)
|
||||||
|
|
||||||
@ -4143,8 +4163,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
assertNull(viewModel.stateFlow.value.dialogState)
|
assertNull(viewModel.stateFlow.value.dialogState)
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultItemListingEvent.CompleteFido2Registration(
|
VaultItemListingEvent.CompleteCredentialRegistration(
|
||||||
result = RegisterFido2CredentialResult.Cancelled,
|
result = CreateCredentialResult.Cancelled,
|
||||||
),
|
),
|
||||||
awaitItem(),
|
awaitItem(),
|
||||||
)
|
)
|
||||||
@ -4277,6 +4297,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.UserVerificationSuccess(
|
VaultItemListingsAction.UserVerificationSuccess(
|
||||||
@ -4314,6 +4335,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
responseJson = "mockResponse",
|
responseJson = "mockResponse",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.UserVerificationSuccess(
|
VaultItemListingsAction.UserVerificationSuccess(
|
||||||
@ -5013,6 +5035,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
} returns UserVerificationRequirement.REQUIRED
|
} returns UserVerificationRequirement.REQUIRED
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.ConfirmOverwriteExistingPasskeyClick(
|
VaultItemListingsAction.ConfirmOverwriteExistingPasskeyClick(
|
||||||
@ -5339,6 +5362,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `InternetConnectionErrorReceived should show network error if no internet connection`() =
|
fun `InternetConnectionErrorReceived should show network error if no internet connection`() =
|
||||||
runTest {
|
runTest {
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
VaultItemListingsAction.Internal.InternetConnectionErrorReceived,
|
VaultItemListingsAction.Internal.InternetConnectionErrorReceived,
|
||||||
@ -5372,6 +5396,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
originManager.validateOrigin(any(), any())
|
originManager.validateOrigin(any(), any())
|
||||||
} returns ValidateOriginResult.Error.PrivilegedAppNotAllowed
|
} returns ValidateOriginResult.Error.PrivilegedAppNotAllowed
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
@ -5403,6 +5428,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
mockCallingAppInfo.getSignatureFingerprintAsHexString()
|
mockCallingAppInfo.getSignatureFingerprintAsHexString()
|
||||||
} returns null
|
} returns null
|
||||||
|
|
||||||
|
setupFido2CreateRequest()
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
@ -5791,6 +5817,24 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupFido2CreateRequest(
|
||||||
|
mockCallingAppInfo: CallingAppInfo = this.mockCallingAppInfo,
|
||||||
|
mockCreatePublicKeyCredentialRequest: CreatePublicKeyCredentialRequest =
|
||||||
|
mockk<CreatePublicKeyCredentialRequest> {
|
||||||
|
every { requestJson } returns "mockRequestJson"
|
||||||
|
every { origin } returns "mockOrigin"
|
||||||
|
},
|
||||||
|
mockProviderCreateCredentialRequest: ProviderCreateCredentialRequest =
|
||||||
|
mockk<ProviderCreateCredentialRequest> {
|
||||||
|
every { callingAppInfo } returns mockCallingAppInfo
|
||||||
|
every { callingRequest } returns mockCreatePublicKeyCredentialRequest
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
every {
|
||||||
|
ProviderCreateCredentialRequest.fromBundle(any())
|
||||||
|
} returns mockProviderCreateCredentialRequest
|
||||||
|
}
|
||||||
|
|
||||||
private fun createSavedStateHandleWithVaultItemListingType(
|
private fun createSavedStateHandleWithVaultItemListingType(
|
||||||
vaultItemListingType: VaultItemListingType,
|
vaultItemListingType: VaultItemListingType,
|
||||||
): SavedStateHandle = SavedStateHandle().apply {
|
): SavedStateHandle = SavedStateHandle().apply {
|
||||||
|
|||||||
@ -691,6 +691,7 @@ Do you want to switch to this account?</string>
|
|||||||
<string name="follow_the_steps_from_duo_to_finish_logging_in">Follow the steps from Duo to finish logging in.</string>
|
<string name="follow_the_steps_from_duo_to_finish_logging_in">Follow the steps from Duo to finish logging in.</string>
|
||||||
<string name="launch_duo">Launch Duo</string>
|
<string name="launch_duo">Launch Duo</string>
|
||||||
<string name="your_passkey_will_be_saved_to_your_bitwarden_vault_for_x">Your passkey will be saved to your Bitwarden vault for %1$s</string>
|
<string name="your_passkey_will_be_saved_to_your_bitwarden_vault_for_x">Your passkey will be saved to your Bitwarden vault for %1$s</string>
|
||||||
|
<string name="your_password_will_be_saved_to_your_bitwarden_vault_for_x">Your password will be saved to your Bitwarden vault for %1$s</string>
|
||||||
<string name="passkeys_not_supported_for_this_app">Passkeys not supported for this app</string>
|
<string name="passkeys_not_supported_for_this_app">Passkeys not supported for this app</string>
|
||||||
<string name="passkey_operation_failed_because_browser_is_not_privileged">Passkey operation failed because browser is not privileged</string>
|
<string name="passkey_operation_failed_because_browser_is_not_privileged">Passkey operation failed because browser is not privileged</string>
|
||||||
<string name="passkey_operation_failed_because_browser_signature_does_not_match">Passkey operation failed because browser signature does not match</string>
|
<string name="passkey_operation_failed_because_browser_signature_does_not_match">Passkey operation failed because browser signature does not match</string>
|
||||||
@ -917,6 +918,8 @@ Do you want to switch to this account?</string>
|
|||||||
<string name="passkey_operation_failed_because_the_request_is_unsupported">Passkey operation failed because the request is unsupported.</string>
|
<string name="passkey_operation_failed_because_the_request_is_unsupported">Passkey operation failed because the request is unsupported.</string>
|
||||||
<string name="password_operation_failed_because_the_selected_item_does_not_exist">Password operation failed because the selected item does not exist.</string>
|
<string name="password_operation_failed_because_the_selected_item_does_not_exist">Password operation failed because the selected item does not exist.</string>
|
||||||
<string name="password_operation_failed_because_no_item_was_selected">Password operation failed because no item was selected.</string>
|
<string name="password_operation_failed_because_no_item_was_selected">Password operation failed because no item was selected.</string>
|
||||||
|
<string name="credential_operation_failed_because_the_request_is_invalid">Credential operation failed because the request is invalid.</string>
|
||||||
|
<string name="credential_operation_failed_because_the_request_is_unsupported">Credential operation failed because the request is unsupported.</string>
|
||||||
<string name="credential_operation_failed_because_user_verification_attempts_exceeded">Credential operation failed because user verification attempts exceeded.</string>
|
<string name="credential_operation_failed_because_user_verification_attempts_exceeded">Credential operation failed because user verification attempts exceeded.</string>
|
||||||
<string name="credential_operation_failed_because_user_is_locked_out">Credential operation failed because user is locked out.</string>
|
<string name="credential_operation_failed_because_user_is_locked_out">Credential operation failed because user is locked out.</string>
|
||||||
<string name="credential_operation_failed_because_the_selected_item_does_not_exist">Credential operation failed because the selected item does not exist.</string>
|
<string name="credential_operation_failed_because_the_selected_item_does_not_exist">Credential operation failed because the selected item does not exist.</string>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user