Add trusted device logic (#1224)

This commit is contained in:
David Perez 2024-04-04 11:13:18 -05:00 committed by Álison Fernandes
parent 5d40d68b3f
commit 9685c6057a
23 changed files with 180 additions and 1 deletions

View file

@ -211,6 +211,7 @@ class AuthRepositoryImpl(
isBiometricsEnabledProvider = ::isBiometricsEnabled,
vaultUnlockTypeProvider = ::getVaultUnlockType,
isLoggedInProvider = ::isUserLoggedIn,
isDeviceTrustedProvider = ::isDeviceTrusted,
)
}
.filter {
@ -230,6 +231,7 @@ class AuthRepositoryImpl(
isBiometricsEnabledProvider = ::isBiometricsEnabled,
vaultUnlockTypeProvider = ::getVaultUnlockType,
isLoggedInProvider = ::isUserLoggedIn,
isDeviceTrustedProvider = ::isDeviceTrusted,
),
)
@ -1154,6 +1156,10 @@ class AuthRepositoryImpl(
userId: String,
): Boolean = authDiskSource.getUserBiometricUnlockKey(userId = userId) != null
private fun isDeviceTrusted(
userId: String,
): Boolean = authDiskSource.getDeviceKey(userId = userId) != null
private fun isUserLoggedIn(
userId: String,
): Boolean = authDiskSource.getAccountTokens(userId = userId)?.isLoggedIn == true

View file

@ -59,8 +59,20 @@ data class UserState(
val isVaultUnlocked: Boolean,
val needsPasswordReset: Boolean,
val needsMasterPassword: Boolean,
val trustedDevice: TrustedDevice?,
val organizations: List<Organization>,
val isBiometricsEnabled: Boolean,
val vaultUnlockType: VaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
)
/**
* Models the data related to trusted device encryption (TDE).
*/
data class TrustedDevice(
val isDeviceTrusted: Boolean,
val hasMasterPassword: Boolean,
val hasAdminApproval: Boolean,
val hasLoginApprovingDevice: Boolean,
val hasResetPasswordPermission: Boolean,
)
}

View file

