From d538e37606055f47e55efbe79c60a32e2c1438fe Mon Sep 17 00:00:00 2001 From: Shannon Draeker <125921730+shannon-livefront@users.noreply.github.com> Date: Wed, 31 Jan 2024 19:14:27 -0700 Subject: [PATCH] Create policy manager (#899) --- .../data/auth/repository/AuthRepository.kt | 5 - .../auth/repository/AuthRepositoryImpl.kt | 24 +-- .../repository/di/AuthRepositoryModule.kt | 3 + .../util/AuthDiskSourceExtensions.kt | 21 --- .../data/platform/manager/PolicyManager.kt | 20 +++ .../platform/manager/PolicyManagerImpl.kt | 101 ++++++++++++ .../manager/di/PlatformManagerModule.kt | 10 ++ .../manager/util/PolicyManagerExtensions.kt | 26 ++++ .../repository/GeneratorRepositoryImpl.kt | 14 +- .../di/GeneratorRepositoryModule.kt | 3 + .../network/model/OrganizationStatusType.kt | 36 +++++ .../network/model/OrganizationType.kt | 47 ++++++ .../network/model/SyncResponseJson.kt | 4 +- .../exportvault/ExportVaultViewModel.kt | 7 +- .../auth/repository/AuthRepositoryTest.kt | 79 ++++------ .../util/AuthDiskSourceExtensionsTest.kt | 34 ----- .../platform/manager/PolicyManagerTest.kt | 144 ++++++++++++++++++ .../repository/GeneratorRepositoryTest.kt | 50 +++--- .../network/model/SyncResponsePolicyUtil.kt | 3 +- .../network/model/SyncResponseProfileUtil.kt | 14 +- .../exportvault/ExportVaultViewModelTest.kt | 16 +- 21 files changed, 488 insertions(+), 173 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManager.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationStatusType.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationType.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt index 3ba31b0290..58d29f7b65 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepository.kt @@ -86,11 +86,6 @@ interface AuthRepository : AuthenticatorProvider { */ val passwordPolicies: List - /** - * Return whether there are any export vault policies enabled for the current user. - */ - val hasExportVaultPoliciesEnabled: Boolean - /** * The reason for resetting the password. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt index a4ee365316..08e707b14e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt @@ -56,7 +56,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult -import com.x8bit.bitwarden.data.auth.repository.util.currentUserPoliciesListFlow import com.x8bit.bitwarden.data.auth.repository.util.policyInformation import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.auth.repository.util.toUserState @@ -65,8 +64,10 @@ import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsList import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsListFlow import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS import com.x8bit.bitwarden.data.auth.util.toSdkParams +import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow @@ -123,6 +124,7 @@ class AuthRepositoryImpl( private val vaultRepository: VaultRepository, private val userLogoutManager: UserLogoutManager, private val pushManager: PushManager, + private val policyManager: PolicyManager, dispatcherManager: DispatcherManager, private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() }, ) : AuthRepository { @@ -235,21 +237,7 @@ class AuthRepositoryImpl( by mutableHasPendingAccountAdditionStateFlow::value override val passwordPolicies: List - get() = activeUserId?.let { userId -> - authDiskSource - .getPolicies(userId) - ?.filter { it.type == PolicyTypeJson.MASTER_PASSWORD && it.isEnabled } - ?.mapNotNull { it.policyInformation as? PolicyInformation.MasterPassword } - .orEmpty() - } ?: emptyList() - - override val hasExportVaultPoliciesEnabled: Boolean - get() = activeUserId?.let { userId -> - authDiskSource - .getPolicies(userId) - ?.any { it.type == PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT && it.isEnabled } - ?: false - } ?: false + get() = policyManager.getActivePolicies() override val passwordResetReason: ForcePasswordResetReason? get() = authDiskSource @@ -274,7 +262,8 @@ class AuthRepositoryImpl( .launchIn(unconfinedScope) // When the policies for the user have been set, complete the login process. - authDiskSource.currentUserPoliciesListFlow + policyManager + .getActivePoliciesFlow(type = PolicyTypeJson.MASTER_PASSWORD) .onEach { policies -> val userId = activeUserId ?: return@onEach @@ -1148,7 +1137,6 @@ class AuthRepositoryImpl( // If there are no master password policies that are enabled and should be // enforced on login, the check should complete. val passwordPolicies = policyList - .filter { it.type == PolicyTypeJson.MASTER_PASSWORD && it.isEnabled } .mapNotNull { it.policyInformation as? PolicyInformation.MasterPassword } .filter { it.enforceOnLogin == true } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/AuthRepositoryModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/AuthRepositoryModule.kt index b599e50fb5..4b072e6e43 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/AuthRepositoryModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/AuthRepositoryModule.kt @@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl +import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository @@ -53,6 +54,7 @@ object AuthRepositoryModule { vaultRepository: VaultRepository, userLogoutManager: UserLogoutManager, pushManager: PushManager, + policyManager: PolicyManager, ): AuthRepository = AuthRepositoryImpl( clock = clock, accountsService = accountsService, @@ -71,5 +73,6 @@ object AuthRepositoryModule { vaultRepository = vaultRepository, userLogoutManager = userLogoutManager, pushManager = pushManager, + policyManager = policyManager, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensions.kt index 459a15dc4b..5eaba051e0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensions.kt @@ -2,12 +2,10 @@ package com.x8bit.bitwarden.data.auth.repository.util import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations -import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map @@ -55,22 +53,3 @@ val AuthDiskSource.userOrganizationsListFlow: Flow> ) { values -> values.toList() } } .distinctUntilChanged() - -/** - * Returns a [Flow] that emits distinct updates to the - * current user's [SyncResponseJson.Policy] list. - */ -@OptIn(ExperimentalCoroutinesApi::class) -val AuthDiskSource.currentUserPoliciesListFlow: Flow?> - get() = - this - .userStateFlow - .flatMapLatest { userStateJson -> - userStateJson - ?.activeUserId - ?.let { activeUserId -> - this.getPoliciesFlow(activeUserId) - } - ?: emptyFlow() - } - .distinctUntilChanged() diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManager.kt new file mode 100644 index 0000000000..19749c57b1 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManager.kt @@ -0,0 +1,20 @@ +package com.x8bit.bitwarden.data.platform.manager + +import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson +import kotlinx.coroutines.flow.Flow + +/** + * A manager for pulling policies from the local data store and filtering them as needed. + */ +interface PolicyManager { + /** + * Returns a flow of all the active policies of the given type. + */ + fun getActivePoliciesFlow(type: PolicyTypeJson): Flow> + + /** + * Get all the policies of the given [type] that are enabled and applicable to the user. + */ + fun getActivePolicies(type: PolicyTypeJson): List +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerImpl.kt new file mode 100644 index 0000000000..1b0c97ad11 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerImpl.kt @@ -0,0 +1,101 @@ +package com.x8bit.bitwarden.data.platform.manager + +import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationStatusType +import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationType +import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map + +/** + * The default [PolicyManager] implementation. This class is responsible for + * loading policies for the current user and filtering them as needed. + */ +class PolicyManagerImpl( + private val authDiskSource: AuthDiskSource, +) : PolicyManager { + @OptIn(ExperimentalCoroutinesApi::class) + override fun getActivePoliciesFlow(type: PolicyTypeJson): Flow> = + authDiskSource + .userStateFlow + .flatMapLatest { userStateJson -> + userStateJson + ?.activeUserId + ?.let { activeUserId -> + authDiskSource.getPoliciesFlow(activeUserId) + .map { + filterPolicies( + userId = activeUserId, + type = type, + policies = it, + ) + } + } + ?: emptyFlow() + } + .distinctUntilChanged() + + override fun getActivePolicies(type: PolicyTypeJson): List = + authDiskSource + .userState + ?.activeUserId + ?.let { userId -> + filterPolicies( + userId = userId, + type = type, + policies = authDiskSource.getPolicies(userId = userId), + ) + } + ?: emptyList() + + /** + * A helper method to filter policies. + */ + private fun filterPolicies( + userId: String, + type: PolicyTypeJson, + policies: List?, + ): List { + if (policies.isNullOrEmpty()) return emptyList() + + // Get a list of the user's organizations that enforce policies. + val organizationIdsWithActivePolicies = authDiskSource + .getOrganizations(userId) + ?.filter { + it.shouldUsePolicies && + it.isEnabled && + it.status >= OrganizationStatusType.ACCEPTED && + !isOrganizationExemptFromPolicies(it, type) + } + ?.map { it.id } + .orEmpty() + + // Filter the policies based on the type, whether the policy is active, + // and whether the organization rules except the user from the policy. + return policies.filter { + it.type == type && + it.isEnabled && + organizationIdsWithActivePolicies.contains(it.organizationId) + } + } + + /** + * A helper method to determine if the organization is exempt from policies. + */ + private fun isOrganizationExemptFromPolicies( + organization: SyncResponseJson.Profile.Organization, + policyType: PolicyTypeJson, + ): Boolean = + if (policyType == PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT) { + organization.type == OrganizationType.OWNER + } else { + (organization.type == OrganizationType.OWNER || + organization.type == OrganizationType.ADMIN) || + organization.permissions.shouldManagePolicies + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt index 9df738fdae..1b78bb69cc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt @@ -20,6 +20,8 @@ import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManagerImpl import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManagerImpl +import com.x8bit.bitwarden.data.platform.manager.PolicyManager +import com.x8bit.bitwarden.data.platform.manager.PolicyManagerImpl import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.PushManagerImpl import com.x8bit.bitwarden.data.platform.manager.SdkClientManager @@ -124,6 +126,14 @@ object PlatformManagerModule { context = application.applicationContext, ) + @Provides + @Singleton + fun providePolicyManager( + authDiskSource: AuthDiskSource, + ): PolicyManager = PolicyManagerImpl( + authDiskSource = authDiskSource, + ) + @Provides @Singleton fun providePushManager( diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt new file mode 100644 index 0000000000..d32c504a03 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt @@ -0,0 +1,26 @@ +package com.x8bit.bitwarden.data.platform.manager.util + +import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation +import com.x8bit.bitwarden.data.auth.repository.util.policyInformation +import com.x8bit.bitwarden.data.platform.manager.PolicyManager +import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson + +/** + * Get a list of active policies with the data decoded to the specified type. + */ +inline fun PolicyManager.getActivePolicies(): List { + val type = when (T::class.java) { + PolicyInformation.MasterPassword::class.java -> PolicyTypeJson.MASTER_PASSWORD + PolicyInformation.PasswordGenerator::class.java -> PolicyTypeJson.PASSWORD_GENERATOR + + else -> { + throw IllegalStateException( + "Looks like you are missing a branch in your when statement. Update " + + "getActivePolicies() to handle all PolicyInformation implementations.", + ) + } + } + return this + .getActivePolicies(type = type) + .mapNotNull { it.policyInformation as? T } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt index 986e8ce6fe..dc8f59765a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt @@ -8,8 +8,9 @@ import com.bitwarden.generators.PasswordGeneratorRequest import com.bitwarden.generators.UsernameGeneratorRequest import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation -import com.x8bit.bitwarden.data.auth.repository.util.policyInformation +import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource @@ -26,7 +27,6 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandom import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions -import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel @@ -48,12 +48,14 @@ import kotlin.math.max * Default implementation of [GeneratorRepository]. */ @Singleton +@Suppress("LongParameterList") class GeneratorRepositoryImpl( private val generatorSdkSource: GeneratorSdkSource, private val generatorDiskSource: GeneratorDiskSource, private val authDiskSource: AuthDiskSource, private val vaultSdkSource: VaultSdkSource, private val passwordHistoryDiskSource: PasswordHistoryDiskSource, + private val policyManager: PolicyManager, dispatcherManager: DispatcherManager, ) : GeneratorRepository { @@ -201,10 +203,9 @@ class GeneratorRepositoryImpl( }, ) - @Suppress("LongMethod", "ReturnCount", "CyclomaticComplexMethod") + @Suppress("LongMethod", "CyclomaticComplexMethod") override fun getPasswordGeneratorPolicy(): PolicyInformation.PasswordGenerator? { - val userId = authDiskSource.userState?.activeUserId ?: return null - val policies = authDiskSource.getPolicies(userId) ?: return null + val policies: List = policyManager.getActivePolicies() var minLength: Int? = null var useUpper = false @@ -218,8 +219,7 @@ class GeneratorRepositoryImpl( var includeNumber = false var isPassphrasePresent = false - policies.filter { it.type == PolicyTypeJson.PASSWORD_GENERATOR && it.isEnabled } - .mapNotNull { it.policyInformation as? PolicyInformation.PasswordGenerator } + policies .forEach { policy -> if (policy.defaultType == PolicyInformation.PasswordGenerator.TYPE_PASSPHRASE) { isPassphrasePresent = true diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/di/GeneratorRepositoryModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/di/GeneratorRepositoryModule.kt index 398ad04914..9c88a3fe5d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/di/GeneratorRepositoryModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/di/GeneratorRepositoryModule.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.tools.generator.repository.di import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource @@ -30,6 +31,7 @@ object GeneratorRepositoryModule { vaultSdkSource: VaultSdkSource, passwordHistoryDiskSource: PasswordHistoryDiskSource, dispatcherManager: DispatcherManager, + policyManager: PolicyManager, ): GeneratorRepository = GeneratorRepositoryImpl( generatorSdkSource = generatorSdkSource, generatorDiskSource = generatorDiskSource, @@ -37,5 +39,6 @@ object GeneratorRepositoryModule { vaultSdkSource = vaultSdkSource, passwordHistoryDiskSource = passwordHistoryDiskSource, dispatcherManager = dispatcherManager, + policyManager = policyManager, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationStatusType.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationStatusType.kt new file mode 100644 index 0000000000..dc033ab042 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationStatusType.kt @@ -0,0 +1,36 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +import androidx.annotation.Keep +import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a user's status in an organization. + */ +@Serializable(OrganizationStatusTypeSerializer::class) +enum class OrganizationStatusType { + /** + * The user has been invited to the organization. + */ + @SerialName("0") + INVITED, + + /** + * The user has accepted the invite to the organization. + */ + @SerialName("1") + ACCEPTED, + + /** + * The user has been confirmed in the organization. + */ + @SerialName("2") + CONFIRMED, +} + +@Keep +private class OrganizationStatusTypeSerializer : + BaseEnumeratedIntSerializer( + OrganizationStatusType.entries.toTypedArray(), + ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationType.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationType.kt new file mode 100644 index 0000000000..e91370a9bd --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/OrganizationType.kt @@ -0,0 +1,47 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +import androidx.annotation.Keep +import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a user's role in an organization. + */ +@Serializable(OrganizationTypeSerializer::class) +enum class OrganizationType { + /** + * The user is an owner of the organization. + */ + @SerialName("0") + OWNER, + + /** + * The user is an admin in the organization. + */ + @SerialName("1") + ADMIN, + + /** + * The user is an ordinary user in the organization. + */ + @SerialName("2") + USER, + + /** + * The user is a manager in the organization. + */ + @SerialName("3") + MANAGER, + + /** + * The user has a custom role in the organization. + */ + @SerialName("4") + CUSTOM, +} + +@Keep +private class OrganizationTypeSerializer : BaseEnumeratedIntSerializer( + OrganizationType.entries.toTypedArray(), +) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt index b9cc26407f..fc14a49855 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt @@ -246,7 +246,7 @@ data class SyncResponseJson( val keyConnectorUrl: String?, @SerialName("type") - val type: Int, + val type: OrganizationType, @SerialName("seats") val seats: Int?, @@ -326,7 +326,7 @@ data class SyncResponseJson( val familySponsorshipValidUntil: ZonedDateTime?, @SerialName("status") - val status: Int, + val status: OrganizationStatusType, ) /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt index 455b503a8c..7b2f8d0f0d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult +import com.x8bit.bitwarden.data.platform.manager.PolicyManager +import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText @@ -26,6 +28,7 @@ private const val KEY_STATE = "state" @HiltViewModel class ExportVaultViewModel @Inject constructor( private val authRepository: AuthRepository, + private val policyManager: PolicyManager, savedStateHandle: SavedStateHandle, ) : BaseViewModel( initialState = savedStateHandle[KEY_STATE] @@ -33,7 +36,9 @@ class ExportVaultViewModel @Inject constructor( dialogState = null, exportFormat = ExportVaultFormat.JSON, passwordInput = "", - policyPreventsExport = authRepository.hasExportVaultPoliciesEnabled, + policyPreventsExport = policyManager + .getActivePolicies(type = PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT) + .any(), ), ) { init { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt index c54271f554..2b4da7d1b7 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt @@ -70,6 +70,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUserState import com.x8bit.bitwarden.data.auth.repository.util.toUserStateJson import com.x8bit.bitwarden.data.auth.util.toSdkParams import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager +import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -79,6 +80,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.util.asSuccess import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource @@ -194,10 +196,16 @@ class AuthRepositoryTest { private val mutableLogoutFlow = bufferedMutableSharedFlow() private val mutableSyncOrgKeysFlow = bufferedMutableSharedFlow() + private val mutableActivePolicyFlow = bufferedMutableSharedFlow>() private val pushManager: PushManager = mockk { every { logoutFlow } returns mutableLogoutFlow every { syncOrgKeysFlow } returns mutableSyncOrgKeysFlow } + private val policyManager: PolicyManager = mockk { + every { + getActivePoliciesFlow(type = PolicyTypeJson.MASTER_PASSWORD) + } returns mutableActivePolicyFlow + } private var elapsedRealtimeMillis = 123456789L @@ -219,6 +227,7 @@ class AuthRepositoryTest { userLogoutManager = userLogoutManager, dispatcherManager = dispatcherManager, pushManager = pushManager, + policyManager = policyManager, elapsedRealtimeMillisProvider = { elapsedRealtimeMillis }, ) @@ -406,9 +415,8 @@ class AuthRepositoryTest { val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) // Set policies that will fail the password. - fakeAuthDiskSource.storePolicies( - userId = USER_ID_1, - policies = listOf( + mutableActivePolicyFlow.emit( + listOf( createMockPolicy( type = PolicyTypeJson.MASTER_PASSWORD, isEnabled = true, @@ -509,38 +517,6 @@ class AuthRepositoryTest { assertNull(repository.rememberedOrgIdentifier) } - @Test - fun `hasExportVaultPoliciesEnabled checks if any export vault policies are enabled`() { - fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - - // No stored policies returns false. - assertFalse(repository.hasExportVaultPoliciesEnabled) - - // Stored but disabled policies returns false. - fakeAuthDiskSource.storePolicies( - userId = USER_ID_1, - policies = listOf( - createMockPolicy( - type = PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT, - isEnabled = false, - ), - ), - ) - assertFalse(repository.hasExportVaultPoliciesEnabled) - - // Stored enabled policies returns true. - fakeAuthDiskSource.storePolicies( - userId = USER_ID_1, - policies = listOf( - createMockPolicy( - type = PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT, - isEnabled = true, - ), - ), - ) - assertTrue(repository.hasExportVaultPoliciesEnabled) - } - @Test fun `passwordResetReason should pull from the user's profile in AuthDiskSource`() = runTest { val updatedProfile = ACCOUNT_1.profile.copy( @@ -3702,7 +3678,7 @@ class AuthRepositoryTest { fun `validatePasswordAgainstPolicy validates password against policy requirements`() = runTest { fakeAuthDiskSource.userState = SINGLE_USER_STATE_1 - // A helper method to set a policy in the store with the given parameters. + // A helper method to set a policy with the given parameters. fun setPolicy( minLength: Int = 0, minComplexity: Int? = null, @@ -3711,22 +3687,21 @@ class AuthRepositoryTest { requireNumbers: Boolean = false, requireSpecial: Boolean = false, ) { - fakeAuthDiskSource.storePolicies( - userId = USER_ID_1, - policies = listOf( - createMockPolicy( - type = PolicyTypeJson.MASTER_PASSWORD, - isEnabled = true, - data = buildJsonObject { - put(key = "minLength", value = minLength) - put(key = "minComplexity", value = minComplexity) - put(key = "requireUpper", value = requireUpper) - put(key = "requireLower", value = requireLower) - put(key = "requireNumbers", value = requireNumbers) - put(key = "requireSpecial", value = requireSpecial) - put(key = "enforceOnLogin", value = true) - }, - ), + every { + policyManager.getActivePolicies(type = PolicyTypeJson.MASTER_PASSWORD) + } returns listOf( + createMockPolicy( + type = PolicyTypeJson.MASTER_PASSWORD, + isEnabled = true, + data = buildJsonObject { + put(key = "minLength", value = minLength) + put(key = "minComplexity", value = minComplexity) + put(key = "requireUpper", value = requireUpper) + put(key = "requireLower", value = requireLower) + put(key = "requireNumbers", value = requireNumbers) + put(key = "requireSpecial", value = requireSpecial) + put(key = "enforceOnLogin", value = true) + }, ), ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensionsTest.kt index d96c3809a2..38de9f80fd 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/util/AuthDiskSourceExtensionsTest.kt @@ -8,7 +8,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.auth.repository.model.Organization import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization -import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -155,37 +154,4 @@ class AuthDiskSourceExtensionsTest { ) } } - - @Test - fun `currentUserPoliciesListFlow should emit changes to current user's policy data`() = - runTest { - val userId = "userId1" - val userStateJson = mockk() { - every { activeUserId } returns userId - } - authDiskSource.apply { - userState = userStateJson - storePolicies( - userId = userId, - policies = listOf(createMockPolicy()), - ) - } - - authDiskSource.currentUserPoliciesListFlow.test { - assertEquals( - listOf(createMockPolicy()), - awaitItem(), - ) - - authDiskSource.storePolicies( - userId = userId, - policies = listOf(createMockPolicy(number = 3)), - ) - - assertEquals( - listOf(createMockPolicy(number = 3)), - awaitItem(), - ) - } - } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerTest.kt new file mode 100644 index 0000000000..ec8262729b --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/PolicyManagerTest.kt @@ -0,0 +1,144 @@ +package com.x8bit.bitwarden.data.platform.manager + +import app.cash.turbine.test +import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class PolicyManagerTest { + private val mutableUserStateFlow = MutableStateFlow(null) + private val mutablePolicyFlow = MutableStateFlow?>(null) + private val authDiskSource: AuthDiskSource = mockk { + every { userStateFlow } returns mutableUserStateFlow + every { getPoliciesFlow(USER_ID) } returns mutablePolicyFlow + } + + private lateinit var policyManager: PolicyManager + + @BeforeEach + fun setUp() { + policyManager = PolicyManagerImpl( + authDiskSource = authDiskSource, + ) + } + + @Test + fun `currentUserPoliciesListFlow should emit changes to current user's policy data`() = + runTest { + val userStateJson = mockk { + every { activeUserId } returns USER_ID + } + val organizationsOne = createMockOrganization( + number = 1, + isEnabled = true, + shouldUsePolicies = true, + ) + val organizationsTwo = createMockOrganization( + number = 2, + isEnabled = true, + shouldUsePolicies = true, + ) + val expectedPolicyOne = createMockPolicy( + isEnabled = true, + number = 1, + organizationId = organizationsOne.id, + type = PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT, + ) + val expectedPolicyTwo = createMockPolicy( + isEnabled = true, + number = 2, + organizationId = organizationsTwo.id, + type = PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT, + ) + every { + authDiskSource.getOrganizations(USER_ID) + } returns listOf(organizationsOne) andThen listOf(organizationsTwo) + + mutableUserStateFlow.value = userStateJson + mutablePolicyFlow.value = listOf(expectedPolicyOne) + + policyManager + .getActivePoliciesFlow(type = PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT) + .test { + assertEquals(listOf(expectedPolicyOne), awaitItem()) + + mutablePolicyFlow.value = listOf(expectedPolicyTwo) + + assertEquals(listOf(expectedPolicyTwo), awaitItem()) + } + } + + @Test + fun `getActivePolicies returns empty list if user id is null`() { + every { + authDiskSource.userState + } returns null + + assertTrue(policyManager.getActivePolicies(type = PolicyTypeJson.MASTER_PASSWORD).isEmpty()) + } + + @Test + fun `getActivePolicies returns empty list if the policies are not active`() { + val userState: UserStateJson = mockk { + every { activeUserId } returns USER_ID + } + every { authDiskSource.userState } returns userState + every { + authDiskSource.getOrganizations(USER_ID) + } returns listOf( + createMockOrganization( + number = 3, + isEnabled = true, + ), + ) + every { + authDiskSource.getPolicies(USER_ID) + } returns listOf( + createMockPolicy( + organizationId = "mockId-3", + isEnabled = true, + ), + ) + + assertTrue(policyManager.getActivePolicies(type = PolicyTypeJson.MASTER_PASSWORD).isEmpty()) + } + + @Test + fun `getActivePolicies returns active and applied policies`() { + val userState: UserStateJson = mockk { + every { activeUserId } returns USER_ID + } + every { authDiskSource.userState } returns userState + every { + authDiskSource.getOrganizations(USER_ID) + } returns listOf( + createMockOrganization( + number = 3, + isEnabled = false, + ), + ) + every { + authDiskSource.getPolicies(USER_ID) + } returns listOf( + createMockPolicy( + organizationId = "mockId-3", + isEnabled = true, + ), + ) + + assertTrue(policyManager.getActivePolicies(type = PolicyTypeJson.MASTER_PASSWORD).isEmpty()) + } +} + +private const val USER_ID = "userId" diff --git a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt index 4b49ac22ed..17c69822ea 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt @@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserD import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager +import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource @@ -72,6 +73,7 @@ class GeneratorRepositoryTest { private val passwordHistoryDiskSource: PasswordHistoryDiskSource = mockk() private val vaultSdkSource: VaultSdkSource = mockk() private val dispatcherManager = FakeDispatcherManager() + private val policyManager: PolicyManager = mockk() private val repository = GeneratorRepositoryImpl( generatorSdkSource = generatorSdkSource, @@ -80,6 +82,7 @@ class GeneratorRepositoryTest { passwordHistoryDiskSource = passwordHistoryDiskSource, vaultSdkSource = vaultSdkSource, dispatcherManager = dispatcherManager, + policyManager = policyManager, ) @AfterEach @@ -769,35 +772,35 @@ class GeneratorRepositoryTest { @Suppress("MaxLineLength") @Test - fun `getPasswordGeneratorPolicy returns default settings when no policies are present`() = runTest { - val userId = "testUserId" - coEvery { authDiskSource.userState?.activeUserId } returns userId - coEvery { authDiskSource.getPolicies(userId) } returns emptyList() + fun `getPasswordGeneratorPolicy returns default settings when no policies are present`() = + runTest { + every { + policyManager.getActivePolicies(type = PolicyTypeJson.PASSWORD_GENERATOR) + } returns emptyList() - val policy = repository.getPasswordGeneratorPolicy() + val policy = repository.getPasswordGeneratorPolicy() - val expectedPolicy = PolicyInformation.PasswordGenerator( - defaultType = "password", - minLength = null, - useUpper = false, - useLower = false, - useNumbers = false, - useSpecial = false, - minNumbers = null, - minSpecial = null, - minNumberWords = null, - capitalize = false, - includeNumber = false, - ) + val expectedPolicy = PolicyInformation.PasswordGenerator( + defaultType = "password", + minLength = null, + useUpper = false, + useLower = false, + useNumbers = false, + useSpecial = false, + minNumbers = null, + minSpecial = null, + minNumberWords = null, + capitalize = false, + includeNumber = false, + ) - assertNotNull(policy) - assertEquals(expectedPolicy, policy) - } + assertNotNull(policy) + assertEquals(expectedPolicy, policy) + } @Suppress("MaxLineLength") @Test fun `getPasswordGeneratorPolicy applies strictest settings from multiple policies`() = runTest { - val userId = "testUserId" val policy1 = PolicyInformation.PasswordGenerator( defaultType = "password", minLength = 8, @@ -840,8 +843,7 @@ class GeneratorRepositoryTest { organizationId = "id2", ), ) - coEvery { authDiskSource.userState?.activeUserId } returns userId - coEvery { authDiskSource.getPolicies(userId) } returns policies + every { policyManager.getActivePolicies(type = PolicyTypeJson.PASSWORD_GENERATOR) } returns policies val resultPolicy = repository.getPasswordGeneratorPolicy() diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt index fb7c110716..06fa393479 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt @@ -7,12 +7,13 @@ import kotlinx.serialization.json.JsonObject */ fun createMockPolicy( number: Int = 1, + organizationId: String = "mockOrganizationId-$number", type: PolicyTypeJson = PolicyTypeJson.MASTER_PASSWORD, isEnabled: Boolean = false, data: JsonObject? = null, ): SyncResponseJson.Policy = SyncResponseJson.Policy( - organizationId = "mockOrganizationId-$number", + organizationId = organizationId, id = "mockId-$number", type = type, isEnabled = isEnabled, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt index 5d4f25000b..eca66f0d23 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt @@ -30,13 +30,17 @@ fun createMockProfile(number: Int): SyncResponseJson.Profile = /** * Create a mock [SyncResponseJson.Profile.Organization] with a given [number]. */ -fun createMockOrganization(number: Int): SyncResponseJson.Profile.Organization = +fun createMockOrganization( + number: Int, + isEnabled: Boolean = false, + shouldUsePolicies: Boolean = false, +): SyncResponseJson.Profile.Organization = SyncResponseJson.Profile.Organization( - shouldUsePolicies = false, + shouldUsePolicies = shouldUsePolicies, keyConnectorUrl = "mockKeyConnectorUrl-$number", - type = 1, + type = OrganizationType.ADMIN, seats = 1, - isEnabled = false, + isEnabled = isEnabled, providerType = 1, maxCollections = 1, isSelfHost = false, @@ -60,7 +64,7 @@ fun createMockOrganization(number: Int): SyncResponseJson.Profile.Organization = name = "mockName-$number", shouldUseApi = false, familySponsorshipValidUntil = ZonedDateTime.parse("2023-10-27T12:00:00Z"), - status = 1, + status = OrganizationStatusType.ACCEPTED, ) /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt index 07d76a390e..a1b8f67902 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt @@ -5,6 +5,9 @@ import app.cash.turbine.test import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult +import com.x8bit.bitwarden.data.platform.manager.PolicyManager +import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.model.ExportVaultFormat @@ -16,15 +19,21 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class ExportVaultViewModelTest : BaseViewModelTest() { - private val authRepository: AuthRepository = mockk { - every { hasExportVaultPoliciesEnabled } returns false + private val authRepository: AuthRepository = mockk() + + private val policyManager: PolicyManager = mockk { + every { + getActivePolicies(type = PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT) + } returns emptyList() } private val savedStateHandle = SavedStateHandle() @Test fun `initial state should be correct`() = runTest { - every { authRepository.hasExportVaultPoliciesEnabled } returns true + every { + policyManager.getActivePolicies(type = PolicyTypeJson.DISABLE_PERSONAL_VAULT_EXPORT) + } returns listOf(createMockPolicy()) val viewModel = createViewModel() viewModel.stateFlow.test { @@ -185,6 +194,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { private fun createViewModel(): ExportVaultViewModel = ExportVaultViewModel( authRepository = authRepository, + policyManager = policyManager, savedStateHandle = savedStateHandle, ) }