mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1719 Log a user out on a notificaiton (#1013)
This commit is contained in:
parent
cb20a6d690
commit
44b65e16b0
8 changed files with 85 additions and 15 deletions
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.manager
|
|||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
|
@ -35,9 +36,7 @@ class UserLogoutManagerImpl(
|
|||
val currentUserState = authDiskSource.userState ?: return
|
||||
|
||||
if (isExpired) {
|
||||
mainScope.launch {
|
||||
Toast.makeText(context, R.string.login_expired, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
showToast(message = R.string.login_expired)
|
||||
}
|
||||
|
||||
// Remove the active user from the accounts map
|
||||
|
@ -47,6 +46,10 @@ class UserLogoutManagerImpl(
|
|||
|
||||
// Check if there is a new active user
|
||||
if (updatedAccounts.isNotEmpty()) {
|
||||
if (userId == currentUserState.activeUserId && !isExpired) {
|
||||
showToast(message = R.string.account_switched_automatically)
|
||||
}
|
||||
|
||||
// If we logged out a non-active user, we want to leave the active user unchanged.
|
||||
// If we logged out the active user, we want to set the active user to the first one
|
||||
// in the list.
|
||||
|
@ -118,4 +121,8 @@ class UserLogoutManagerImpl(
|
|||
vaultDiskSource.deleteVaultData(userId = userId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showToast(@StringRes message: Int) {
|
||||
mainScope.launch { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,7 +263,7 @@ class AuthRepositoryImpl(
|
|||
|
||||
pushManager
|
||||
.logoutFlow
|
||||
.onEach { logout() }
|
||||
.onEach { logout(userId = it.userId) }
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
// When the policies for the user have been set, complete the login process.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||
|
@ -21,7 +22,7 @@ interface PushManager {
|
|||
/**
|
||||
* Flow that represents requests intended to log a user out.
|
||||
*/
|
||||
val logoutFlow: Flow<Unit>
|
||||
val logoutFlow: Flow<NotificationLogoutData>
|
||||
|
||||
/**
|
||||
* Flow that represents requests intended to trigger a passwordless request.
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenReque
|
|||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.BitwardenNotification
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationPayload
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationType
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
|
@ -50,7 +51,7 @@ class PushManagerImpl @Inject constructor(
|
|||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
private val mutableFullSyncSharedFlow = bufferedMutableSharedFlow<Unit>()
|
||||
private val mutableLogoutSharedFlow = bufferedMutableSharedFlow<Unit>()
|
||||
private val mutableLogoutSharedFlow = bufferedMutableSharedFlow<NotificationLogoutData>()
|
||||
private val mutablePasswordlessRequestSharedFlow =
|
||||
bufferedMutableSharedFlow<PasswordlessRequestData>()
|
||||
private val mutableSyncCipherDeleteSharedFlow =
|
||||
|
@ -70,7 +71,7 @@ class PushManagerImpl @Inject constructor(
|
|||
override val fullSyncFlow: SharedFlow<Unit>
|
||||
get() = mutableFullSyncSharedFlow.asSharedFlow()
|
||||
|
||||
override val logoutFlow: SharedFlow<Unit>
|
||||
override val logoutFlow: SharedFlow<NotificationLogoutData>
|
||||
get() = mutableLogoutSharedFlow.asSharedFlow()
|
||||
|
||||
override val passwordlessRequestFlow: SharedFlow<PasswordlessRequestData>
|
||||
|
@ -140,8 +141,11 @@ class PushManagerImpl @Inject constructor(
|
|||
}
|
||||
|
||||
NotificationType.LOG_OUT -> {
|
||||
if (!isLoggedIn) return
|
||||
mutableLogoutSharedFlow.tryEmit(Unit)
|
||||
val payload: NotificationPayload.UserNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
mutableLogoutSharedFlow.tryEmit(
|
||||
NotificationLogoutData(payload.userId),
|
||||
)
|
||||
}
|
||||
|
||||
NotificationType.SYNC_CIPHER_CREATE,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
/**
|
||||
* Required data for notification logout operation.
|
||||
*
|
||||
* @property userId The ID of the user being logged out.
|
||||
*/
|
||||
data class NotificationLogoutData(
|
||||
val userId: String,
|
||||
)
|
|
@ -1,5 +1,8 @@
|
|||
package com.x8bit.bitwarden.data.auth.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
|
@ -11,15 +14,21 @@ import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
|||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.ui.platform.base.MainDispatcherExtension
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@ExtendWith(MainDispatcherExtension::class)
|
||||
class UserLogoutManagerTest {
|
||||
private val authDiskSource: AuthDiskSource = mockk {
|
||||
every { userState = any() } just runs
|
||||
|
@ -42,10 +51,11 @@ class UserLogoutManagerTest {
|
|||
private val vaultDiskSource: VaultDiskSource = mockk {
|
||||
coEvery { deleteVaultData(any()) } just runs
|
||||
}
|
||||
private val context: Context = mockk()
|
||||
|
||||
private val userLogoutManager: UserLogoutManager =
|
||||
UserLogoutManagerImpl(
|
||||
context = mockk(),
|
||||
context = context,
|
||||
authDiskSource = authDiskSource,
|
||||
generatorDiskSource = generatorDiskSource,
|
||||
passwordHistoryDiskSource = passwordHistoryDiskSource,
|
||||
|
@ -55,6 +65,11 @@ class UserLogoutManagerTest {
|
|||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(Toast::class)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout for single account should clear data associated with the given user and null out the user state`() {
|
||||
|
@ -70,6 +85,18 @@ class UserLogoutManagerTest {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout for multiple accounts should clear data associated with the given user and change to the new active user`() {
|
||||
mockkStatic(Toast::class)
|
||||
|
||||
every {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
R.string.account_switched_automatically,
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
} just runs
|
||||
|
||||
val userId = USER_ID_1
|
||||
every { authDiskSource.userState } returns MULTI_USER_STATE
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
|||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
|
@ -201,7 +202,7 @@ class AuthRepositoryTest {
|
|||
every { logout(any(), any()) } just runs
|
||||
}
|
||||
|
||||
private val mutableLogoutFlow = bufferedMutableSharedFlow<Unit>()
|
||||
private val mutableLogoutFlow = bufferedMutableSharedFlow<NotificationLogoutData>()
|
||||
private val mutableSyncOrgKeysFlow = bufferedMutableSharedFlow<Unit>()
|
||||
private val mutableActivePolicyFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Policy>>()
|
||||
private val pushManager: PushManager = mockk {
|
||||
|
@ -4031,7 +4032,7 @@ class AuthRepositoryTest {
|
|||
val userId = USER_ID_1
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
|
||||
mutableLogoutFlow.tryEmit(Unit)
|
||||
mutableLogoutFlow.tryEmit(NotificationLogoutData(userId = userId))
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
userLogoutManager.logout(userId = userId)
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkMo
|
|||
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||
|
@ -156,6 +157,22 @@ class PushManagerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived logout should emit to logoutFlow`() = runTest {
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
|
||||
pushManager.logoutFlow.test {
|
||||
pushManager.onMessageReceived(LOGOUT_NOTIFICATION_JSON)
|
||||
assertEquals(
|
||||
NotificationLogoutData(userId = "078966a2-93c2-4618-ae2a-0a2394c88d37"),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class LoggedOutUserState {
|
||||
@BeforeEach
|
||||
|
@ -168,10 +185,13 @@ class PushManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived logout does nothing`() = runTest {
|
||||
fun `onMessageReceived logout emits to logoutFlow`() = runTest {
|
||||
pushManager.logoutFlow.test {
|
||||
pushManager.onMessageReceived(LOGOUT_NOTIFICATION_JSON)
|
||||
expectNoEvents()
|
||||
assertEquals(
|
||||
NotificationLogoutData(userId = "078966a2-93c2-4618-ae2a-0a2394c88d37"),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -541,7 +561,7 @@ class PushManagerTest {
|
|||
pushManager.logoutFlow.test {
|
||||
pushManager.onMessageReceived(LOGOUT_NOTIFICATION_JSON)
|
||||
assertEquals(
|
||||
Unit,
|
||||
NotificationLogoutData(userId = "078966a2-93c2-4618-ae2a-0a2394c88d37"),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue