mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
[PM-23696] Hide cards from export when policy is enabled. (#5520)
This commit is contained in:
parent
33cfaa5e95
commit
f26d54a2e2
@ -9,6 +9,7 @@ import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.send.SendView
|
||||
import com.bitwarden.vault.CipherListView
|
||||
import com.bitwarden.vault.CipherType
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.CollectionView
|
||||
import com.bitwarden.vault.FolderView
|
||||
@ -262,6 +263,12 @@ interface VaultRepository : CipherManager, VaultLockManager {
|
||||
|
||||
/**
|
||||
* Attempt to get the user's vault data for export.
|
||||
*
|
||||
* @param format The export format to use.
|
||||
* @param restrictedTypes A list of restricted types to export.
|
||||
*/
|
||||
suspend fun exportVaultDataToString(format: ExportFormat): ExportVaultDataResult
|
||||
suspend fun exportVaultDataToString(
|
||||
format: ExportFormat,
|
||||
restrictedTypes: List<CipherType>,
|
||||
): ExportVaultDataResult
|
||||
}
|
||||
|
||||
@ -931,7 +931,10 @@ class VaultRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun exportVaultDataToString(format: ExportFormat): ExportVaultDataResult {
|
||||
override suspend fun exportVaultDataToString(
|
||||
format: ExportFormat,
|
||||
restrictedTypes: List<CipherType>,
|
||||
): ExportVaultDataResult {
|
||||
val userId = activeUserId
|
||||
?: return ExportVaultDataResult.Error(error = NoActiveUserException())
|
||||
val folders = vaultDiskSource
|
||||
@ -945,7 +948,11 @@ class VaultRepositoryImpl(
|
||||
.firstOrNull()
|
||||
.orEmpty()
|
||||
.map { it.toEncryptedSdkCipher() }
|
||||
.filter { it.collectionIds.isEmpty() && it.deletedDate == null }
|
||||
.filter {
|
||||
it.collectionIds.isEmpty() &&
|
||||
it.deletedDate == null &&
|
||||
!restrictedTypes.contains(it.type)
|
||||
}
|
||||
|
||||
return vaultSdkSource
|
||||
.exportVaultDataToString(
|
||||
|
||||
@ -9,6 +9,7 @@ import com.bitwarden.network.model.PolicyTypeJson
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.vault.CipherType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
@ -16,7 +17,9 @@ import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||
@ -41,15 +44,16 @@ private const val KEY_STATE = "state"
|
||||
/**
|
||||
* Manages application state for the Export Vault screen.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@Suppress("TooManyFunctions", "LongParameterList")
|
||||
@HiltViewModel
|
||||
class ExportVaultViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
policyManager: PolicyManager,
|
||||
private val policyManager: PolicyManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val fileManager: FileManager,
|
||||
private val clock: Clock,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
) : BaseViewModel<ExportVaultState, ExportVaultEvent, ExportVaultAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: ExportVaultState(
|
||||
@ -433,6 +437,7 @@ class ExportVaultViewModel @Inject constructor(
|
||||
state.passwordInput
|
||||
},
|
||||
),
|
||||
restrictedTypes = getRestrictedItemTypes(),
|
||||
)
|
||||
|
||||
sendAction(
|
||||
@ -454,6 +459,22 @@ class ExportVaultViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRestrictedItemTypes(): List<CipherType> {
|
||||
val isRemoveCardPolicyFeatureEnabled =
|
||||
featureFlagManager.getFeatureFlag(FlagKey.RemoveCardPolicy)
|
||||
if (!isRemoveCardPolicyFeatureEnabled) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val hasActiveRestrictItemTypesPolicy =
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.RESTRICT_ITEM_TYPES).isNotEmpty()
|
||||
if (!hasActiveRestrictItemTypesPolicy) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return listOf(CipherType.CARD)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,6 +15,7 @@ import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||
import com.bitwarden.data.manager.DispatcherManager
|
||||
import com.bitwarden.exporters.ExportFormat
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.network.model.CipherTypeJson
|
||||
import com.bitwarden.network.model.CreateFileSendResponse
|
||||
import com.bitwarden.network.model.CreateSendJsonResponse
|
||||
import com.bitwarden.network.model.FolderJsonRequest
|
||||
@ -41,6 +42,7 @@ import com.bitwarden.network.service.SyncService
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.bitwarden.send.SendType
|
||||
import com.bitwarden.send.SendView
|
||||
import com.bitwarden.vault.CipherType
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.CollectionView
|
||||
import com.bitwarden.vault.Folder
|
||||
@ -4405,7 +4407,63 @@ class VaultRepositoryTest {
|
||||
} returns "TestResult".asSuccess()
|
||||
|
||||
val expected = ExportVaultDataResult.Success(vaultData = "TestResult")
|
||||
val result = vaultRepository.exportVaultDataToString(format = format)
|
||||
val result = vaultRepository.exportVaultDataToString(
|
||||
format = format,
|
||||
restrictedTypes = emptyList(),
|
||||
)
|
||||
|
||||
coVerify {
|
||||
vaultSdkSource.exportVaultDataToString(
|
||||
userId = userId,
|
||||
ciphers = listOf(userCipher.toEncryptedSdkCipher()),
|
||||
folders = listOf(createMockSdkFolder(1)),
|
||||
format = ExportFormat.Json,
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exportVaultDataToString with restrictedTypes should filter out restricted cipher types`() =
|
||||
runTest {
|
||||
val format = ExportFormat.Json
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
|
||||
val userCipher = createMockCipher(1).copy(
|
||||
type = CipherTypeJson.LOGIN,
|
||||
collectionIds = null,
|
||||
deletedDate = null,
|
||||
)
|
||||
val userCipherCard = createMockCipher(2).copy(
|
||||
type = CipherTypeJson.CARD,
|
||||
collectionIds = null,
|
||||
deletedDate = null,
|
||||
)
|
||||
val deletedCipher = createMockCipher(2).copy(collectionIds = null)
|
||||
val orgCipher = createMockCipher(3).copy(deletedDate = null)
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.getCiphersFlow(userId)
|
||||
} returns flowOf(listOf(userCipher, userCipherCard, deletedCipher, orgCipher))
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.getFolders(userId)
|
||||
} returns flowOf(listOf(createMockFolder(1)))
|
||||
|
||||
coEvery {
|
||||
vaultSdkSource.exportVaultDataToString(userId, any(), any(), format)
|
||||
} returns "TestResult".asSuccess()
|
||||
|
||||
val expected = ExportVaultDataResult.Success(vaultData = "TestResult")
|
||||
val result = vaultRepository.exportVaultDataToString(
|
||||
format = format,
|
||||
restrictedTypes = listOf(CipherType.CARD),
|
||||
)
|
||||
|
||||
coVerify {
|
||||
vaultSdkSource.exportVaultDataToString(
|
||||
@ -4442,7 +4500,10 @@ class VaultRepositoryTest {
|
||||
} returns error.asFailure()
|
||||
|
||||
val expected = ExportVaultDataResult.Error(error = error)
|
||||
val result = vaultRepository.exportVaultDataToString(format = format)
|
||||
val result = vaultRepository.exportVaultDataToString(
|
||||
format = format,
|
||||
restrictedTypes = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
|
||||
@ -9,6 +9,7 @@ import com.bitwarden.network.model.PolicyTypeJson
|
||||
import com.bitwarden.network.model.createMockPolicy
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.vault.CipherType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
@ -18,8 +19,10 @@ import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
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.FlagKey
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||
@ -49,6 +52,9 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
every {
|
||||
getActivePolicies(type = PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT)
|
||||
} returns emptyList()
|
||||
every {
|
||||
getActivePolicies(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
} returns emptyList()
|
||||
}
|
||||
|
||||
private val clock: Clock = Clock.fixed(
|
||||
@ -57,10 +63,27 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
coEvery { exportVaultDataToString(any()) } returns ExportVaultDataResult.Success("data")
|
||||
coEvery {
|
||||
exportVaultDataToString(
|
||||
format = any(),
|
||||
restrictedTypes = emptyList(),
|
||||
)
|
||||
} returns ExportVaultDataResult.Success("data")
|
||||
coEvery {
|
||||
exportVaultDataToString(
|
||||
format = any(),
|
||||
restrictedTypes = listOf(CipherType.CARD),
|
||||
)
|
||||
} returns ExportVaultDataResult.Success("data")
|
||||
}
|
||||
private val fileManager: FileManager = mockk()
|
||||
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlag(FlagKey.RemoveCardPolicy)
|
||||
} returns false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
every {
|
||||
@ -106,7 +129,62 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
|
||||
|
||||
coVerify {
|
||||
vaultRepository.exportVaultDataToString(any())
|
||||
vaultRepository.exportVaultDataToString(any(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ConfirmExportVaultClicked correct password should call exportVaultDataToString with restricted item types when policy and feature flags enabled`() {
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepository.validatePassword(
|
||||
password = password,
|
||||
)
|
||||
} returns ValidatePasswordResult.Success(isValid = true)
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.RESTRICT_ITEM_TYPES)
|
||||
} returns listOf(createMockPolicy())
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.RemoveCardPolicy)
|
||||
} returns true
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
|
||||
|
||||
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
|
||||
|
||||
coVerify {
|
||||
vaultRepository.exportVaultDataToString(
|
||||
format = any(),
|
||||
restrictedTypes = listOf(CipherType.CARD),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ConfirmExportVaultClicked correct password should call exportVaultDataToString without restricted item types when policy is disabled and feature flag is enabled`() {
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepository.validatePassword(
|
||||
password = password,
|
||||
)
|
||||
} returns ValidatePasswordResult.Success(isValid = true)
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.RemoveCardPolicy)
|
||||
} returns true
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
|
||||
|
||||
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
|
||||
|
||||
coVerify {
|
||||
vaultRepository.exportVaultDataToString(
|
||||
format = any(),
|
||||
restrictedTypes = listOf(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +213,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
coVerify {
|
||||
vaultRepository.exportVaultDataToString(any())
|
||||
vaultRepository.exportVaultDataToString(any(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,7 +246,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
coVerify(exactly = 0) {
|
||||
vaultRepository.exportVaultDataToString(any())
|
||||
vaultRepository.exportVaultDataToString(any(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,6 +292,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
authRepository.validatePassword(password)
|
||||
vaultRepository.exportVaultDataToString(
|
||||
format = ExportFormat.EncryptedJson(filePassword),
|
||||
restrictedTypes = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -746,6 +825,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
fileManager = fileManager,
|
||||
vaultRepository = vaultRepository,
|
||||
clock = clock,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user