Add additional auto-unlock key store/clear logic to VaultLockManager (#574)

This commit is contained in:
Brian Yencho 2024-01-11 11:44:03 -06:00 committed by Álison Fernandes
parent 6d51d514b8
commit faca927c0f
2 changed files with 285 additions and 65 deletions

View file

@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
* Primary implementation [VaultLockManager].
@ -145,6 +146,9 @@ class VaultLockManagerImpl(
unlockedVaultUserIds = it.unlockedVaultUserIds + userId,
)
}
// If we are unlocking an account with a timeout of Never, we should make sure to store the
// auto-unlock key.
storeUserAutoUnlockKeyIfNecessary(userId = userId)
}
private fun setVaultToLocked(userId: String) {
@ -154,6 +158,10 @@ class VaultLockManagerImpl(
unlockedVaultUserIds = it.unlockedVaultUserIds - userId,
)
}
authDiskSource.storeUserAutoUnlockKey(
userId = userId,
userAutoUnlockKey = null,
)
}
private fun setVaultToUnlocking(userId: String) {
@ -172,6 +180,26 @@ class VaultLockManagerImpl(
}
}
private fun storeUserAutoUnlockKeyIfNecessary(userId: String) {
val vaultTimeout = settingsRepository.getVaultTimeoutStateFlow(userId = userId).value
if (
vaultTimeout == VaultTimeout.Never &&
authDiskSource.getUserAutoUnlockKey(userId = userId) == null
) {
unconfinedScope.launch {
vaultSdkSource
.getUserEncryptionKey(userId = userId)
.getOrNull()
?.let {
authDiskSource.storeUserAutoUnlockKey(
userId = userId,
userAutoUnlockKey = it,
)
}
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun observeVaultTimeoutChanges() {
authDiskSource

View file

@ -216,8 +216,64 @@ class VaultLockManagerTest {
assertFalse(vaultLockManager.isVaultUnlocking(userId = userId))
}
@Suppress("MaxLineLength")
@Test
fun `lockVaultIfNecessary should lock the given account if it is currently unlocked`() =
fun `lockVault when non-Never timeout should lock the given account if it is currently unlocked`() =
runTest {
val userId = "userId"
verifyUnlockedVault(userId = userId)
mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes
assertEquals(
VaultState(
unlockedVaultUserIds = setOf(userId),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
vaultLockManager.lockVault(userId = userId)
assertEquals(
VaultState(
unlockedVaultUserIds = emptySet(),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
verify { vaultSdkSource.clearCrypto(userId = userId) }
}
@Test
fun `lockVault when Never timeout should lock the given account if it is currently unlocked`() =
runTest {
val userId = "userId"
verifyUnlockedVault(userId = userId)
mutableVaultTimeoutStateFlow.value = VaultTimeout.Never
assertEquals(
VaultState(
unlockedVaultUserIds = setOf(userId),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
vaultLockManager.lockVault(userId = userId)
assertEquals(
VaultState(
unlockedVaultUserIds = emptySet(),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
verify { vaultSdkSource.clearCrypto(userId = userId) }
}
@Suppress("MaxLineLength")
@Test
fun `lockVaultIfNecessary when non-Never timeout should lock the given account if it is currently unlocked`() =
runTest {
val userId = "userId"
verifyUnlockedVault(userId = userId)
@ -242,6 +298,32 @@ class VaultLockManagerTest {
verify { vaultSdkSource.clearCrypto(userId = userId) }
}
@Suppress("MaxLineLength")
@Test
fun `lockVaultIfNecessary when Never timeout should not lock the given account if it is currently unlocked`() =
runTest {
val userId = "userId"
verifyUnlockedVault(userId = userId)
mutableVaultTimeoutStateFlow.value = VaultTimeout.Never
val initialVaultState = VaultState(
unlockedVaultUserIds = setOf(userId),
unlockingVaultUserIds = emptySet(),
)
assertEquals(
initialVaultState,
vaultLockManager.vaultStateFlow.value,
)
vaultLockManager.lockVaultIfNecessary(userId = userId)
assertEquals(
initialVaultState,
vaultLockManager.vaultStateFlow.value,
)
verify(exactly = 0) { vaultSdkSource.clearCrypto(userId = userId) }
}
@Suppress("MaxLineLength")
@Test
fun `lockVaultForCurrentUser should lock the vault for the current user if it is currently unlocked`() =
@ -270,84 +352,194 @@ class VaultLockManagerTest {
verify { vaultSdkSource.clearCrypto(userId = userId) }
}
@Suppress("MaxLineLength")
@Test
fun `unlockVault with initializeCrypto success should return Success`() = runTest {
val userId = "userId"
val kdf = MOCK_PROFILE.toSdkParams()
val email = MOCK_PROFILE.email
val masterPassword = "drowssap"
val userKey = "12345"
val privateKey = "54321"
val organizationKeys = mapOf("orgId1" to "orgKey1")
coEvery {
vaultSdkSource.initializeCrypto(
userId = userId,
request = InitUserCryptoRequest(
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
fun `unlockVault with initializeCrypto success for a non-Never VaultTimeout should return Success`() =
runTest {
val userId = "userId"
val kdf = MOCK_PROFILE.toSdkParams()
val email = MOCK_PROFILE.email
val masterPassword = "drowssap"
val userKey = "12345"
val privateKey = "54321"
val organizationKeys = mapOf("orgId1" to "orgKey1")
coEvery {
vaultSdkSource.initializeCrypto(
userId = userId,
request = InitUserCryptoRequest(
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
userId = userId,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
)
} returns InitializeCryptoResult.Success.asSuccess()
assertEquals(
VaultState(
unlockedVaultUserIds = emptySet(),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes
fakeAuthDiskSource.storeUserAutoUnlockKey(
userId = userId,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
userAutoUnlockKey = null,
)
} returns InitializeCryptoResult.Success.asSuccess()
assertEquals(
VaultState(
unlockedVaultUserIds = emptySet(),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
val result = vaultLockManager.unlockVault(
userId = userId,
kdf = kdf,
email = email,
initUserCryptoMethod = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
privateKey = privateKey,
organizationKeys = organizationKeys,
)
assertEquals(VaultUnlockResult.Success, result)
assertEquals(
VaultState(
unlockedVaultUserIds = setOf(userId),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
coVerify(exactly = 1) {
vaultSdkSource.initializeCrypto(
val result = vaultLockManager.unlockVault(
userId = userId,
request = InitUserCryptoRequest(
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
kdf = kdf,
email = email,
initUserCryptoMethod = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
privateKey = privateKey,
organizationKeys = organizationKeys,
)
assertEquals(VaultUnlockResult.Success, result)
assertEquals(
VaultState(
unlockedVaultUserIds = setOf(userId),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
fakeAuthDiskSource.assertUserAutoUnlockKey(
userId = userId,
userAutoUnlockKey = null,
)
coVerify(exactly = 1) {
vaultSdkSource.initializeCrypto(
userId = userId,
request = InitUserCryptoRequest(
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
),
)
}
coVerify(exactly = 1) {
vaultSdkSource.initializeOrganizationCrypto(
userId = userId,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `unlockVault with initializeCrypto success for a Never VaultTimeout should return Success and save the auto-unlock key`() =
runTest {
val userId = "userId"
val kdf = MOCK_PROFILE.toSdkParams()
val email = MOCK_PROFILE.email
val masterPassword = "drowssap"
val userKey = "12345"
val privateKey = "54321"
val organizationKeys = mapOf("orgId1" to "orgKey1")
val userAutoUnlockKey = "userAutoUnlockKey"
coEvery {
vaultSdkSource.initializeCrypto(
userId = userId,
request = InitUserCryptoRequest(
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
userId = userId,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultSdkSource.getUserEncryptionKey(userId = userId)
} returns userAutoUnlockKey.asSuccess()
assertEquals(
VaultState(
unlockedVaultUserIds = emptySet(),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
}
coVerify(exactly = 1) {
vaultSdkSource.initializeOrganizationCrypto(
mutableVaultTimeoutStateFlow.value = VaultTimeout.Never
fakeAuthDiskSource.storeUserAutoUnlockKey(
userId = userId,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
userAutoUnlockKey = null,
)
val result = vaultLockManager.unlockVault(
userId = userId,
kdf = kdf,
email = email,
initUserCryptoMethod = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
privateKey = privateKey,
organizationKeys = organizationKeys,
)
assertEquals(VaultUnlockResult.Success, result)
assertEquals(
VaultState(
unlockedVaultUserIds = setOf(userId),
unlockingVaultUserIds = emptySet(),
),
vaultLockManager.vaultStateFlow.value,
)
fakeAuthDiskSource.assertUserAutoUnlockKey(
userId = userId,
userAutoUnlockKey = userAutoUnlockKey,
)
coVerify(exactly = 1) {
vaultSdkSource.initializeCrypto(
userId = userId,
request = InitUserCryptoRequest(
kdfParams = kdf,
email = email,
privateKey = privateKey,
method = InitUserCryptoMethod.Password(
password = masterPassword,
userKey = userKey,
),
),
)
}
coVerify(exactly = 1) {
vaultSdkSource.initializeOrganizationCrypto(
userId = userId,
request = InitOrgCryptoRequest(organizationKeys = organizationKeys),
)
}
coVerify {
vaultSdkSource.getUserEncryptionKey(userId = userId)
}
}
}
@Suppress("MaxLineLength")
@Test