mirror of
https://github.com/bitwarden/android.git
synced 2026-03-12 05:04:17 -05:00
[PM-19309] Fix search when restrict item policy is enabled (#5497)
This commit is contained in:
@@ -24,10 +24,12 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
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.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.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
||||
@@ -54,6 +56,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendItemType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
|
||||
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.toVaultFilterData
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
@@ -61,6 +64,11 @@ 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.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -75,6 +83,7 @@ private const val KEY_STATE = "state"
|
||||
/**
|
||||
* View model for the search screen.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
|
||||
@HiltViewModel
|
||||
class SearchViewModel @Inject constructor(
|
||||
@@ -87,6 +96,7 @@ class SearchViewModel @Inject constructor(
|
||||
private val organizationEventManager: OrganizationEventManager,
|
||||
private val vaultRepo: VaultRepository,
|
||||
private val authRepo: AuthRepository,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
environmentRepo: EnvironmentRepository,
|
||||
settingsRepo: SettingsRepository,
|
||||
snackbarRelayManager: SnackbarRelayManager,
|
||||
@@ -125,6 +135,7 @@ class SearchViewModel @Inject constructor(
|
||||
totpData = specialCircumstance?.toTotpDataOrNull(),
|
||||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||
isPremium = userState.activeAccount.isPremium,
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -140,6 +151,28 @@ class SearchViewModel @Inject constructor(
|
||||
.map { SearchAction.Internal.VaultDataReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
featureFlagManager
|
||||
.getFeatureFlagFlow(FlagKey.RemoveCardPolicy)
|
||||
.flatMapLatest { isFlagEnabled ->
|
||||
if (isFlagEnabled) {
|
||||
policyManager
|
||||
.getActivePoliciesFlow(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
.map { policies ->
|
||||
policies.map { it.organizationId }
|
||||
}
|
||||
} else {
|
||||
flowOf(emptyList<String>())
|
||||
}
|
||||
}
|
||||
.map { organizationIds ->
|
||||
SearchAction.Internal.RestrictItemTypesPolicyUpdateReceive(
|
||||
restrictItemTypesPolicyOrdIds = organizationIds,
|
||||
)
|
||||
}
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
snackbarRelayManager
|
||||
.getSnackbarDataFlow(
|
||||
SnackbarRelay.CIPHER_DELETED,
|
||||
@@ -499,6 +532,10 @@ class SearchViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is SearchAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||
|
||||
is SearchAction.Internal.RestrictItemTypesPolicyUpdateReceive -> {
|
||||
handleRestrictItemTypesPolicyUpdateReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,6 +724,22 @@ class SearchViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRestrictItemTypesPolicyUpdateReceive(
|
||||
action: SearchAction.Internal.RestrictItemTypesPolicyUpdateReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
restrictItemTypesPolicyOrgIds = action
|
||||
.restrictItemTypesPolicyOrdIds
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
vaultRepo.vaultDataStateFlow.value.data?.let { vaultData ->
|
||||
updateStateWithVaultData(vaultData = vaultData, clearDialogState = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
||||
vaultData
|
||||
.data
|
||||
@@ -770,6 +823,9 @@ class SearchViewModel @Inject constructor(
|
||||
vaultData
|
||||
.cipherViewList
|
||||
.filterAndOrganize(searchType, state.searchTerm)
|
||||
.applyRestrictItemTypesPolicy(
|
||||
restrictItemTypesPolicyOrgIds = state.restrictItemTypesPolicyOrgIds,
|
||||
)
|
||||
.toFilteredList(
|
||||
vaultFilterType = state
|
||||
.vaultFilterData
|
||||
@@ -829,6 +885,7 @@ data class SearchState(
|
||||
val totpData: TotpData?,
|
||||
val hasMasterPassword: Boolean,
|
||||
val isPremium: Boolean,
|
||||
val restrictItemTypesPolicyOrgIds: ImmutableList<String>,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
@@ -1205,6 +1262,13 @@ sealed class SearchAction {
|
||||
val result: RemovePasswordSendResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a restrict item types policy update has been received.
|
||||
*/
|
||||
data class RestrictItemTypesPolicyUpdateReceive(
|
||||
val restrictItemTypesPolicyOrdIds: List<String>,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that snackbar data has been received.
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,7 @@ import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -1028,6 +1029,7 @@ private val DEFAULT_STATE: SearchState = SearchState(
|
||||
totpData = null,
|
||||
autofillSelectionData = null,
|
||||
isPremium = true,
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||
)
|
||||
|
||||
private fun createStateForAutofill(
|
||||
|
||||
@@ -28,12 +28,14 @@ import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySele
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
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
|
||||
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.repository.EnvironmentRepository
|
||||
@@ -72,6 +74,7 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -100,10 +103,16 @@ class SearchViewModelTest : 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<PolicyManager> {
|
||||
every {
|
||||
getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||
} returns emptyList()
|
||||
every {
|
||||
getActivePoliciesFlow(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
} returns mutableActivePoliciesFlow
|
||||
}
|
||||
private val mutableVaultDataStateFlow =
|
||||
MutableStateFlow<DataState<VaultData>>(DataState.Loading)
|
||||
@@ -141,6 +150,13 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
} returns mutableSnackbarDataFlow
|
||||
}
|
||||
|
||||
private val mutableRemoveCardPolicyFeatureFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.RemoveCardPolicy)
|
||||
} returns mutableRemoveCardPolicyFeatureFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(
|
||||
@@ -1584,6 +1600,64 @@ class SearchViewModelTest : 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.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(
|
||||
DEFAULT_STATE.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 = 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 = persistentListOf()),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun createViewModel(
|
||||
initialState: SearchState? = null,
|
||||
@@ -1630,6 +1704,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
autofillSelectionManager = autofillSelectionManager,
|
||||
organizationEventManager = organizationEventManager,
|
||||
snackbarRelayManager = snackbarRelayManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -1703,6 +1778,7 @@ private val DEFAULT_STATE: SearchState = SearchState(
|
||||
totpData = null,
|
||||
autofillSelectionData = null,
|
||||
isPremium = true,
|
||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
||||
Reference in New Issue
Block a user