Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ interface AuthDiskSource : AppIdProvider {
accountCryptographicState: WrappedAccountCryptographicState?,
)

/**
* Returns the id of the user key for the given [userId].
*/
fun getUserKeyId(userId: String): String?

/**
* Stores the id of the user key for the given [userId].
*/
fun storeUserKeyId(userId: String, userKeyId: String?)

/**
* Retrieves a user auto-unlock key for the given [userId].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private const val SHOW_IMPORT_LOGINS_KEY = "showImportLogins"
private const val LAST_LOCK_TIMESTAMP = "lastLockTimestamp"
private const val PROFILE_ACCOUNT_KEYS_KEY = "profileAccountKeys"
private const val V2_UPGRADE_TOKEN = "v2UpgradeToken"
private const val USER_KEY_ID_KEY = "userKeyId"

/**
* Primary implementation of [AuthDiskSource].
Expand Down Expand Up @@ -201,6 +202,7 @@ class AuthDiskSourceImpl(
pinProtectedUserKeyEnvelope = null,
)
storeV2UpgradeToken(userId = userId, v2UpgradeToken = null)
storeUserKeyId(userId = userId, userKeyId = null)

// Certain values are never removed as required by the feature requirements:
// * DeviceKey
Expand Down Expand Up @@ -290,6 +292,13 @@ class AuthDiskSourceImpl(
)
}

override fun getUserKeyId(userId: String): String? =
getString(key = USER_KEY_ID_KEY.appendIdentifier(userId))

override fun storeUserKeyId(userId: String, userKeyId: String?) {
putString(key = USER_KEY_ID_KEY.appendIdentifier(userId), value = userKeyId)
}

override fun getUserAutoUnlockKey(userId: String): String? =
getEncryptedString(key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ internal class SdkStateBridge(
inMemoryUserKey = null
}

override suspend fun setUserKeyId(value: String) {
authDiskSource.storeUserKeyId(userId = userId, userKeyId = value)
}

override suspend fun getUserKeyId(): String? = authDiskSource.getUserKeyId(userId = userId)

override suspend fun clearUserKeyId() {
authDiskSource.storeUserKeyId(userId = userId, userKeyId = null)
}

override suspend fun setPersistentPinEnvelope(value: PasswordProtectedKeyEnvelope) {
authDiskSource.storePersistentPinProtectedUserKeyEnvelope(
userId = userId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ class AuthDiskSourceTest {
wrappedUserKey2 = "wrappedUserKey2",
),
)
authDiskSource.storeUserKeyId(userId = userId, userKeyId = "userKeyId")

authDiskSource.clearData(userId = userId)

Expand Down Expand Up @@ -410,6 +411,7 @@ class AuthDiskSourceTest {
assertNull(authDiskSource.getEphemeralPinProtectedUserKeyEnvelope(userId = userId))
assertNull(authDiskSource.getPersistentPinProtectedUserKeyEnvelope(userId = userId))
assertNull(authDiskSource.getV2UpgradeToken(userId = userId))
assertNull(authDiskSource.getUserKeyId(userId = userId))
}

@Test
Expand Down Expand Up @@ -538,6 +540,28 @@ class AuthDiskSourceTest {
)
}

@Test
fun `getUserKeyId should pull from SharedPreferences`() {
val userKeyIdBaseKey = "bwPreferencesStorage:userKeyId"
val mockUserId = "mockUserId"
val mockUserKeyId = "mockUserKeyId"
fakeSharedPreferences.edit {
putString("${userKeyIdBaseKey}_$mockUserId", mockUserKeyId)
}
val actual = authDiskSource.getUserKeyId(userId = mockUserId)
assertEquals(mockUserKeyId, actual)
}

@Test
fun `storeUserKeyId should update SharedPreferences`() {
val userKeyIdBaseKey = "bwPreferencesStorage:userKeyId"
val mockUserId = "mockUserId"
val mockUserKeyId = "mockUserKeyId"
authDiskSource.storeUserKeyId(userId = mockUserId, userKeyId = mockUserKeyId)
val actual = fakeSharedPreferences.getString("${userKeyIdBaseKey}_$mockUserId", null)
assertEquals(mockUserKeyId, actual)
}

@Test
fun `getV2UpgradeToken should pull from SharedPreferences`() {
val v2UpgradeTokenBaseKey = "bwPreferencesStorage:v2UpgradeToken"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class FakeAuthDiskSource : AuthDiskSource {
private val mutablePersistentPinProtectedUserKeyEnvelopesFlowMap =
mutableMapOf<String, MutableSharedFlow<String?>>()
private val storedV2UpgradeTokens = mutableMapOf<String, V2UpgradeTokenJson?>()
private val storedUserKeyIds = mutableMapOf<String, String?>()

override var userState: UserStateJson? = null
set(value) {
Expand Down Expand Up @@ -100,6 +101,7 @@ class FakeAuthDiskSource : AuthDiskSource {
storedEncryptedPins.remove(userId)
storedPinProtectedUserKeys.remove(userId)
storedV2UpgradeTokens.remove(userId)
storedUserKeyIds.remove(userId)

mutableShouldUseKeyConnectorFlowMap.remove(userId)
mutableOrganizationsFlowMap.remove(userId)
Expand Down Expand Up @@ -163,6 +165,12 @@ class FakeAuthDiskSource : AuthDiskSource {
storedAccountCryptographicState[userId] = accountCryptographicState
}

override fun getUserKeyId(userId: String): String? = storedUserKeyIds[userId]

override fun storeUserKeyId(userId: String, userKeyId: String?) {
storedUserKeyIds[userId] = userKeyId
}

override fun getV2UpgradeToken(userId: String): V2UpgradeTokenJson? =
storedV2UpgradeTokens[userId]

Expand Down Expand Up @@ -449,6 +457,13 @@ class FakeAuthDiskSource : AuthDiskSource {
assertEquals(accountCryptographicState, storedAccountCryptographicState[userId])
}

/**
* Assert that the [userKeyId] was stored successfully using the [userId].
*/
fun assertUserKeyId(userId: String, userKeyId: String?) {
assertEquals(userKeyId, storedUserKeyIds[userId])
}

