mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
PM-28355: Clear pin data on hard-logout or security stamp (#6232)
This commit is contained in:
parent
28db795790
commit
2eb8ad4221
@ -159,11 +159,11 @@ class AuthDiskSourceImpl(
|
||||
storeAuthenticatorSyncUnlockKey(userId = userId, authenticatorSyncUnlockKey = null)
|
||||
storeShowImportLogins(userId = userId, showImportLogins = null)
|
||||
storeLastLockTimestamp(userId = userId, lastLockTimestamp = null)
|
||||
storeEncryptedPin(userId = userId, encryptedPin = null)
|
||||
storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null)
|
||||
storePinProtectedUserKeyEnvelope(userId = userId, pinProtectedUserKeyEnvelope = null)
|
||||
|
||||
// Certain values are never removed as required by the feature requirements:
|
||||
// * EncryptedPin
|
||||
// * PinProtectedUserKey
|
||||
// * PinProtectedUserKeyEnvelope
|
||||
// * DeviceKey
|
||||
// * PendingAuthRequest
|
||||
// * OnboardingStatus
|
||||
|
||||
@ -49,14 +49,14 @@ class UserLogoutManagerImpl(
|
||||
override fun logout(userId: String, reason: LogoutReason) {
|
||||
authDiskSource.userState ?: return
|
||||
Timber.d("logout reason=$reason")
|
||||
val isExpired = reason == LogoutReason.SecurityStamp
|
||||
if (isExpired) {
|
||||
val isSecurityStamp = reason == LogoutReason.SecurityStamp
|
||||
if (isSecurityStamp) {
|
||||
showToast(message = BitwardenString.login_expired)
|
||||
}
|
||||
|
||||
val ableToSwitchToNewAccount = switchUserIfAvailable(
|
||||
currentUserId = userId,
|
||||
isExpired = isExpired,
|
||||
isSecurityStamp = isSecurityStamp,
|
||||
removeCurrentUserFromAccounts = true,
|
||||
)
|
||||
|
||||
@ -73,19 +73,24 @@ class UserLogoutManagerImpl(
|
||||
|
||||
override fun softLogout(userId: String, reason: LogoutReason) {
|
||||
Timber.d("softLogout reason=$reason")
|
||||
val isExpired = reason == LogoutReason.SecurityStamp
|
||||
if (isExpired) {
|
||||
val isSecurityStamp = reason == LogoutReason.SecurityStamp
|
||||
if (isSecurityStamp) {
|
||||
showToast(message = BitwardenString.login_expired)
|
||||
}
|
||||
|
||||
// Save any data that will still need to be retained after otherwise clearing all dat
|
||||
// Save any data that will still need to be retained after otherwise clearing all data
|
||||
val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
val vaultTimeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
val encryptedPin = authDiskSource.getEncryptedPin(userId = userId)
|
||||
val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId)
|
||||
val pinProtectedUserKeyEnvelope = authDiskSource.getPinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
)
|
||||
|
||||
switchUserIfAvailable(
|
||||
currentUserId = userId,
|
||||
removeCurrentUserFromAccounts = false,
|
||||
isExpired = isExpired,
|
||||
isSecurityStamp = isSecurityStamp,
|
||||
)
|
||||
|
||||
clearData(userId = userId)
|
||||
@ -102,6 +107,14 @@ class UserLogoutManagerImpl(
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
}
|
||||
authDiskSource.apply {
|
||||
storeEncryptedPin(userId = userId, encryptedPin = encryptedPin)
|
||||
storePinProtectedUserKey(userId = userId, pinProtectedUserKey = pinProtectedUserKey)
|
||||
storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearData(userId: String) {
|
||||
@ -123,7 +136,7 @@ class UserLogoutManagerImpl(
|
||||
private fun switchUserIfAvailable(
|
||||
currentUserId: String,
|
||||
removeCurrentUserFromAccounts: Boolean,
|
||||
isExpired: Boolean = false,
|
||||
isSecurityStamp: Boolean,
|
||||
): Boolean {
|
||||
val currentUserState = authDiskSource.userState ?: return false
|
||||
|
||||
@ -135,7 +148,7 @@ class UserLogoutManagerImpl(
|
||||
|
||||
// Check if there is a new active user
|
||||
return if (updatedAccounts.isNotEmpty()) {
|
||||
if (currentUserId == currentUserState.activeUserId && !isExpired) {
|
||||
if (currentUserId == currentUserState.activeUserId && !isSecurityStamp) {
|
||||
showToast(message = BitwardenString.account_switched_automatically)
|
||||
}
|
||||
|
||||
|
||||
@ -310,7 +310,7 @@ class VaultSyncManagerImpl(
|
||||
localSecurityStamp?.let {
|
||||
if (serverSecurityStamp != localSecurityStamp) {
|
||||
// Ensure UserLogoutManager is available
|
||||
userLogoutManager.softLogout(
|
||||
userLogoutManager.logout(
|
||||
userId = userId,
|
||||
reason = LogoutReason.SecurityStamp,
|
||||
)
|
||||
|
||||
@ -267,15 +267,14 @@ class AuthDiskSourceTest {
|
||||
userId = userId,
|
||||
biometricsKey = "1234-9876-0192",
|
||||
)
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = "encryptedPin")
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
pinProtectedUserKey = "pinProtectedUserKey",
|
||||
)
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope",
|
||||
)
|
||||
authDiskSource.storeInvalidUnlockAttempts(
|
||||
userId = userId,
|
||||
@ -310,8 +309,6 @@ class AuthDiskSourceTest {
|
||||
refreshToken = "refreshToken",
|
||||
),
|
||||
)
|
||||
val encryptedPin = "encryptedPin"
|
||||
authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin)
|
||||
authDiskSource.storeMasterPasswordHash(userId = userId, passwordHash = "passwordHash")
|
||||
authDiskSource.storeAuthenticatorSyncUnlockKey(
|
||||
userId = userId,
|
||||
@ -333,12 +330,6 @@ class AuthDiskSourceTest {
|
||||
OnboardingStatus.AUTOFILL_SETUP,
|
||||
authDiskSource.getOnboardingStatus(userId = userId),
|
||||
)
|
||||
assertEquals(encryptedPin, authDiskSource.getEncryptedPin(userId = userId))
|
||||
assertEquals(pinProtectedUserKey, authDiskSource.getPinProtectedUserKey(userId = userId))
|
||||
assertEquals(
|
||||
pinProtectedUserKeyEnvelope,
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId),
|
||||
)
|
||||
|
||||
// These should be cleared
|
||||
assertNull(authDiskSource.getUserBiometricInitVector(userId = userId))
|
||||
@ -357,6 +348,9 @@ class AuthDiskSourceTest {
|
||||
assertNull(authDiskSource.getIsTdeLoginComplete(userId = userId))
|
||||
assertNull(authDiskSource.getAuthenticatorSyncUnlockKey(userId = userId))
|
||||
assertNull(authDiskSource.getShowImportLogins(userId = userId))
|
||||
assertNull(authDiskSource.getEncryptedPin(userId = userId))
|
||||
assertNull(authDiskSource.getPinProtectedUserKey(userId = userId))
|
||||
assertNull(authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -91,11 +91,14 @@ class FakeAuthDiskSource : AuthDiskSource {
|
||||
storedBiometricKeys.remove(userId)
|
||||
storedOrganizationKeys.remove(userId)
|
||||
storedPinProtectedUserKeyEnvelopes.remove(userId)
|
||||
storedEncryptedPins.remove(userId)
|
||||
storedPinProtectedUserKeys.remove(userId)
|
||||
|
||||
mutableShouldUseKeyConnectorFlowMap.remove(userId)
|
||||
mutableOrganizationsFlowMap.remove(userId)
|
||||
mutablePoliciesFlowMap.remove(userId)
|
||||
mutableAccountTokensFlowMap.remove(userId)
|
||||
mutablePinProtectedUserKeyEnvelopesFlowMap.remove(userId)
|
||||
}
|
||||
|
||||
private fun getMutablePinProtectedUserKeyEnvelopeFlow(
|
||||
|
||||
@ -120,6 +120,21 @@ class UserLogoutManagerTest {
|
||||
assertDataCleared(userId = userId)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout with security stamp reason should switch active user and display the login expired toast`() {
|
||||
val userId = USER_ID_1
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
|
||||
userLogoutManager.logout(userId = userId, reason = LogoutReason.SecurityStamp)
|
||||
|
||||
verify(exactly = 1) {
|
||||
authDiskSource.userState = SINGLE_USER_STATE_2
|
||||
toastManager.show(messageId = BitwardenString.login_expired)
|
||||
}
|
||||
assertDataCleared(userId = userId)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softLogout should clear most data associated with the given user and remove token data in the authDiskSource`() {
|
||||
@ -127,6 +142,8 @@ class UserLogoutManagerTest {
|
||||
val vaultTimeoutInMinutes = 360
|
||||
val vaultTimeoutAction = VaultTimeoutAction.LOGOUT
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
val encryptedPin = "encryptedPin"
|
||||
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
every {
|
||||
@ -135,10 +152,26 @@ class UserLogoutManagerTest {
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
} returns vaultTimeoutAction
|
||||
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
} returns pinProtectedUserKey
|
||||
} returns pinProtectedUserKeyEnvelope
|
||||
every {
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
} just runs
|
||||
every { authDiskSource.getPinProtectedUserKey(userId = userId) } returns pinProtectedUserKey
|
||||
every {
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
)
|
||||
} just runs
|
||||
every { authDiskSource.getEncryptedPin(userId = userId) } returns encryptedPin
|
||||
every {
|
||||
authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin)
|
||||
} just runs
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout)
|
||||
|
||||
@ -162,6 +195,15 @@ class UserLogoutManagerTest {
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
)
|
||||
authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin)
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,6 +213,8 @@ class UserLogoutManagerTest {
|
||||
val vaultTimeoutInMinutes = 360
|
||||
val vaultTimeoutAction = VaultTimeoutAction.LOGOUT
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope"
|
||||
val encryptedPin = "encryptedPin"
|
||||
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
every {
|
||||
@ -181,7 +225,24 @@ class UserLogoutManagerTest {
|
||||
} returns vaultTimeoutAction
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)
|
||||
} returns pinProtectedUserKey
|
||||
} returns pinProtectedUserKeyEnvelope
|
||||
every {
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
} just runs
|
||||
every { authDiskSource.getPinProtectedUserKey(userId = userId) } returns pinProtectedUserKey
|
||||
every {
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
)
|
||||
} just runs
|
||||
every { authDiskSource.getEncryptedPin(userId = userId) } returns encryptedPin
|
||||
every {
|
||||
authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin)
|
||||
} just runs
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout)
|
||||
|
||||
@ -199,44 +260,15 @@ class UserLogoutManagerTest {
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softLogout with security stamp reason should switch active user and keep previous user in accounts list but display the login expired toast`() {
|
||||
val userId = USER_ID_1
|
||||
val vaultTimeoutInMinutes = 360
|
||||
val vaultTimeoutAction = VaultTimeoutAction.LOGOUT
|
||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
every {
|
||||
authDiskSource.getPinProtectedUserKeyEnvelope(userId)
|
||||
} returns pinProtectedUserKey
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
} returns vaultTimeoutInMinutes
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
} returns vaultTimeoutAction
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.SecurityStamp)
|
||||
|
||||
verify(exactly = 1) {
|
||||
authDiskSource.userState = UserStateJson(
|
||||
activeUserId = USER_ID_2,
|
||||
accounts = MULTI_USER_STATE.accounts,
|
||||
)
|
||||
toastManager.show(messageId = BitwardenString.login_expired)
|
||||
settingsDiskSource.storeVaultTimeoutInMinutes(
|
||||
authDiskSource.storePinProtectedUserKeyEnvelope(
|
||||
userId = userId,
|
||||
vaultTimeoutInMinutes = vaultTimeoutInMinutes,
|
||||
pinProtectedUserKeyEnvelope = pinProtectedUserKeyEnvelope,
|
||||
)
|
||||
settingsDiskSource.storeVaultTimeoutAction(
|
||||
authDiskSource.storePinProtectedUserKey(
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
pinProtectedUserKey = pinProtectedUserKey,
|
||||
)
|
||||
authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -126,7 +126,7 @@ class VaultSyncManagerTest {
|
||||
}
|
||||
}
|
||||
private val userLogoutManager: UserLogoutManager = mockk {
|
||||
every { softLogout(any(), any()) } just runs
|
||||
every { logout(userId = any(), reason = LogoutReason.SecurityStamp) } just runs
|
||||
}
|
||||
private val userStateManager: UserStateManager = mockk {
|
||||
val blockSlot = slot<suspend () -> SyncVaultDataResult>()
|
||||
@ -786,7 +786,7 @@ class VaultSyncManagerTest {
|
||||
vaultSyncManager.sync()
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
userLogoutManager.softLogout(userId = userId, reason = LogoutReason.SecurityStamp)
|
||||
userLogoutManager.logout(userId = userId, reason = LogoutReason.SecurityStamp)
|
||||
}
|
||||
|
||||
coVerify(exactly = 0) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user