[PM-18092] Update cipher delete restore permissions (#5075)

Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
This commit is contained in:
André Bispo
2025-05-05 13:56:58 +00:00
committed by GitHub
co-authored by Patrick Honkonen
parent 639ca02739
commit 1a2beea770
30 changed files with 655 additions and 14 deletions
@@ -41,6 +41,7 @@ sealed class FlagKey<out T : Any> {
ChromeAutofill,
MobileErrorReporting,
FlightRecorder,
RestrictCipherItemDeletion,
PreAuthSettings,
)
}
@@ -194,6 +195,15 @@ sealed class FlagKey<out T : Any> {
override val isRemotelyConfigured: Boolean = true
}
/**
* Data object holding the feature flag key to enable the restriction of cipher item deletion
*/
data object RestrictCipherItemDeletion : FlagKey<Boolean>() {
override val keyName: String = "pm-15493-restrict-item-deletion-to-can-manage-permission"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = false
}
/**
* Data object holding the feature flag key to enable the settings menu before login.
*/
@@ -4,6 +4,7 @@ import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.fido.Fido2CredentialAutofillView
import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
@@ -24,15 +25,23 @@ class Fido2CredentialStoreImpl(
/**
* Return all active ciphers that contain FIDO 2 credentials.
*/
override suspend fun allCredentials(): List<CipherView> {
override suspend fun allCredentials(): List<CipherListView> {
val syncResult = vaultRepository.syncForResult()
if (syncResult is SyncVaultDataResult.Error) {
syncResult.throwable
?.let { throw it }
?: throw IllegalStateException("Sync failed.")
}
return vaultRepository.ciphersStateFlow.value.data
val activeCipherIds = vaultRepository.ciphersStateFlow.value.data
?.filter { it.isActiveWithFido2Credentials }
?.map { it.id }
?: emptyList()
return vaultRepository.ciphersListViewStateFlow.value.data
?.filter { clv ->
activeCipherIds
.contains(clv.id)
}
?: emptyList()
}
@@ -8,6 +8,7 @@ import com.bitwarden.fido.Fido2CredentialAutofillView
import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.send.SendType
import com.bitwarden.send.SendView
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherView
import com.bitwarden.vault.CollectionView
import com.bitwarden.vault.FolderView
@@ -65,6 +66,14 @@ interface VaultRepository : CipherManager, VaultLockManager {
*/
val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
/**
* Flow that represents all ciphers for the active user.
*
* Note that the [StateFlow.value] will return the last known value but the [StateFlow] itself
* must be collected in order to trigger state changes.
*/
val ciphersListViewStateFlow: StateFlow<DataState<List<CipherListView>>>
/**
* Flow that represents all collections for the active user.
*
@@ -30,6 +30,7 @@ import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.send.Send
import com.bitwarden.send.SendType
import com.bitwarden.send.SendView
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherType
import com.bitwarden.vault.CipherView
import com.bitwarden.vault.CollectionView
@@ -169,6 +170,9 @@ class VaultRepositoryImpl(
private val mutableCiphersStateFlow =
MutableStateFlow<DataState<List<CipherView>>>(DataState.Loading)
private val mutableCiphersListViewStateFlow =
MutableStateFlow<DataState<List<CipherListView>>>(DataState.Loading)
private val mutableFoldersStateFlow =
MutableStateFlow<DataState<List<FolderView>>>(DataState.Loading)
@@ -214,6 +218,9 @@ class VaultRepositoryImpl(
override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
get() = mutableCiphersStateFlow.asStateFlow()
override val ciphersListViewStateFlow: StateFlow<DataState<List<CipherListView>>>
get() = mutableCiphersListViewStateFlow.asStateFlow()
override val domainsStateFlow: StateFlow<DataState<DomainsData>>
get() = mutableDomainsStateFlow.asStateFlow()
@@ -260,6 +267,15 @@ class VaultRepositoryImpl(
}
.launchIn(unconfinedScope)
mutableCiphersListViewStateFlow
.observeWhenSubscribedAndUnlocked(
userStateFlow = authDiskSource.userStateFlow,
vaultUnlockFlow = vaultUnlockDataStateFlow,
) { activeUserId ->
observeVaultDiskCiphersToCipherListView(activeUserId)
}
.launchIn(unconfinedScope)
// Setup domains MutableStateFlow
mutableDomainsStateFlow
.observeWhenSubscribedAndLoggedIn(
@@ -1071,6 +1087,27 @@ class VaultRepositoryImpl(
.map { it.orLoadingIfNotSynced(userId = userId) }
.onEach { mutableCiphersStateFlow.value = it }
private fun observeVaultDiskCiphersToCipherListView(
userId: String,
): Flow<DataState<List<CipherListView>>> =
vaultDiskSource
.getCiphers(userId = userId)
.onStart { mutableCiphersListViewStateFlow.updateToPendingOrLoading() }
.map {
waitUntilUnlocked(userId = userId)
vaultSdkSource
.decryptCipherListCollection(
userId = userId,
cipherList = it.toEncryptedSdkCipherList(),
)
.fold(
onSuccess = { ciphers -> DataState.Loaded(ciphers.sortAlphabetically()) },
onFailure = { throwable -> DataState.Error(throwable) },
)
}
.map { it.orLoadingIfNotSynced(userId = userId) }
.onEach { mutableCiphersListViewStateFlow.value = it }
private fun observeVaultDiskDomains(
userId: String,
): Flow<DataState<DomainsData>> =
@@ -15,6 +15,8 @@ import com.bitwarden.network.model.UriMatchTypeJson
import com.bitwarden.vault.Attachment
import com.bitwarden.vault.Card
import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherPermissions
import com.bitwarden.vault.CipherRepromptType
import com.bitwarden.vault.CipherType
import com.bitwarden.vault.CipherView
@@ -68,6 +70,7 @@ fun Cipher.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
notes = notes,
reprompt = reprompt.toNetworkRepromptType(),
passwordHistory = passwordHistory?.toEncryptedNetworkPasswordHistoryList(),
permissions = permissions?.toEncryptedNetworkCipherPermissions(),
type = type.toNetworkCipherType(),
login = login?.toEncryptedNetworkLogin(),
secureNote = secureNote?.toEncryptedNetworkSecureNote(),
@@ -299,6 +302,17 @@ private fun PasswordHistory.toEncryptedNetworkPasswordHistory(): SyncResponseJso
lastUsedDate = ZonedDateTime.ofInstant(lastUsedDate, ZoneOffset.UTC),
)
/**
* Converts a Bitwarden SDK [CipherPermissions] object to a corresponding
* [SyncResponseJson.Cipher.CipherPermissions] object.
*/
@Suppress("MaxLineLength")
private fun CipherPermissions.toEncryptedNetworkCipherPermissions(): SyncResponseJson.Cipher.CipherPermissions =
SyncResponseJson.Cipher.CipherPermissions(
delete = delete,
restore = restore,
)
/**
* Converts a Bitwarden SDK [CipherRepromptType] object to a corresponding
* [CipherRepromptTypeJson] object.
@@ -357,6 +371,7 @@ fun SyncResponseJson.Cipher.toEncryptedSdkCipher(): Cipher =
attachments = attachments?.toSdkAttachmentList(),
fields = fields?.toSdkFieldList(),
passwordHistory = passwordHistory?.toSdkPasswordHistoryList(),
permissions = permissions?.toSdkPermissions(),
creationDate = creationDate.toInstant(),
deletedDate = deletedDate?.toInstant(),
revisionDate = revisionDate.toInstant(),
@@ -531,6 +546,16 @@ fun SyncResponseJson.Cipher.PasswordHistory.toSdkPasswordHistory(): PasswordHist
lastUsedDate = lastUsedDate.toInstant(),
)
/**
* Transforms a [SyncResponseJson.Cipher.CipherPermissions] into
* a corresponding Bitwarden SDK [CipherPermissions].
*/
fun SyncResponseJson.Cipher.CipherPermissions.toSdkPermissions(): CipherPermissions =
CipherPermissions(
delete = delete,
restore = restore,
)
/**
* Transforms a [CipherTypeJson] to the corresponding Bitwarden SDK [CipherType].
*/
@@ -588,3 +613,16 @@ fun List<CipherView>.sortAlphabetically(): List<CipherView> {
},
)
}
/**
* Sorts the data in alphabetical order by name. Using lexicographical sorting but giving
* precedence to special characters over letters and digits.
*/
@JvmName("toAlphabeticallySortedCipherListView")
fun List<CipherListView>.sortAlphabetically(): List<CipherListView> {
return this.sortedWith(
comparator = { cipher1, cipher2 ->
SpecialCharWithPrecedenceComparator.compare(cipher1.name, cipher2.name)
},
)
}
@@ -41,6 +41,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
FlagKey.ChromeAutofill,
FlagKey.MobileErrorReporting,
FlagKey.FlightRecorder,
FlagKey.RestrictCipherItemDeletion,
FlagKey.PreAuthSettings,
-> {
@Suppress("UNCHECKED_CAST")
@@ -102,5 +103,6 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
FlagKey.ChromeAutofill -> stringResource(R.string.enable_chrome_autofill)
FlagKey.MobileErrorReporting -> stringResource(R.string.enable_error_reporting_dialog)
FlagKey.FlightRecorder -> stringResource(R.string.enable_flight_recorder)
FlagKey.RestrictCipherItemDeletion -> stringResource(R.string.restrict_item_deletion)
FlagKey.PreAuthSettings -> stringResource(R.string.enable_pre_auth_settings)
}
@@ -26,12 +26,14 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResu
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
import com.x8bit.bitwarden.data.autofill.fido2.util.getCreatePasskeyCredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.CoachMarkTourType
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
@@ -119,6 +121,7 @@ class VaultAddEditViewModel @Inject constructor(
private val organizationEventManager: OrganizationEventManager,
private val networkConnectionManager: NetworkConnectionManager,
private val firstTimeActionManager: FirstTimeActionManager,
private val featureFlagManager: FeatureFlagManager,
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
// We load the state from the savedStateHandle for testing purposes.
initialState = savedStateHandle[KEY_STATE]
@@ -1719,6 +1722,10 @@ class VaultAddEditViewModel @Inject constructor(
vaultData: VaultData?,
userData: UserState?,
): VaultAddEditState {
val restrictCipherItemDeletionEnabled = featureFlagManager
.getFeatureFlag(
FlagKey.RestrictCipherItemDeletion,
)
val internalVaultData = vaultData
?: VaultData(
cipherViewList = emptyList(),
@@ -1737,10 +1744,15 @@ class VaultAddEditViewModel @Inject constructor(
currentAccount = userData?.activeAccount,
vaultAddEditType = vaultAddEditType,
) { currentAccount, cipherView ->
val canDelete = internalVaultData
.collectionViewList
.hasDeletePermissionInAtLeastOneCollection(cipherView?.collectionIds)
val canDelete = if (restrictCipherItemDeletionEnabled &&
cipherView?.permissions?.delete != null
) {
cipherView.permissions?.delete == true
} else {
internalVaultData
.collectionViewList
.hasDeletePermissionInAtLeastOneCollection(cipherView?.collectionIds)
}
val canAssignToCollections = internalVaultData
.collectionViewList
@@ -2241,16 +2253,21 @@ data class VaultAddEditState(
* This is only present when editing a pre-existing cipher.
* @property name Represents the name for the item type. This is an abstract property
* that must be overridden to save the item.
* @property isUnlockWithPasswordEnabled Indicates whether the user is allowed to
* unlock with a password.
* @property masterPasswordReprompt Indicates if a master password reprompt is required.
* @property favorite Indicates whether this item is marked as a favorite.
* @property customFieldData Additional custom fields associated with the item.
* @property notes Any additional notes or comments associated with the item.
* @property selectedCollectionId The ID of the collection that this item belongs to.
* @property selectedFolderId The ID of the folder that this item belongs to.
* @property availableFolders The list of folders that this item could be added too.
* @property selectedOwnerId The ID of the owner associated with the item.
* @property availableOwners A list of available owners.
* @property hasOrganizations Indicates if the user is part of any organizations.
* @property canDelete Indicates whether the current user can delete the item.
* @property canAssignToCollections Indicates whether the current user can assign the
* item to a collection.
*/
@Parcelize
data class Common(
@@ -166,7 +166,7 @@ fun VaultItemScreen(
{ viewModel.trySendAction(VaultItemAction.Common.CloseClick) }
},
actions = {
if (state.isCipherDeleted && state.canDelete) {
if (state.canRestore) {
BitwardenTextButton(
label = stringResource(id = R.string.restore),
onClick = remember(viewModel) {
@@ -17,8 +17,10 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
@@ -70,6 +72,7 @@ class VaultItemViewModel @Inject constructor(
private val organizationEventManager: OrganizationEventManager,
private val environmentRepository: EnvironmentRepository,
private val settingsRepository: SettingsRepository,
private val featureFlagManager: FeatureFlagManager,
) : BaseViewModel<VaultItemState, VaultItemEvent, VaultItemAction>(
// We load the state from the savedStateHandle for testing purposes.
initialState = savedStateHandle[KEY_STATE] ?: run {
@@ -105,6 +108,10 @@ class VaultItemViewModel @Inject constructor(
vaultRepository.collectionsStateFlow,
vaultRepository.foldersStateFlow,
) { cipherViewState, userState, authCodeState, collectionsState, folderState ->
val restrictCipherItemDeletionEnabled = featureFlagManager
.getFeatureFlag(
FlagKey.RestrictCipherItemDeletion,
)
val totpCodeData = authCodeState.data?.let {
TotpCodeItemData(
periodSeconds = it.periodSeconds,
@@ -126,10 +133,24 @@ class VaultItemViewModel @Inject constructor(
}
.mapNullable {
val cipherView = cipherViewState.data
val canDelete = collectionsState.data
.hasDeletePermissionInAtLeastOneCollection(
val canDelete = if (restrictCipherItemDeletionEnabled &&
cipherView?.permissions?.delete != null
) {
cipherView.permissions?.delete == true
} else {
collectionsState.data.hasDeletePermissionInAtLeastOneCollection(
collectionIds = cipherView?.collectionIds,
)
}
val canRestore = if (restrictCipherItemDeletionEnabled &&
cipherView?.permissions?.restore != null
) {
cipherView.permissions?.restore == true &&
cipherView.deletedDate != null
} else {
canDelete && cipherView?.deletedDate != null
}
val canAssignToCollections = collectionsState.data
.canAssignToCollections(cipherView?.collectionIds)
@@ -167,6 +188,7 @@ class VaultItemViewModel @Inject constructor(
cipher = cipherView,
totpCodeItemData = totpCodeData,
canDelete = canDelete,
canRestore = canRestore,
canAssociateToCollections = canAssignToCollections,
canEdit = canEdit,
relatedLocations = relatedLocations,
@@ -1200,6 +1222,7 @@ class VaultItemViewModel @Inject constructor(
hasMasterPassword = account.hasMasterPassword,
totpCodeItemData = this.data?.totpCodeItemData,
canDelete = this.data?.canDelete == true,
canRestore = this.data?.canRestore == true,
canAssignToCollections = this.data?.canAssociateToCollections == true,
canEdit = this.data?.canEdit == true,
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
@@ -1496,6 +1519,14 @@ data class VaultItemState(
?.common
?.canDelete == true
/**
* Whether or not the cipher can be deleted.
*/
val canRestore: Boolean
get() = viewState.asContentOrNull()
?.common
?.canRestore == true
val canAssignToCollections: Boolean
get() = viewState.asContentOrNull()
?.common
@@ -1555,6 +1586,7 @@ data class VaultItemState(
* @property currentCipher The cipher that is currently being viewed (nullable).
* @property attachments A list of attachments associated with the cipher.
* @property canDelete Indicates if the cipher can be deleted.
* @property canRestore Indicates if the cipher can be restored.
* @property canAssignToCollections Indicates if the cipher can be assigned to
* collections.
* @property favorite Indicates that the cipher is favorite.
@@ -1572,6 +1604,7 @@ data class VaultItemState(
val currentCipher: CipherView? = null,
val attachments: List<AttachmentItem>?,
val canDelete: Boolean,
val canRestore: Boolean,
val canAssignToCollections: Boolean,
val canEdit: Boolean,
val favorite: Boolean,
@@ -17,6 +17,7 @@ data class VaultItemStateData(
val cipher: CipherView?,
val totpCodeItemData: TotpCodeItemData?,
val canDelete: Boolean,
val canRestore: Boolean,
val canAssociateToCollections: Boolean,
val canEdit: Boolean,
val relatedLocations: ImmutableList<VaultItemLocation>,
@@ -45,6 +45,7 @@ fun CipherView.toViewState(
totpCodeItemData: TotpCodeItemData?,
clock: Clock = Clock.systemDefaultZone(),
canDelete: Boolean,
canRestore: Boolean,
canAssignToCollections: Boolean,
canEdit: Boolean,
baseIconUrl: String,
@@ -98,6 +99,7 @@ fun CipherView.toViewState(
}
.orEmpty(),
canDelete = canDelete,
canRestore = canRestore,
canAssignToCollections = canAssignToCollections,
canEdit = canEdit,
favorite = this.favorite,
@@ -40,6 +40,7 @@ fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
attachments = common.originalCipher?.attachments,
organizationUseTotp = common.originalCipher?.organizationUseTotp ?: false,
passwordHistory = toPasswordHistory(),
permissions = common.originalCipher?.permissions,
creationDate = common.originalCipher?.creationDate ?: Instant.now(),
deletedDate = common.originalCipher?.deletedDate,
revisionDate = common.originalCipher?.revisionDate ?: Instant.now(),
@@ -26,6 +26,7 @@
<string name="enable_chrome_autofill">Enable chrome autofill</string>
<string name="enable_error_reporting_dialog">Enable error reporting dialog</string>
<string name="enable_flight_recorder">Enable flight recorder</string>
<string name="restrict_item_deletion">Restrict item deletion</string>
<string name="enable_pre_auth_settings">Enabled pre-auth settings</string>
<!-- /Debug Menu -->
</resources>
@@ -69,6 +69,10 @@ class FlagKeyTest {
FlagKey.FlightRecorder.keyName,
"enable-pm-flight-recorder",
)
assertEquals(
FlagKey.RestrictCipherItemDeletion.keyName,
"pm-15493-restrict-item-deletion-to-can-manage-permission",
)
assertEquals(
FlagKey.PreAuthSettings.keyName,
"enable-pm-prelogin-settings",
@@ -94,6 +98,7 @@ class FlagKeyTest {
FlagKey.ChromeAutofill,
FlagKey.MobileErrorReporting,
FlagKey.FlightRecorder,
FlagKey.RestrictCipherItemDeletion,
FlagKey.PreAuthSettings,
).all {
!it.defaultValue
@@ -129,6 +134,7 @@ class FlagKeyTest {
listOf(
FlagKey.FlightRecorder,
FlagKey.PreAuthSettings,
FlagKey.RestrictCipherItemDeletion,
).all {
!it.isRemotelyConfigured
},
@@ -326,6 +326,10 @@ private const val CIPHER_JSON = """
"lastUsedDate": "2023-10-27T12:00:00.000Z"
}
],
"permissions": {
"delete": true,
"restore": true
},
"revisionDate": "2023-10-27T12:00:00.000Z",
"type": 1,
"login": {
@@ -83,6 +83,7 @@ fun createMockCipherView(
sshKey = sshKey.takeIf { cipherType == CipherType.SSH_KEY },
favorite = false,
passwordHistory = listOf(createMockPasswordHistoryView(number = number, clock)),
permissions = createMockSdkCipherPermissions(),
reprompt = repromptType,
secureNote = createMockSecureNoteView().takeIf { cipherType == CipherType.SECURE_NOTE },
edit = true,
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk.model
import com.bitwarden.vault.Attachment
import com.bitwarden.vault.Card
import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherPermissions
import com.bitwarden.vault.CipherRepromptType
import com.bitwarden.vault.CipherType
import com.bitwarden.vault.Field
@@ -53,6 +54,7 @@ fun createMockSdkCipher(number: Int, clock: Clock = FIXED_CLOCK): Cipher =
sshKey = createMockSdkSshKey(number = number),
favorite = false,
passwordHistory = listOf(createMockSdkPasswordHistory(number = number, clock = clock)),
permissions = createMockSdkCipherPermissions(),
reprompt = CipherRepromptType.NONE,
secureNote = createMockSdkSecureNote(),
edit = false,
@@ -78,6 +80,18 @@ fun createMockSdkPasswordHistory(number: Int, clock: Clock): PasswordHistory =
lastUsedDate = clock.instant(),
)
/**
* Create a mock [CipherPermissions] with a given [delete] and [restore] permissions.
*/
fun createMockSdkCipherPermissions(
delete: Boolean = true,
restore: Boolean = true,
): CipherPermissions =
CipherPermissions(
delete = delete,
restore = restore,
)
/**
* Create a mock [Identity] with a given [number].
*/
@@ -135,6 +135,7 @@ private val DEFAULT_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf
FlagKey.ChromeAutofill to true,
FlagKey.MobileErrorReporting to true,
FlagKey.FlightRecorder to true,
FlagKey.RestrictCipherItemDeletion to true,
FlagKey.PreAuthSettings to true,
)
@@ -154,6 +155,7 @@ private val UPDATED_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf
FlagKey.ChromeAutofill to false,
FlagKey.MobileErrorReporting to false,
FlagKey.FlightRecorder to false,
FlagKey.RestrictCipherItemDeletion to false,
FlagKey.PreAuthSettings to false,
)
@@ -38,6 +38,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
@@ -46,6 +47,7 @@ import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardMan
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.CoachMarkTourType
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
@@ -56,6 +58,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createEditCollectionV
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createEditExceptPasswordsCollectionView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createManageCollectionView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipherPermissions
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createViewCollectionView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createViewExceptPasswordsCollectionView
@@ -190,6 +193,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
every { shouldShowAddLoginCoachMarkFlow } returns mutableShouldShowAddLoginCoachMarkFlow
}
private val featureFlagManager: FeatureFlagManager = mockk {
every { getFeatureFlag(key = FlagKey.RestrictCipherItemDeletion) } returns false
}
@BeforeEach
fun setup() {
mockkStatic(CipherView::toViewState)
@@ -1146,10 +1153,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
.copy(shouldExitOnSave = true)
val mockProviderCreateCredentialRequest: ProviderCreateCredentialRequest =
mockk<ProviderCreateCredentialRequest>(relaxed = true) {
every { callingAppInfo } returns mockk(relaxed = true)
every { callingRequest } returns mockk<CreatePasswordRequest>(relaxed = true)
}
mockk<ProviderCreateCredentialRequest>(relaxed = true) {
every { callingAppInfo } returns mockk(relaxed = true)
every { callingRequest } returns mockk<CreatePasswordRequest>(relaxed = true)
}
every {
ProviderCreateCredentialRequest.fromBundle(any())
@@ -1304,6 +1311,152 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `in edit mode, canDelete should be false when cipher permission is false`() =
runTest {
val cipherView = createMockCipherView(1)
.copy(
permissions = createMockSdkCipherPermissions(
delete = false,
restore = false,
),
)
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
val stateWithName = createVaultAddItemState(
vaultAddEditType = vaultAddEditType,
commonContentViewState = createCommonContentViewState(
name = "mockName-1",
originalCipher = cipherView,
customFieldData = listOf(
VaultAddEditState.Custom.HiddenField(
itemId = "testId",
name = "mockName-1",
value = "mockValue-1",
),
),
notes = "mockNotes-1",
canDelete = false,
),
)
every {
featureFlagManager.getFeatureFlag(FlagKey.RestrictCipherItemDeletion)
} returns true
every {
cipherView.toViewState(
isClone = false,
isIndividualVaultDisabled = false,
totpData = null,
resourceManager = resourceManager,
clock = fixedClock,
canDelete = false,
canAssignToCollections = true,
)
} returns stateWithName.viewState
mutableVaultDataFlow.value = DataState.Loaded(
data = createVaultData(
cipherView = cipherView,
collectionViewList = listOf(
createEditCollectionView(number = 1),
),
),
)
createAddVaultItemViewModel(
createSavedStateHandleWithState(
state = stateWithName,
vaultAddEditType = vaultAddEditType,
vaultItemCipherType = VaultItemCipherType.LOGIN,
),
)
verify {
cipherView.toViewState(
isClone = false,
isIndividualVaultDisabled = false,
totpData = null,
resourceManager = resourceManager,
clock = fixedClock,
canDelete = false,
canAssignToCollections = true,
)
}
}
@Test
fun `in edit mode, canDelete should be true when cipher permission is true`() =
runTest {
val cipherView = createMockCipherView(1)
.copy(
permissions = createMockSdkCipherPermissions(
delete = true,
restore = false,
),
)
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
val stateWithName = createVaultAddItemState(
vaultAddEditType = vaultAddEditType,
commonContentViewState = createCommonContentViewState(
name = "mockName-1",
originalCipher = cipherView,
customFieldData = listOf(
VaultAddEditState.Custom.HiddenField(
itemId = "testId",
name = "mockName-1",
value = "mockValue-1",
),
),
notes = "mockNotes-1",
canDelete = true,
),
)
every {
featureFlagManager.getFeatureFlag(FlagKey.RestrictCipherItemDeletion)
} returns true
every {
cipherView.toViewState(
isClone = false,
isIndividualVaultDisabled = false,
totpData = null,
resourceManager = resourceManager,
clock = fixedClock,
canDelete = true,
canAssignToCollections = true,
)
} returns stateWithName.viewState
mutableVaultDataFlow.value = DataState.Loaded(
data = createVaultData(
cipherView = cipherView,
collectionViewList = listOf(
createEditCollectionView(number = 1),
),
),
)
createAddVaultItemViewModel(
createSavedStateHandleWithState(
state = stateWithName,
vaultAddEditType = vaultAddEditType,
vaultItemCipherType = VaultItemCipherType.LOGIN,
),
)
verify {
cipherView.toViewState(
isClone = false,
isIndividualVaultDisabled = false,
totpData = null,
resourceManager = resourceManager,
clock = fixedClock,
canDelete = true,
canAssignToCollections = true,
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in edit mode, canDelete should be false when cipher is in a collection the user cannot manage`() =
@@ -3243,6 +3396,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
organizationEventManager = organizationEventManager,
networkConnectionManager = networkConnectionManager,
firstTimeActionManager = firstTimeActionManager,
featureFlagManager = featureFlagManager,
)
}
@@ -4582,6 +4736,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
organizationEventManager = organizationEventManager,
networkConnectionManager = networkConnectionManager,
firstTimeActionManager = firstTimeActionManager,
featureFlagManager = featureFlagManager,
)
private fun createVaultData(
@@ -25,6 +25,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
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.createMockFolderView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipherPermissions
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
@@ -636,6 +637,7 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
lastUsedDate = FIXED_CLOCK.instant(),
),
),
permissions = createMockSdkCipherPermissions(),
creationDate = FIXED_CLOCK.instant(),
deletedDate = null,
revisionDate = FIXED_CLOCK.instant(),
@@ -3190,6 +3190,7 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
),
),
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
favorite = false,
@@ -3278,6 +3279,7 @@ private val EMPTY_COMMON: VaultItemState.ViewState.Content.Common =
requiresCloneConfirmation = false,
attachments = emptyList(),
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
favorite = false,
@@ -20,9 +20,11 @@ import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.Organization
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
@@ -30,6 +32,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentReposito
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.createMockFolderView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipherPermissions
import com.x8bit.bitwarden.data.vault.manager.FileManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
@@ -102,6 +105,8 @@ class VaultItemViewModelTest : BaseViewModelTest() {
every { edit } returns true
every { folderId } returns null
every { organizationId } returns null
every { permissions } returns null
every { deletedDate } returns null
}
private val mockEnvironmentRepository = FakeEnvironmentRepository()
private val mutableIsIconLoadingDisabledFlow = MutableStateFlow(false)
@@ -109,6 +114,9 @@ class VaultItemViewModelTest : BaseViewModelTest() {
every { isIconLoadingDisabled } returns false
every { isIconLoadingDisabledFlow } returns mutableIsIconLoadingDisabledFlow
}
private val featureFlagManager: FeatureFlagManager = mockk {
every { getFeatureFlag(key = FlagKey.RestrictCipherItemDeletion) } returns false
}
@BeforeEach
fun setup() {
@@ -198,6 +206,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -228,6 +237,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -251,6 +261,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -300,6 +311,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -338,6 +350,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -387,6 +400,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -441,6 +455,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -485,6 +500,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -524,6 +540,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -567,6 +584,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -613,6 +631,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -670,6 +689,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -708,6 +728,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -731,6 +752,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -762,6 +784,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -831,6 +854,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -891,6 +915,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -963,6 +988,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -993,6 +1019,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1013,6 +1040,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1037,6 +1065,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1082,6 +1111,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1120,6 +1150,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1160,6 +1191,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -1197,6 +1229,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1222,6 +1255,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1252,6 +1286,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1276,6 +1311,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1316,6 +1352,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1348,6 +1385,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1376,6 +1414,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1430,6 +1469,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1460,6 +1500,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1484,6 +1525,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1522,6 +1564,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1552,6 +1595,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1576,6 +1620,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1624,6 +1669,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1686,6 +1732,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1758,6 +1805,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = null,
@@ -1931,6 +1979,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -1976,6 +2025,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2022,6 +2072,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2045,6 +2096,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2078,6 +2130,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2098,6 +2151,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2124,6 +2178,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2180,6 +2235,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremiumUser = true,
hasMasterPassword = true,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
totpCodeItemData = createTotpCodeData(),
@@ -2207,6 +2263,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2251,6 +2308,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2282,6 +2340,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2302,6 +2361,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2335,6 +2395,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2356,6 +2417,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2391,6 +2453,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2415,6 +2478,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2453,6 +2517,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = createTotpCodeData(),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2492,6 +2557,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2524,6 +2590,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2544,6 +2611,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2572,6 +2640,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2592,6 +2661,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2624,6 +2694,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2644,6 +2715,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2675,6 +2747,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2695,6 +2768,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2727,6 +2801,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2747,6 +2822,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2775,6 +2851,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2795,6 +2872,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2827,6 +2905,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2846,6 +2925,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2877,6 +2957,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2909,6 +2990,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2947,6 +3029,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -2981,6 +3064,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3002,6 +3086,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3035,6 +3120,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3055,6 +3141,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3091,6 +3178,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3132,6 +3220,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3173,6 +3262,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3328,6 +3418,138 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
isIconLoadingDisabled = false,
relatedLocations = persistentListOf(
VaultItemLocation.Organization("mockOrganizationName"),
VaultItemLocation.Collection("mockName-1"),
VaultItemLocation.Folder("mockName-1"),
),
)
} returns viewState
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
accounts = listOf(
DEFAULT_USER_ACCOUNT.copy(
organizations = listOf(
Organization(
id = "mockOrganizationId",
name = "mockOrganizationName",
shouldManageResetPassword = false,
shouldUseKeyConnector = false,
role = OrganizationType.OWNER,
keyConnectorUrl = null,
userIsClaimedByOrganization = true,
),
),
),
),
)
val viewModel = createViewModel(state = null)
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
mutableCollectionsStateFlow.value = DataState.Loaded(
listOf(createMockCollectionView(number = 1)),
)
mutableFoldersStateFlow.value = DataState.Loaded(
listOf(createMockFolderView(number = 1)),
)
assertEquals(
DEFAULT_STATE.copy(viewState = viewState),
viewModel.stateFlow.value,
)
}
@Test
@Suppress("MaxLineLength")
fun `on VaultDataReceive with Loaded and nonnull false permission data should update the ViewState with cipher permissions`() {
val viewState = mockk<VaultItemState.ViewState>()
every {
featureFlagManager.getFeatureFlag(FlagKey.RestrictCipherItemDeletion)
} returns true
every { mockCipherView.organizationId } returns "mockOrganizationId"
every { mockCipherView.collectionIds } returns listOf("mockId-1")
every { mockCipherView.folderId } returns "mockId-1"
every {
mockCipherView.permissions
} returns createMockSdkCipherPermissions(delete = false, restore = false)
every {
mockCipherView.toViewState(
previousState = null,
isPremiumUser = true,
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = false,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
isIconLoadingDisabled = false,
relatedLocations = persistentListOf(
VaultItemLocation.Organization("mockOrganizationName"),
VaultItemLocation.Collection("mockName-1"),
VaultItemLocation.Folder("mockName-1"),
),
)
} returns viewState
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
accounts = listOf(
DEFAULT_USER_ACCOUNT.copy(
organizations = listOf(
Organization(
id = "mockOrganizationId",
name = "mockOrganizationName",
shouldManageResetPassword = false,
shouldUseKeyConnector = false,
role = OrganizationType.OWNER,
keyConnectorUrl = null,
userIsClaimedByOrganization = true,
),
),
),
),
)
val viewModel = createViewModel(state = null)
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
mutableCollectionsStateFlow.value = DataState.Loaded(
listOf(createMockCollectionView(number = 1)),
)
mutableFoldersStateFlow.value = DataState.Loaded(
listOf(createMockFolderView(number = 1)),
)
assertEquals(
DEFAULT_STATE.copy(viewState = viewState),
viewModel.stateFlow.value,
)
}
@Test
@Suppress("MaxLineLength")
fun `on VaultDataReceive with Loaded and nonnull true permission data should update the ViewState with cipher permissions`() {
val viewState = mockk<VaultItemState.ViewState>()
every {
featureFlagManager.getFeatureFlag(FlagKey.RestrictCipherItemDeletion)
} returns true
every { mockCipherView.organizationId } returns "mockOrganizationId"
every { mockCipherView.deletedDate } returns Instant.MIN
every { mockCipherView.collectionIds } returns listOf("mockId-1")
every { mockCipherView.folderId } returns "mockId-1"
every { mockCipherView.permissions } returns createMockSdkCipherPermissions()
every {
mockCipherView.toViewState(
previousState = null,
isPremiumUser = true,
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3401,6 +3623,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3446,6 +3669,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3486,6 +3710,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
hasMasterPassword = true,
totpCodeItemData = null,
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
@@ -3568,6 +3793,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
organizationEventManager = eventManager,
environmentRepository = environmentRepository,
settingsRepository = settingsRepository,
featureFlagManager = featureFlagManager,
)
private fun createViewState(
@@ -3748,6 +3974,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
),
),
canDelete = true,
canRestore = false,
canAssignToCollections = true,
canEdit = true,
favorite = false,
@@ -68,6 +68,7 @@ class CipherViewExtensionsTest {
),
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -109,6 +110,7 @@ class CipherViewExtensionsTest {
),
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -143,6 +145,7 @@ class CipherViewExtensionsTest {
),
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -177,6 +180,7 @@ class CipherViewExtensionsTest {
),
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -217,6 +221,7 @@ class CipherViewExtensionsTest {
),
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -247,6 +252,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -275,6 +281,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -306,6 +313,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -347,6 +355,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -393,6 +402,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -441,6 +451,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -473,6 +484,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -503,6 +515,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -550,6 +563,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -577,6 +591,7 @@ class CipherViewExtensionsTest {
totpCodeItemData = null,
clock = fixedClock,
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
baseIconUrl = "https://example.com/",
@@ -15,6 +15,7 @@ import com.bitwarden.vault.SshKeyView
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipherPermissions
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData
@@ -153,6 +154,7 @@ fun createCipherView(type: CipherType, isEmpty: Boolean): CipherView =
),
)
.takeUnless { isEmpty },
permissions = createMockSdkCipherPermissions(),
creationDate = Instant.ofEpochSecond(1_000L),
deletedDate = null,
revisionDate = Instant.ofEpochSecond(1_000L),
@@ -174,6 +176,7 @@ fun createCommonContent(
requiresCloneConfirmation = false,
attachments = emptyList(),
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
favorite = false,
@@ -236,6 +239,7 @@ fun createCommonContent(
),
),
canDelete = true,
canRestore = true,
canAssignToCollections = true,
canEdit = true,
favorite = false,
@@ -110,6 +110,7 @@ class VaultAddItemStateExtensionsTest {
attachments = null,
fields = emptyList(),
passwordHistory = null,
permissions = null,
creationDate = Instant.MIN,
deletedDate = null,
revisionDate = Instant.MIN,
@@ -294,6 +295,7 @@ class VaultAddItemStateExtensionsTest {
),
),
passwordHistory = null,
permissions = null,
creationDate = Instant.MIN,
deletedDate = null,
revisionDate = Instant.MIN,
@@ -426,6 +428,7 @@ class VaultAddItemStateExtensionsTest {
attachments = null,
fields = emptyList(),
passwordHistory = null,
permissions = null,
creationDate = Instant.MIN,
deletedDate = null,
revisionDate = Instant.MIN,
@@ -612,6 +615,7 @@ class VaultAddItemStateExtensionsTest {
attachments = null,
fields = emptyList(),
passwordHistory = null,
permissions = null,
creationDate = Instant.MIN,
deletedDate = null,
revisionDate = Instant.MIN,
@@ -754,6 +758,7 @@ class VaultAddItemStateExtensionsTest {
attachments = null,
fields = emptyList(),
passwordHistory = null,
permissions = null,
creationDate = Instant.MIN,
deletedDate = null,
revisionDate = Instant.MIN,
@@ -972,6 +977,7 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
lastUsedDate = Instant.MIN,
),
),
permissions = null,
creationDate = Instant.MIN,
deletedDate = null,
revisionDate = Instant.MIN,