mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
[PM-23278] Add func to update to minimum kdf settings to AuthRepository
This commit is contained in:
parent
a0bbc1a313
commit
0ff6bb4aeb
@ -1,6 +1,7 @@
|
|||||||
package com.x8bit.bitwarden.data.auth.datasource.sdk.util
|
package com.x8bit.bitwarden.data.auth.datasource.sdk.util
|
||||||
|
|
||||||
import com.bitwarden.crypto.Kdf
|
import com.bitwarden.crypto.Kdf
|
||||||
|
import com.bitwarden.network.model.KdfJsonRequest
|
||||||
import com.bitwarden.network.model.KdfTypeJson
|
import com.bitwarden.network.model.KdfTypeJson
|
||||||
import com.bitwarden.network.model.KdfTypeJson.ARGON2_ID
|
import com.bitwarden.network.model.KdfTypeJson.ARGON2_ID
|
||||||
import com.bitwarden.network.model.KdfTypeJson.PBKDF2_SHA256
|
import com.bitwarden.network.model.KdfTypeJson.PBKDF2_SHA256
|
||||||
@ -13,3 +14,22 @@ fun Kdf.toKdfTypeJson(): KdfTypeJson =
|
|||||||
is Kdf.Argon2id -> ARGON2_ID
|
is Kdf.Argon2id -> ARGON2_ID
|
||||||
is Kdf.Pbkdf2 -> PBKDF2_SHA256
|
is Kdf.Pbkdf2 -> PBKDF2_SHA256
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a [Kdf] to [KdfJsonRequest]
|
||||||
|
*/
|
||||||
|
fun Kdf.toKdfRequestModel(): KdfJsonRequest =
|
||||||
|
when (this) {
|
||||||
|
is Kdf.Argon2id -> KdfJsonRequest(
|
||||||
|
kdfType = toKdfTypeJson(),
|
||||||
|
iterations = iterations.toInt(),
|
||||||
|
memory = memory.toInt(),
|
||||||
|
parallelism = parallelism.toInt(),
|
||||||
|
)
|
||||||
|
is Kdf.Pbkdf2 -> KdfJsonRequest(
|
||||||
|
kdfType = toKdfTypeJson(),
|
||||||
|
iterations = iterations.toInt(),
|
||||||
|
memory = null,
|
||||||
|
parallelism = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.ResetPasswordResult
|
|||||||
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.UpdateKdfMinimumsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
@ -351,6 +352,16 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager, UserStateM
|
|||||||
*/
|
*/
|
||||||
suspend fun getPasswordStrength(email: String? = null, password: String): PasswordStrengthResult
|
suspend fun getPasswordStrength(email: String? = null, password: String): PasswordStrengthResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if their current settings are below the minimums and needs update
|
||||||
|
*/
|
||||||
|
suspend fun needsKdfUpdateToMinimums(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the user's KDF settings if their current settings are below the minimums
|
||||||
|
*/
|
||||||
|
suspend fun updateKdfToMinimumsIfNeeded(password: String): UpdateKdfMinimumsResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the master password for the current logged in user.
|
* Validates the master password for the current logged in user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -15,6 +15,9 @@ import com.bitwarden.data.repository.util.toEnvironmentUrlsOrDefault
|
|||||||
import com.bitwarden.network.model.DeleteAccountResponseJson
|
import com.bitwarden.network.model.DeleteAccountResponseJson
|
||||||
import com.bitwarden.network.model.GetTokenResponseJson
|
import com.bitwarden.network.model.GetTokenResponseJson
|
||||||
import com.bitwarden.network.model.IdentityTokenAuthModel
|
import com.bitwarden.network.model.IdentityTokenAuthModel
|
||||||
|
import com.bitwarden.network.model.KdfTypeJson
|
||||||
|
import com.bitwarden.network.model.MasterPasswordAuthenticationDataJsonRequest
|
||||||
|
import com.bitwarden.network.model.MasterPasswordUnlockDataJsonRequest
|
||||||
import com.bitwarden.network.model.OrganizationType
|
import com.bitwarden.network.model.OrganizationType
|
||||||
import com.bitwarden.network.model.PasswordHintResponseJson
|
import com.bitwarden.network.model.PasswordHintResponseJson
|
||||||
import com.bitwarden.network.model.PolicyTypeJson
|
import com.bitwarden.network.model.PolicyTypeJson
|
||||||
@ -33,6 +36,7 @@ import com.bitwarden.network.model.SyncResponseJson
|
|||||||
import com.bitwarden.network.model.TrustedDeviceUserDecryptionOptionsJson
|
import com.bitwarden.network.model.TrustedDeviceUserDecryptionOptionsJson
|
||||||
import com.bitwarden.network.model.TwoFactorAuthMethod
|
import com.bitwarden.network.model.TwoFactorAuthMethod
|
||||||
import com.bitwarden.network.model.TwoFactorDataModel
|
import com.bitwarden.network.model.TwoFactorDataModel
|
||||||
|
import com.bitwarden.network.model.UpdateKdfJsonRequest
|
||||||
import com.bitwarden.network.model.VerifyEmailTokenRequestJson
|
import com.bitwarden.network.model.VerifyEmailTokenRequestJson
|
||||||
import com.bitwarden.network.model.VerifyEmailTokenResponseJson
|
import com.bitwarden.network.model.VerifyEmailTokenResponseJson
|
||||||
import com.bitwarden.network.service.AccountsService
|
import com.bitwarden.network.service.AccountsService
|
||||||
@ -49,6 +53,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
|||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfRequestModel
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfTypeJson
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfTypeJson
|
||||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
|
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
|
||||||
import com.x8bit.bitwarden.data.auth.manager.KeyConnectorManager
|
import com.x8bit.bitwarden.data.auth.manager.KeyConnectorManager
|
||||||
@ -77,6 +82,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.ResetPasswordResult
|
|||||||
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.UpdateKdfMinimumsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
@ -129,6 +135,8 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import kotlin.text.set
|
||||||
|
import kotlin.text.toInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of [AuthRepository].
|
* Default implementation of [AuthRepository].
|
||||||
@ -1212,6 +1220,98 @@ class AuthRepositoryImpl(
|
|||||||
onFailure = { PasswordStrengthResult.Error(error = it) },
|
onFailure = { PasswordStrengthResult.Error(error = it) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun needsKdfUpdateToMinimums(): Boolean {
|
||||||
|
val account = authDiskSource
|
||||||
|
.userState
|
||||||
|
?.accounts
|
||||||
|
?.get(activeUserId)
|
||||||
|
?: return false
|
||||||
|
|
||||||
|
return account.profile.kdfType == KdfTypeJson.PBKDF2_SHA256 &&
|
||||||
|
account.profile.kdfIterations != null &&
|
||||||
|
account.profile.kdfIterations < DEFAULT_PBKDF2_ITERATIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateKdfToMinimumsIfNeeded(password: String): UpdateKdfMinimumsResult {
|
||||||
|
val userId = activeUserId ?: return UpdateKdfMinimumsResult.ActiveAccountNotFound
|
||||||
|
val account = authDiskSource.userState?.accounts?.get(userId)
|
||||||
|
?: return UpdateKdfMinimumsResult.ActiveAccountNotFound
|
||||||
|
account.profile
|
||||||
|
|
||||||
|
// Check if needs update kdf
|
||||||
|
if (!needsKdfUpdateToMinimums()) {
|
||||||
|
return UpdateKdfMinimumsResult.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate updated KDF data
|
||||||
|
val updateKdfResponse = vaultSdkSource.makeUpdateKdf(
|
||||||
|
userId = userId,
|
||||||
|
password = password,
|
||||||
|
kdf = account.profile.toSdkParams(),
|
||||||
|
).getOrElse { error ->
|
||||||
|
return UpdateKdfMinimumsResult.Error(error = error)
|
||||||
|
}
|
||||||
|
|
||||||
|
val authData = updateKdfResponse.masterPasswordAuthenticationData
|
||||||
|
val oldAuthData = updateKdfResponse.oldMasterPasswordAuthenticationData
|
||||||
|
val unlockData = updateKdfResponse.masterPasswordUnlockData
|
||||||
|
// Send update to server
|
||||||
|
val updateKdfRequest = UpdateKdfJsonRequest(
|
||||||
|
authenticationData = MasterPasswordAuthenticationDataJsonRequest(
|
||||||
|
kdf = authData.kdf.toKdfRequestModel(),
|
||||||
|
masterPasswordAuthenticationHash =
|
||||||
|
authData.masterPasswordAuthenticationHash,
|
||||||
|
salt = authData.salt,
|
||||||
|
),
|
||||||
|
key = unlockData.masterKeyWrappedUserKey,
|
||||||
|
masterPasswordHash = oldAuthData.masterPasswordAuthenticationHash,
|
||||||
|
newMasterPasswordHash = authData.masterPasswordAuthenticationHash,
|
||||||
|
unlockData = MasterPasswordUnlockDataJsonRequest(
|
||||||
|
kdf = unlockData.kdf.toKdfRequestModel(),
|
||||||
|
masterKeyWrappedUserKey = unlockData.masterKeyWrappedUserKey,
|
||||||
|
salt = unlockData.salt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
accountsService
|
||||||
|
.updateKdf(body = updateKdfRequest)
|
||||||
|
.getOrElse { error ->
|
||||||
|
return UpdateKdfMinimumsResult.Error(error = error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO CHECK IF WE NEED TO SAVE NEW VALUES TO STATE
|
||||||
|
/**
|
||||||
|
// Update local storage
|
||||||
|
authDiskSource.storeMasterPasswordHash(
|
||||||
|
userId = profile.userId,
|
||||||
|
passwordHash = updateKdfResponse
|
||||||
|
.masterPasswordAuthenticationData.masterPasswordAuthenticationHash,
|
||||||
|
)
|
||||||
|
authDiskSource.storeUserKey(
|
||||||
|
userId = profile.userId,
|
||||||
|
userKey = updateKdfResponse.masterPasswordUnlockData.masterKeyWrappedUserKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update profile with new KDF parameters
|
||||||
|
val updatedProfile = profile.copy(
|
||||||
|
kdfType = authData.kdf.toKdfRequestModel().kdfType,
|
||||||
|
kdfIterations = authData.kdf.toKdfRequestModel().iterations,
|
||||||
|
kdfMemory = authData.kdf.toKdfRequestModel().memory,
|
||||||
|
kdfParallelism = authData.kdf.toKdfRequestModel().parallelism,
|
||||||
|
)
|
||||||
|
|
||||||
|
val updatedUserState = authDiskSource.userState?.copy(
|
||||||
|
accounts = authDiskSource.userState!!.accounts.toMutableMap().apply {
|
||||||
|
this[profile.userId] = account.copy(profile = updatedProfile)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
authDiskSource.userState = updatedUserState
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
return UpdateKdfMinimumsResult.Success
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun validatePassword(password: String): ValidatePasswordResult {
|
override suspend fun validatePassword(password: String): ValidatePasswordResult {
|
||||||
val userId = activeUserId ?: return ValidatePasswordResult.Error(NoActiveUserException())
|
val userId = activeUserId ?: return ValidatePasswordResult.Error(NoActiveUserException())
|
||||||
return authDiskSource
|
return authDiskSource
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.x8bit.bitwarden.data.auth.repository.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models result of updating a user's kdf settings to minimums
|
||||||
|
*/
|
||||||
|
sealed class UpdateKdfMinimumsResult {
|
||||||
|
/**
|
||||||
|
* Active account was not found
|
||||||
|
*/
|
||||||
|
object ActiveAccountNotFound : UpdateKdfMinimumsResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account with userId was not found
|
||||||
|
*/
|
||||||
|
object AccountNotFound : UpdateKdfMinimumsResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There was an error updating user to minimum kdf settings.
|
||||||
|
*
|
||||||
|
* @param error the error.
|
||||||
|
*/
|
||||||
|
data class Error(
|
||||||
|
val error: Throwable?,
|
||||||
|
) : UpdateKdfMinimumsResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updated user to minimum kdf settings successfully.
|
||||||
|
*/
|
||||||
|
object Success : UpdateKdfMinimumsResult()
|
||||||
|
}
|
||||||
@ -5,8 +5,11 @@ import com.bitwarden.core.AuthRequestMethod
|
|||||||
import com.bitwarden.core.AuthRequestResponse
|
import com.bitwarden.core.AuthRequestResponse
|
||||||
import com.bitwarden.core.InitUserCryptoMethod
|
import com.bitwarden.core.InitUserCryptoMethod
|
||||||
import com.bitwarden.core.KeyConnectorResponse
|
import com.bitwarden.core.KeyConnectorResponse
|
||||||
|
import com.bitwarden.core.MasterPasswordAuthenticationData
|
||||||
|
import com.bitwarden.core.MasterPasswordUnlockData
|
||||||
import com.bitwarden.core.RegisterKeyResponse
|
import com.bitwarden.core.RegisterKeyResponse
|
||||||
import com.bitwarden.core.RegisterTdeKeyResponse
|
import com.bitwarden.core.RegisterTdeKeyResponse
|
||||||
|
import com.bitwarden.core.UpdateKdfResponse
|
||||||
import com.bitwarden.core.UpdatePasswordResponse
|
import com.bitwarden.core.UpdatePasswordResponse
|
||||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
import com.bitwarden.core.data.util.asFailure
|
import com.bitwarden.core.data.util.asFailure
|
||||||
@ -99,6 +102,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.ResetPasswordResult
|
|||||||
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.UpdateKdfMinimumsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
@ -158,7 +162,8 @@ import java.time.ZonedDateTime
|
|||||||
import javax.net.ssl.SSLHandshakeException
|
import javax.net.ssl.SSLHandshakeException
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class AuthRepositoryTest {
|
class
|
||||||
|
AuthRepositoryTest {
|
||||||
|
|
||||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||||
private val accountsService: AccountsService = mockk()
|
private val accountsService: AccountsService = mockk()
|
||||||
@ -6888,6 +6893,212 @@ class AuthRepositoryTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `needsKdfUpdateToMinimums with no active user should return false`() = runTest {
|
||||||
|
fakeAuthDiskSource.userState = null
|
||||||
|
|
||||||
|
val result = repository.needsKdfUpdateToMinimums()
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `needsKdfUpdateToMinimums with kdfType null should return false`() = runTest {
|
||||||
|
val nullKdfProfile = PROFILE_1.copy(
|
||||||
|
kdfType = null,
|
||||||
|
kdfIterations = null,
|
||||||
|
kdfMemory = null,
|
||||||
|
kdfParallelism = null,
|
||||||
|
)
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1.copy(
|
||||||
|
accounts = mapOf(
|
||||||
|
USER_ID_1 to ACCOUNT_1.copy(profile = nullKdfProfile),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = repository.needsKdfUpdateToMinimums()
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `needsKdfUpdateToMinimums with PBKDF2 below minimum iterations should return true`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||||
|
|
||||||
|
val result = repository.needsKdfUpdateToMinimums()
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `needsKdfUpdateToMinimums with PBKDF2 meeting minimum iterations should return false`() =
|
||||||
|
runTest {
|
||||||
|
val sufficientIterationsProfile = PROFILE_1.copy(
|
||||||
|
kdfType = KdfTypeJson.PBKDF2_SHA256,
|
||||||
|
kdfIterations = 600000, // Meets minimum
|
||||||
|
kdfMemory = null,
|
||||||
|
kdfParallelism = null,
|
||||||
|
)
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1.copy(
|
||||||
|
accounts = mapOf(
|
||||||
|
USER_ID_1 to ACCOUNT_1.copy(profile = sufficientIterationsProfile),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = repository.needsKdfUpdateToMinimums()
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `needsKdfUpdateToMinimums with Argon2id below minimum parameters should return false`() =
|
||||||
|
runTest {
|
||||||
|
val lowArgon2idProfile = PROFILE_1.copy(
|
||||||
|
kdfType = KdfTypeJson.ARGON2_ID,
|
||||||
|
kdfIterations = 1, // Below minimum of 3
|
||||||
|
kdfMemory = 16, // Below minimum of 64
|
||||||
|
kdfParallelism = 1, // Below minimum of 4
|
||||||
|
)
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1.copy(
|
||||||
|
accounts = mapOf(
|
||||||
|
USER_ID_1 to ACCOUNT_1.copy(profile = lowArgon2idProfile),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = repository.needsKdfUpdateToMinimums()
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `needsKdfUpdateToMinimums with Argon2id meeting minimum parameters should return false`() =
|
||||||
|
runTest {
|
||||||
|
val sufficientArgon2idProfile = PROFILE_1.copy(
|
||||||
|
kdfType = KdfTypeJson.ARGON2_ID,
|
||||||
|
kdfIterations = 600000, // Meets minimum
|
||||||
|
kdfMemory = 64,
|
||||||
|
kdfParallelism = 4,
|
||||||
|
)
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1.copy(
|
||||||
|
accounts = mapOf(
|
||||||
|
USER_ID_1 to ACCOUNT_1.copy(profile = sufficientArgon2idProfile),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = repository.needsKdfUpdateToMinimums()
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `updateKdfToMinimumsIfNeeded with no active user should return ActiveAccountNotFound`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = null
|
||||||
|
|
||||||
|
val result = repository.updateKdfToMinimumsIfNeeded(password = PASSWORD)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
UpdateKdfMinimumsResult.ActiveAccountNotFound,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `updateKdfToMinimumsIfNeeded with minimum Kdf iterations should return Success`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1.copy(
|
||||||
|
accounts = mapOf(
|
||||||
|
USER_ID_1 to ACCOUNT_1.copy(
|
||||||
|
profile = PROFILE_1.copy(
|
||||||
|
kdfType = KdfTypeJson.PBKDF2_SHA256,
|
||||||
|
kdfIterations = 600000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = repository.updateKdfToMinimumsIfNeeded(password = PASSWORD)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
UpdateKdfMinimumsResult.Success,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `updateKdfToMinimumsIfNeeded if sdk throws an error should return Error`() = runTest {
|
||||||
|
val error = Throwable("Kdf update failed")
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.makeUpdateKdf(
|
||||||
|
userId = any(),
|
||||||
|
password = any(),
|
||||||
|
kdf = any(),
|
||||||
|
)
|
||||||
|
} returns error.asFailure()
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||||
|
|
||||||
|
val result = repository.updateKdfToMinimumsIfNeeded(password = PASSWORD)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
UpdateKdfMinimumsResult.Error(error = error),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `updateKdfToMinimumsIfNeeded with PBKDF2 below minimums and updateKdf API failure should return Error`() = runTest {
|
||||||
|
val error = Throwable("API failed")
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.makeUpdateKdf(
|
||||||
|
userId = any(),
|
||||||
|
password = any(),
|
||||||
|
kdf = any(),
|
||||||
|
)
|
||||||
|
} returns UPDATE_KDF_RESPONSE.asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
accountsService.updateKdf(any())
|
||||||
|
} returns error.asFailure()
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||||
|
|
||||||
|
val result = repository.updateKdfToMinimumsIfNeeded(password = PASSWORD)
|
||||||
|
|
||||||
|
assertEquals(UpdateKdfMinimumsResult.Error(error = error), result)
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
accountsService.updateKdf(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `updateKdfToMinimumsIfNeeded with PBKDF2 below minimums should return Success`() =
|
||||||
|
runTest {
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.makeUpdateKdf(
|
||||||
|
userId = any(),
|
||||||
|
password = any(),
|
||||||
|
kdf = any(),
|
||||||
|
)
|
||||||
|
} returns UPDATE_KDF_RESPONSE.asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
accountsService.updateKdf(any())
|
||||||
|
} returns Unit.asSuccess()
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||||
|
|
||||||
|
val result = repository.updateKdfToMinimumsIfNeeded(password = PASSWORD)
|
||||||
|
|
||||||
|
assertEquals(UpdateKdfMinimumsResult.Success, result)
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
accountsService.updateKdf(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||||
Instant.parse("2023-10-27T12:00:00Z"),
|
Instant.parse("2023-10-27T12:00:00Z"),
|
||||||
@ -7132,5 +7343,23 @@ class AuthRepositoryTest {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val UPDATE_KDF_RESPONSE = UpdateKdfResponse(
|
||||||
|
masterPasswordAuthenticationData = MasterPasswordAuthenticationData(
|
||||||
|
kdf = mockk<Kdf>(relaxed = true),
|
||||||
|
salt = "mockSalt",
|
||||||
|
masterPasswordAuthenticationHash = "mockHash",
|
||||||
|
),
|
||||||
|
masterPasswordUnlockData = MasterPasswordUnlockData(
|
||||||
|
kdf = mockk<Kdf>(relaxed = true),
|
||||||
|
masterKeyWrappedUserKey = "mockKey",
|
||||||
|
salt = "mockSalt",
|
||||||
|
),
|
||||||
|
oldMasterPasswordAuthenticationData = MasterPasswordAuthenticationData(
|
||||||
|
kdf = mockk<Kdf>(relaxed = true),
|
||||||
|
salt = "mockSalt",
|
||||||
|
masterPasswordAuthenticationHash = "mockHash",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user