From 183255cbff96e2a05d03d6d9c43c05fcc27d7248 Mon Sep 17 00:00:00 2001 From: David Perez Date: Tue, 17 Mar 2026 15:28:14 -0500 Subject: [PATCH] PM-33160: Instantiate SDK client with Repositories class (#6681) --- .../auth/datasource/disk/AuthDiskSource.kt | 10 + .../datasource/disk/AuthDiskSourceImpl.kt | 9 + .../platform/manager/SdkClientManagerImpl.kt | 8 +- .../manager/sdk/SdkRepositoryFactory.kt | 6 +- .../manager/sdk/SdkRepositoryFactoryImpl.kt | 24 ++- .../SdkLocalUserDataKeyStateRepository.kt | 49 +++++ .../vault/manager/VaultLockManagerImpl.kt | 1 + .../util/InitUserCryptoMethodExtensions.kt | 1 + .../datasource/disk/AuthDiskSourceTest.kt | 22 +++ .../disk/util/FakeAuthDiskSource.kt | 8 + .../platform/manager/SdkClientManagerTest.kt | 2 +- .../manager/sdk/SdkRepositoryFactoryTests.kt | 13 +- .../SdkLocalUserDataKeyStateRepositoryTest.kt | 171 ++++++++++++++++++ gradle/libs.versions.toml | 2 +- 14 files changed, 305 insertions(+), 21 deletions(-) create mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepository.kt create mode 100644 app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepositoryTest.kt diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt index 314dc35cb4..d890872ea6 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt @@ -124,6 +124,16 @@ interface AuthDiskSource : AppIdProvider { */ fun storeUserKey(userId: String, userKey: String?) + /** + * Retrieves the local user data key for the given [userId]. + */ + fun getLocalUserDataKey(userId: String): String? + + /** + * Stores the local user data key for a given [userId]. + */ + fun storeLocalUserDataKey(userId: String, wrappedKey: String?) + /** * Retrieves a private key using a [userId]. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt index 621b79070d..d2924cf53f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt @@ -35,6 +35,7 @@ private const val REMEMBERED_ORG_IDENTIFIER_KEY = "rememberedOrgIdentifier" private const val STATE_KEY = "state" private const val INVALID_UNLOCK_ATTEMPTS_KEY = "invalidUnlockAttempts" private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey" +private const val LOCAL_USER_DATA_KEY = "localUserDataKey" private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey" private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey" private const val PIN_PROTECTED_USER_KEY_KEY_ENVELOPE = "pinKeyEncryptedUserKeyEnvelope" @@ -144,6 +145,7 @@ class AuthDiskSourceImpl( override fun clearData(userId: String) { storeInvalidUnlockAttempts(userId = userId, invalidUnlockAttempts = null) storeUserKey(userId = userId, userKey = null) + storeLocalUserDataKey(userId = userId, wrappedKey = null) storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null) storePrivateKey(userId = userId, privateKey = null) storeAccountKeys(userId = userId, accountKeys = null) @@ -237,6 +239,13 @@ class AuthDiskSourceImpl( ) } + override fun getLocalUserDataKey(userId: String): String? = + getString(key = LOCAL_USER_DATA_KEY.appendIdentifier(userId)) + + override fun storeLocalUserDataKey(userId: String, wrappedKey: String?) { + putString(key = LOCAL_USER_DATA_KEY.appendIdentifier(userId), value = wrappedKey) + } + @Deprecated("Use getAccountKeys instead.", replaceWith = ReplaceWith("getAccountKeys")) override fun getPrivateKey(userId: String): String? = getString(key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId)) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt index 1f818cfa4a..445cbeac18 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerImpl.kt @@ -26,11 +26,9 @@ class SdkClientManagerImpl( repository = sdkRepoFactory.getServerCommunicationConfigRepository(), platformApi = sdkPlatformApiFactory.getServerCommunicationConfigPlatformApi(), ) - userId?.let { - platform().state().apply { - registerCipherRepository(sdkRepoFactory.getCipherRepository(userId = it)) - } - } + platform().state().registerClientManagedRepositories( + repositories = sdkRepoFactory.getRepositories(userId = userId), + ) } }, ) : SdkClientManager { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactory.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactory.kt index cd9185b5d2..f1d4091a09 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactory.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactory.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.platform.manager.sdk import com.bitwarden.core.ClientManagedTokens -import com.bitwarden.sdk.CipherRepository +import com.bitwarden.sdk.Repositories import com.bitwarden.sdk.ServerCommunicationConfigRepository /** @@ -9,9 +9,9 @@ import com.bitwarden.sdk.ServerCommunicationConfigRepository */ interface SdkRepositoryFactory { /** - * Retrieves or creates a [CipherRepository] for use with the Bitwarden SDK. + * Retrieves or creates a [Repositories] for use with the Bitwarden SDK. */ - fun getCipherRepository(userId: String): CipherRepository + fun getRepositories(userId: String?): Repositories /** * Retrieves or creates a [ClientManagedTokens] for use with the Bitwarden SDK. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryImpl.kt index 1a0cc7fd32..adeb833a95 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryImpl.kt @@ -2,11 +2,12 @@ package com.x8bit.bitwarden.data.platform.manager.sdk import com.bitwarden.core.ClientManagedTokens import com.bitwarden.data.datasource.disk.ConfigDiskSource -import com.bitwarden.sdk.CipherRepository +import com.bitwarden.sdk.Repositories import com.bitwarden.sdk.ServerCommunicationConfigRepository import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.platform.datasource.disk.CookieDiskSource import com.x8bit.bitwarden.data.platform.manager.sdk.repository.SdkCipherRepository +import com.x8bit.bitwarden.data.platform.manager.sdk.repository.SdkLocalUserDataKeyStateRepository import com.x8bit.bitwarden.data.platform.manager.sdk.repository.SdkTokenRepository import com.x8bit.bitwarden.data.platform.manager.sdk.repository.ServerCommunicationConfigRepositoryImpl import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource @@ -20,12 +21,15 @@ class SdkRepositoryFactoryImpl( private val configDiskSource: ConfigDiskSource, private val authDiskSource: AuthDiskSource, ) : SdkRepositoryFactory { - override fun getCipherRepository( - userId: String, - ): CipherRepository = - SdkCipherRepository( - userId = userId, - vaultDiskSource = vaultDiskSource, + override fun getRepositories(userId: String?): Repositories = + Repositories( + cipher = getSdkRepository(userId = userId), + folder = null, + userKeyState = null, + localUserDataKeyState = SdkLocalUserDataKeyStateRepository( + authDiskSource = authDiskSource, + ), + ephemeralPinEnvelopeState = null, ) override fun getClientManagedTokens( @@ -41,4 +45,10 @@ class SdkRepositoryFactoryImpl( cookieDiskSource = cookieDiskSource, configDiskSource = configDiskSource, ) + + private fun getSdkRepository( + userId: String?, + ): SdkCipherRepository? = userId?.let { + SdkCipherRepository(userId = it, vaultDiskSource = vaultDiskSource) + } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepository.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepository.kt new file mode 100644 index 0000000000..d483553e03 --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepository.kt @@ -0,0 +1,49 @@ +package com.x8bit.bitwarden.data.platform.manager.sdk.repository + +import com.bitwarden.core.LocalUserDataKeyState +import com.bitwarden.sdk.LocalUserDataKeyStateRepository +import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource + +/** + * An implementation of a Bitwarden SDK [LocalUserDataKeyStateRepository]. + */ +class SdkLocalUserDataKeyStateRepository( + private val authDiskSource: AuthDiskSource, +) : LocalUserDataKeyStateRepository { + override suspend fun get(id: String): LocalUserDataKeyState? { + return authDiskSource + .getLocalUserDataKey(userId = id) + ?.let { LocalUserDataKeyState(wrappedKey = it) } + } + + override suspend fun has( + id: String, + ): Boolean = authDiskSource.getLocalUserDataKey(userId = id) != null + + override suspend fun list(): List = + authDiskSource + .userState + ?.accounts + ?.mapNotNull { get(id = it.key) } + .orEmpty() + + override suspend fun remove(id: String) { + authDiskSource.storeLocalUserDataKey(userId = id, wrappedKey = null) + } + + override suspend fun removeAll() { + removeBulk(keys = authDiskSource.userState?.accounts.orEmpty().keys.toList()) + } + + override suspend fun removeBulk(keys: List) { + keys.forEach { remove(id = it) } + } + + override suspend fun set(id: String, value: LocalUserDataKeyState) { + authDiskSource.storeLocalUserDataKey(userId = id, value.wrappedKey) + } + + override suspend fun setBulk(values: Map) { + values.forEach { (id, value) -> set(id = id, value = value) } + } +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt index e5fd83d460..b6cf173e79 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt @@ -711,6 +711,7 @@ class VaultLockManagerImpl( is InitUserCryptoMethod.DecryptedKey, is InitUserCryptoMethod.DeviceKey, is InitUserCryptoMethod.KeyConnector, + is InitUserCryptoMethod.KeyConnectorUrl, is InitUserCryptoMethod.Pin, is InitUserCryptoMethod.PinEnvelope, -> return diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/InitUserCryptoMethodExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/InitUserCryptoMethodExtensions.kt index 22916e705b..edc5b1d004 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/InitUserCryptoMethodExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/InitUserCryptoMethodExtensions.kt @@ -14,5 +14,6 @@ val InitUserCryptoMethod.logTag: String is InitUserCryptoMethod.KeyConnector -> "Key Connector" is InitUserCryptoMethod.Pin -> "Pin" is InitUserCryptoMethod.PinEnvelope -> "Pin Envelope" + is InitUserCryptoMethod.KeyConnectorUrl -> "Key Connector Url" is InitUserCryptoMethod.MasterPasswordUnlock -> "Master Password Unlock" } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt index d750fb67c6..780b9740ae 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt @@ -448,6 +448,28 @@ class AuthDiskSourceTest { ) } + @Test + fun `getLocalUserDataKey should pull from SharedPreferences`() { + val userKeyBaseKey = "bwPreferencesStorage:localUserDataKey" + val mockUserId = "mockUserId" + val mockLocalUserKey = "mockLocalUserDataKey" + fakeSharedPreferences.edit { + putString("${userKeyBaseKey}_$mockUserId", mockLocalUserKey) + } + val actual = authDiskSource.getLocalUserDataKey(userId = mockUserId) + assertEquals(mockLocalUserKey, actual) + } + + @Test + fun `storeLocalUserDataKey should update SharedPreferences`() { + val userKeyBaseKey = "bwPreferencesStorage:localUserDataKey" + val mockUserId = "mockUserId" + val mockLocalUserKey = "mockLocalUserDataKey" + authDiskSource.storeLocalUserDataKey(userId = mockUserId, wrappedKey = mockLocalUserKey) + val actual = fakeSharedPreferences.getString("${userKeyBaseKey}_$mockUserId", null) + assertEquals(mockLocalUserKey, actual) + } + @Test fun `getPrivateKey should pull from SharedPreferences`() { val privateKeyBaseKey = "bwPreferencesStorage:encPrivateKey" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt index a85a7331c8..5377fd78bd 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt @@ -45,6 +45,7 @@ class FakeAuthDiskSource : AuthDiskSource { private val storedShouldTrustDevice = mutableMapOf() private val storedInvalidUnlockAttempts = mutableMapOf() private val storedUserKeys = mutableMapOf() + private val storedLocalUserDataKeys = mutableMapOf() private val storedPrivateKeys = mutableMapOf() private val storedTwoFactorTokens = mutableMapOf() private val storedUserAutoUnlockKeys = mutableMapOf() @@ -81,6 +82,7 @@ class FakeAuthDiskSource : AuthDiskSource { override fun clearData(userId: String) { storedInvalidUnlockAttempts.remove(userId) storedUserKeys.remove(userId) + storedLocalUserDataKeys.remove(userId) storedPrivateKeys.remove(userId) storedTwoFactorTokens.clear() storedUserAutoUnlockKeys.remove(userId) @@ -150,6 +152,12 @@ class FakeAuthDiskSource : AuthDiskSource { storedUserKeys[userId] = userKey } + override fun getLocalUserDataKey(userId: String): String? = storedLocalUserDataKeys[userId] + + override fun storeLocalUserDataKey(userId: String, wrappedKey: String?) { + storedLocalUserDataKeys[userId] = wrappedKey + } + @Deprecated("Use getAccountKeys instead.", replaceWith = ReplaceWith("getAccountKeys")) override fun getPrivateKey(userId: String): String? = storedPrivateKeys[userId] diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt index 7f483c9c9b..47f8d8be8c 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/SdkClientManagerTest.kt @@ -22,7 +22,7 @@ class SdkClientManagerTest { every { loadLibrary(any()) } returns Result.success(Unit) } private val sdkRepoFactory: SdkRepositoryFactory = mockk { - every { getCipherRepository(userId = any()) } returns mockk() + every { getRepositories(userId = any()) } returns mockk() } private val sdkPlatformApiFactory: SdkPlatformApiFactory = mockk { every { getServerCommunicationConfigPlatformApi() } returns mockk() diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryTests.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryTests.kt index ac7f8e1a59..33ef9d4434 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryTests.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/SdkRepositoryFactoryTests.kt @@ -23,18 +23,23 @@ class SdkRepositoryFactoryTests { ) @Test - fun `getCipherRepository should create a new client`() { + fun `getRepositories should create a new client`() { val userId = "userId" - val firstClient = sdkRepoFactory.getCipherRepository(userId = userId) + val firstClient = sdkRepoFactory.getRepositories(userId = userId) // Additional calls for the same userId should create a repo - val secondClient = sdkRepoFactory.getCipherRepository(userId = userId) + val secondClient = sdkRepoFactory.getRepositories(userId = userId) assertNotEquals(firstClient, secondClient) // Additional calls for different userIds should return a different repo val otherUserId = "otherUserId" - val thirdClient = sdkRepoFactory.getCipherRepository(userId = otherUserId) + val thirdClient = sdkRepoFactory.getRepositories(userId = otherUserId) assertNotEquals(firstClient, thirdClient) + + // Null should get its own client too + val fourthClient = sdkRepoFactory.getRepositories(userId = null) + assertNotEquals(firstClient, fourthClient) + assertNotEquals(thirdClient, fourthClient) } @Test diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepositoryTest.kt new file mode 100644 index 0000000000..37568595c2 --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/repository/SdkLocalUserDataKeyStateRepositoryTest.kt @@ -0,0 +1,171 @@ +package com.x8bit.bitwarden.data.platform.manager.sdk.repository + +import com.bitwarden.core.LocalUserDataKeyState +import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson +import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class SdkLocalUserDataKeyStateRepositoryTest { + + private val fakeAuthDiskSource = FakeAuthDiskSource() + + private val repository = SdkLocalUserDataKeyStateRepository( + authDiskSource = fakeAuthDiskSource, + ) + + @Test + fun `get should return null when no key is stored for the given id`() = runTest { + assertNull(repository.get(id = USER_ID)) + } + + @Test + fun `get should return LocalUserDataKeyState when key is stored for the given id`() = runTest { + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + + assertEquals( + LocalUserDataKeyState(wrappedKey = WRAPPED_KEY), + repository.get(id = USER_ID), + ) + } + + @Test + fun `has should return false when no key is stored for the given id`() = runTest { + assertFalse(repository.has(id = USER_ID)) + } + + @Test + fun `has should return true when a key is stored for the given id`() = runTest { + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + + assertTrue(repository.has(id = USER_ID)) + } + + @Test + fun `list should return empty list when userState is null`() = runTest { + fakeAuthDiskSource.userState = null + + assertEquals(emptyList(), repository.list()) + } + + @Test + fun `list should return empty list when no keys are stored for any account`() = runTest { + fakeAuthDiskSource.userState = UserStateJson( + activeUserId = USER_ID, + accounts = mapOf(USER_ID to mockk()), + ) + + assertEquals(emptyList(), repository.list()) + } + + @Test + fun `list should return LocalUserDataKeyState for each account that has a stored key`() = + runTest { + fakeAuthDiskSource.userState = UserStateJson( + activeUserId = USER_ID, + accounts = mapOf( + USER_ID to mockk(), + USER_ID_2 to mockk(), + ), + ) + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID_2, wrappedKey = WRAPPED_KEY_2) + + assertEquals( + listOf( + LocalUserDataKeyState(wrappedKey = WRAPPED_KEY), + LocalUserDataKeyState(wrappedKey = WRAPPED_KEY_2), + ), + repository.list(), + ) + } + + @Test + fun `list should omit accounts that have no stored key`() = runTest { + fakeAuthDiskSource.userState = UserStateJson( + activeUserId = USER_ID, + accounts = mapOf(USER_ID to mockk(), USER_ID_2 to mockk()), + ) + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + + assertEquals( + listOf(LocalUserDataKeyState(wrappedKey = WRAPPED_KEY)), + repository.list(), + ) + } + + @Test + fun `remove should clear the stored key for the given id`() = runTest { + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + + repository.remove(id = USER_ID) + + assertNull(fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID)) + } + + @Test + fun `removeAll should clear the stored key for all accounts`() = runTest { + fakeAuthDiskSource.userState = UserStateJson( + activeUserId = USER_ID, + accounts = mapOf( + USER_ID to mockk(), + USER_ID_2 to mockk(), + ), + ) + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID_2, wrappedKey = WRAPPED_KEY_2) + + repository.removeAll() + + assertNull(fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID)) + assertNull(fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID_2)) + } + + @Test + fun `removeAll should do nothing when userState is null`() = runTest { + fakeAuthDiskSource.userState = null + + repository.removeAll() + } + + @Test + fun `removeBulk should clear the stored key for each given id`() = runTest { + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID, wrappedKey = WRAPPED_KEY) + fakeAuthDiskSource.storeLocalUserDataKey(userId = USER_ID_2, wrappedKey = WRAPPED_KEY_2) + + repository.removeBulk(keys = listOf(USER_ID, USER_ID_2)) + + assertNull(fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID)) + assertNull(fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID_2)) + } + + @Test + fun `set should store the wrapped key for the given id`() = runTest { + repository.set(id = USER_ID, value = LocalUserDataKeyState(wrappedKey = WRAPPED_KEY)) + + assertEquals(WRAPPED_KEY, fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID)) + } + + @Test + fun `setBulk should store the wrapped key for each given id`() = runTest { + repository.setBulk( + values = mapOf( + USER_ID to LocalUserDataKeyState(wrappedKey = WRAPPED_KEY), + USER_ID_2 to LocalUserDataKeyState(wrappedKey = WRAPPED_KEY_2), + ), + ) + + assertEquals(WRAPPED_KEY, fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID)) + assertEquals(WRAPPED_KEY_2, fakeAuthDiskSource.getLocalUserDataKey(userId = USER_ID_2)) + } +} + +private const val USER_ID: String = "userId" +private const val USER_ID_2: String = "userId2" +private const val WRAPPED_KEY: String = "wrappedKey" +private const val WRAPPED_KEY_2: String = "wrappedKey2" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e4c168e71..b0adf4b0f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ androidxRoom = "2.8.4" androidxSecurityCrypto = "1.1.0" androidxSplash = "1.2.0" androidxWork = "2.11.1" -bitwardenSdk = "2.0.0-5451-c73f9161" +bitwardenSdk = "2.0.0-5676-14521973" crashlytics = "3.0.6" detekt = "1.23.8" firebaseBom = "34.10.0"