@ -81,6 +81,7 @@ fun UserStateJson.toUserState(
isBiometricsEnabledProvider: (userId: String) -> Boolean,
vaultUnlockTypeProvider: (userId: String) -> VaultUnlockType,
isLoggedInProvider: (userId: String) -> Boolean,
isDeviceTrustedProvider: (userId: String) -> Boolean,
): UserState =
UserState(
activeUserId = this.activeUserId,
@ -92,7 +93,21 @@ fun UserStateJson.toUserState(
val userId = profile.userId
val vaultUnlocked = vaultState.statusFor(userId) == VaultUnlockData.Status.UNLOCKED
val needsPasswordReset = profile.forcePasswordResetReason != null
val needsMasterPassword = profile.userDecryptionOptions?.hasMasterPassword == false
val decryptionOptions = profile.userDecryptionOptions
val trustedDeviceOptions = decryptionOptions?.trustedDeviceUserDecryptionOptions
val keyConnectorOptions = decryptionOptions?.keyConnectorUserDecryptionOptions
val needsMasterPassword = decryptionOptions?.hasMasterPassword == false &&
trustedDeviceOptions?.hasManageResetPasswordPermission != false &&
keyConnectorOptions == null
val trustedDevice = trustedDeviceOptions?.let {
UserState.TrustedDevice(
isDeviceTrusted = isDeviceTrustedProvider(userId),
hasMasterPassword = decryptionOptions.hasMasterPassword,
hasAdminApproval = it.hasAdminApproval,
hasLoginApprovingDevice = it.hasLoginApprovingDevice,
hasResetPasswordPermission = it.hasManageResetPasswordPermission,
)
}
UserState.Account(
userId = userId,
@ -114,6 +129,7 @@ fun UserStateJson.toUserState(
isBiometricsEnabled = isBiometricsEnabledProvider(userId),
vaultUnlockType = vaultUnlockTypeProvider(userId),
needsMasterPassword = needsMasterPassword,
trustedDevice = trustedDevice,
)
},
hasPendingAccountAddition = hasPendingAccountAddition,

View file

@ -306,6 +306,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
),
repository.userStateFlow.value,
)
@ -329,6 +330,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
),
repository.userStateFlow.value,
)
@ -343,6 +345,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
),
repository.userStateFlow.value,
)
@ -369,6 +372,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
),
repository.userStateFlow.value,
)
@ -563,6 +567,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
)
val finalUserState = SINGLE_USER_STATE_2.toUserState(
vaultState = VAULT_UNLOCK_DATA,
@ -571,6 +576,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
)
val kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams()
coEvery {
@ -3150,6 +3156,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
)
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
assertEquals(
@ -3181,6 +3188,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
)
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
assertEquals(
@ -3210,6 +3218,7 @@ class AuthRepositoryTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
)
fakeAuthDiskSource.userState = MULTI_USER_STATE
assertEquals(

View file

@ -229,6 +229,7 @@ class UserStateJsonExtensionsTest {
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.PIN,
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -281,6 +282,7 @@ class UserStateJsonExtensionsTest {
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
isLoggedInProvider = { true },
isDeviceTrustedProvider = { false },
),
)
}
@ -311,6 +313,7 @@ class UserStateJsonExtensionsTest {
isBiometricsEnabled = true,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = true,
trustedDevice = null,
),
),
hasPendingAccountAddition = true,
@ -359,6 +362,97 @@ class UserStateJsonExtensionsTest {
isBiometricsEnabledProvider = { true },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { false },
),
)
}
@Suppress("MaxLineLength")
@Test
fun `toUserState should preserve values of trustedDeviceUserDecryptionOptions`() {
assertEquals(
UserState(
activeUserId = "activeUserId",
accounts = listOf(
UserState.Account(
userId = "activeUserId",
name = "activeName",
email = "activeEmail",
// This value is calculated from the userId
avatarColorHex = "#ffecbc49",
environment = Environment.Eu,
isPremium = true,
isLoggedIn = false,
isVaultUnlocked = false,
needsPasswordReset = false,
organizations = listOf(
Organization(
id = "organizationId",
name = "organizationName",
),
),
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = false,
trustedDevice = UserState.TrustedDevice(
isDeviceTrusted = true,
hasMasterPassword = false,
hasAdminApproval = false,
hasLoginApprovingDevice = true,
hasResetPasswordPermission = false,
),
),
),
hasPendingAccountAddition = true,
),
UserStateJson(
activeUserId = "activeUserId",
accounts = mapOf(
"activeUserId" to AccountJson(
profile = mockk {
every { userId } returns "activeUserId"
every { name } returns "activeName"
every { email } returns "activeEmail"
every { avatarColorHex } returns null
every { hasPremium } returns true
every { forcePasswordResetReason } returns null
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
hasMasterPassword = false,
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
encryptedPrivateKey = null,
encryptedUserKey = null,
hasAdminApproval = false,
hasLoginApprovingDevice = true,
hasManageResetPasswordPermission = false,
),
keyConnectorUserDecryptionOptions = null,
)
},
tokens = null,
settings = AccountJson.Settings(
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
),
),
),
)
.toUserState(
vaultState = emptyList(),
userOrganizationsList = listOf(
UserOrganizations(
userId = "activeUserId",
organizations = listOf(
Organization(
id = "organizationId",
name = "organizationName",
),
),
),
),
hasPendingAccountAddition = true,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isLoggedInProvider = { false },
isDeviceTrustedProvider = { true },
),
)
}

View file