/**
* Assert that the [v2UpgradeToken] was stored successfully using the [userId].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,31 @@ class SdkStateBridgeTest {
assertNull(stateBridge.getUserKey())
}

@Test
fun `setUserKeyId should store the user key id`() = runTest {
stateBridge.setUserKeyId(value = "userKeyId")

authDiskSource.assertUserKeyId(userId = USER_ID, userKeyId = "userKeyId")
}

@Test
fun `getUserKeyId should return the stored user key id`() = runTest {
assertNull(stateBridge.getUserKeyId())

authDiskSource.storeUserKeyId(userId = USER_ID, userKeyId = "userKeyId")

assertEquals("userKeyId", stateBridge.getUserKeyId())
}

@Test
fun `clearUserKeyId should clear the stored user key id`() = runTest {
authDiskSource.storeUserKeyId(userId = USER_ID, userKeyId = "userKeyId")

stateBridge.clearUserKeyId()

authDiskSource.assertUserKeyId(userId = USER_ID, userKeyId = null)
}

@Test
fun `setPersistentPinEnvelope should store the persistent pin envelope`() = runTest {
stateBridge.setPersistentPinEnvelope(value = "pinEnvelope")
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ androidxRoom = "2.8.4"
androidxSecurityCrypto = "1.1.0"
androidxSplash = "1.2.0"
androidxWork = "2.11.2"
bitwardenSdk = "3.0.0-8288-99ffb6ef"
bitwardenSdk = "3.0.0-8379-cc7daf10"
crashlytics = "3.0.7"
detekt = "1.23.8"
firebaseBom = "34.15.0"
Expand Down
Loading