mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 04:39:19 -06:00
[PM-27118] Restrict Credential Exchange import based on Personal Ownership policy (#6220)
This commit is contained in:
parent
1904c4ffb9
commit
e1bb3a4b5d
@ -2,14 +2,17 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.vault
|
|||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.bitwarden.core.data.manager.model.FlagKey
|
import com.bitwarden.core.data.manager.model.FlagKey
|
||||||
|
import com.bitwarden.network.model.PolicyTypeJson
|
||||||
import com.bitwarden.ui.platform.base.BackgroundEvent
|
import com.bitwarden.ui.platform.base.BackgroundEvent
|
||||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
|
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
|
||||||
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -25,6 +28,7 @@ class VaultSettingsViewModel @Inject constructor(
|
|||||||
snackbarRelayManager: SnackbarRelayManager<SnackbarRelay>,
|
snackbarRelayManager: SnackbarRelayManager<SnackbarRelay>,
|
||||||
private val firstTimeActionManager: FirstTimeActionManager,
|
private val firstTimeActionManager: FirstTimeActionManager,
|
||||||
private val featureFlagManager: FeatureFlagManager,
|
private val featureFlagManager: FeatureFlagManager,
|
||||||
|
private val policyManager: PolicyManager,
|
||||||
) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
|
) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
|
||||||
initialState = run {
|
initialState = run {
|
||||||
val firstTimeState = firstTimeActionManager.currentOrDefaultUserFirstTimeState
|
val firstTimeState = firstTimeActionManager.currentOrDefaultUserFirstTimeState
|
||||||
@ -55,7 +59,13 @@ class VaultSettingsViewModel @Inject constructor(
|
|||||||
|
|
||||||
featureFlagManager
|
featureFlagManager
|
||||||
.getFeatureFlagFlow(key = FlagKey.CredentialExchangeProtocolImport)
|
.getFeatureFlagFlow(key = FlagKey.CredentialExchangeProtocolImport)
|
||||||
.map { VaultSettingsAction.Internal.ImportFeatureUpdated(it) }
|
.combine(
|
||||||
|
policyManager.getActivePoliciesFlow(type = PolicyTypeJson.PERSONAL_OWNERSHIP),
|
||||||
|
) { isEnabled, policies ->
|
||||||
|
VaultSettingsAction.Internal.CredentialExchangeAvailabilityChanged(
|
||||||
|
isEnabled = isEnabled && policies.isEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
@ -80,8 +90,8 @@ class VaultSettingsViewModel @Inject constructor(
|
|||||||
handleSnackbarDataReceived(action)
|
handleSnackbarDataReceived(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultSettingsAction.Internal.ImportFeatureUpdated -> {
|
is VaultSettingsAction.Internal.CredentialExchangeAvailabilityChanged -> {
|
||||||
handleImportFeatureUpdated(action)
|
handleCredentialExchangeAvailabilityChanged(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,8 +102,8 @@ class VaultSettingsViewModel @Inject constructor(
|
|||||||
sendEvent(VaultSettingsEvent.ShowSnackbar(action.data))
|
sendEvent(VaultSettingsEvent.ShowSnackbar(action.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleImportFeatureUpdated(
|
private fun handleCredentialExchangeAvailabilityChanged(
|
||||||
action: VaultSettingsAction.Internal.ImportFeatureUpdated,
|
action: VaultSettingsAction.Internal.CredentialExchangeAvailabilityChanged,
|
||||||
) {
|
) {
|
||||||
mutableStateFlow.update { it.copy(showImportItemsChevron = action.isEnabled) }
|
mutableStateFlow.update { it.copy(showImportItemsChevron = action.isEnabled) }
|
||||||
}
|
}
|
||||||
@ -128,7 +138,9 @@ class VaultSettingsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleImportItemsClicked() {
|
private fun handleImportItemsClicked() {
|
||||||
if (featureFlagManager.getFeatureFlag(FlagKey.CredentialExchangeProtocolImport)) {
|
if (featureFlagManager.getFeatureFlag(FlagKey.CredentialExchangeProtocolImport) &&
|
||||||
|
policyManager.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP).isEmpty()
|
||||||
|
) {
|
||||||
sendEvent(VaultSettingsEvent.NavigateToImportItems)
|
sendEvent(VaultSettingsEvent.NavigateToImportItems)
|
||||||
} else {
|
} else {
|
||||||
sendEvent(VaultSettingsEvent.NavigateToImportVault)
|
sendEvent(VaultSettingsEvent.NavigateToImportVault)
|
||||||
@ -218,9 +230,9 @@ sealed class VaultSettingsAction {
|
|||||||
*/
|
*/
|
||||||
sealed class Internal : VaultSettingsAction() {
|
sealed class Internal : VaultSettingsAction() {
|
||||||
/**
|
/**
|
||||||
* Indicates that the import feature flag has been updated.
|
* Indicates that the CXF import feature availability has changed.
|
||||||
*/
|
*/
|
||||||
data class ImportFeatureUpdated(
|
data class CredentialExchangeAvailabilityChanged(
|
||||||
val isEnabled: Boolean,
|
val isEnabled: Boolean,
|
||||||
) : Internal()
|
) : Internal()
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,15 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.vault
|
|||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.bitwarden.core.data.manager.model.FlagKey
|
import com.bitwarden.core.data.manager.model.FlagKey
|
||||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.bitwarden.network.model.PolicyTypeJson
|
||||||
|
import com.bitwarden.network.model.SyncResponseJson
|
||||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
|
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
|
||||||
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||||
import com.bitwarden.ui.util.asText
|
import com.bitwarden.ui.util.asText
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||||
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@ -38,6 +41,11 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
|||||||
getFeatureFlagFlow(FlagKey.CredentialExchangeProtocolImport)
|
getFeatureFlagFlow(FlagKey.CredentialExchangeProtocolImport)
|
||||||
} returns mutableFeatureFlagFlow
|
} returns mutableFeatureFlagFlow
|
||||||
}
|
}
|
||||||
|
private val mutablePoliciesFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Policy>>()
|
||||||
|
private val policyManager = mockk<PolicyManager> {
|
||||||
|
every { getActivePolicies(any()) } returns emptyList()
|
||||||
|
every { getActivePoliciesFlow(any()) } returns mutablePoliciesFlow
|
||||||
|
}
|
||||||
|
|
||||||
private val mutableSnackbarSharedFlow = bufferedMutableSharedFlow<BitwardenSnackbarData>()
|
private val mutableSnackbarSharedFlow = bufferedMutableSharedFlow<BitwardenSnackbarData>()
|
||||||
private val snackbarRelayManager = mockk<SnackbarRelayManager<SnackbarRelay>> {
|
private val snackbarRelayManager = mockk<SnackbarRelayManager<SnackbarRelay>> {
|
||||||
@ -84,6 +92,25 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ImportItemsClick should emit NavigateToImportVault when policy is not empty`() = runTest {
|
||||||
|
every {
|
||||||
|
featureFlagManager.getFeatureFlag(FlagKey.CredentialExchangeProtocolImport)
|
||||||
|
} returns true
|
||||||
|
every {
|
||||||
|
policyManager.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||||
|
} returns listOf(mockk())
|
||||||
|
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(VaultSettingsAction.ImportItemsClick)
|
||||||
|
assertEquals(
|
||||||
|
VaultSettingsEvent.NavigateToImportVault,
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `ImportItemsClick should emit NavigateToImportItems when CredentialExchangeProtocolImport is enabled`() =
|
fun `ImportItemsClick should emit NavigateToImportItems when CredentialExchangeProtocolImport is enabled`() =
|
||||||
@ -160,21 +187,36 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CredentialExchangeProtocolImport flag changes should update state accordingly`() {
|
fun `showImportItemsChevron should display based on feature flag and policies`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
// Verify chevron is shown when feature flag is enabled and no policies (default state)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = true),
|
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = true),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Verify chevron is hidden when feature flag is disabled and no policies
|
||||||
mutableFeatureFlagFlow.tryEmit(false)
|
mutableFeatureFlagFlow.tryEmit(false)
|
||||||
|
mutablePoliciesFlow.tryEmit(emptyList())
|
||||||
assertEquals(
|
assertEquals(
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = false),
|
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = false),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Verify chevron is hidden when feature flag is enabled and policies exist
|
||||||
mutableFeatureFlagFlow.tryEmit(true)
|
mutableFeatureFlagFlow.tryEmit(true)
|
||||||
|
mutablePoliciesFlow.tryEmit(listOf(mockk()))
|
||||||
assertEquals(
|
assertEquals(
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = true),
|
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = false),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify chevron is hidden when feature flag is disabled and no policies
|
||||||
|
mutableFeatureFlagFlow.tryEmit(false)
|
||||||
|
mutablePoliciesFlow.tryEmit(emptyList())
|
||||||
|
assertEquals(
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
VaultSettingsState(showImportActionCard = true, showImportItemsChevron = false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +224,7 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
|||||||
firstTimeActionManager = firstTimeActionManager,
|
firstTimeActionManager = firstTimeActionManager,
|
||||||
snackbarRelayManager = snackbarRelayManager,
|
snackbarRelayManager = snackbarRelayManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
|
policyManager = policyManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user