mirror of
https://github.com/bitwarden/android.git
synced 2026-08-23 13:07:48 -05:00
[PM-19309] Handle restrict item types policy (#5357)
This commit is contained in:
@@ -37,6 +37,7 @@ sealed class FlagKey<out T : Any> {
|
||||
RestrictCipherItemDeletion,
|
||||
PreAuthSettings,
|
||||
UserManagedPrivilegedApps,
|
||||
RemoveCardPolicy,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -181,6 +182,15 @@ sealed class FlagKey<out T : Any> {
|
||||
override val defaultValue: Boolean = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Data object holding the feature flag key to enable the removal of card item types.
|
||||
* This flag will hide card types from organizations with policy enable and individual vaults
|
||||
*/
|
||||
data object RemoveCardPolicy : FlagKey<Boolean>() {
|
||||
override val keyName: String = "pm-16442-remove-card-item-type-policy"
|
||||
override val defaultValue: Boolean = false
|
||||
}
|
||||
|
||||
//region Dummy keys for testing
|
||||
/**
|
||||
* Data object holding the key for a [Boolean] flag to be used in tests.
|
||||
|
||||
+2
@@ -42,6 +42,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
||||
FlagKey.RestrictCipherItemDeletion,
|
||||
FlagKey.PreAuthSettings,
|
||||
FlagKey.UserManagedPrivilegedApps,
|
||||
FlagKey.RemoveCardPolicy,
|
||||
-> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
BooleanFlagItem(
|
||||
@@ -105,4 +106,5 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
|
||||
FlagKey.UserManagedPrivilegedApps -> {
|
||||
stringResource(R.string.user_trusted_privileged_app_management)
|
||||
}
|
||||
FlagKey.RemoveCardPolicy -> stringResource(R.string.remove_card_policy)
|
||||
}
|
||||
|
||||
+78
-11
@@ -11,7 +11,6 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.map
|
||||
import com.bitwarden.core.util.persistentListOfNotNull
|
||||
import com.bitwarden.data.repository.util.baseIconUrl
|
||||
import com.bitwarden.data.repository.util.baseWebSendUrl
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
@@ -49,11 +48,13 @@ import com.x8bit.bitwarden.data.credentials.model.ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.credentials.parser.RelyingPartyParser
|
||||
import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepository
|
||||
import com.x8bit.bitwarden.data.credentials.util.getCreatePasskeyCredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
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
|
||||
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.manager.network.NetworkConnectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
||||
@@ -93,6 +94,7 @@ import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toSendItemType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toVaultItemCipherType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.updateWithAdditionalDataIfNecessary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary
|
||||
@@ -102,7 +104,10 @@ import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||
import com.x8bit.bitwarden.ui.vault.util.toVaultItemCipherType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -111,6 +116,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.map
|
||||
|
||||
/**
|
||||
* Manages [VaultItemListingState], handles [VaultItemListingsAction],
|
||||
@@ -136,6 +142,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
private val bitwardenCredentialManager: BitwardenCredentialManager,
|
||||
private val organizationEventManager: OrganizationEventManager,
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
private val snackbarRelayManager: SnackbarRelayManager,
|
||||
private val relyingPartyParser: RelyingPartyParser,
|
||||
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
||||
@@ -165,6 +172,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
policyDisablesSend = policyManager
|
||||
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
||||
.any(),
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||
autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull(),
|
||||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||
totpData = specialCircumstance?.toTotpDataOrNull(),
|
||||
@@ -195,6 +203,21 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
policyManager
|
||||
.getActivePoliciesFlow(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
.combine(
|
||||
featureFlagManager.getFeatureFlagFlow(FlagKey.RemoveCardPolicy),
|
||||
) { policies, enabledFlag ->
|
||||
if (enabledFlag) {
|
||||
policies.map { it.organizationId }
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
.map { VaultItemListingsAction.Internal.RestrictItemTypesPolicyUpdateReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(SnackbarRelay.SEND_DELETED, SnackbarRelay.SEND_UPDATED)
|
||||
.map { VaultItemListingsAction.Internal.SnackbarDataReceived(it) }
|
||||
@@ -763,16 +786,29 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVaultItemTypeSelectionExcludedOptions(): ImmutableList<CreateVaultItemType> {
|
||||
// If policy is enable for any organization, exclude the card option
|
||||
return if (state.restrictItemTypesPolicyOrgIds.isNotEmpty()) {
|
||||
persistentListOf(
|
||||
CreateVaultItemType.CARD,
|
||||
CreateVaultItemType.FOLDER,
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
)
|
||||
} else {
|
||||
persistentListOf(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.FOLDER,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddVaultItemClick() {
|
||||
when (val itemListingType = state.itemListingType) {
|
||||
is VaultItemListingState.ItemListingType.Vault.Collection -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.VaultItemTypeSelection(
|
||||
excludedOptions = persistentListOfNotNull(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.FOLDER,
|
||||
),
|
||||
excludedOptions = createVaultItemTypeSelectionExcludedOptions(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -782,10 +818,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.VaultItemTypeSelection(
|
||||
excludedOptions = persistentListOfNotNull(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.FOLDER,
|
||||
),
|
||||
excludedOptions = createVaultItemTypeSelectionExcludedOptions(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1427,12 +1460,32 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
handleGetCredentialEntriesResultReceive(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.Internal.RestrictItemTypesPolicyUpdateReceive -> {
|
||||
handleRestrictItemTypesPolicyUpdateReceive(action)
|
||||
}
|
||||
|
||||
is VaultItemListingsAction.Internal.SnackbarDataReceived -> {
|
||||
handleSnackbarDataReceived(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRestrictItemTypesPolicyUpdateReceive(
|
||||
action: VaultItemListingsAction.Internal.RestrictItemTypesPolicyUpdateReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
restrictItemTypesPolicyOrgIds = action
|
||||
.restrictItemTypesPolicyOrdIds
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
vaultRepository.vaultDataStateFlow.value.data?.let { vaultData ->
|
||||
updateStateWithVaultData(vaultData, clearDialogState = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInternetConnectionErrorReceived() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
@@ -2119,6 +2172,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
.fido2CredentialAutofillViewList,
|
||||
totpData = state.totpData,
|
||||
isPremiumUser = state.isPremium,
|
||||
restrictItemTypesPolicyOrgIds = state.restrictItemTypesPolicyOrgIds,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2263,6 +2317,7 @@ data class VaultItemListingState(
|
||||
val isIconLoadingDisabled: Boolean,
|
||||
val dialogState: DialogState?,
|
||||
val policyDisablesSend: Boolean,
|
||||
val restrictItemTypesPolicyOrgIds: ImmutableList<String>,
|
||||
// Internal
|
||||
private val isPullToRefreshSettingEnabled: Boolean,
|
||||
val totpData: TotpData? = null,
|
||||
@@ -2278,8 +2333,13 @@ data class VaultItemListingState(
|
||||
* Whether or not the add FAB should be shown.
|
||||
*/
|
||||
val hasAddItemFabButton: Boolean
|
||||
get() = itemListingType.hasFab ||
|
||||
(viewState is ViewState.NoItems && viewState.shouldShowAddButton)
|
||||
get() = if (restrictItemTypesPolicyOrgIds.isNotEmpty() &&
|
||||
itemListingType == VaultItemListingState.ItemListingType.Vault.Card) {
|
||||
false
|
||||
} else {
|
||||
itemListingType.hasFab ||
|
||||
(viewState as? ViewState.NoItems)?.shouldShowAddButton == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this represents a listing screen for autofill.
|
||||
@@ -3160,6 +3220,13 @@ sealed class VaultItemListingsAction {
|
||||
val policyDisablesSend: Boolean,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a restrict item types policy update has been received.
|
||||
*/
|
||||
data class RestrictItemTypesPolicyUpdateReceive(
|
||||
val restrictItemTypesPolicyOrdIds: List<String>,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a credential creation request has been received from the
|
||||
* CredentialManager.
|
||||
|
||||
+8
-6
@@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.vault.feature.util.toFolderDisplayName
|
||||
import com.x8bit.bitwarden.ui.vault.feature.util.toLabelIcons
|
||||
import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.applyRestrictItemTypesPolicy
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
@@ -111,11 +112,13 @@ fun VaultData.toViewState(
|
||||
fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?,
|
||||
totpData: TotpData?,
|
||||
isPremiumUser: Boolean,
|
||||
restrictItemTypesPolicyOrgIds: List<String>,
|
||||
): VaultItemListingState.ViewState {
|
||||
val filteredCipherViewList = cipherViewList
|
||||
.filter { cipherView ->
|
||||
cipherView.determineListingPredicate(itemListingType)
|
||||
}
|
||||
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds)
|
||||
.toFilteredList(vaultFilterType)
|
||||
|
||||
val folderList =
|
||||
@@ -214,13 +217,12 @@ fun VaultData.toViewState(
|
||||
}
|
||||
.asText()
|
||||
}
|
||||
val shouldShowAddButton = when (itemListingType) {
|
||||
VaultItemListingState.ItemListingType.Vault.Trash,
|
||||
VaultItemListingState.ItemListingType.Vault.SshKey,
|
||||
-> false
|
||||
|
||||
else -> true
|
||||
}
|
||||
val restrictItemTypePolicyEnabled = restrictItemTypesPolicyOrgIds.isNotEmpty() &&
|
||||
itemListingType == VaultItemListingState.ItemListingType.Vault.Card
|
||||
|
||||
val shouldShowAddButton = !restrictItemTypePolicyEnabled && itemListingType.hasFab
|
||||
|
||||
VaultItemListingState.ViewState.NoItems(
|
||||
header = totpData
|
||||
?.let { R.string.no_items_for_vault.asText(it.issuer ?: it.accountName ?: "--") },
|
||||
|
||||
@@ -36,7 +36,7 @@ private const val TRASH_TYPES_COUNT: Int = 1
|
||||
* Content view for the [VaultScreen].
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
fun VaultContent(
|
||||
state: VaultState.ViewState.Content,
|
||||
vaultHandlers: VaultHandlers,
|
||||
@@ -183,20 +183,22 @@ fun VaultContent(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
BitwardenGroupItem(
|
||||
startIcon = rememberVectorPainter(id = BitwardenDrawable.ic_payment_card),
|
||||
startIconTestTag = "CardCipherIcon",
|
||||
label = stringResource(id = R.string.type_card),
|
||||
supportingLabel = state.cardItemsCount.toString(),
|
||||
onClick = vaultHandlers.cardGroupClick,
|
||||
showDivider = false,
|
||||
cardStyle = CardStyle.Middle(dividerPadding = 56.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("CardFilter")
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
if (state.showCardGroup) {
|
||||
item {
|
||||
BitwardenGroupItem(
|
||||
startIcon = rememberVectorPainter(id = BitwardenDrawable.ic_payment_card),
|
||||
startIconTestTag = "CardCipherIcon",
|
||||
label = stringResource(id = R.string.type_card),
|
||||
supportingLabel = state.cardItemsCount.toString(),
|
||||
onClick = vaultHandlers.cardGroupClick,
|
||||
showDivider = false,
|
||||
cardStyle = CardStyle.Middle(dividerPadding = 56.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("CardFilter")
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
|
||||
@@ -422,11 +422,12 @@ private fun VaultDialogs(
|
||||
onDismissRequest = vaultHandlers.dialogDismiss,
|
||||
)
|
||||
|
||||
VaultState.DialogState.SelectVaultAddItemType -> VaultItemSelectionDialog(
|
||||
is VaultState.DialogState.SelectVaultAddItemType -> VaultItemSelectionDialog(
|
||||
onOptionSelected = {
|
||||
vaultHandlers.addItemClickAction(it)
|
||||
},
|
||||
onDismissRequest = vaultHandlers.dialogDismiss,
|
||||
excludedOptions = dialogState.excludedOptions,
|
||||
)
|
||||
|
||||
null -> Unit
|
||||
|
||||
@@ -72,6 +72,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.emptyList
|
||||
import kotlin.collections.map
|
||||
|
||||
/**
|
||||
* Manages [VaultState], handles [VaultAction], and launches [VaultEvent] for the [VaultScreen].
|
||||
@@ -120,6 +122,7 @@ class VaultViewModel @Inject constructor(
|
||||
flightRecorderSnackBar = settingsRepository
|
||||
.flightRecorderData
|
||||
.toSnackbarData(clock = clock),
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -177,6 +180,21 @@ class VaultViewModel @Inject constructor(
|
||||
.map { VaultAction.Internal.FlightRecorderDataReceive(data = it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
policyManager
|
||||
.getActivePoliciesFlow(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
.combine(
|
||||
featureFlagManager.getFeatureFlagFlow(FlagKey.RemoveCardPolicy),
|
||||
) { policies, enabledFlag ->
|
||||
if (enabledFlag && policies.isNotEmpty()) {
|
||||
policies.map { it.organizationId }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.map { VaultAction.Internal.PolicyUpdateReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: VaultAction) {
|
||||
@@ -233,9 +251,20 @@ class VaultViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleSelectAddItemType() {
|
||||
// If policy is enable for any organization, exclude the card option
|
||||
var excludedOptions =
|
||||
if (!state.restrictItemTypesPolicyOrgIds.isNullOrEmpty()) {
|
||||
persistentListOf(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.CARD,
|
||||
)
|
||||
} else {
|
||||
persistentListOf(CreateVaultItemType.SSH_KEY)
|
||||
}
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType,
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType(excludedOptions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -647,6 +676,20 @@ class VaultViewModel @Inject constructor(
|
||||
is VaultAction.Internal.FlightRecorderDataReceive -> {
|
||||
handleFlightRecorderDataReceive(action)
|
||||
}
|
||||
|
||||
is VaultAction.Internal.PolicyUpdateReceive -> {
|
||||
handlePolicyUpdateReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePolicyUpdateReceive(action: VaultAction.Internal.PolicyUpdateReceive) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(restrictItemTypesPolicyOrgIds = action.restrictItemTypesPolicyOrdIds)
|
||||
}
|
||||
|
||||
vaultRepository.vaultDataStateFlow.value.data?.let { vaultData ->
|
||||
updateVaultState(vaultData, clearDialog = false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,6 +810,7 @@ class VaultViewModel @Inject constructor(
|
||||
errorTitle = R.string.an_error_has_occurred.asText(),
|
||||
errorMessage = R.string.generic_error_message.asText(),
|
||||
isRefreshing = false,
|
||||
restrictItemTypesPolicyOrgIds = state.restrictItemTypesPolicyOrgIds,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -783,6 +827,7 @@ class VaultViewModel @Inject constructor(
|
||||
|
||||
private fun updateVaultState(
|
||||
vaultData: VaultData,
|
||||
clearDialog: Boolean = true,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
@@ -792,8 +837,9 @@ class VaultViewModel @Inject constructor(
|
||||
isPremium = state.isPremium,
|
||||
hasMasterPassword = state.hasMasterPassword,
|
||||
vaultFilterType = vaultFilterTypeOrDefault,
|
||||
restrictItemTypesPolicyOrgIds = state.restrictItemTypesPolicyOrgIds,
|
||||
),
|
||||
dialog = null,
|
||||
dialog = if (clearDialog) null else state.dialog,
|
||||
isRefreshing = false,
|
||||
)
|
||||
}
|
||||
@@ -826,6 +872,7 @@ class VaultViewModel @Inject constructor(
|
||||
isPremium = state.isPremium,
|
||||
hasMasterPassword = state.hasMasterPassword,
|
||||
vaultFilterType = vaultFilterTypeOrDefault,
|
||||
restrictItemTypesPolicyOrgIds = state.restrictItemTypesPolicyOrgIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -931,6 +978,7 @@ data class VaultState(
|
||||
private val isPullToRefreshSettingEnabled: Boolean,
|
||||
val baseIconUrl: String,
|
||||
val isIconLoadingDisabled: Boolean,
|
||||
val restrictItemTypesPolicyOrgIds: List<String>?,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
@@ -1025,6 +1073,7 @@ data class VaultState(
|
||||
val noFolderItems: List<VaultItem>,
|
||||
val collectionItems: List<CollectionItem>,
|
||||
val trashItemsCount: Int,
|
||||
val showCardGroup: Boolean,
|
||||
) : ViewState() {
|
||||
override val hasFab: Boolean get() = true
|
||||
override val isPullToRefreshEnabled: Boolean get() = true
|
||||
@@ -1246,7 +1295,9 @@ data class VaultState(
|
||||
* Represents a dialog for selecting a vault item type to add.
|
||||
*/
|
||||
@Parcelize
|
||||
data object SelectVaultAddItemType : DialogState()
|
||||
data class SelectVaultAddItemType(
|
||||
val excludedOptions: ImmutableList<CreateVaultItemType>,
|
||||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Represents an error dialog with the given [title] and [message].
|
||||
@@ -1612,6 +1663,13 @@ sealed class VaultAction {
|
||||
data class FlightRecorderDataReceive(
|
||||
val data: FlightRecorderDataSet,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a policy update has been received.
|
||||
*/
|
||||
data class PolicyUpdateReceive(
|
||||
val restrictItemTypesPolicyOrdIds: List<String>?,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1626,6 +1684,7 @@ private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
|
||||
errorTitle: Text,
|
||||
errorMessage: Text,
|
||||
isRefreshing: Boolean,
|
||||
restrictItemTypesPolicyOrgIds: List<String>?,
|
||||
) {
|
||||
this.update {
|
||||
if (vaultData != null) {
|
||||
@@ -1636,6 +1695,7 @@ private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
|
||||
hasMasterPassword = hasMasterPassword,
|
||||
vaultFilterType = vaultFilterType,
|
||||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
restrictItemTypesPolicyOrgIds = restrictItemTypesPolicyOrgIds,
|
||||
),
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = errorTitle,
|
||||
|
||||
+30
-2
@@ -40,10 +40,13 @@ fun VaultData.toViewState(
|
||||
isIconLoadingDisabled: Boolean,
|
||||
baseIconUrl: String,
|
||||
vaultFilterType: VaultFilterType,
|
||||
restrictItemTypesPolicyOrgIds: List<String>?,
|
||||
): VaultState.ViewState {
|
||||
|
||||
val filteredCipherViewListWithDeletedItems =
|
||||
cipherViewList.toFilteredList(vaultFilterType)
|
||||
cipherViewList
|
||||
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds ?: emptyList())
|
||||
.toFilteredList(vaultFilterType)
|
||||
|
||||
val filteredCipherViewList = filteredCipherViewListWithDeletedItems
|
||||
.filter { it.deletedDate == null }
|
||||
@@ -70,6 +73,7 @@ fun VaultData.toViewState(
|
||||
val totpItems = filteredCipherViewList.filter { it.login?.totp != null }
|
||||
val shouldShowUnGroupedItems = filteredCollectionViewList.isEmpty() &&
|
||||
noFolderItems.size < NO_FOLDER_ITEM_THRESHOLD
|
||||
val cardCount = filteredCipherViewList.count { it.type == CipherType.CARD }
|
||||
VaultState.ViewState.Content(
|
||||
itemTypesCount = itemTypesCount,
|
||||
totpItemsCount = if (isPremium) {
|
||||
@@ -78,7 +82,7 @@ fun VaultData.toViewState(
|
||||
totpItems.count { it.organizationUseTotp }
|
||||
},
|
||||
loginItemsCount = filteredCipherViewList.count { it.type == CipherType.LOGIN },
|
||||
cardItemsCount = filteredCipherViewList.count { it.type == CipherType.CARD },
|
||||
cardItemsCount = cardCount,
|
||||
identityItemsCount = filteredCipherViewList.count { it.type == CipherType.IDENTITY },
|
||||
secureNoteItemsCount = filteredCipherViewList
|
||||
.count { it.type == CipherType.SECURE_NOTE },
|
||||
@@ -145,6 +149,7 @@ fun VaultData.toViewState(
|
||||
trashItemsCount = filteredCipherViewListWithDeletedItems.count {
|
||||
it.deletedDate != null
|
||||
},
|
||||
showCardGroup = cardCount != 0 || restrictItemTypesPolicyOrgIds == null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -356,3 +361,26 @@ fun List<CollectionView>.toFilteredList(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out [CipherType.CARD] [CipherView]s that are in [restrictItemTypesPolicyOrgIds] list.
|
||||
* When [restrictItemTypesPolicyOrgIds] is not empty, individual vault items are also removed.
|
||||
*/
|
||||
fun List<CipherView>.applyRestrictItemTypesPolicy(
|
||||
restrictItemTypesPolicyOrgIds: List<String>,
|
||||
): List<CipherView> =
|
||||
this
|
||||
.filterNot { cipherView ->
|
||||
if (restrictItemTypesPolicyOrgIds.isEmpty()) {
|
||||
// No policy, so don't apply removal
|
||||
false
|
||||
} else if (cipherView.type != CipherType.CARD) {
|
||||
// Policy only for cards
|
||||
false
|
||||
} else {
|
||||
// If a policy is enable for a given organization then
|
||||
// also hide cards from individual vault
|
||||
cipherView.organizationId.isNullOrEmpty() ||
|
||||
restrictItemTypesPolicyOrgIds.contains(cipherView.organizationId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
<string name="generate_error_report">Generate error report</string>
|
||||
<string name="error_reports">Error reports</string>
|
||||
<string name="user_trusted_privileged_app_management">User-trusted privileged app management</string>
|
||||
<string name="remove_card_policy">Remove Card Policy</string>
|
||||
<!-- /Debug Menu -->
|
||||
</resources>
|
||||
|
||||
@@ -73,6 +73,10 @@ class FlagKeyTest {
|
||||
FlagKey.UserManagedPrivilegedApps.keyName,
|
||||
"pm-18970-user-managed-privileged-apps",
|
||||
)
|
||||
assertEquals(
|
||||
FlagKey.RemoveCardPolicy.keyName,
|
||||
"pm-16442-remove-card-item-type-policy",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -95,6 +99,7 @@ class FlagKeyTest {
|
||||
FlagKey.RestrictCipherItemDeletion,
|
||||
FlagKey.PreAuthSettings,
|
||||
FlagKey.UserManagedPrivilegedApps,
|
||||
FlagKey.RemoveCardPolicy,
|
||||
).all {
|
||||
!it.defaultValue
|
||||
},
|
||||
|
||||
+2
@@ -160,6 +160,7 @@ private val DEFAULT_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf
|
||||
FlagKey.RestrictCipherItemDeletion to true,
|
||||
FlagKey.PreAuthSettings to true,
|
||||
FlagKey.UserManagedPrivilegedApps to true,
|
||||
FlagKey.RemoveCardPolicy to true,
|
||||
)
|
||||
|
||||
private val UPDATED_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf(
|
||||
@@ -179,6 +180,7 @@ private val UPDATED_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf
|
||||
FlagKey.RestrictCipherItemDeletion to false,
|
||||
FlagKey.PreAuthSettings to false,
|
||||
FlagKey.UserManagedPrivilegedApps to false,
|
||||
FlagKey.RemoveCardPolicy to false,
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = DebugMenuState(
|
||||
|
||||
+1
@@ -2384,6 +2384,7 @@ private val DEFAULT_STATE = VaultItemListingState(
|
||||
hasMasterPassword = true,
|
||||
isPremium = false,
|
||||
isRefreshing = false,
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||
)
|
||||
|
||||
private val STATE_FOR_AUTOFILL = DEFAULT_STATE.copy(
|
||||
|
||||
+177
@@ -23,6 +23,7 @@ import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.data.repository.util.baseIconUrl
|
||||
import com.bitwarden.data.repository.util.baseWebSendUrl
|
||||
import com.bitwarden.network.model.PolicyTypeJson
|
||||
import com.bitwarden.network.model.SyncResponseJson
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.bitwarden.ui.platform.components.icon.model.IconData
|
||||
@@ -60,6 +61,7 @@ import com.x8bit.bitwarden.data.credentials.model.createMockFido2CredentialAsser
|
||||
import com.x8bit.bitwarden.data.credentials.model.createMockGetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.credentials.parser.RelyingPartyParser
|
||||
import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
@@ -67,6 +69,7 @@ import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingMa
|
||||
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.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
||||
@@ -197,9 +200,14 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
private val mutableActivePoliciesFlow: MutableStateFlow<List<SyncResponseJson.Policy>> =
|
||||
MutableStateFlow(emptyList())
|
||||
private val policyManager: PolicyManager = mockk {
|
||||
every { getActivePolicies(type = PolicyTypeJson.DISABLE_SEND) } returns emptyList()
|
||||
every { getActivePoliciesFlow(type = PolicyTypeJson.DISABLE_SEND) } returns emptyFlow()
|
||||
every {
|
||||
getActivePoliciesFlow(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
} returns mutableActivePoliciesFlow
|
||||
}
|
||||
private val bitwardenCredentialManager: BitwardenCredentialManager = mockk {
|
||||
every { isUserVerified } returns false
|
||||
@@ -232,6 +240,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
coEvery { addTrustedPrivilegedApp(any(), any()) } just runs
|
||||
}
|
||||
|
||||
private val mutableRemoveCardPolicyFeatureFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.RemoveCardPolicy)
|
||||
} returns mutableRemoveCardPolicyFeatureFlow
|
||||
}
|
||||
|
||||
private val initialState = createVaultItemListingState()
|
||||
private val initialSavedStateHandle
|
||||
get() = createSavedStateHandleWithVaultItemListingType(
|
||||
@@ -346,6 +361,64 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RESTRICT_ITEM_TYPES policy changes should update restrictItemTypesPolicyOrgIds accordingly if RemoveCardPolicy flag is enable`() =
|
||||
runTest {
|
||||
mutableRemoveCardPolicyFeatureFlow.value = true
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
assertEquals(
|
||||
initialState.copy(restrictItemTypesPolicyOrgIds = persistentListOf()),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf("Test Organization"),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RESTRICT_ITEM_TYPES policy changes should update restrictItemTypesPolicyOrgIds accordingly if RemoveCardPolicy flag is disabled`() =
|
||||
runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
assertEquals(
|
||||
initialState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
initialState.copy(restrictItemTypesPolicyOrgIds = persistentListOf()),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LockAccountClick should call lockVault for the given account`() {
|
||||
val accountUserId = "userId"
|
||||
@@ -1227,6 +1300,108 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddVaultItemClick inside a collection should show item selection dialog state`() {
|
||||
val viewModel = createVaultItemListingViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType = VaultItemListingType.Collection(collectionId = "id"),
|
||||
),
|
||||
)
|
||||
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Collection(
|
||||
collectionId = "id",
|
||||
),
|
||||
dialogState = VaultItemListingState.DialogState.VaultItemTypeSelection(
|
||||
excludedOptions = persistentListOf(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.FOLDER,
|
||||
),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `AddVaultItemClick inside a folder should hide card item selection dialog state when RESTRICT_ITEM_TYPES policy is enabled`() =
|
||||
runTest {
|
||||
mutableRemoveCardPolicyFeatureFlow.value = true
|
||||
val viewModel = createVaultItemListingViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType = VaultItemListingType.Folder(folderId = "id"),
|
||||
),
|
||||
)
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Folder(
|
||||
folderId = "id",
|
||||
),
|
||||
dialogState = VaultItemListingState.DialogState.VaultItemTypeSelection(
|
||||
excludedOptions = persistentListOf(
|
||||
CreateVaultItemType.CARD,
|
||||
CreateVaultItemType.FOLDER,
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
),
|
||||
),
|
||||
).copy(restrictItemTypesPolicyOrgIds = persistentListOf("Test Organization")),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `AddVaultItemClick inside a collection should hide card item selection dialog state when RESTRICT_ITEM_TYPES policy is enabled`() =
|
||||
runTest {
|
||||
mutableRemoveCardPolicyFeatureFlow.value = true
|
||||
val viewModel = createVaultItemListingViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType = VaultItemListingType.Collection(collectionId = "id"),
|
||||
),
|
||||
)
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Collection(
|
||||
collectionId = "id",
|
||||
),
|
||||
dialogState = VaultItemListingState.DialogState.VaultItemTypeSelection(
|
||||
excludedOptions = persistentListOf(
|
||||
CreateVaultItemType.CARD,
|
||||
CreateVaultItemType.FOLDER,
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
),
|
||||
),
|
||||
).copy(restrictItemTypesPolicyOrgIds = persistentListOf("Test Organization")),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddVaultItemClick for vault item should emit NavigateToAddVaultItem`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
@@ -5112,6 +5287,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
originManager = originManager,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
privilegedAppRepository = privilegedAppRepository,
|
||||
featureFlagManager = featureFlagManager,
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
relyingPartyParser = relyingPartyParser,
|
||||
)
|
||||
@@ -5140,6 +5316,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
createCredentialRequest = null,
|
||||
isPremium = true,
|
||||
isRefreshing = false,
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+128
@@ -470,6 +470,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -563,6 +564,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = fido2CredentialAutofillViews,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -649,6 +651,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = fido2CredentialAutofillViews,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -715,6 +718,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -738,6 +742,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -759,6 +764,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -781,6 +787,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -802,6 +809,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -823,6 +831,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -844,6 +853,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -869,6 +879,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -894,6 +905,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -920,6 +932,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
every { issuer } returns "issuer"
|
||||
},
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1162,6 +1175,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -1206,6 +1220,7 @@ class VaultItemListingDataExtensionsTest {
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -1228,4 +1243,117 @@ class VaultItemListingDataExtensionsTest {
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should properly filter cards when cipher have organizationId in restrictItemTypesPolicyOrgIds`() {
|
||||
mockkStatic(CipherView::subtitle)
|
||||
mockkStatic(Uri::class)
|
||||
val uriMock = mockk<Uri>()
|
||||
every { any<CipherView>().subtitle } returns null
|
||||
every { Uri.parse(any()) } returns uriMock
|
||||
every { uriMock.host } returns "www.mockuri.com"
|
||||
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
organizationId = "restrict_item_type_policy_id",
|
||||
cipherType = CipherType.LOGIN,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 2,
|
||||
organizationId = "restrict_item_type_policy_id",
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 3,
|
||||
organizationId = null,
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 4,
|
||||
organizationId = "another_id",
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
),
|
||||
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
fido2CredentialAutofillViewList = listOf(),
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Card,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
createCredentialRequestData = null,
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = listOf("restrict_item_type_policy_id"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(
|
||||
number = 4,
|
||||
cipherType = CipherType.CARD,
|
||||
subtitle = null,
|
||||
).copy(secondSubtitleTestTag = "PasskeySite"),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should set properly shouldShowAddButton false when restrictItemTypesPolicyOrgIds has values, vault type is card and there is an empty vault state`() {
|
||||
mockkStatic(CipherView::subtitle)
|
||||
mockkStatic(Uri::class)
|
||||
val uriMock = mockk<Uri>()
|
||||
every { any<CipherView>().subtitle } returns null
|
||||
every { Uri.parse(any()) } returns uriMock
|
||||
every { uriMock.host } returns "www.mockuri.com"
|
||||
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = listOf(),
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
fido2CredentialAutofillViewList = listOf(),
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Vault.Card,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = null,
|
||||
createCredentialRequestData = null,
|
||||
fido2CredentialAutofillViews = null,
|
||||
totpData = null,
|
||||
isPremiumUser = true,
|
||||
restrictItemTypesPolicyOrgIds = listOf("restrict_item_type_policy_id"),
|
||||
)
|
||||
|
||||
// Card type
|
||||
assertEquals(
|
||||
VaultItemListingState.ViewState.NoItems(
|
||||
message = R.string.no_cards.asText(),
|
||||
shouldShowAddButton = false,
|
||||
buttonText = R.string.new_card.asText(),
|
||||
),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1551,6 +1551,36 @@ class VaultScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `card section should be visible based on state`() {
|
||||
mutableStateFlow.update { state ->
|
||||
state.copy(
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
||||
cardItemsCount = 1,
|
||||
showCardGroup = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Card")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { state ->
|
||||
state.copy(
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
||||
cardItemsCount = 0,
|
||||
showCardGroup = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Card")
|
||||
.assertIsNotDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `card item count should update according to state`() {
|
||||
val rowText = "Card"
|
||||
@@ -1857,7 +1887,11 @@ class VaultScreenTest : BitwardenComposeTest() {
|
||||
@Test
|
||||
fun `SelectVaultAddItemType dialog state show vault item type selection dialog`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultState.DialogState.SelectVaultAddItemType)
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType(
|
||||
persistentListOf(CreateVaultItemType.SSH_KEY),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
@@ -1870,10 +1904,52 @@ class VaultScreenTest : BitwardenComposeTest() {
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SelectVaultAddItemType dialog state hide vault item type selection if excluded`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType(
|
||||
persistentListOf(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.CARD,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(isDialog())
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Type")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Card")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertDoesNotExist()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("SSH key")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertDoesNotExist()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Card")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when option is selected in SelectVaultAddItemType dialog add item action is sent`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultState.DialogState.SelectVaultAddItemType)
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType(
|
||||
persistentListOf(CreateVaultItemType.SSH_KEY),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
@@ -1998,6 +2074,7 @@ private val DEFAULT_STATE: VaultState = VaultState(
|
||||
isRefreshing = false,
|
||||
showImportActionCard = false,
|
||||
flightRecorderSnackBar = null,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultState.ViewState.Content(
|
||||
@@ -2013,4 +2090,5 @@ private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultStat
|
||||
totpItemsCount = 0,
|
||||
itemTypesCount = 4,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
)
|
||||
|
||||
+112
-1
@@ -93,10 +93,16 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
private val clipboardManager: BitwardenClipboardManager = mockk {
|
||||
every { setText(text = any<String>(), toastDescriptorOverride = any<Text>()) } just runs
|
||||
}
|
||||
|
||||
private val mutableActivePoliciesFlow: MutableStateFlow<List<SyncResponseJson.Policy>> =
|
||||
MutableStateFlow(emptyList())
|
||||
private val policyManager: PolicyManager = mockk {
|
||||
every {
|
||||
getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||
} returns emptyList()
|
||||
every {
|
||||
getActivePoliciesFlow(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
} returns mutableActivePoliciesFlow
|
||||
}
|
||||
|
||||
private val mutablePullToRefreshEnabledFlow = MutableStateFlow(false)
|
||||
@@ -152,11 +158,15 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
private val mutableImportLoginsFeatureFlow = MutableStateFlow(true)
|
||||
private val mutableRemoveCardPolicyFeatureFlow = MutableStateFlow(false)
|
||||
private val mutableSshKeyVaultItemsEnabledFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.ImportLoginsFlow)
|
||||
} returns mutableImportLoginsFeatureFlow
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.RemoveCardPolicy)
|
||||
} returns mutableRemoveCardPolicyFeatureFlow
|
||||
}
|
||||
private val reviewPromptManager: ReviewPromptManager = mockk()
|
||||
|
||||
@@ -392,6 +402,62 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RESTRICT_ITEM_TYPES policy changes should update restrictItemTypesPolicyOrgIds accordingly if RemoveCardPolicy flag is enable`() =
|
||||
runTest {
|
||||
mutableRemoveCardPolicyFeatureFlow.value = true
|
||||
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(restrictItemTypesPolicyOrgIds = listOf("Test Organization")),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RESTRICT_ITEM_TYPES policy changes should update restrictItemTypesPolicyOrgIds accordingly if RemoveCardPolicy flag is disabled`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(restrictItemTypesPolicyOrgIds = null),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Flight Recorder changes should update flightRecorderSnackbar accordingly`() = runTest {
|
||||
mockkStatic(FlightRecorderDataSet::toSnackbarData)
|
||||
@@ -648,6 +714,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
isIconLoadingDisabled = viewModel.stateFlow.value.isIconLoadingDisabled,
|
||||
baseIconUrl = viewModel.stateFlow.value.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
),
|
||||
)
|
||||
.copy(
|
||||
@@ -672,6 +739,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
isIconLoadingDisabled = viewModel.stateFlow.value.isIconLoadingDisabled,
|
||||
baseIconUrl = viewModel.stateFlow.value.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
@@ -785,6 +853,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = CipherType.entries.size,
|
||||
sshKeyItemsCount = 1,
|
||||
showCardGroup = true,
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
@@ -809,6 +878,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
@@ -928,6 +998,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
@@ -1033,6 +1104,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
@@ -1136,6 +1208,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
dialog = null,
|
||||
),
|
||||
@@ -1213,6 +1286,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = CipherType.entries.size,
|
||||
sshKeyItemsCount = 1,
|
||||
showCardGroup = true,
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
@@ -2143,7 +2217,9 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.SelectAddItemType)
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType,
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType(
|
||||
excludedOptions = persistentListOf(CreateVaultItemType.SSH_KEY),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
expectedState,
|
||||
@@ -2151,6 +2227,40 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `SelectAddItemType action should set dialog state to SelectVaultAddItemType accordingly when RESTRICT_ITEM_TYPES is enabled`() =
|
||||
runTest {
|
||||
mutableRemoveCardPolicyFeatureFlow.value = true
|
||||
val viewModel = createViewModel()
|
||||
mutableActivePoliciesFlow.emit(
|
||||
listOf(
|
||||
SyncResponseJson.Policy(
|
||||
organizationId = "Test Organization",
|
||||
id = "testId",
|
||||
type = PolicyTypeJson.RESTRICT_ITEM_TYPES,
|
||||
isEnabled = true,
|
||||
data = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(VaultAction.SelectAddItemType)
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
dialog = VaultState.DialogState.SelectVaultAddItemType(
|
||||
excludedOptions = persistentListOf(
|
||||
CreateVaultItemType.SSH_KEY,
|
||||
CreateVaultItemType.CARD,
|
||||
),
|
||||
),
|
||||
restrictItemTypesPolicyOrgIds = listOf("Test Organization"),
|
||||
)
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `InternetConnectionErrorReceived should show network error if no internet connection`() =
|
||||
runTest {
|
||||
@@ -2292,4 +2402,5 @@ private fun createMockVaultState(
|
||||
showImportActionCard = true,
|
||||
isRefreshing = false,
|
||||
flightRecorderSnackBar = null,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
+146
@@ -64,6 +64,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -107,6 +108,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -131,6 +133,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.MyVault,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -153,6 +156,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -186,6 +190,7 @@ class VaultDataExtensionsTest {
|
||||
organizationName = "Mock Organization 1",
|
||||
),
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -219,6 +224,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -239,6 +245,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -262,6 +269,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -286,6 +294,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -302,6 +311,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -323,6 +333,7 @@ class VaultDataExtensionsTest {
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -339,6 +350,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 0,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -360,6 +372,7 @@ class VaultDataExtensionsTest {
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -376,6 +389,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -399,6 +413,7 @@ class VaultDataExtensionsTest {
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -415,6 +430,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -622,6 +638,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -638,6 +655,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -661,6 +679,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -677,6 +696,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 0,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -703,6 +723,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -725,6 +746,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 100,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -752,6 +774,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -785,6 +808,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -818,6 +842,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -866,6 +891,123 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 5,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should excluded card vault items and adjust type count for ciphers with orgId in restrictItemTypesPolicyOrgIds and set showCardGroup to true if there are remaining cards`() {
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 2,
|
||||
organizationId = "restrict_item_type_policy_id",
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 3,
|
||||
organizationId = "another_id",
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 4,
|
||||
organizationId = null,
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
),
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
fido2CredentialAutofillViewList = null,
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isPremium = true,
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = listOf("restrict_item_type_policy_id"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultState.ViewState.Content(
|
||||
loginItemsCount = 0,
|
||||
cardItemsCount = 2,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
sshKeyItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
collectionItems = listOf(),
|
||||
folderItems = listOf(),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
totpItemsCount = 0,
|
||||
itemTypesCount = CipherType.entries.size,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should excluded card vault items for ciphers with orgId in restrictItemTypesPolicyOrgIds and set showCardGroup to false if there are no remaining cards`() {
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
organizationId = "restrict_item_type_policy_id",
|
||||
cipherType = CipherType.LOGIN,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 2,
|
||||
organizationId = "restrict_item_type_policy_id",
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
createMockCipherView(
|
||||
number = 3,
|
||||
organizationId = "restrict_item_type_policy_id",
|
||||
cipherType = CipherType.CARD,
|
||||
),
|
||||
),
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
fido2CredentialAutofillViewList = null,
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isPremium = true,
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = listOf("restrict_item_type_policy_id"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultState.ViewState.Content(
|
||||
loginItemsCount = 1,
|
||||
cardItemsCount = 0,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
sshKeyItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
collectionItems = listOf(),
|
||||
folderItems = listOf(),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = CipherType.entries.size,
|
||||
showCardGroup = false,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -890,6 +1032,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -908,6 +1051,7 @@ class VaultDataExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
// Verify item types count includes all CipherTypes when showSshKeys is true.
|
||||
itemTypesCount = CipherType.entries.size,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
@@ -947,6 +1091,7 @@ class VaultDataExtensionsTest {
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
hasMasterPassword = true,
|
||||
restrictItemTypesPolicyOrgIds = null,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -978,6 +1123,7 @@ class VaultDataExtensionsTest {
|
||||
trashItemsCount = 0,
|
||||
totpItemsCount = 0,
|
||||
itemTypesCount = CipherType.entries.size,
|
||||
showCardGroup = true,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
|
||||
+1
@@ -85,5 +85,6 @@ class VaultStateExtensionsTest {
|
||||
totpItemsCount = 1,
|
||||
itemTypesCount = 4,
|
||||
sshKeyItemsCount = 0,
|
||||
showCardGroup = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,6 +88,12 @@ enum class PolicyTypeJson {
|
||||
@SerialName("14")
|
||||
REMOVE_UNLOCK_WITH_PIN,
|
||||
|
||||
/**
|
||||
* Restricts the types of items that can be shown in the vault.
|
||||
*/
|
||||
@SerialName("15")
|
||||
RESTRICT_ITEM_TYPES,
|
||||
|
||||
/**
|
||||
* Represents an unknown policy type.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user