diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceImpl.kt index 413601651b..d418ee2c20 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceImpl.kt @@ -12,6 +12,10 @@ import com.bitwarden.sdk.ClientPlatform import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toPasswordStrengthOrNull import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte +import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** * Primary implementation of [AuthSdkSource] that serves as a convenience wrapper around a @@ -20,8 +24,18 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte class AuthSdkSourceImpl( private val clientAuth: ClientAuth, private val clientPlatform: ClientPlatform, + dispatcherManager: DispatcherManager, + featureFlagManager: BitwardenFeatureFlagManager, ) : AuthSdkSource { + private val ioScope = CoroutineScope(dispatcherManager.io) + + init { + ioScope.launch { + clientPlatform.loadFlags(featureFlagManager.featureFlags) + } + } + override suspend fun getTrustDevice(): Result = runCatching { clientAuth.trustDevice() } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/di/AuthSdkModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/di/AuthSdkModule.kt index c115ec09e7..37399d56ad 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/di/AuthSdkModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/sdk/di/AuthSdkModule.kt @@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.auth.datasource.sdk.di import com.bitwarden.sdk.Client import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSourceImpl +import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,8 +22,12 @@ object AuthSdkModule { @Singleton fun provideAuthSdkSource( client: Client, + featureFlagManager: BitwardenFeatureFlagManager, + dispatcherManager: DispatcherManager, ): AuthSdkSource = AuthSdkSourceImpl( clientAuth = client.auth(), clientPlatform = client.platform(), + featureFlagManager = featureFlagManager, + dispatcherManager = dispatcherManager, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManager.kt index 445f12ae31..b40b1f752d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManager.kt @@ -11,7 +11,7 @@ interface SdkClientManager { * Returns the cached [Client] instance for the given [userId], otherwise creates and caches * a new one and returns it. */ - fun getOrCreateClient(userId: String): Client + suspend fun getOrCreateClient(userId: String): Client /** * Clears any resources from the [Client] associated with the given [userId] and removes it diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt index 26f4f7e21b..28d4e5b1d6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt @@ -1,16 +1,21 @@ package com.x8bit.bitwarden.data.platform.manager import com.bitwarden.sdk.Client +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManager /** * Primary implementation of [SdkClientManager]. */ class SdkClientManagerImpl( - private val clientProvider: () -> Client = { Client(null) }, + private val featureFlagManager: BitwardenFeatureFlagManager, + private val clientProvider: suspend () -> Client = { + Client(null) + .apply { platform().loadFlags(featureFlagManager.featureFlags) } + }, ) : SdkClientManager { private val userIdToClientMap = mutableMapOf() - override fun getOrCreateClient( + override suspend fun getOrCreateClient( userId: String, ): Client = userIdToClientMap.getOrPut(key = userId) { clientProvider() } 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 1b78bb69cc..f6d75425c5 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 @@ -34,6 +34,7 @@ import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManagerImpl import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManager import com.x8bit.bitwarden.data.vault.repository.VaultRepository import dagger.Module import dagger.Provides @@ -97,7 +98,11 @@ object PlatformManagerModule { @Provides @Singleton - fun provideSdkClientManager(): SdkClientManager = SdkClientManagerImpl() + fun provideSdkClientManager( + featureFlagManager: BitwardenFeatureFlagManager, + ): SdkClientManager = SdkClientManagerImpl( + featureFlagManager = featureFlagManager, + ) @Provides @Singleton diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManager.kt new file mode 100644 index 0000000000..f95931d143 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManager.kt @@ -0,0 +1,11 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk + +/** + * Manages the available feature flags for the Bitwarden application. + */ +interface BitwardenFeatureFlagManager { + /** + * Returns a map of feature flags. + */ + val featureFlags: Map +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManagerImpl.kt new file mode 100644 index 0000000000..2b5fe6ac9e --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManagerImpl.kt @@ -0,0 +1,11 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk + +private const val CIPHER_KEY_ENCRYPTION_KEY = "enableCipherKeyEncryption" + +/** + * Primary implementation of [BitwardenFeatureFlagManager]. + */ +class BitwardenFeatureFlagManagerImpl : BitwardenFeatureFlagManager { + override val featureFlags: Map + get() = mapOf(CIPHER_KEY_ENCRYPTION_KEY to true) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt index cb32145a45..78663e7ddc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt @@ -388,7 +388,7 @@ class VaultSdkSourceImpl( ) } - private fun getClient( + private suspend fun getClient( userId: String, ): Client = sdkClientManager.getOrCreateClient(userId = userId) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt index 46dc7eeac6..828f697d08 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt @@ -1,6 +1,8 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk.di import com.x8bit.bitwarden.data.platform.manager.SdkClientManager +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManager +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManagerImpl import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSourceImpl import dagger.Module @@ -24,4 +26,9 @@ object VaultSdkModule { VaultSdkSourceImpl( sdkClientManager = sdkClientManager, ) + + @Provides + @Singleton + fun providesBitwardenFeatureFlagManager(): BitwardenFeatureFlagManager = + BitwardenFeatureFlagManagerImpl() } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceTest.kt index fe195f0161..aaa57eb9b7 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/sdk/AuthSdkSourceTest.kt @@ -10,25 +10,46 @@ import com.bitwarden.crypto.TrustDeviceResponse import com.bitwarden.sdk.ClientAuth import com.bitwarden.sdk.ClientPlatform import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength +import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.util.asSuccess +import com.x8bit.bitwarden.data.vault.datasource.sdk.BitwardenFeatureFlagManager import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class AuthSdkSourceTest { private val clientAuth = mockk() - private val clientPlatform = mockk() + private val clientPlatform = mockk { + coEvery { loadFlags(any()) } just runs + } + private val featureFlagManager = mockk { + coEvery { featureFlags } returns emptyMap() + } + private val dispatcherManager = FakeDispatcherManager() private val authSkdSource: AuthSdkSource = AuthSdkSourceImpl( clientAuth = clientAuth, clientPlatform = clientPlatform, + featureFlagManager = featureFlagManager, + dispatcherManager = dispatcherManager, ) + @BeforeEach + fun setup() { + coVerify(exactly = 1) { + featureFlagManager.featureFlags + clientPlatform.loadFlags(any()) + } + } + @Test fun `getTrustDevice with trustDevice success should return success with correct data`() = runBlocking { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt index ccd4c3c9e1..c66436e3e9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.manager import io.mockk.mockk import io.mockk.verify +import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test @@ -10,26 +11,28 @@ class SdkClientManagerTest { private val sdkClientManager = SdkClientManagerImpl( clientProvider = { mockk(relaxed = true) }, + featureFlagManager = mockk(), ) @Suppress("MaxLineLength") @Test - fun `getOrCreateClient should create a new client for each userId and return a cached client for subsequent calls`() { - val userId = "userId" - val firstClient = sdkClientManager.getOrCreateClient(userId = userId) + fun `getOrCreateClient should create a new client for each userId and return a cached client for subsequent calls`() = + runTest { + val userId = "userId" + val firstClient = sdkClientManager.getOrCreateClient(userId = userId) - // Additional calls for the same userId return the same value - val secondClient = sdkClientManager.getOrCreateClient(userId = userId) - assertEquals(firstClient, secondClient) + // Additional calls for the same userId return the same value + val secondClient = sdkClientManager.getOrCreateClient(userId = userId) + assertEquals(firstClient, secondClient) - // Additional calls for different userIds should return different values - val otherUserId = "otherUserId" - val thirdClient = sdkClientManager.getOrCreateClient(userId = otherUserId) - assertNotEquals(firstClient, thirdClient) - } + // Additional calls for different userIds should return different values + val otherUserId = "otherUserId" + val thirdClient = sdkClientManager.getOrCreateClient(userId = otherUserId) + assertNotEquals(firstClient, thirdClient) + } @Test - fun `destroyClient should call close on the Client and remove it from the cache`() { + fun `destroyClient should call close on the Client and remove it from the cache`() = runTest { val userId = "userId" val firstClient = sdkClientManager.getOrCreateClient(userId = userId) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManagerTest.kt new file mode 100644 index 0000000000..267b7236ec --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/BitwardenFeatureFlagManagerTest.kt @@ -0,0 +1,18 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk + +import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class BitwardenFeatureFlagManagerTest { + + private val bitwardenFeatureFlagManager = BitwardenFeatureFlagManagerImpl() + + @Test + fun `featureFlags should return set feature flags`() { + val expected = mapOf("enableCipherKeyEncryption" to true) + + val actual = bitwardenFeatureFlagManager.featureFlags + + assertEquals(expected, actual) + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt index 769fe58abc..880cbfc390 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt @@ -67,7 +67,7 @@ class VaultSdkSourceTest { every { exporters() } returns clientExporters } private val sdkClientManager = mockk { - every { getOrCreateClient(any()) } returns client + coEvery { getOrCreateClient(any()) } returns client every { destroyClient(any()) } just runs } private val vaultSdkSource: VaultSdkSource = VaultSdkSourceImpl( @@ -102,7 +102,7 @@ class VaultSdkSourceTest { coVerify { clientCrypto.derivePinKey(pin) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -125,7 +125,7 @@ class VaultSdkSourceTest { coVerify { clientCrypto.derivePinUserKey(encryptedPin = encryptedPin) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -144,7 +144,7 @@ class VaultSdkSourceTest { coVerify { clientCrypto.getUserEncryptionKey() } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -192,7 +192,7 @@ class VaultSdkSourceTest { req = mockInitCryptoRequest, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -218,7 +218,7 @@ class VaultSdkSourceTest { req = mockInitCryptoRequest, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -245,7 +245,7 @@ class VaultSdkSourceTest { req = mockInitCryptoRequest, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -271,7 +271,7 @@ class VaultSdkSourceTest { req = mockInitCryptoRequest, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -297,7 +297,7 @@ class VaultSdkSourceTest { req = mockInitCryptoRequest, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -324,7 +324,7 @@ class VaultSdkSourceTest { req = mockInitCryptoRequest, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -350,7 +350,7 @@ class VaultSdkSourceTest { cipherView = mockCipher, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -376,7 +376,7 @@ class VaultSdkSourceTest { cipher = mockCipher, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -403,7 +403,7 @@ class VaultSdkSourceTest { ciphers = mockCiphers, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -429,7 +429,7 @@ class VaultSdkSourceTest { cipher = mockCiphers, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -455,7 +455,7 @@ class VaultSdkSourceTest { collection = mockCollection, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -482,7 +482,7 @@ class VaultSdkSourceTest { collections = mockCollectionsList, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -509,7 +509,7 @@ class VaultSdkSourceTest { send = mockSend, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -586,7 +586,7 @@ class VaultSdkSourceTest { send = mockSend, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -614,7 +614,7 @@ class VaultSdkSourceTest { folder = mockFolder, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -640,7 +640,7 @@ class VaultSdkSourceTest { folder = mockFolder, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -666,7 +666,7 @@ class VaultSdkSourceTest { folders = mockFolders, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -702,7 +702,7 @@ class VaultSdkSourceTest { decryptedFilePath = "decrypted_path", ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -728,7 +728,7 @@ class VaultSdkSourceTest { passwordHistory = mockPasswordHistoryView, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -754,7 +754,7 @@ class VaultSdkSourceTest { list = mockPasswordHistoryList, ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test @@ -781,7 +781,7 @@ class VaultSdkSourceTest { ) } - verify { sdkClientManager.getOrCreateClient(userId = userId) } + coVerify { sdkClientManager.getOrCreateClient(userId = userId) } } @Test