mirror of
https://github.com/bitwarden/android.git
synced 2024-11-28 06:04:16 +03:00
PM-9439: Update cipher list item for passkeys (#3422)
This commit is contained in:
parent
a84694b100
commit
eb771e9dfa
20 changed files with 457 additions and 47 deletions
|
@ -15,6 +15,7 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
|||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
|
@ -144,6 +145,13 @@ interface VaultRepository : CipherManager, VaultLockManager {
|
|||
*/
|
||||
fun getAuthCodesFlow(): StateFlow<DataState<List<VerificationCodeItem>>>
|
||||
|
||||
/**
|
||||
* Get the decrypted list of fido credentials for the current ciphers and user id.
|
||||
*/
|
||||
suspend fun getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList: List<CipherView>,
|
||||
): DecryptFido2CredentialAutofillViewResult
|
||||
|
||||
/**
|
||||
* Emits the totp code result flow to listeners.
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.bitwarden.core.InitOrgCryptoRequest
|
|||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.bitwarden.exporters.ExportFormat
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.send.Send
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.send.SendView
|
||||
|
@ -57,6 +56,7 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
|||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
|
@ -183,6 +183,7 @@ class VaultRepositoryImpl(
|
|||
) { ciphersData, foldersData, collectionsData, sendsData ->
|
||||
VaultData(
|
||||
cipherViewList = ciphersData,
|
||||
fido2CredentialAutofillViewList = null,
|
||||
folderViewList = foldersData,
|
||||
collectionViewList = collectionsData,
|
||||
sendViewList = sendsData.sendViewList,
|
||||
|
@ -523,6 +524,20 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList: List<CipherView>,
|
||||
): DecryptFido2CredentialAutofillViewResult {
|
||||
return vaultSdkSource
|
||||
.decryptFido2CredentialAutofillViews(
|
||||
userId = activeUserId ?: return DecryptFido2CredentialAutofillViewResult.Error,
|
||||
cipherViews = cipherViewList.toTypedArray(),
|
||||
)
|
||||
.fold(
|
||||
onFailure = { DecryptFido2CredentialAutofillViewResult.Error },
|
||||
onSuccess = { DecryptFido2CredentialAutofillViewResult.Success(it) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun emitTotpCodeResult(totpCodeResult: TotpCodeResult) {
|
||||
mutableTotpCodeResultFlow.tryEmit(totpCodeResult)
|
||||
}
|
||||
|
@ -861,22 +876,6 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a filtered list containing elements that match the given [relyingPartyId] and a
|
||||
* credential ID contained in [credentialIds].
|
||||
*/
|
||||
private fun List<Fido2CredentialAutofillView>.filterMatchingCredentials(
|
||||
credentialIds: List<ByteArray>,
|
||||
relyingPartyId: String,
|
||||
): List<Fido2CredentialAutofillView> {
|
||||
val skipCredentialIdFiltering = credentialIds.isEmpty()
|
||||
return filter { fido2CredentialView ->
|
||||
fido2CredentialView.rpId == relyingPartyId &&
|
||||
(skipCredentialIdFiltering ||
|
||||
credentialIds.contains(fido2CredentialView.credentialId))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given [userId] has an associated encrypted PIN key but not a pin-protected user
|
||||
* key. This indicates a scenario in which a user has requested PIN unlocking but requires
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
|
||||
/**
|
||||
* Models result of decrypting the fido2 credential autofill views.
|
||||
*/
|
||||
sealed class DecryptFido2CredentialAutofillViewResult {
|
||||
/**
|
||||
* Credentials decrypted successfully.
|
||||
*/
|
||||
data class Success(
|
||||
val fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>,
|
||||
) : DecryptFido2CredentialAutofillViewResult()
|
||||
|
||||
/**
|
||||
* Generic error while decrypting credentials.
|
||||
*/
|
||||
data object Error : DecryptFido2CredentialAutofillViewResult()
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.send.SendView
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.CollectionView
|
||||
|
@ -12,10 +13,12 @@ import com.bitwarden.vault.FolderView
|
|||
* @param collectionViewList List of decrypted collections.
|
||||
* @param folderViewList List of decrypted folders.
|
||||
* @param sendViewList List of decrypted sends.
|
||||
* @param fido2CredentialAutofillViewList List of decrypted fido 2 credentials.
|
||||
*/
|
||||
data class VaultData(
|
||||
val cipherViewList: List<CipherView>,
|
||||
val collectionViewList: List<CollectionView>,
|
||||
val folderViewList: List<FolderView>,
|
||||
val sendViewList: List<SendView>,
|
||||
val fido2CredentialAutofillViewList: List<Fido2CredentialAutofillView>? = null,
|
||||
)
|
||||
|
|
|
@ -53,6 +53,9 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
* This allows the caller to specify things like padding, size, etc.
|
||||
* @param labelTestTag The optional test tag for the [label].
|
||||
* @param optionsTestTag The optional test tag for the options button.
|
||||
* @param secondSupportingLabel An additional optional text label to display beneath the label and
|
||||
* above the optional supporting label.
|
||||
* @param secondSupportingLabelTestTag The optional test tag for the [secondSupportingLabel].
|
||||
* @param supportingLabel An optional secondary text label to display beneath the label.
|
||||
* @param supportingLabelTestTag The optional test tag for the [supportingLabel].
|
||||
* @param startIconTestTag The optional test tag for the [startIcon].
|
||||
|
@ -68,6 +71,8 @@ fun BitwardenListItem(
|
|||
modifier: Modifier = Modifier,
|
||||
labelTestTag: String? = null,
|
||||
optionsTestTag: String? = null,
|
||||
secondSupportingLabel: String? = null,
|
||||
secondSupportingLabelTestTag: String? = null,
|
||||
supportingLabel: String? = null,
|
||||
supportingLabelTestTag: String? = null,
|
||||
startIconTestTag: String? = null,
|
||||
|
@ -124,6 +129,17 @@ fun BitwardenListItem(
|
|||
}
|
||||
}
|
||||
|
||||
secondSupportingLabel?.let { secondSupportLabel ->
|
||||
Text(
|
||||
text = secondSupportLabel,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.semantics {
|
||||
secondSupportingLabelTestTag?.let { testTag = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
supportingLabel?.let { supportLabel ->
|
||||
Text(
|
||||
text = supportLabel,
|
||||
|
|
|
@ -222,6 +222,7 @@ private fun CipherView.toIconData(
|
|||
login?.uris.toLoginIconData(
|
||||
baseIconUrl = baseIconUrl,
|
||||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
usePasskeyDefaultIcon = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -198,6 +198,8 @@ fun VaultItemListingContent(
|
|||
startIconTestTag = it.iconTestTag,
|
||||
label = it.title,
|
||||
labelTestTag = it.titleTestTag,
|
||||
secondSupportingLabel = it.secondSubtitle,
|
||||
secondSupportingLabelTestTag = it.secondSubtitleTestTag,
|
||||
supportingLabel = it.subtitle,
|
||||
supportingLabelTestTag = it.subtitleTestTag,
|
||||
optionsTestTag = it.optionsTestTag,
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
|
@ -12,6 +13,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResu
|
|||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
|
||||
|
@ -28,6 +30,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.map
|
|||
import com.x8bit.bitwarden.data.platform.util.getFido2RpIdOrNull
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
|
@ -857,6 +860,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||
autofillSelectionData = state.autofillSelectionData,
|
||||
fido2CreationData = state.fido2CredentialRequest,
|
||||
fido2CredentialAutofillViews = vaultData
|
||||
.fido2CredentialAutofillViewList,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -899,6 +904,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
ciphers = vaultData.cipherViewList,
|
||||
matchUri = matchUri,
|
||||
),
|
||||
fido2CredentialAutofillViewList = vaultData.toFido2CredentialAutofillViews(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -919,9 +925,24 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
ciphers = vaultData.cipherViewList,
|
||||
matchUri = matchUri,
|
||||
),
|
||||
fido2CredentialAutofillViewList = vaultData.toFido2CredentialAutofillViews(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt and filter the fido 2 autofill credentials.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
private suspend fun VaultData.toFido2CredentialAutofillViews(): List<Fido2CredentialAutofillView>? =
|
||||
(vaultRepository
|
||||
.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = this
|
||||
.cipherViewList
|
||||
.filter { it.isActiveWithFido2Credentials },
|
||||
)
|
||||
as? DecryptFido2CredentialAutofillViewResult.Success)
|
||||
?.fido2CredentialAutofillViews
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1089,6 +1110,8 @@ data class VaultItemListingState(
|
|||
* @property id the id of the item.
|
||||
* @property title title of the item.
|
||||
* @property titleTestTag The test tag associated with the [title].
|
||||
* @property secondSubtitle The second subtitle of the item (nullable).
|
||||
* @property secondSubtitleTestTag The test tag associated with the [secondSubtitle].
|
||||
* @property subtitle subtitle of the item (nullable).
|
||||
* @property subtitleTestTag The test tag associated with the [subtitle].
|
||||
* @property iconData data for the icon to be displayed (nullable).
|
||||
|
@ -1104,6 +1127,8 @@ data class VaultItemListingState(
|
|||
val id: String,
|
||||
val title: String,
|
||||
val titleTestTag: String,
|
||||
val secondSubtitle: String?,
|
||||
val secondSubtitleTestTag: String?,
|
||||
val subtitle: String?,
|
||||
val subtitleTestTag: String,
|
||||
val iconData: IconData,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.send.SendView
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
|
@ -13,6 +14,7 @@ import com.bitwarden.vault.FolderView
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
|
||||
import com.x8bit.bitwarden.data.platform.util.subtitle
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -101,6 +103,7 @@ fun VaultData.toViewState(
|
|||
isIconLoadingDisabled: Boolean,
|
||||
autofillSelectionData: AutofillSelectionData?,
|
||||
fido2CreationData: Fido2CredentialRequest?,
|
||||
fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?,
|
||||
): VaultItemListingState.ViewState {
|
||||
val filteredCipherViewList = cipherViewList
|
||||
.filter { cipherView ->
|
||||
|
@ -129,6 +132,7 @@ fun VaultData.toViewState(
|
|||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
isAutofill = autofillSelectionData != null,
|
||||
isFido2Creation = fido2CreationData != null,
|
||||
fido2CredentialAutofillViews = fido2CredentialAutofillViews,
|
||||
),
|
||||
displayFolderList = folderList.map { folderView ->
|
||||
VaultItemListingState.FolderDisplayItem(
|
||||
|
@ -259,12 +263,14 @@ fun VaultItemListingState.ItemListingType.updateWithAdditionalDataIfNecessary(
|
|||
is VaultItemListingState.ItemListingType.Send.SendText -> this
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun List<CipherView>.toDisplayItemList(
|
||||
baseIconUrl: String,
|
||||
hasMasterPassword: Boolean,
|
||||
isIconLoadingDisabled: Boolean,
|
||||
isAutofill: Boolean,
|
||||
isFido2Creation: Boolean,
|
||||
fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?,
|
||||
): List<VaultItemListingState.DisplayItem> =
|
||||
this.map {
|
||||
it.toDisplayItem(
|
||||
|
@ -273,6 +279,10 @@ private fun List<CipherView>.toDisplayItemList(
|
|||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
isAutofill = isAutofill,
|
||||
isFido2Creation = isFido2Creation,
|
||||
fido2CredentialAutofillView = fido2CredentialAutofillViews
|
||||
?.firstOrNull { fido2CredentialAutofillView ->
|
||||
fido2CredentialAutofillView.cipherId == it.id
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -287,32 +297,55 @@ private fun List<SendView>.toDisplayItemList(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun CipherView.toDisplayItem(
|
||||
baseIconUrl: String,
|
||||
hasMasterPassword: Boolean,
|
||||
isIconLoadingDisabled: Boolean,
|
||||
isAutofill: Boolean,
|
||||
isFido2Creation: Boolean,
|
||||
fido2CredentialAutofillView: Fido2CredentialAutofillView?,
|
||||
): VaultItemListingState.DisplayItem =
|
||||
VaultItemListingState.DisplayItem(
|
||||
id = id.orEmpty(),
|
||||
title = name,
|
||||
titleTestTag = "CipherNameLabel",
|
||||
subtitle = subtitle,
|
||||
subtitleTestTag = "CipherSubTitleLabel",
|
||||
secondSubtitle = this.toSecondSubtitle(fido2CredentialAutofillView?.rpId),
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
subtitle = this.subtitle,
|
||||
subtitleTestTag = this.toSubtitleTestTag(
|
||||
isAutofill = isAutofill,
|
||||
isFido2Creation = isFido2Creation,
|
||||
),
|
||||
iconData = this.toIconData(
|
||||
baseIconUrl = baseIconUrl,
|
||||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
usePasskeyDefaultIcon = (isAutofill || isFido2Creation) &&
|
||||
this.isActiveWithFido2Credentials,
|
||||
),
|
||||
iconTestTag = toIconTestTag(),
|
||||
extraIconList = toLabelIcons(),
|
||||
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword),
|
||||
iconTestTag = this.toIconTestTag(),
|
||||
extraIconList = this.toLabelIcons(),
|
||||
overflowOptions = this.toOverflowActions(hasMasterPassword = hasMasterPassword),
|
||||
optionsTestTag = "CipherOptionsButton",
|
||||
isAutofill = isAutofill,
|
||||
isFido2Creation = isFido2Creation,
|
||||
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
)
|
||||
|
||||
private fun CipherView.toSecondSubtitle(fido2CredentialRpId: String?): String? =
|
||||
fido2CredentialRpId
|
||||
?.takeIf { this.type == CipherType.LOGIN && it.isNotEmpty() && it != this.name }
|
||||
|
||||
private fun CipherView.toSubtitleTestTag(
|
||||
isAutofill: Boolean,
|
||||
isFido2Creation: Boolean,
|
||||
): String =
|
||||
if ((isAutofill || isFido2Creation)) {
|
||||
if (this.isActiveWithFido2Credentials) "PasskeyName" else "PasswordName"
|
||||
} else {
|
||||
"CipherSubTitleLabel"
|
||||
}
|
||||
|
||||
private fun CipherView.toIconTestTag(): String =
|
||||
when (type) {
|
||||
CipherType.LOGIN -> "LoginCipherIcon"
|
||||
|
@ -324,12 +357,14 @@ private fun CipherView.toIconTestTag(): String =
|
|||
private fun CipherView.toIconData(
|
||||
baseIconUrl: String,
|
||||
isIconLoadingDisabled: Boolean,
|
||||
usePasskeyDefaultIcon: Boolean,
|
||||
): IconData {
|
||||
return when (this.type) {
|
||||
CipherType.LOGIN -> {
|
||||
login?.uris.toLoginIconData(
|
||||
baseIconUrl = baseIconUrl,
|
||||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
usePasskeyDefaultIcon = usePasskeyDefaultIcon,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -347,6 +382,8 @@ private fun SendView.toDisplayItem(
|
|||
id = id.orEmpty(),
|
||||
title = name,
|
||||
titleTestTag = "SendNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = deletionDate.toFormattedPattern(DELETION_DATE_PATTERN, clock),
|
||||
subtitleTestTag = "SendDateLabel",
|
||||
iconData = IconData.Local(
|
||||
|
|
|
@ -146,13 +146,18 @@ fun VaultData.toViewState(
|
|||
fun List<LoginUriView>?.toLoginIconData(
|
||||
isIconLoadingDisabled: Boolean,
|
||||
baseIconUrl: String,
|
||||
usePasskeyDefaultIcon: Boolean,
|
||||
): IconData {
|
||||
val localIconData = IconData.Local(R.drawable.ic_login_item)
|
||||
val defaultIconRes = if (usePasskeyDefaultIcon) {
|
||||
R.drawable.ic_login_item_passkey
|
||||
} else {
|
||||
R.drawable.ic_login_item
|
||||
}
|
||||
|
||||
var uri = this
|
||||
?.map { it.uri }
|
||||
?.firstOrNull { uri -> uri?.contains(".") == true }
|
||||
?: return localIconData
|
||||
?: return IconData.Local(defaultIconRes)
|
||||
|
||||
if (uri.startsWith(ANDROID_URI)) {
|
||||
return IconData.Local(R.drawable.ic_android)
|
||||
|
@ -163,7 +168,7 @@ fun List<LoginUriView>?.toLoginIconData(
|
|||
}
|
||||
|
||||
if (isIconLoadingDisabled) {
|
||||
return localIconData
|
||||
return IconData.Local(defaultIconRes)
|
||||
}
|
||||
|
||||
if (!uri.contains("://")) {
|
||||
|
@ -177,7 +182,7 @@ fun List<LoginUriView>?.toLoginIconData(
|
|||
|
||||
return IconData.Network(
|
||||
uri = url,
|
||||
fallbackIconRes = R.drawable.ic_login_item,
|
||||
fallbackIconRes = defaultIconRes,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -199,6 +204,7 @@ private fun CipherView.toVaultItemOrNull(
|
|||
startIcon = login?.uris.toLoginIconData(
|
||||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
baseIconUrl = baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
),
|
||||
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword),
|
||||
extraIconList = toLabelIcons(),
|
||||
|
|
|
@ -299,6 +299,7 @@ class VerificationCodeViewModel @Inject constructor(
|
|||
startIcon = item.uriLoginViewList.toLoginIconData(
|
||||
baseIconUrl = state.baseIconUrl,
|
||||
isIconLoadingDisabled = state.isIconLoadingDisabled,
|
||||
usePasskeyDefaultIcon = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
7
app/src/main/res/drawable/ic_login_item_passkey.xml
Normal file
7
app/src/main/res/drawable/ic_login_item_passkey.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="23" android:viewportWidth="24" android:width="25.043478dp">
|
||||
|
||||
<path android:fillColor="#175DDC" android:pathData="M4.019,18.894C3.344,18.894 2.797,18.347 2.797,17.672C2.797,16.997 3.344,16.45 4.019,16.45C4.694,16.45 5.241,16.997 5.241,17.672C5.241,18.347 4.694,18.894 4.019,18.894Z"/>
|
||||
|
||||
<path android:fillColor="#175DDC" android:fillType="evenOdd" android:pathData="M7.872,11.322C6.193,10.258 5.079,8.384 5.079,6.25C5.079,2.936 7.765,0.25 11.078,0.25C14.392,0.25 17.079,2.936 17.079,6.25C17.079,8.384 15.964,10.258 14.285,11.322C15.731,11.858 17.002,12.745 17.991,13.879C18.256,14.174 18.489,14.441 18.65,14.669L21.328,14.667L23.991,17.229L20.241,20.575L18.741,19.075L17.241,20.575L15.741,19.075L14.241,20.515L9.761,20.509C8.767,21.896 7.107,22.788 5.246,22.749C2.281,22.687 -0.071,20.287 -0.008,17.388C0.056,14.489 2.51,12.189 5.475,12.251C5.643,12.255 5.81,12.266 5.975,12.284C6.123,12.188 6.291,12.085 6.482,11.975C6.923,11.72 7.387,11.502 7.872,11.322ZM9.888,14.677L16.663,14.671C16.465,14.454 16.201,14.19 15.808,13.879C14.51,12.859 12.866,12.25 11.078,12.25C9.968,12.25 8.914,12.485 7.963,12.907C8.746,13.332 9.408,13.943 9.888,14.677ZM6.579,6.25C6.579,8.735 8.593,10.75 11.078,10.75C13.564,10.75 15.578,8.735 15.578,6.25C15.578,3.765 13.564,1.75 11.078,1.75C8.593,1.75 6.579,3.765 6.579,6.25ZM8.991,19.008L8.542,19.635C7.829,20.629 6.632,21.277 5.277,21.249C3.11,21.204 1.448,19.459 1.492,17.421C1.536,15.381 3.275,13.706 5.443,13.751C6.798,13.779 7.964,14.476 8.632,15.497L9.077,16.178L20.724,16.167L21.785,17.187L20.3,18.512L18.741,16.954L17.241,18.454L15.763,16.975L13.639,19.014L8.991,19.008Z"/>
|
||||
|
||||
</vector>
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.sdk.model
|
||||
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.vault.AttachmentView
|
||||
import com.bitwarden.vault.CardView
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
|
@ -138,6 +139,21 @@ fun createMockSdkFido2Credential(
|
|||
creationDate = clock.instant(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a mock [Fido2CredentialAutofillView] with a given [number] and optional [cipherId].
|
||||
*/
|
||||
fun createMockFido2CredentialAutofillView(
|
||||
number: Int,
|
||||
cipherId: String? = null,
|
||||
): Fido2CredentialAutofillView =
|
||||
Fido2CredentialAutofillView(
|
||||
credentialId = "mockCredentialId-$number".encodeToByteArray(),
|
||||
cipherId = cipherId ?: "mockCipherId-$number",
|
||||
rpId = "mockRpId-$number",
|
||||
userNameForUi = "mockUserNameForUi-$number",
|
||||
userHandle = "mockUserHandle-$number".encodeToByteArray(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a mock [LoginUriView] with a given [number].
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.bitwarden.core.DateTime
|
|||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.exporters.ExportFormat
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.send.SendView
|
||||
import com.bitwarden.vault.CipherView
|
||||
|
@ -77,6 +78,7 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
|||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
|
@ -4166,6 +4168,94 @@ class VaultRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDecryptedFido2CredentialAutofillViews should return error when userId not found`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = null
|
||||
|
||||
val expected = DecryptFido2CredentialAutofillViewResult.Error
|
||||
val result = vaultRepository
|
||||
.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1)),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
result,
|
||||
)
|
||||
coVerify(exactly = 0) {
|
||||
vaultSdkSource.decryptFido2CredentialAutofillViews(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDecryptedFido2CredentialAutofillViews should return error when decryption fails`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val cipherViewList = listOf(createMockCipherView(number = 1))
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFido2CredentialAutofillViews(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherViews = cipherViewList.toTypedArray(),
|
||||
)
|
||||
} returns Throwable().asFailure()
|
||||
|
||||
turbineScope {
|
||||
val expected = DecryptFido2CredentialAutofillViewResult.Error
|
||||
val result = vaultRepository
|
||||
.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = cipherViewList,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
result,
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultSdkSource.decryptFido2CredentialAutofillViews(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherViews = cipherViewList.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `getDecryptedFido2CredentialAutofillViews should return correct results when decryption succeeds`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val cipherViewList = listOf(createMockCipherView(number = 1))
|
||||
val autofillViewList = mockk<List<Fido2CredentialAutofillView>>()
|
||||
val expected = DecryptFido2CredentialAutofillViewResult.Success(
|
||||
fido2CredentialAutofillViews = autofillViewList,
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFido2CredentialAutofillViews(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherViews = cipherViewList.toTypedArray(),
|
||||
)
|
||||
} returns autofillViewList.asSuccess()
|
||||
|
||||
turbineScope {
|
||||
val result = vaultRepository
|
||||
.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = cipherViewList,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
result,
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultSdkSource.decryptFido2CredentialAutofillViews(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherViews = cipherViewList.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//region Helper functions
|
||||
|
||||
/**
|
||||
|
|
|
@ -1536,6 +1536,8 @@ private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
|
|||
id = "mockId-$number",
|
||||
title = "mockTitle-$number",
|
||||
titleTestTag = "SendNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = "mockSubtitle-$number",
|
||||
subtitleTestTag = "SendDateLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_card_item),
|
||||
|
@ -1580,6 +1582,8 @@ private fun createCipherDisplayItem(number: Int): VaultItemListingState.DisplayI
|
|||
id = "mockId-$number",
|
||||
title = "mockTitle-$number",
|
||||
titleTestTag = "CipherNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = "mockSubtitle-$number",
|
||||
subtitleTestTag = "CipherSubTitleLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_vault),
|
||||
|
|
|
@ -36,6 +36,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionV
|
|||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
|
@ -44,6 +45,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayItemForCipher
|
||||
|
@ -284,6 +286,11 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
runTest {
|
||||
setupMockUri()
|
||||
val cipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView),
|
||||
)
|
||||
} returns DecryptFido2CredentialAutofillViewResult.Error
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
|
@ -309,6 +316,11 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -354,7 +366,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1),
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -404,7 +417,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1),
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -928,7 +942,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1),
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -946,6 +961,12 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
val cipherView1 = createMockCipherView(number = 1)
|
||||
val cipherView2 = createMockCipherView(number = 2)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
)
|
||||
} returns DecryptFido2CredentialAutofillViewResult.Success(emptyList())
|
||||
|
||||
// Set up the data to be filtered
|
||||
mockFilteredCiphers = listOf(cipherView1)
|
||||
|
||||
|
@ -976,7 +997,15 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1).copy(isAutofill = true),
|
||||
createMockDisplayItemForCipher(number = 1).copy(
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
subtitleTestTag = "PasskeyName",
|
||||
iconData = IconData.Network(
|
||||
uri = "https://vault.bitwarden.com/icons/www.mockuri.com/icon.png",
|
||||
fallbackIconRes = R.drawable.ic_login_item_passkey,
|
||||
),
|
||||
isAutofill = true,
|
||||
),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -987,6 +1016,11 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
coVerify {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -995,13 +1029,18 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
runTest {
|
||||
setupMockUri()
|
||||
|
||||
val cipherView1 = createMockCipherView(number = 1)
|
||||
val cipherView2 = createMockCipherView(number = 2)
|
||||
|
||||
coEvery {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
)
|
||||
} returns DecryptFido2CredentialAutofillViewResult.Success(emptyList())
|
||||
coEvery {
|
||||
fido2CredentialManager.validateOrigin(any())
|
||||
} returns Fido2ValidateOriginResult.Success
|
||||
|
||||
val cipherView1 = createMockCipherView(number = 1)
|
||||
val cipherView2 = createMockCipherView(number = 2)
|
||||
|
||||
mockFilteredCiphers = listOf(cipherView1)
|
||||
|
||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
||||
|
@ -1035,7 +1074,15 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(isFido2Creation = true),
|
||||
.copy(
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
subtitleTestTag = "PasskeyName",
|
||||
iconData = IconData.Network(
|
||||
uri = "https://vault.bitwarden.com/icons/www.mockuri.com/icon.png",
|
||||
fallbackIconRes = R.drawable.ic_login_item_passkey,
|
||||
),
|
||||
isFido2Creation = true,
|
||||
),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -1046,6 +1093,12 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
coVerify {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
)
|
||||
fido2CredentialManager.validateOrigin(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1139,7 +1192,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1),
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -1249,7 +1303,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1),
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -1369,7 +1424,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1),
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
|
|
@ -16,10 +16,12 @@ import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
|
|||
import com.x8bit.bitwarden.data.platform.util.subtitle
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFido2CredentialAutofillView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
import io.mockk.every
|
||||
|
@ -397,6 +399,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -408,22 +411,28 @@ class VaultItemListingDataExtensionsTest {
|
|||
cipherType = CipherType.LOGIN,
|
||||
subtitle = null,
|
||||
)
|
||||
.copy(shouldShowMasterPasswordReprompt = true),
|
||||
.copy(
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
shouldShowMasterPasswordReprompt = true,
|
||||
),
|
||||
createMockDisplayItemForCipher(
|
||||
number = 2,
|
||||
cipherType = CipherType.CARD,
|
||||
subtitle = null,
|
||||
),
|
||||
)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
createMockDisplayItemForCipher(
|
||||
number = 3,
|
||||
cipherType = CipherType.SECURE_NOTE,
|
||||
subtitle = null,
|
||||
),
|
||||
)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
createMockDisplayItemForCipher(
|
||||
number = 4,
|
||||
cipherType = CipherType.IDENTITY,
|
||||
subtitle = null,
|
||||
),
|
||||
)
|
||||
.copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -455,6 +464,12 @@ class VaultItemListingDataExtensionsTest {
|
|||
folderId = "mockId-1",
|
||||
),
|
||||
)
|
||||
val fido2CredentialAutofillViews = listOf(
|
||||
createMockFido2CredentialAutofillView(
|
||||
cipherId = "mockId-1",
|
||||
number = 1,
|
||||
),
|
||||
)
|
||||
|
||||
val result = VaultData(
|
||||
cipherViewList = cipherViewList,
|
||||
|
@ -472,6 +487,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
),
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = fido2CredentialAutofillViews,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -484,6 +500,13 @@ class VaultItemListingDataExtensionsTest {
|
|||
subtitle = null,
|
||||
)
|
||||
.copy(
|
||||
secondSubtitle = "mockRpId-1",
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
subtitleTestTag = "PasskeyName",
|
||||
iconData = IconData.Network(
|
||||
uri = "https://vault.bitwarden.com/icons/www.mockuri.com/icon.png",
|
||||
fallbackIconRes = R.drawable.ic_login_item_passkey,
|
||||
),
|
||||
isAutofill = true,
|
||||
shouldShowMasterPasswordReprompt = true,
|
||||
),
|
||||
|
@ -492,7 +515,11 @@ class VaultItemListingDataExtensionsTest {
|
|||
cipherType = CipherType.CARD,
|
||||
subtitle = null,
|
||||
)
|
||||
.copy(isAutofill = true),
|
||||
.copy(
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
subtitleTestTag = "PasswordName",
|
||||
isAutofill = true,
|
||||
),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
|
@ -525,6 +552,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -545,6 +573,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -563,6 +592,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
autofillSelectionData = null,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -584,6 +614,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
),
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -608,6 +639,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
origin = "https://www.test.com",
|
||||
),
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -748,6 +780,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -789,6 +822,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
fido2CreationData = null,
|
||||
hasMasterPassword = true,
|
||||
fido2CredentialAutofillViews = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
|
|
@ -23,6 +23,8 @@ fun createMockDisplayItemForCipher(
|
|||
id = "mockId-$number",
|
||||
title = "mockName-$number",
|
||||
titleTestTag = "CipherNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = subtitle,
|
||||
subtitleTestTag = "CipherSubTitleLabel",
|
||||
iconData = IconData.Network(
|
||||
|
@ -75,6 +77,8 @@ fun createMockDisplayItemForCipher(
|
|||
id = "mockId-$number",
|
||||
title = "mockName-$number",
|
||||
titleTestTag = "CipherNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = subtitle,
|
||||
subtitleTestTag = "CipherSubTitleLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_secure_note_item),
|
||||
|
@ -113,6 +117,8 @@ fun createMockDisplayItemForCipher(
|
|||
id = "mockId-$number",
|
||||
title = "mockName-$number",
|
||||
titleTestTag = "CipherNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = subtitle,
|
||||
subtitleTestTag = "CipherSubTitleLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_card_item),
|
||||
|
@ -157,6 +163,8 @@ fun createMockDisplayItemForCipher(
|
|||
id = "mockId-$number",
|
||||
title = "mockName-$number",
|
||||
titleTestTag = "CipherNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = subtitle,
|
||||
subtitleTestTag = "CipherSubTitleLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_identity_item),
|
||||
|
@ -202,6 +210,8 @@ fun createMockDisplayItemForSend(
|
|||
id = "mockId-$number",
|
||||
title = "mockName-$number",
|
||||
titleTestTag = "SendNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = "Oct 27, 2023, 12:00 PM",
|
||||
subtitleTestTag = "SendDateLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_send_file),
|
||||
|
@ -241,6 +251,8 @@ fun createMockDisplayItemForSend(
|
|||
id = "mockId-$number",
|
||||
title = "mockName-$number",
|
||||
titleTestTag = "SendNameLabel",
|
||||
secondSubtitle = null,
|
||||
secondSubtitleTestTag = null,
|
||||
subtitle = "Oct 27, 2023, 12:00 PM",
|
||||
subtitleTestTag = "SendDateLabel",
|
||||
iconData = IconData.Local(R.drawable.ic_send_text),
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.time.Clock
|
|||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class VaultDataExtensionsTest {
|
||||
|
||||
private val clock: Clock = Clock.fixed(
|
||||
|
@ -352,7 +353,6 @@ class VaultDataExtensionsTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should omit non org related totp codes when user does not have premium`() {
|
||||
val vaultData = VaultData(
|
||||
|
@ -390,7 +390,6 @@ class VaultDataExtensionsTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if isIconLoadingDisabled is true`() {
|
||||
val actual =
|
||||
|
@ -403,6 +402,7 @@ class VaultDataExtensionsTest {
|
|||
.toLoginIconData(
|
||||
isIconLoadingDisabled = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
)
|
||||
|
||||
val expected = IconData.Local(iconRes = R.drawable.ic_login_item)
|
||||
|
@ -411,6 +411,26 @@ class VaultDataExtensionsTest {
|
|||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if isIconLoadingDisabled is true and usePasskeyDefaultIcon true`() {
|
||||
val actual =
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
cipherType = CipherType.LOGIN,
|
||||
)
|
||||
.login
|
||||
?.uris
|
||||
.toLoginIconData(
|
||||
isIconLoadingDisabled = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = true,
|
||||
)
|
||||
|
||||
val expected = IconData.Local(iconRes = R.drawable.ic_login_item_passkey)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if no valid uris are found`() {
|
||||
val actual = listOf(
|
||||
|
@ -423,6 +443,7 @@ class VaultDataExtensionsTest {
|
|||
.toLoginIconData(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
)
|
||||
|
||||
val expected = IconData.Local(iconRes = R.drawable.ic_login_item)
|
||||
|
@ -431,6 +452,26 @@ class VaultDataExtensionsTest {
|
|||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if no valid uris are found and usePasskeyDefaultIcon true`() {
|
||||
val actual = listOf(
|
||||
LoginUriView(
|
||||
uri = "",
|
||||
match = UriMatchType.HOST,
|
||||
uriChecksum = null,
|
||||
),
|
||||
)
|
||||
.toLoginIconData(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = true,
|
||||
)
|
||||
|
||||
val expected = IconData.Local(iconRes = R.drawable.ic_login_item_passkey)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if an Android uri is detected`() {
|
||||
val actual = listOf(
|
||||
|
@ -443,6 +484,7 @@ class VaultDataExtensionsTest {
|
|||
.toLoginIconData(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
)
|
||||
|
||||
val expected = IconData.Local(iconRes = R.drawable.ic_android)
|
||||
|
@ -450,7 +492,6 @@ class VaultDataExtensionsTest {
|
|||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if an iOS uri is detected`() {
|
||||
val actual = listOf(
|
||||
|
@ -463,6 +504,7 @@ class VaultDataExtensionsTest {
|
|||
.toLoginIconData(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
)
|
||||
|
||||
val expected = IconData.Local(iconRes = R.drawable.ic_ios)
|
||||
|
@ -470,7 +512,6 @@ class VaultDataExtensionsTest {
|
|||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return IconData Network type if isIconLoadingDisabled is false`() {
|
||||
mockkStatic(Uri::class)
|
||||
|
@ -488,6 +529,7 @@ class VaultDataExtensionsTest {
|
|||
.toLoginIconData(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
)
|
||||
|
||||
val expected = IconData.Network(
|
||||
|
@ -501,6 +543,36 @@ class VaultDataExtensionsTest {
|
|||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return IconData Network type if isIconLoadingDisabled is false and usePasskeyDefaultIcon`() {
|
||||
mockkStatic(Uri::class)
|
||||
val uriMock = mockk<Uri>()
|
||||
every { Uri.parse(any()) } returns uriMock
|
||||
every { uriMock.host } returns "www.mockuri1.com"
|
||||
|
||||
val actual =
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
cipherType = CipherType.LOGIN,
|
||||
)
|
||||
.login
|
||||
?.uris
|
||||
.toLoginIconData(
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
usePasskeyDefaultIcon = true,
|
||||
)
|
||||
|
||||
val expected = IconData.Network(
|
||||
uri = "https://vault.bitwarden.com/icons/www.mockuri1.com/icon.png",
|
||||
fallbackIconRes = R.drawable.ic_login_item_passkey,
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
|
||||
unmockkStatic(Uri::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState should only count deleted items for the trash count`() {
|
||||
val vaultData = VaultData(
|
||||
|
@ -539,7 +611,6 @@ class VaultDataExtensionsTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState with over 100 no folder items should show no folder option`() {
|
||||
mockkStatic(Uri::class)
|
||||
|
|
|
@ -546,6 +546,7 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
startIcon = cipherView.login?.uris.toLoginIconData(
|
||||
isIconLoadingDisabled = initialState.isIconLoadingDisabled,
|
||||
baseIconUrl = initialState.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
@ -566,6 +567,7 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
startIcon = cipherView.login?.uris.toLoginIconData(
|
||||
isIconLoadingDisabled = initialState.isIconLoadingDisabled,
|
||||
baseIconUrl = initialState.baseIconUrl,
|
||||
usePasskeyDefaultIcon = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue