Add isBiometricsEnabled boolean to UserState (#806)

This commit is contained in:
David Perez 2024-01-26 16:52:54 -06:00 committed by Álison Fernandes
parent b1c6567df2
commit 9338a51d68
19 changed files with 100 additions and 46 deletions

View file

@ -153,6 +153,7 @@ class AuthRepositoryImpl(
vaultState = vaultState,
userOrganizationsList = userOrganizationsList,
hasPendingAccountAddition = hasPendingAccountAddition,
isBiometricsEnabledProvider = ::isBiometricsEnabled,
vaultUnlockTypeProvider = ::getVaultUnlockType,
)
}
@ -170,6 +171,7 @@ class AuthRepositoryImpl(
vaultState = vaultRepository.vaultStateFlow.value,
userOrganizationsList = authDiskSource.userOrganizationsList,
hasPendingAccountAddition = mutableHasPendingAccountAdditionStateFlow.value,
isBiometricsEnabledProvider = ::isBiometricsEnabled,
vaultUnlockTypeProvider = ::getVaultUnlockType,
),
)
@ -710,6 +712,10 @@ class AuthRepositoryImpl(
)
}
private fun isBiometricsEnabled(
userId: String,
): Boolean = authDiskSource.getUserBiometricUnlockKey(userId = userId) != null
private fun getVaultUnlockType(
userId: String,
): VaultUnlockType =

View file

@ -12,7 +12,6 @@ import com.x8bit.bitwarden.data.platform.repository.model.Environment
* that user.
* @property hasPendingAccountAddition Returns `true` if there is an additional account that is
* pending login/registration in order to have multiple accounts available.
* @property specialCircumstance A special circumstance (if any) that may be present.
*/
data class UserState(
val activeUserId: String,
@ -42,6 +41,8 @@ data class UserState(
* authentication to view their vault.
* @property isVaultUnlocked Whether or not the user's vault is currently unlocked.
* @property organizations List of [Organization]s the user is associated with, if any.
* @property isBiometricsEnabled Indicates that the biometrics mechanism for unlocking the
* user's vault is enabled.
* @property vaultUnlockType The mechanism by which the user's vault may be unlocked.
*/
data class Account(
@ -54,6 +55,7 @@ data class UserState(
val isLoggedIn: Boolean,
val isVaultUnlocked: Boolean,
val organizations: List<Organization>,
val isBiometricsEnabled: Boolean,
val vaultUnlockType: VaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
)
}

View file

@ -41,13 +41,13 @@ fun UserStateJson.toUpdatedUserStateJson(
}
/**
* Converts the given [UserStateJson] to a [UserState] using the given [vaultState] and
* [specialCircumstance].
* Converts the given [UserStateJson] to a [UserState] using the given [vaultState].
*/
fun UserStateJson.toUserState(
vaultState: VaultState,
userOrganizationsList: List<UserOrganizations>,
hasPendingAccountAddition: Boolean,
isBiometricsEnabledProvider: (userId: String) -> Boolean,
vaultUnlockTypeProvider: (userId: String) -> VaultUnlockType,
): UserState =
UserState(
@ -74,6 +74,7 @@ fun UserStateJson.toUserState(
.find { it.userId == userId }
?.organizations
.orEmpty(),
isBiometricsEnabled = isBiometricsEnabledProvider(userId),
vaultUnlockType = vaultUnlockTypeProvider(userId),
)
},

View file

@ -1,8 +1,8 @@
package com.x8bit.bitwarden
import android.content.Intent
import app.cash.turbine.test
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
@ -193,6 +193,7 @@ class MainViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -243,6 +243,7 @@ class AuthRepositoryTest {
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
),
repository.userStateFlow.value,
@ -264,6 +265,7 @@ class AuthRepositoryTest {
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
),
repository.userStateFlow.value,
@ -279,6 +281,7 @@ class AuthRepositoryTest {
vaultState = emptyVaultState,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
),
repository.userStateFlow.value,
@ -303,6 +306,7 @@ class AuthRepositoryTest {
vaultState = emptyVaultState,
userOrganizationsList = USER_ORGANIZATIONS,
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
),
repository.userStateFlow.value,
@ -347,12 +351,14 @@ class AuthRepositoryTest {
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
val finalUserState = SINGLE_USER_STATE_2.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
val kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams()
@ -1252,54 +1258,54 @@ class AuthRepositoryTest {
}
}
@Suppress("MaxLineLength")
@Test
fun `SSO login get token returns two factor request should return TwoFactorRequired`() = runTest {
coEvery {
identityService.getToken(
email = EMAIL,
authModel = IdentityTokenAuthModel.SingleSignOn(
ssoCode = SSO_CODE,
ssoCodeVerifier = SSO_CODE_VERIFIER,
ssoRedirectUri = SSO_REDIRECT_URI,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
}
.returns(
Result.success(
GetTokenResponseJson.TwoFactorRequired(
TWO_FACTOR_AUTH_METHODS_DATA, null, null,
fun `SSO login get token returns two factor request should return TwoFactorRequired`() =
runTest {
coEvery {
identityService.getToken(
email = EMAIL,
authModel = IdentityTokenAuthModel.SingleSignOn(
ssoCode = SSO_CODE,
ssoCodeVerifier = SSO_CODE_VERIFIER,
ssoRedirectUri = SSO_REDIRECT_URI,
),
),
)
val result = repository.login(
email = EMAIL,
ssoCode = SSO_CODE,
ssoCodeVerifier = SSO_CODE_VERIFIER,
ssoRedirectUri = SSO_REDIRECT_URI,
captchaToken = null,
)
assertEquals(LoginResult.TwoFactorRequired, result)
assertEquals(
repository.twoFactorResponse,
GetTokenResponseJson.TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null),
)
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
coVerify {
identityService.getToken(
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
}
.returns(
Result.success(
GetTokenResponseJson.TwoFactorRequired(
TWO_FACTOR_AUTH_METHODS_DATA, null, null,
),
),
)
val result = repository.login(
email = EMAIL,
authModel = IdentityTokenAuthModel.SingleSignOn(
ssoCode = SSO_CODE,
ssoCodeVerifier = SSO_CODE_VERIFIER,
ssoRedirectUri = SSO_REDIRECT_URI,
),
ssoCode = SSO_CODE,
ssoCodeVerifier = SSO_CODE_VERIFIER,
ssoRedirectUri = SSO_REDIRECT_URI,
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertEquals(LoginResult.TwoFactorRequired, result)
assertEquals(
repository.twoFactorResponse,
GetTokenResponseJson.TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null),
)
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
coVerify {
identityService.getToken(
email = EMAIL,
authModel = IdentityTokenAuthModel.SingleSignOn(
ssoCode = SSO_CODE,
ssoCodeVerifier = SSO_CODE_VERIFIER,
ssoRedirectUri = SSO_REDIRECT_URI,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
}
}
}
@Test
fun `SSO login two factor with remember saves two factor auth token`() = runTest {
@ -1980,6 +1986,7 @@ class AuthRepositoryTest {
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
@ -2010,6 +2017,7 @@ class AuthRepositoryTest {
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
@ -2038,6 +2046,7 @@ class AuthRepositoryTest {
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
fakeAuthDiskSource.userState = MULTI_USER_STATE

View file

@ -113,6 +113,7 @@ class UserStateJsonExtensionsTest {
name = "organizationName",
),
),
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.PIN,
),
),
@ -155,6 +156,7 @@ class UserStateJsonExtensionsTest {
),
),
hasPendingAccountAddition = false,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
),
)
@ -182,6 +184,7 @@ class UserStateJsonExtensionsTest {
name = "organizationName",
),
),
isBiometricsEnabled = true,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
),
),
@ -225,6 +228,7 @@ class UserStateJsonExtensionsTest {
),
),
hasPendingAccountAddition = true,
isBiometricsEnabledProvider = { true },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
),
)

View file

@ -71,6 +71,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -201,6 +202,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
)
val userState = UserState(
@ -250,6 +252,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = false,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
)
val userState = UserState(

View file

@ -127,6 +127,7 @@ class LoginViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -112,6 +112,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -144,6 +145,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = false,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -567,6 +569,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
)

