BIT-2070: Enable individual Cipher Encryption for SDK (#1148)

This commit is contained in:
Ramsey Smith 2024-03-18 12:41:51 -06:00 committed by Álison Fernandes
parent 3c715d39d6
commit 0d2467d8d2
13 changed files with 144 additions and 43 deletions

View file

@ -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<TrustDeviceResponse> = runCatching {
clientAuth.trustDevice()
}

View file

@ -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,
)
}

View file

@ -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

View file

@ -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<String, Client>()
override fun getOrCreateClient(
override suspend fun getOrCreateClient(
userId: String,
): Client =
userIdToClientMap.getOrPut(key = userId) { clientProvider() }

View file

@ -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

View file

@ -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<String, Boolean>
}

View file

@ -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<String, Boolean>
get() = mapOf(CIPHER_KEY_ENCRYPTION_KEY to true)
}

View file

@ -388,7 +388,7 @@ class VaultSdkSourceImpl(
)
}
private fun getClient(
private suspend fun getClient(
userId: String,
): Client = sdkClientManager.getOrCreateClient(userId = userId)
}

View file

@ -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()
}

View file

@ -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<ClientAuth>()
private val clientPlatform = mockk<ClientPlatform>()
private val clientPlatform = mockk<ClientPlatform> {
coEvery { loadFlags(any()) } just runs
}
private val featureFlagManager = mockk<BitwardenFeatureFlagManager> {
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 {

View file

@ -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)

View file

@ -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)
}
}

View file

@ -67,7 +67,7 @@ class VaultSdkSourceTest {
every { exporters() } returns clientExporters
}
private val sdkClientManager = mockk<SdkClientManager> {
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