mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
PM-11642: Security stamp soft logout (#3859)
This commit is contained in:
parent
e3a4a7b153
commit
c017c1b10c
5 changed files with 61 additions and 21 deletions
|
@ -14,15 +14,15 @@ interface UserLogoutManager {
|
|||
val logoutEventFlow: SharedFlow<LogoutEvent>
|
||||
|
||||
/**
|
||||
* Completely logs out the given [userId], removing all data.
|
||||
* If [isExpired] is true, a toast will be displayed
|
||||
* letting the user know the session has expired.
|
||||
* Completely logs out the given [userId], removing all data. If [isExpired] is true, a toast
|
||||
* will be displayed letting the user know the session has expired.
|
||||
*/
|
||||
fun logout(userId: String, isExpired: Boolean = false)
|
||||
|
||||
/**
|
||||
* Partially logs out the given [userId]. All data for the given [userId] will be removed with
|
||||
* the exception of basic account data.
|
||||
* the exception of basic account data. If [isExpired] is true, a toast will be displayed
|
||||
* letting the user know the session has expired.
|
||||
*/
|
||||
fun softLogout(userId: String)
|
||||
fun softLogout(userId: String, isExpired: Boolean = false)
|
||||
}
|
||||
|
|
|
@ -64,7 +64,10 @@ class UserLogoutManagerImpl(
|
|||
mutableLogoutEventFlow.tryEmit(LogoutEvent(loggedOutUserId = userId))
|
||||
}
|
||||
|
||||
override fun softLogout(userId: String) {
|
||||
override fun softLogout(userId: String, isExpired: Boolean) {
|
||||
if (isExpired) {
|
||||
showToast(message = R.string.login_expired)
|
||||
}
|
||||
authDiskSource.storeAccountTokens(
|
||||
userId = userId,
|
||||
accountTokens = null,
|
||||
|
@ -74,7 +77,11 @@ class UserLogoutManagerImpl(
|
|||
val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
val vaultTimeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
|
||||
switchUserIfAvailable(currentUserId = userId, removeCurrentUserFromAccounts = false)
|
||||
switchUserIfAvailable(
|
||||
currentUserId = userId,
|
||||
removeCurrentUserFromAccounts = false,
|
||||
isExpired = isExpired,
|
||||
)
|
||||
|
||||
clearData(userId = userId)
|
||||
mutableLogoutEventFlow.tryEmit(LogoutEvent(loggedOutUserId = userId))
|
||||
|
|
|
@ -358,7 +358,7 @@ class VaultRepositoryImpl(
|
|||
// Log the user out if the stamps do not match
|
||||
localSecurityStamp?.let {
|
||||
if (serverSecurityStamp != localSecurityStamp) {
|
||||
userLogoutManager.logout(userId = userId, isExpired = true)
|
||||
userLogoutManager.softLogout(userId = userId, isExpired = true)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,21 +138,21 @@ class UserLogoutManagerTest {
|
|||
verify { authDiskSource.storeAccountTokens(userId = USER_ID_1, accountTokens = null) }
|
||||
assertDataCleared(userId = userId)
|
||||
|
||||
verify {
|
||||
verify(exactly = 1) {
|
||||
settingsDiskSource.storeVaultTimeoutInMinutes(
|
||||
userId = userId,
|
||||
vaultTimeoutInMinutes = vaultTimeoutInMinutes,
|
||||
)
|
||||
}
|
||||
verify {
|
||||
settingsDiskSource.storeVaultTimeoutAction(
|
||||
userId = userId,
|
||||
vaultTimeoutAction = vaultTimeoutAction,
|
||||
)
|
||||
Toast
|
||||
.makeText(context, R.string.account_switched_automatically, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softLogout should switch active user but keep previous user in accounts list`() {
|
||||
val userId = USER_ID_1
|
||||
|
@ -171,10 +171,44 @@ class UserLogoutManagerTest {
|
|||
|
||||
userLogoutManager.softLogout(userId = userId)
|
||||
|
||||
verify { authDiskSource.storeAccountTokens(userId = USER_ID_1, accountTokens = null) }
|
||||
verify {
|
||||
authDiskSource.userState =
|
||||
UserStateJson(activeUserId = USER_ID_2, accounts = MULTI_USER_STATE.accounts)
|
||||
verify(exactly = 1) {
|
||||
authDiskSource.storeAccountTokens(userId = USER_ID_1, accountTokens = null)
|
||||
authDiskSource.userState = UserStateJson(
|
||||
activeUserId = USER_ID_2,
|
||||
accounts = MULTI_USER_STATE.accounts,
|
||||
)
|
||||
Toast
|
||||
.makeText(context, R.string.account_switched_automatically, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softLogout with isExpired true 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
|
||||
|
||||
mockToast(R.string.login_expired)
|
||||
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
} returns vaultTimeoutInMinutes
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutAction(userId = userId)
|
||||
} returns vaultTimeoutAction
|
||||
|
||||
userLogoutManager.softLogout(userId = userId, isExpired = true)
|
||||
|
||||
verify(exactly = 1) {
|
||||
authDiskSource.storeAccountTokens(userId = USER_ID_1, accountTokens = null)
|
||||
authDiskSource.userState = UserStateJson(
|
||||
activeUserId = USER_ID_2,
|
||||
accounts = MULTI_USER_STATE.accounts,
|
||||
)
|
||||
Toast.makeText(context, R.string.login_expired, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ class VaultRepositoryTest {
|
|||
)
|
||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
private val userLogoutManager: UserLogoutManager = mockk {
|
||||
every { logout(any(), any()) } just runs
|
||||
every { softLogout(any(), any()) } just runs
|
||||
}
|
||||
private val fileManager: FileManager = mockk {
|
||||
coEvery { delete(*anyVararg()) } just runs
|
||||
|
@ -851,9 +851,8 @@ class VaultRepositoryTest {
|
|||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockSyncResponse = createMockSyncResponse(number = 1)
|
||||
coEvery { syncService.sync() } returns mockSyncResponse.copy(
|
||||
profile = createMockProfile(number = 1).copy(securityStamp = "newStamp"),
|
||||
)
|
||||
coEvery { syncService.sync() } returns mockSyncResponse
|
||||
.copy(profile = createMockProfile(number = 1).copy(securityStamp = "newStamp"))
|
||||
.asSuccess()
|
||||
|
||||
coEvery {
|
||||
|
@ -868,7 +867,7 @@ class VaultRepositoryTest {
|
|||
vaultRepository.sync()
|
||||
|
||||
coVerify {
|
||||
userLogoutManager.logout(userId = userId, isExpired = true)
|
||||
userLogoutManager.softLogout(userId = userId, isExpired = true)
|
||||
}
|
||||
|
||||
coVerify(exactly = 0) {
|
||||
|
|
Loading…
Reference in a new issue