Add stored pending auth request (#1204)

This commit is contained in:
David Perez 2024-04-01 14:19:11 -05:00 committed by Álison Fernandes
parent 0cc28e41ba
commit 2185d3847a
5 changed files with 135 additions and 0 deletions

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.coroutines.flow.Flow
@ -121,6 +122,19 @@ interface AuthDiskSource {
*/
fun storeDeviceKey(userId: String, deviceKey: String?)
/**
* Gets the stored [PendingAuthRequestJson] for the given [userId].
*/
fun getPendingAuthRequest(userId: String): PendingAuthRequestJson?
/**
* Stores the [PendingAuthRequestJson] for the given [userId].
*/
fun storePendingAuthRequest(
userId: String,
pendingAuthRequest: PendingAuthRequestJson?,
)
/**
* Gets the biometrics key for the given [userId].
*/

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
@ -21,6 +22,7 @@ private const val ACCOUNT_TOKENS_KEY = "$ENCRYPTED_BASE_KEY:accountTokens"
private const val BIOMETRICS_UNLOCK_KEY = "$ENCRYPTED_BASE_KEY:userKeyBiometricUnlock"
private const val USER_AUTO_UNLOCK_KEY_KEY = "$ENCRYPTED_BASE_KEY:userKeyAutoUnlock"
private const val DEVICE_KEY_KEY = "$ENCRYPTED_BASE_KEY:deviceKey"
private const val PENDING_ADMIN_AUTH_REQUEST_KEY = "$ENCRYPTED_BASE_KEY:pendingAdminAuthRequest"
private const val UNIQUE_APP_ID_KEY = "$BASE_KEY:appId"
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail"
@ -125,6 +127,7 @@ class AuthDiskSourceImpl(
storeOrganizationKeys(userId = userId, organizationKeys = null)
storeOrganizations(userId = userId, organizations = null)
storeDeviceKey(userId = userId, deviceKey = null)
storePendingAuthRequest(userId = userId, pendingAuthRequest = null)
storeUserBiometricUnlockKey(userId = userId, biometricsKey = null)
storeMasterPasswordHash(userId = userId, passwordHash = null)
storePolicies(userId = userId, policies = null)
@ -201,6 +204,22 @@ class AuthDiskSourceImpl(
putEncryptedString(key = "${DEVICE_KEY_KEY}_$userId", value = deviceKey)
}
override fun getPendingAuthRequest(
userId: String,
): PendingAuthRequestJson? =
getEncryptedString(key = "${PENDING_ADMIN_AUTH_REQUEST_KEY}_$userId")
?.let { json.decodeFromStringOrNull(it) }
override fun storePendingAuthRequest(
userId: String,
pendingAuthRequest: PendingAuthRequestJson?,
) {
putEncryptedString(
key = "${PENDING_ADMIN_AUTH_REQUEST_KEY}_$userId",
value = pendingAuthRequest?.let { json.encodeToString(it) },
)
}
override fun getUserBiometricUnlockKey(userId: String): String? =
getEncryptedString(key = "${BIOMETRICS_UNLOCK_KEY}_$userId")

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.auth.datasource.disk.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Container for the user's API tokens.
*
* @property requestId The ID of the pending Auth Request.
* @property requestPrivateKey The private of the pending Auth Request.
*/
@Serializable
data class PendingAuthRequestJson(
@SerialName("Id")
val requestId: String,
@SerialName("PrivateKey")
val requestPrivateKey: String,
)

View file

@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson
@ -185,6 +186,13 @@ class AuthDiskSourceTest {
userId = userId,
deviceKey = "9876-5432-1234",
)
authDiskSource.storePendingAuthRequest(
userId = userId,
pendingAuthRequest = PendingAuthRequestJson(
requestId = "12345",
requestPrivateKey = "67890",
),
)
authDiskSource.storeUserBiometricUnlockKey(
userId = userId,
biometricsKey = "1234-9876-0192",
@ -226,6 +234,7 @@ class AuthDiskSourceTest {
authDiskSource.clearData(userId = userId)
assertNull(authDiskSource.getDeviceKey(userId = userId))
assertNull(authDiskSource.getPendingAuthRequest(userId = userId))
assertNull(authDiskSource.getUserBiometricUnlockKey(userId = userId))
assertNull(authDiskSource.getLastActiveTimeMillis(userId = userId))
assertNull(authDiskSource.getInvalidUnlockAttempts(userId = userId))
@ -542,6 +551,60 @@ class AuthDiskSourceTest {
assertFalse(fakeEncryptedSharedPreferences.contains(deviceKeyKey))
}
@Test
fun `getPendingAuthRequest should pull from SharedPreferences`() {
val pendingAdminAuthRequestBaseKey = "bwSecureStorage:pendingAdminAuthRequest"
val mockUserId = "mockUserId"
val pendingAdminAuthRequestKey = "${pendingAdminAuthRequestBaseKey}_$mockUserId"
fakeEncryptedSharedPreferences.edit {
putString(
pendingAdminAuthRequestKey,
"""
{
"Id": "12345",
"PrivateKey": "67890"
}
""",
)
}
val actual = authDiskSource.getPendingAuthRequest(userId = mockUserId)
assertEquals(
PendingAuthRequestJson(requestId = "12345", requestPrivateKey = "67890"),
actual,
)
}
@Test
fun `storePendingAuthRequest for non-null values should update SharedPreferences`() {
val pendingAdminAuthRequestKeyBaseKey = "bwSecureStorage:pendingAdminAuthRequest"
val mockUserId = "mockUserId"
val pendingAuthRequestKey = "${pendingAdminAuthRequestKeyBaseKey}_$mockUserId"
val pendingAdminAuthRequest = PendingAuthRequestJson(
requestId = "12345",
requestPrivateKey = "67890",
)
authDiskSource.storePendingAuthRequest(
userId = mockUserId,
pendingAuthRequest = pendingAdminAuthRequest,
)
val actual = fakeEncryptedSharedPreferences.getString(
key = pendingAuthRequestKey,
defaultValue = null,
)
assertEquals(
json.parseToJsonElement(
"""
{
"Id": "12345",
"PrivateKey": "67890"
}
"""
.trimIndent(),
),
json.parseToJsonElement(requireNotNull(actual)),
)
}
@Test
fun `getUserBiometricUnlockKey should pull from SharedPreferences`() {
val biometricsKeyBaseKey = "bwSecureStorage:userKeyBiometricUnlock"

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.util
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
@ -38,6 +39,7 @@ class FakeAuthDiskSource : AuthDiskSource {
private val storedOrganizationKeys = mutableMapOf<String, Map<String, String>?>()
private val storedAccountTokens = mutableMapOf<String, AccountTokensJson?>()
private val storedDeviceKey = mutableMapOf<String, String?>()
private val storedPendingAuthRequests = mutableMapOf<String, PendingAuthRequestJson?>()
private val storedBiometricKeys = mutableMapOf<String, String?>()
private val storedMasterPasswordHashes = mutableMapOf<String, String?>()
private val storedPolicies = mutableMapOf<String, List<SyncResponseJson.Policy>?>()
@ -65,6 +67,7 @@ class FakeAuthDiskSource : AuthDiskSource {
storedOrganizations.remove(userId)
storedPolicies.remove(userId)
storedAccountTokens.remove(userId)
storedPendingAuthRequests.remove(userId)
storedBiometricKeys.remove(userId)
storedOrganizationKeys.remove(userId)
@ -170,6 +173,16 @@ class FakeAuthDiskSource : AuthDiskSource {
storedDeviceKey[userId] = deviceKey
}
override fun getPendingAuthRequest(userId: String): PendingAuthRequestJson? =
storedPendingAuthRequests[userId]
override fun storePendingAuthRequest(
userId: String,
pendingAuthRequest: PendingAuthRequestJson?,
) {
storedPendingAuthRequests[userId] = pendingAuthRequest
}
override fun getUserBiometricUnlockKey(userId: String): String? =
storedBiometricKeys[userId]
@ -289,6 +302,13 @@ class FakeAuthDiskSource : AuthDiskSource {
assertEquals(deviceKey, storedDeviceKey[userId])
}
/**
* Assert that the [pendingAuthRequest] was stored successfully using the [userId].
*/
fun assertPendingAuthRequest(userId: String, pendingAuthRequest: PendingAuthRequestJson?) {
assertEquals(pendingAuthRequest, storedPendingAuthRequests[userId])
}
/**
* Assert that the [biometricsKey] was stored successfully using the [userId].
*/