mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 13:57:03 -06:00
[PM-13257] Checking if user is navigating from vault before showing prompt for biometrics (#4846)
This commit is contained in:
parent
f4f669683e
commit
ad6bc883b8
@ -116,7 +116,10 @@ class AuthenticatorBridgeRepositoryImpl(
|
|||||||
|
|
||||||
// Lock the user's vault if we unlocked it for this operation:
|
// Lock the user's vault if we unlocked it for this operation:
|
||||||
if (!isVaultAlreadyUnlocked) {
|
if (!isVaultAlreadyUnlocked) {
|
||||||
vaultRepository.lockVault(userId)
|
vaultRepository.lockVault(
|
||||||
|
userId = userId,
|
||||||
|
isUserInitiated = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedAccountData.Account(
|
SharedAccountData.Account(
|
||||||
|
|||||||
@ -22,6 +22,11 @@ interface VaultLockManager {
|
|||||||
*/
|
*/
|
||||||
val vaultStateEventFlow: Flow<VaultStateEvent>
|
val vaultStateEventFlow: Flow<VaultStateEvent>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user is coming from the lock flow or not.
|
||||||
|
*/
|
||||||
|
var isFromLockFlow: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the vault is currently locked for the given [userId].
|
* Whether or not the vault is currently locked for the given [userId].
|
||||||
*/
|
*/
|
||||||
@ -35,12 +40,12 @@ interface VaultLockManager {
|
|||||||
/**
|
/**
|
||||||
* Locks the vault for the user with the given [userId].
|
* Locks the vault for the user with the given [userId].
|
||||||
*/
|
*/
|
||||||
fun lockVault(userId: String)
|
fun lockVault(userId: String, isUserInitiated: Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locks the vault for the current user if currently unlocked.
|
* Locks the vault for the current user if currently unlocked.
|
||||||
*/
|
*/
|
||||||
fun lockVaultForCurrentUser()
|
fun lockVaultForCurrentUser(isUserInitiated: Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to unlock the vault with the specified user information.
|
* Attempt to unlock the vault with the specified user information.
|
||||||
|
|||||||
@ -101,6 +101,8 @@ class VaultLockManagerImpl(
|
|||||||
override val vaultStateEventFlow: Flow<VaultStateEvent>
|
override val vaultStateEventFlow: Flow<VaultStateEvent>
|
||||||
get() = mutableVaultStateEventSharedFlow.asSharedFlow()
|
get() = mutableVaultStateEventSharedFlow.asSharedFlow()
|
||||||
|
|
||||||
|
override var isFromLockFlow: Boolean = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeAppCreationChanges()
|
observeAppCreationChanges()
|
||||||
observeAppForegroundChanges()
|
observeAppForegroundChanges()
|
||||||
@ -119,13 +121,17 @@ class VaultLockManagerImpl(
|
|||||||
override fun isVaultUnlocking(userId: String): Boolean =
|
override fun isVaultUnlocking(userId: String): Boolean =
|
||||||
mutableVaultUnlockDataStateFlow.value.statusFor(userId) == VaultUnlockData.Status.UNLOCKING
|
mutableVaultUnlockDataStateFlow.value.statusFor(userId) == VaultUnlockData.Status.UNLOCKING
|
||||||
|
|
||||||
override fun lockVault(userId: String) {
|
override fun lockVault(userId: String, isUserInitiated: Boolean) {
|
||||||
|
isFromLockFlow = isUserInitiated
|
||||||
setVaultToLocked(userId = userId)
|
setVaultToLocked(userId = userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lockVaultForCurrentUser() {
|
override fun lockVaultForCurrentUser(isUserInitiated: Boolean) {
|
||||||
activeUserId?.let {
|
activeUserId?.let {
|
||||||
lockVault(it)
|
lockVault(
|
||||||
|
userId = it,
|
||||||
|
isUserInitiated = isUserInitiated,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -127,7 +127,7 @@ class LandingViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockAccountClicked(action: LandingAction.LockAccountClick) {
|
private fun handleLockAccountClicked(action: LandingAction.LockAccountClick) {
|
||||||
vaultRepository.lockVault(userId = action.accountSummary.userId)
|
vaultRepository.lockVault(userId = action.accountSummary.userId, isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogoutAccountClicked(action: LandingAction.LogoutAccountClick) {
|
private fun handleLogoutAccountClicked(action: LandingAction.LogoutAccountClick) {
|
||||||
|
|||||||
@ -108,7 +108,7 @@ class LoginViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockAccountClicked(action: LoginAction.LockAccountClick) {
|
private fun handleLockAccountClicked(action: LoginAction.LockAccountClick) {
|
||||||
vaultRepository.lockVault(userId = action.accountSummary.userId)
|
vaultRepository.lockVault(userId = action.accountSummary.userId, isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogoutAccountClicked(action: LoginAction.LogoutAccountClick) {
|
private fun handleLogoutAccountClicked(action: LoginAction.LogoutAccountClick) {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
|||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.model.UnlockType
|
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.model.UnlockType
|
||||||
@ -54,6 +55,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||||
private val fido2CredentialManager: Fido2CredentialManager,
|
private val fido2CredentialManager: Fido2CredentialManager,
|
||||||
private val appResumeManager: AppResumeManager,
|
private val appResumeManager: AppResumeManager,
|
||||||
|
private val vaultLockManager: VaultLockManager,
|
||||||
environmentRepo: EnvironmentRepository,
|
environmentRepo: EnvironmentRepository,
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
) : BaseViewModel<VaultUnlockState, VaultUnlockEvent, VaultUnlockAction>(
|
) : BaseViewModel<VaultUnlockState, VaultUnlockEvent, VaultUnlockAction>(
|
||||||
@ -100,6 +102,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||||||
// TODO: [PM-13076] Handle Fido2CredentialAssertionRequest special circumstance
|
// TODO: [PM-13076] Handle Fido2CredentialAssertionRequest special circumstance
|
||||||
fido2CredentialAssertionRequest = null,
|
fido2CredentialAssertionRequest = null,
|
||||||
hasMasterPassword = activeAccount.hasMasterPassword,
|
hasMasterPassword = activeAccount.hasMasterPassword,
|
||||||
|
isFromLockFlow = vaultLockManager.isFromLockFlow,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -120,6 +123,11 @@ class VaultUnlockViewModel @Inject constructor(
|
|||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
promptForBiometricsIfAvailable()
|
promptForBiometricsIfAvailable()
|
||||||
|
|
||||||
|
// only when navigating from vault to lock we should not display biometrics
|
||||||
|
// subsequent views of the lock screen should display biometrics if available
|
||||||
|
vaultLockManager.isFromLockFlow = false
|
||||||
|
mutableStateFlow.update { it.copy(isFromLockFlow = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
@ -209,7 +217,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockAccountClick(action: VaultUnlockAction.LockAccountClick) {
|
private fun handleLockAccountClick(action: VaultUnlockAction.LockAccountClick) {
|
||||||
vaultRepo.lockVault(userId = action.accountSummary.userId)
|
vaultRepo.lockVault(userId = action.accountSummary.userId, isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogoutAccountClick(action: VaultUnlockAction.LogoutAccountClick) {
|
private fun handleLogoutAccountClick(action: VaultUnlockAction.LogoutAccountClick) {
|
||||||
@ -425,7 +433,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun promptForBiometricsIfAvailable() {
|
private fun promptForBiometricsIfAvailable() {
|
||||||
val cipher = biometricsEncryptionManager.getOrCreateCipher(state.userId)
|
val cipher = biometricsEncryptionManager.getOrCreateCipher(state.userId)
|
||||||
if (state.showBiometricLogin && cipher != null) {
|
if (state.showBiometricLogin && cipher != null && !state.isFromLockFlow) {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultUnlockEvent.PromptForBiometrics(
|
VaultUnlockEvent.PromptForBiometrics(
|
||||||
cipher = cipher,
|
cipher = cipher,
|
||||||
@ -458,6 +466,7 @@ data class VaultUnlockState(
|
|||||||
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest? = null,
|
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest? = null,
|
||||||
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
||||||
private val hasMasterPassword: Boolean,
|
private val hasMasterPassword: Boolean,
|
||||||
|
val isFromLockFlow: Boolean,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -265,7 +265,7 @@ class AccountSecurityViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockNowClick() {
|
private fun handleLockNowClick() {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePushNotificationConfirm() {
|
private fun handlePushNotificationConfirm() {
|
||||||
|
|||||||
@ -258,7 +258,7 @@ class SendViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockClick() {
|
private fun handleLockClick() {
|
||||||
vaultRepo.lockVaultForCurrentUser()
|
vaultRepo.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRefreshClick() {
|
private fun handleRefreshClick() {
|
||||||
|
|||||||
@ -286,7 +286,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
|
|
||||||
//region VaultItemListing Handlers
|
//region VaultItemListing Handlers
|
||||||
private fun handleLockAccountClick(action: VaultItemListingsAction.LockAccountClick) {
|
private fun handleLockAccountClick(action: VaultItemListingsAction.LockAccountClick) {
|
||||||
vaultRepository.lockVault(userId = action.accountSummary.userId)
|
vaultRepository.lockVault(userId = action.accountSummary.userId, isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogoutAccountClick(action: VaultItemListingsAction.LogoutAccountClick) {
|
private fun handleLogoutAccountClick(action: VaultItemListingsAction.LogoutAccountClick) {
|
||||||
@ -1023,7 +1023,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockClick() {
|
private fun handleLockClick() {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSyncClick() {
|
private fun handleSyncClick() {
|
||||||
|
|||||||
@ -327,7 +327,7 @@ class VaultViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockAccountClick(action: VaultAction.LockAccountClick) {
|
private fun handleLockAccountClick(action: VaultAction.LockAccountClick) {
|
||||||
vaultRepository.lockVault(userId = action.accountSummary.userId)
|
vaultRepository.lockVault(userId = action.accountSummary.userId, isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogoutAccountClick(action: VaultAction.LogoutAccountClick) {
|
private fun handleLogoutAccountClick(action: VaultAction.LogoutAccountClick) {
|
||||||
@ -374,7 +374,7 @@ class VaultViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockClick() {
|
private fun handleLockClick() {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleExitConfirmationClick() {
|
private fun handleExitConfirmationClick() {
|
||||||
|
|||||||
@ -119,7 +119,7 @@ class VerificationCodeViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockClick() {
|
private fun handleLockClick() {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRefreshClick() {
|
private fun handleRefreshClick() {
|
||||||
|
|||||||
@ -88,7 +88,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
every { vaultRepository.isVaultUnlocked(USER_1_ID) } returns true
|
every { vaultRepository.isVaultUnlocked(USER_1_ID) } returns true
|
||||||
// But locked for user 2:
|
// But locked for user 2:
|
||||||
every { vaultRepository.isVaultUnlocked(USER_2_ID) } returns false
|
every { vaultRepository.isVaultUnlocked(USER_2_ID) } returns false
|
||||||
every { vaultRepository.lockVault(USER_2_ID) } returns Unit
|
every { vaultRepository.lockVault(USER_2_ID, isUserInitiated = false) } returns Unit
|
||||||
coEvery {
|
coEvery {
|
||||||
vaultRepository.unlockVaultWithDecryptedUserKey(
|
vaultRepository.unlockVaultWithDecryptedUserKey(
|
||||||
userId = USER_2_ID,
|
userId = USER_2_ID,
|
||||||
@ -147,7 +147,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
decryptedUserKey = USER_2_UNLOCK_KEY,
|
decryptedUserKey = USER_2_UNLOCK_KEY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.lockVault(USER_2_ID) }
|
verify { vaultRepository.lockVault(USER_2_ID, isUserInitiated = false) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_1_ID, USER_1_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_1_ID, USER_1_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.vaultUnlockDataStateFlow }
|
verify { vaultRepository.vaultUnlockDataStateFlow }
|
||||||
verify { vaultRepository.lockVault(USER_2_ID) }
|
verify { vaultRepository.lockVault(USER_2_ID, isUserInitiated = false) }
|
||||||
verify { vaultDiskSource.getCiphers(USER_2_ID) }
|
verify { vaultDiskSource.getCiphers(USER_2_ID) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
}
|
}
|
||||||
@ -198,7 +198,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
coEvery {
|
coEvery {
|
||||||
vaultRepository.unlockVaultWithDecryptedUserKey(USER_1_ID, USER_1_UNLOCK_KEY)
|
vaultRepository.unlockVaultWithDecryptedUserKey(USER_1_ID, USER_1_UNLOCK_KEY)
|
||||||
} returns VaultUnlockResult.Success
|
} returns VaultUnlockResult.Success
|
||||||
every { vaultRepository.lockVault(USER_1_ID) } returns Unit
|
every { vaultRepository.lockVault(USER_1_ID, isUserInitiated = false) } returns Unit
|
||||||
|
|
||||||
val sharedAccounts = authenticatorBridgeRepository.getSharedAccounts()
|
val sharedAccounts = authenticatorBridgeRepository.getSharedAccounts()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -216,7 +216,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
decryptedUserKey = USER_1_UNLOCK_KEY,
|
decryptedUserKey = USER_1_UNLOCK_KEY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.lockVault(USER_1_ID) }
|
verify { vaultRepository.lockVault(USER_1_ID, isUserInitiated = false) }
|
||||||
verify { vaultRepository.isVaultUnlocked(USER_2_ID) }
|
verify { vaultRepository.isVaultUnlocked(USER_2_ID) }
|
||||||
coVerify {
|
coVerify {
|
||||||
vaultRepository.unlockVaultWithDecryptedUserKey(
|
vaultRepository.unlockVaultWithDecryptedUserKey(
|
||||||
@ -225,7 +225,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.vaultUnlockDataStateFlow }
|
verify { vaultRepository.vaultUnlockDataStateFlow }
|
||||||
verify { vaultRepository.lockVault(USER_2_ID) }
|
verify { vaultRepository.lockVault(USER_2_ID, isUserInitiated = false) }
|
||||||
verify { vaultDiskSource.getCiphers(USER_2_ID) }
|
verify { vaultDiskSource.getCiphers(USER_2_ID) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
}
|
}
|
||||||
@ -260,7 +260,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.vaultUnlockDataStateFlow }
|
verify { vaultRepository.vaultUnlockDataStateFlow }
|
||||||
verify { vaultRepository.lockVault(USER_2_ID) }
|
verify { vaultRepository.lockVault(USER_2_ID, isUserInitiated = false) }
|
||||||
verify { vaultDiskSource.getCiphers(USER_2_ID) }
|
verify { vaultDiskSource.getCiphers(USER_2_ID) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
}
|
}
|
||||||
@ -307,7 +307,7 @@ class AuthenticatorBridgeRepositoryTest {
|
|||||||
decryptedUserKey = USER_2_UNLOCK_KEY,
|
decryptedUserKey = USER_2_UNLOCK_KEY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.lockVault(USER_2_ID) }
|
verify { vaultRepository.lockVault(USER_2_ID, isUserInitiated = false) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_1_ID, USER_1_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_1_ID, USER_1_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
coVerify { vaultSdkSource.decryptCipher(USER_2_ID, USER_2_ENCRYPTED_SDK_TOTP_CIPHER) }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -165,7 +165,7 @@ class VaultLockManagerTest {
|
|||||||
verifyUnlockedVault(userId = USER_ID)
|
verifyUnlockedVault(userId = USER_ID)
|
||||||
|
|
||||||
vaultLockManager.vaultStateEventFlow.test {
|
vaultLockManager.vaultStateEventFlow.test {
|
||||||
vaultLockManager.lockVault(userId = USER_ID)
|
vaultLockManager.lockVault(userId = USER_ID, isUserInitiated = false)
|
||||||
assertEquals(VaultStateEvent.Locked(userId = USER_ID), awaitItem())
|
assertEquals(VaultStateEvent.Locked(userId = USER_ID), awaitItem())
|
||||||
fakeAuthDiskSource.assertLastLockTimestamp(
|
fakeAuthDiskSource.assertLastLockTimestamp(
|
||||||
userId = USER_ID,
|
userId = USER_ID,
|
||||||
@ -178,10 +178,10 @@ class VaultLockManagerTest {
|
|||||||
fun `vaultStateEventFlow should not emit Locked event when vault state remains locked`() =
|
fun `vaultStateEventFlow should not emit Locked event when vault state remains locked`() =
|
||||||
runTest {
|
runTest {
|
||||||
// Ensure the vault is locked
|
// Ensure the vault is locked
|
||||||
vaultLockManager.lockVault(userId = USER_ID)
|
vaultLockManager.lockVault(userId = USER_ID, isUserInitiated = false)
|
||||||
|
|
||||||
vaultLockManager.vaultStateEventFlow.test {
|
vaultLockManager.vaultStateEventFlow.test {
|
||||||
vaultLockManager.lockVault(userId = USER_ID)
|
vaultLockManager.lockVault(userId = USER_ID, isUserInitiated = false)
|
||||||
expectNoEvents()
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ class VaultLockManagerTest {
|
|||||||
fun `vaultStateEventFlow should emit Unlocked event when vault state changes to unlocked`() =
|
fun `vaultStateEventFlow should emit Unlocked event when vault state changes to unlocked`() =
|
||||||
runTest {
|
runTest {
|
||||||
// Ensure the vault is locked
|
// Ensure the vault is locked
|
||||||
vaultLockManager.lockVault(userId = USER_ID)
|
vaultLockManager.lockVault(userId = USER_ID, isUserInitiated = false)
|
||||||
|
|
||||||
vaultLockManager.vaultStateEventFlow.test {
|
vaultLockManager.vaultStateEventFlow.test {
|
||||||
verifyUnlockedVault(userId = USER_ID)
|
verifyUnlockedVault(userId = USER_ID)
|
||||||
@ -786,7 +786,7 @@ class VaultLockManagerTest {
|
|||||||
vaultLockManager.vaultUnlockDataStateFlow.value,
|
vaultLockManager.vaultUnlockDataStateFlow.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
vaultLockManager.lockVault(userId = USER_ID)
|
vaultLockManager.lockVault(userId = USER_ID, isUserInitiated = false)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
emptyList<VaultUnlockData>(),
|
emptyList<VaultUnlockData>(),
|
||||||
@ -811,7 +811,7 @@ class VaultLockManagerTest {
|
|||||||
vaultLockManager.vaultUnlockDataStateFlow.value,
|
vaultLockManager.vaultUnlockDataStateFlow.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
vaultLockManager.lockVault(userId = USER_ID)
|
vaultLockManager.lockVault(userId = USER_ID, isUserInitiated = false)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
emptyList<VaultUnlockData>(),
|
emptyList<VaultUnlockData>(),
|
||||||
@ -837,7 +837,7 @@ class VaultLockManagerTest {
|
|||||||
vaultLockManager.vaultUnlockDataStateFlow.value,
|
vaultLockManager.vaultUnlockDataStateFlow.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
vaultLockManager.lockVaultForCurrentUser()
|
vaultLockManager.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
emptyList<VaultUnlockData>(),
|
emptyList<VaultUnlockData>(),
|
||||||
|
|||||||
@ -191,8 +191,8 @@ class VaultRepositoryTest {
|
|||||||
userId in mutableUnlockedUserIdsStateFlow.value
|
userId in mutableUnlockedUserIdsStateFlow.value
|
||||||
}
|
}
|
||||||
every { isVaultUnlocking(any()) } returns false
|
every { isVaultUnlocking(any()) } returns false
|
||||||
every { lockVault(any()) } just runs
|
every { lockVault(any(), any()) } just runs
|
||||||
every { lockVaultForCurrentUser() } just runs
|
every { lockVaultForCurrentUser(any()) } just runs
|
||||||
coEvery {
|
coEvery {
|
||||||
waitUntilUnlocked(any())
|
waitUntilUnlocked(any())
|
||||||
} coAnswers { call ->
|
} coAnswers { call ->
|
||||||
@ -1156,8 +1156,8 @@ class VaultRepositoryTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `lockVaultForCurrentUser should delegate to the VaultLockManager`() {
|
fun `lockVaultForCurrentUser should delegate to the VaultLockManager`() {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
verify { vaultLockManager.lockVaultForCurrentUser() }
|
verify { vaultLockManager.lockVaultForCurrentUser(isUserInitiated = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -35,7 +35,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||||||
every { logout(any()) } just runs
|
every { logout(any()) } just runs
|
||||||
}
|
}
|
||||||
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
||||||
every { lockVault(any()) } just runs
|
every { lockVault(any(), any()) } just runs
|
||||||
}
|
}
|
||||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.trySendAction(LandingAction.LockAccountClick(accountSummary))
|
viewModel.trySendAction(LandingAction.LockAccountClick(accountSummary))
|
||||||
|
|
||||||
verify { vaultRepository.lockVault(userId = accountUserId) }
|
verify { vaultRepository.lockVault(userId = accountUserId, isUserInitiated = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -51,7 +51,7 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||||||
every { logout(any()) } just runs
|
every { logout(any()) } just runs
|
||||||
}
|
}
|
||||||
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
||||||
every { lockVault(any()) } just runs
|
every { lockVault(any(), any()) } just runs
|
||||||
}
|
}
|
||||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.trySendAction(LoginAction.LockAccountClick(accountSummary))
|
viewModel.trySendAction(LoginAction.LockAccountClick(accountSummary))
|
||||||
|
|
||||||
verify { vaultRepository.lockVault(userId = accountUserId) }
|
verify { vaultRepository.lockVault(userId = accountUserId, isUserInitiated = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -646,4 +646,5 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
|||||||
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
||||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
|
isFromLockFlow = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
|||||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.model.UnlockType
|
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.model.UnlockType
|
||||||
@ -60,7 +61,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
every { switchAccount(any()) } returns SwitchAccountResult.AccountSwitched
|
every { switchAccount(any()) } returns SwitchAccountResult.AccountSwitched
|
||||||
}
|
}
|
||||||
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
private val vaultRepository: VaultRepository = mockk(relaxed = true) {
|
||||||
every { lockVault(any()) } just runs
|
every { lockVault(any(), any()) } just runs
|
||||||
}
|
}
|
||||||
private val encryptionManager: BiometricsEncryptionManager = mockk {
|
private val encryptionManager: BiometricsEncryptionManager = mockk {
|
||||||
every { getOrCreateCipher(USER_ID) } returns CIPHER
|
every { getOrCreateCipher(USER_ID) } returns CIPHER
|
||||||
@ -91,6 +92,10 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
every { getResumeSpecialCircumstance() } returns null
|
every { getResumeSpecialCircumstance() } returns null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val vaultLockManager: VaultLockManager = mockk(relaxed = true) {
|
||||||
|
every { isFromLockFlow } returns false
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on init with biometrics enabled and valid should emit PromptForBiometrics`() = runTest {
|
fun `on init with biometrics enabled and valid should emit PromptForBiometrics`() = runTest {
|
||||||
val initialState = DEFAULT_STATE.copy(
|
val initialState = DEFAULT_STATE.copy(
|
||||||
@ -502,6 +507,25 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
verify { encryptionManager.getOrCreateCipher(USER_ID) }
|
verify { encryptionManager.getOrCreateCipher(USER_ID) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
fun `on BiometricsUnlockClick should not emit PromptForBiometrics when isFromLockFlow is true`() =
|
||||||
|
runTest {
|
||||||
|
val initialState =
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
isBiometricsValid = true,
|
||||||
|
isBiometricEnabled = true,
|
||||||
|
isFromLockFlow = true,
|
||||||
|
)
|
||||||
|
val viewModel = createViewModel(
|
||||||
|
state = initialState,
|
||||||
|
)
|
||||||
|
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
expectNoEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `on BiometricsUnlockClick should disable isBiometricsValid and show message when cipher is null and integrity check returns false`() {
|
fun `on BiometricsUnlockClick should disable isBiometricsValid and show message when cipher is null and integrity check returns false`() {
|
||||||
@ -629,7 +653,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.trySendAction(VaultUnlockAction.LockAccountClick(accountSummary))
|
viewModel.trySendAction(VaultUnlockAction.LockAccountClick(accountSummary))
|
||||||
|
|
||||||
verify { vaultRepository.lockVault(userId = accountUserId) }
|
verify { vaultRepository.lockVault(userId = accountUserId, isUserInitiated = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -1301,12 +1325,14 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
state: VaultUnlockState? = null,
|
state: VaultUnlockState? = null,
|
||||||
unlockType: UnlockType = UnlockType.STANDARD,
|
unlockType: UnlockType = UnlockType.STANDARD,
|
||||||
environmentRepo: EnvironmentRepository = environmentRepository,
|
environmentRepo: EnvironmentRepository = environmentRepository,
|
||||||
vaultRepo: VaultRepository = vaultRepository,
|
vaultRepo: VaultRepository = vaultRepository,
|
||||||
biometricsEncryptionManager: BiometricsEncryptionManager = encryptionManager,
|
biometricsEncryptionManager: BiometricsEncryptionManager = encryptionManager,
|
||||||
|
lockManager: VaultLockManager = vaultLockManager,
|
||||||
): VaultUnlockViewModel = VaultUnlockViewModel(
|
): VaultUnlockViewModel = VaultUnlockViewModel(
|
||||||
savedStateHandle = SavedStateHandle().apply {
|
savedStateHandle = SavedStateHandle().apply {
|
||||||
set("state", state)
|
set("state", state)
|
||||||
@ -1319,6 +1345,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
fido2CredentialManager = fido2CredentialManager,
|
fido2CredentialManager = fido2CredentialManager,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
appResumeManager = appResumeManager,
|
appResumeManager = appResumeManager,
|
||||||
|
vaultLockManager = lockManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1351,6 +1378,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
|||||||
userId = USER_ID,
|
userId = USER_ID,
|
||||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
|
isFromLockFlow = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val TRUSTED_DEVICE: UserState.TrustedDevice = UserState.TrustedDevice(
|
private val TRUSTED_DEVICE: UserState.TrustedDevice = UserState.TrustedDevice(
|
||||||
|
|||||||
@ -383,10 +383,10 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on LockNowClick should call lockVaultForCurrentUser`() {
|
fun `on LockNowClick should call lockVaultForCurrentUser`() {
|
||||||
every { vaultRepository.lockVaultForCurrentUser() } just runs
|
every { vaultRepository.lockVaultForCurrentUser(any()) } just runs
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(AccountSecurityAction.LockNowClick)
|
viewModel.trySendAction(AccountSecurityAction.LockNowClick)
|
||||||
verify { vaultRepository.lockVaultForCurrentUser() }
|
verify { vaultRepository.lockVaultForCurrentUser(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -102,12 +102,12 @@ class SendViewModelTest : BaseViewModelTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `LockClick should lock the vault`() {
|
fun `LockClick should lock the vault`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
every { vaultRepo.lockVaultForCurrentUser() } just runs
|
every { vaultRepo.lockVaultForCurrentUser(any()) } just runs
|
||||||
|
|
||||||
viewModel.trySendAction(SendAction.LockClick)
|
viewModel.trySendAction(SendAction.LockClick)
|
||||||
|
|
||||||
verify {
|
verify {
|
||||||
vaultRepo.lockVaultForCurrentUser()
|
vaultRepo.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -143,7 +143,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
private val vaultRepository: VaultRepository = mockk {
|
private val vaultRepository: VaultRepository = mockk {
|
||||||
every { vaultFilterType } returns VaultFilterType.AllVaults
|
every { vaultFilterType } returns VaultFilterType.AllVaults
|
||||||
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
||||||
every { lockVault(any()) } just runs
|
every { lockVault(any(), any()) } just runs
|
||||||
every { sync(forced = any()) } just runs
|
every { sync(forced = any()) } just runs
|
||||||
coEvery {
|
coEvery {
|
||||||
getDecryptedFido2CredentialAutofillViews(any())
|
getDecryptedFido2CredentialAutofillViews(any())
|
||||||
@ -247,7 +247,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.trySendAction(VaultItemListingsAction.LockAccountClick(accountSummary))
|
viewModel.trySendAction(VaultItemListingsAction.LockAccountClick(accountSummary))
|
||||||
|
|
||||||
verify { vaultRepository.lockVault(userId = accountUserId) }
|
verify { vaultRepository.lockVault(userId = accountUserId, isUserInitiated = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -360,13 +360,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `LockClick should call lockVaultForCurrentUser`() {
|
fun `LockClick should call lockVaultForCurrentUser`() {
|
||||||
every { vaultRepository.lockVaultForCurrentUser() } just runs
|
every { vaultRepository.lockVaultForCurrentUser(any()) } just runs
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
viewModel.trySendAction(VaultItemListingsAction.LockClick)
|
viewModel.trySendAction(VaultItemListingsAction.LockClick)
|
||||||
|
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -131,8 +131,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||||||
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
||||||
every { sync(forced = any()) } just runs
|
every { sync(forced = any()) } just runs
|
||||||
every { syncIfNecessary() } just runs
|
every { syncIfNecessary() } just runs
|
||||||
every { lockVaultForCurrentUser() } just runs
|
every { lockVaultForCurrentUser(any()) } just runs
|
||||||
every { lockVault(any()) } just runs
|
every { lockVault(any(), any()) } just runs
|
||||||
}
|
}
|
||||||
|
|
||||||
private val organizationEventManager = mockk<OrganizationEventManager> {
|
private val organizationEventManager = mockk<OrganizationEventManager> {
|
||||||
@ -382,7 +382,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
viewModel.trySendAction(VaultAction.LockAccountClick(accountSummary))
|
viewModel.trySendAction(VaultAction.LockAccountClick(accountSummary))
|
||||||
|
|
||||||
verify { vaultRepository.lockVault(userId = accountUserId) }
|
verify { vaultRepository.lockVault(userId = accountUserId, isUserInitiated = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@ -520,7 +520,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(VaultAction.LockClick)
|
viewModel.trySendAction(VaultAction.LockClick)
|
||||||
verify {
|
verify {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -131,13 +131,13 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `LockClick should call lockVaultForCurrentUser`() {
|
fun `LockClick should call lockVaultForCurrentUser`() {
|
||||||
every { vaultRepository.lockVaultForCurrentUser() } just runs
|
every { vaultRepository.lockVaultForCurrentUser(any()) } just runs
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
|
||||||
viewModel.trySendAction(VerificationCodeAction.LockClick)
|
viewModel.trySendAction(VerificationCodeAction.LockClick)
|
||||||
|
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
vaultRepository.lockVaultForCurrentUser()
|
vaultRepository.lockVaultForCurrentUser(isUserInitiated = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user