View file

@ -46,6 +46,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = false,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -71,6 +72,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -96,6 +98,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -129,6 +132,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),
@ -156,6 +160,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = false,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -926,6 +926,7 @@ private val DEFAULT_USER_STATE = UserState(
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -1786,6 +1786,7 @@ private val DEFAULT_USER_STATE = UserState(
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -1002,6 +1002,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
isPremium = false,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
)

View file

@ -553,6 +553,7 @@ private val DEFAULT_USER_STATE = UserState(
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -1166,6 +1166,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -421,6 +421,7 @@ private val DEFAULT_USER_STATE = UserState(
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "mockOrganizationId-1",

View file

@ -94,6 +94,7 @@ private fun createMockUserState(hasOrganizations: Boolean = true): UserState =
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = if (hasOrganizations) {
listOf(
Organization(

View file

@ -153,6 +153,7 @@ class VaultViewModelTest : BaseViewModelTest() {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organiationId",
@ -1223,6 +1224,7 @@ private val DEFAULT_USER_STATE = UserState(
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
),
UserState.Account(
@ -1234,6 +1236,7 @@ private val DEFAULT_USER_STATE = UserState(
isPremium = false,
isLoggedIn = true,
isVaultUnlocked = false,
isBiometricsEnabled = false,
organizations = emptyList(),
),
),

View file

@ -69,6 +69,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -85,6 +86,7 @@ class UserStateExtensionsTest {
isPremium = false,
isLoggedIn = true,
isVaultUnlocked = false,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -105,6 +107,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -125,6 +128,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = false,
isVaultUnlocked = false,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -160,6 +164,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -193,6 +198,7 @@ class UserStateExtensionsTest {
isPremium = false,
isLoggedIn = true,
isVaultUnlocked = false,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -230,6 +236,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId",
@ -255,6 +262,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = emptyList(),
)
.toVaultFilterData(),
@ -289,6 +297,7 @@ class UserStateExtensionsTest {
isPremium = true,
isLoggedIn = true,
isVaultUnlocked = true,
isBiometricsEnabled = false,
organizations = listOf(
Organization(
id = "organizationId-B",