PM-11642: Security stamp soft logout (#3859)

This commit is contained in:
David Perez 2024-09-04 11:54:31 -05:00 committed by GitHub
parent e3a4a7b153
commit c017c1b10c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 61 additions and 21 deletions

View file

@ -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)
}

View file

@ -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))

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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) {