@ -75,6 +75,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)
@ -208,6 +209,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
)
val userState = UserState(
activeUserId = "activeUserId",
@ -260,6 +262,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
)
val userState = UserState(
activeUserId = "activeUserId",

View file

@ -129,6 +129,7 @@ class LoginViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -121,6 +121,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)
@ -156,6 +157,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = true,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)
@ -762,6 +764,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
)
private val DEFAULT_USER_STATE = UserState(

View file

@ -51,6 +51,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -78,6 +79,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -105,6 +107,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = true,
trustedDevice = null,
),
),
),
@ -136,6 +139,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
hasPendingAccountAddition = true,
@ -164,6 +168,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -200,6 +205,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -236,6 +242,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -278,6 +285,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),
@ -311,6 +319,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
),

View file

@ -1369,6 +1369,7 @@ private val DEFAULT_USER_STATE = UserState(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -291,6 +291,7 @@ private val DEFAULT_USER_STATE = UserState(
needsPasswordReset = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -596,6 +596,7 @@ private val DEFAULT_USER_STATE = UserState(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -2377,6 +2377,7 @@ private val DEFAULT_USER_STATE = UserState(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -1098,6 +1098,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
)
private val DEFAULT_USER_STATE = UserState(

View file

@ -2490,6 +2490,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = true,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = false,
trustedDevice = null,
),
),
hasPendingAccountAddition = false,

View file

@ -432,6 +432,7 @@ class CipherViewExtensionsTest {
isBiometricsEnabled = true,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = false,
trustedDevice = null,
)
}

View file

@ -557,6 +557,7 @@ private val DEFAULT_USER_STATE = UserState(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -2014,6 +2014,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -1459,6 +1459,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
)
private val DEFAULT_USER_STATE = UserState(

View file

@ -491,6 +491,7 @@ private val DEFAULT_USER_STATE = UserState(
name = "mockOrganizationName-3",
),
),
trustedDevice = null,
),
),
)

View file

@ -115,6 +115,7 @@ private fun createMockUserState(hasOrganizations: Boolean = true): UserState =
} else {
emptyList()
},
trustedDevice = null,
),
),
)

View file

@ -183,6 +183,7 @@ class VaultViewModelTest : BaseViewModelTest() {
name = "Test Organization",
),
),
trustedDevice = null,
),
),
)
@ -261,6 +262,7 @@ class VaultViewModelTest : BaseViewModelTest() {
name = "Test Organization",
),
),
trustedDevice = null,
),
),
)
@ -1473,6 +1475,7 @@ private val DEFAULT_USER_STATE = UserState(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
UserState.Account(
userId = "lockedUserId",
@ -1487,6 +1490,7 @@ private val DEFAULT_USER_STATE = UserState(
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
),
),
)

View file

@ -78,6 +78,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
),
UserState.Account(
userId = "lockedUserId",
@ -97,6 +98,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
),
UserState.Account(
userId = "unlockedUserId",
@ -120,6 +122,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
),
UserState.Account(
userId = "loggedOutUserId",
@ -143,6 +146,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
),
),
)
@ -181,6 +185,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
)
.toAccountSummary(isActive = true),
)
@ -217,6 +222,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
)
.toAccountSummary(isActive = false),
)
@ -257,6 +263,7 @@ class UserStateExtensionsTest {
name = "organizationName",
),
),
trustedDevice = null,
),
),
)
@ -280,6 +287,7 @@ class UserStateExtensionsTest {
isBiometricsEnabled = false,
organizations = emptyList(),
needsMasterPassword = false,
trustedDevice = null,
)
.toVaultFilterData(isIndividualVaultDisabled = false),
)
@ -326,6 +334,7 @@ class UserStateExtensionsTest {
name = "Organization A",
),
),
trustedDevice = null,
)
.toVaultFilterData(
isIndividualVaultDisabled = false,
@ -373,6 +382,7 @@ class UserStateExtensionsTest {
name = "Organization A",
),
),
trustedDevice = null,
)
.toVaultFilterData(
isIndividualVaultDisabled = true,