mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 20:09:59 +03:00
BIT-1915: Migrate account tokens to encrypted shared preferences (#1039)
This commit is contained in:
parent
2e2b80470c
commit
7b7a1d15f5
18 changed files with 257 additions and 205 deletions
|
@ -51,12 +51,6 @@ class AuthDiskSourceImpl(
|
|||
),
|
||||
AuthDiskSource {
|
||||
|
||||
init {
|
||||
// We must migrate if necessary before any of the migrated values would be initialized
|
||||
// and accessed.
|
||||
legacySecureStorageMigrator.migrateIfNecessary()
|
||||
}
|
||||
|
||||
private val inMemoryPinProtectedUserKeys = mutableMapOf<String, String?>()
|
||||
private val mutableOrganizationsFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Profile.Organization>?>>()
|
||||
|
@ -64,6 +58,27 @@ class AuthDiskSourceImpl(
|
|||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Policy>?>>()
|
||||
private val mutableAccountTokensFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<AccountTokensJson?>>()
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override var userState: UserStateJson?
|
||||
get() = getString(key = STATE_KEY)?.let { json.decodeFromStringOrNull(it) }
|
||||
set(value) {
|
||||
putString(
|
||||
key = STATE_KEY,
|
||||
value = value?.let { json.encodeToString(value) },
|
||||
)
|
||||
mutableUserStateFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
init {
|
||||
// We must migrate if necessary before any of the migrated values would be initialized
|
||||
// and accessed.
|
||||
legacySecureStorageMigrator.migrateIfNecessary()
|
||||
|
||||
// We must migrate the tokens from being stored in the UserState(shared preferences) to
|
||||
// being stored separately in encrypted shared preferences.
|
||||
migrateAccountTokens()
|
||||
}
|
||||
|
||||
override val uniqueAppId: String
|
||||
get() = getString(key = UNIQUE_APP_ID_KEY) ?: generateAndStoreUniqueAppId()
|
||||
|
@ -86,22 +101,10 @@ class AuthDiskSourceImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override var userState: UserStateJson?
|
||||
get() = getString(key = STATE_KEY)?.let { json.decodeFromStringOrNull(it) }
|
||||
set(value) {
|
||||
putString(
|
||||
key = STATE_KEY,
|
||||
value = value?.let { json.encodeToString(value) },
|
||||
)
|
||||
mutableUserStateFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val userStateFlow: Flow<UserStateJson?>
|
||||
get() = mutableUserStateFlow
|
||||
.onSubscription { emit(userState) }
|
||||
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override fun clearData(userId: String) {
|
||||
storeLastActiveTimeMillis(userId = userId, lastActiveTimeMillis = null)
|
||||
storeInvalidUnlockAttempts(userId = userId, invalidUnlockAttempts = null)
|
||||
|
@ -357,4 +360,21 @@ class AuthDiskSourceImpl(
|
|||
mutableAccountTokensFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun migrateAccountTokens() {
|
||||
userState
|
||||
?.accounts
|
||||
.orEmpty()
|
||||
.values
|
||||
.forEach { accountJson ->
|
||||
@Suppress("DEPRECATION")
|
||||
accountJson.tokens?.let { storeAccountTokens(accountJson.profile.userId, it) }
|
||||
}
|
||||
userState = userState?.copy(
|
||||
accounts = userState
|
||||
?.accounts
|
||||
?.mapValues { (_, accountJson) -> accountJson.copy(tokens = null) }
|
||||
.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,17 +17,16 @@ data class AccountJson(
|
|||
@SerialName("profile")
|
||||
val profile: Profile,
|
||||
|
||||
@Deprecated(
|
||||
"This is always null except the first time after migrating from the Xamarin app. " +
|
||||
"Please use the accountTokens stored in the AuthDiskSource.",
|
||||
)
|
||||
@SerialName("tokens")
|
||||
val tokens: AccountTokensJson,
|
||||
val tokens: AccountTokensJson? = null,
|
||||
|
||||
@SerialName("settings")
|
||||
val settings: Settings,
|
||||
) {
|
||||
/**
|
||||
* Whether or not the account should be considered logged in.
|
||||
*/
|
||||
val isLoggedIn: Boolean get() = tokens.accessToken != null
|
||||
|
||||
/**
|
||||
* Represents a user's personal profile.
|
||||
*
|
||||
|
|
|
@ -16,4 +16,9 @@ data class AccountTokensJson(
|
|||
|
||||
@SerialName("refreshToken")
|
||||
val refreshToken: String?,
|
||||
)
|
||||
) {
|
||||
/**
|
||||
* Returns `true` if the user is logged in, `false otherwise.
|
||||
*/
|
||||
val isLoggedIn: Boolean get() = accessToken != null
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ 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.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
|
@ -72,25 +71,10 @@ class UserLogoutManagerImpl(
|
|||
}
|
||||
|
||||
override fun softLogout(userId: String) {
|
||||
val userState = authDiskSource.userState ?: return
|
||||
val updatedAccount = userState
|
||||
.accounts[userId]
|
||||
// Clear the tokens for the current user if present
|
||||
?.copy(
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = null,
|
||||
refreshToken = null,
|
||||
),
|
||||
)
|
||||
authDiskSource.userState = userState
|
||||
.copy(
|
||||
accounts = userState
|
||||
.accounts
|
||||
.toMutableMap()
|
||||
.apply {
|
||||
updatedAccount?.let { set(userId, updatedAccount) }
|
||||
},
|
||||
)
|
||||
authDiskSource.storeAccountTokens(
|
||||
userId = userId,
|
||||
accountTokens = null,
|
||||
)
|
||||
|
||||
// Save any data that will still need to be retained after otherwise clearing all dat
|
||||
val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.bitwarden.core.InitUserCryptoMethod
|
|||
import com.bitwarden.crypto.HashPurpose
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
|
@ -51,6 +52,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
||||
|
@ -75,6 +77,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
|||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
@ -83,6 +86,8 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -148,17 +153,22 @@ class AuthRepositoryImpl(
|
|||
|
||||
override val activeUserId: String? get() = authDiskSource.userState?.activeUserId
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val authStateFlow: StateFlow<AuthState> = authDiskSource
|
||||
.userStateFlow
|
||||
.map { userState ->
|
||||
userState
|
||||
?.activeAccount
|
||||
?.tokens
|
||||
?.accessToken
|
||||
?.let {
|
||||
AuthState.Authenticated(accessToken = it)
|
||||
.activeUserIdChangesFlow
|
||||
.flatMapLatest { activeUserId ->
|
||||
activeUserId
|
||||
?.let { userId ->
|
||||
authDiskSource
|
||||
.getAccountTokensFlow(userId)
|
||||
.map { accountTokens ->
|
||||
accountTokens
|
||||
?.accessToken
|
||||
?.let { AuthState.Authenticated(it) }
|
||||
?: AuthState.Unauthenticated
|
||||
}
|
||||
}
|
||||
?: AuthState.Unauthenticated
|
||||
?: flowOf(AuthState.Unauthenticated)
|
||||
}
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
|
@ -186,6 +196,7 @@ class AuthRepositoryImpl(
|
|||
hasPendingAccountAddition = hasPendingAccountAddition,
|
||||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isLoggedInProvider = ::isUserLoggedIn,
|
||||
)
|
||||
}
|
||||
.filter {
|
||||
|
@ -204,6 +215,7 @@ class AuthRepositoryImpl(
|
|||
hasPendingAccountAddition = mutableHasPendingAccountAdditionStateFlow.value,
|
||||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isLoggedInProvider = ::isUserLoggedIn,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -522,6 +534,13 @@ class AuthRepositoryImpl(
|
|||
// it is validated.
|
||||
}
|
||||
|
||||
authDiskSource.storeAccountTokens(
|
||||
userId = userStateJson.activeUserId,
|
||||
accountTokens = AccountTokensJson(
|
||||
accessToken = loginResponse.accessToken,
|
||||
refreshToken = loginResponse.refreshToken,
|
||||
),
|
||||
)
|
||||
authDiskSource.userState = userStateJson
|
||||
authDiskSource.storeUserKey(
|
||||
userId = userStateJson.activeUserId,
|
||||
|
@ -548,16 +567,20 @@ class AuthRepositoryImpl(
|
|||
|
||||
override fun refreshAccessTokenSynchronously(userId: String): Result<RefreshTokenResponseJson> {
|
||||
val refreshToken = authDiskSource
|
||||
.userState
|
||||
?.accounts
|
||||
?.get(userId)
|
||||
?.tokens
|
||||
.getAccountTokens(userId = userId)
|
||||
?.refreshToken
|
||||
?: return IllegalStateException("Must be logged in.").asFailure()
|
||||
return identityService
|
||||
.refreshTokenSynchronously(refreshToken)
|
||||
.onSuccess {
|
||||
// Update the existing UserState with updated token information
|
||||
authDiskSource.storeAccountTokens(
|
||||
userId = userId,
|
||||
accountTokens = AccountTokensJson(
|
||||
accessToken = it.accessToken,
|
||||
refreshToken = it.refreshToken,
|
||||
),
|
||||
)
|
||||
authDiskSource.userState = it.toUserStateJson(
|
||||
userId = userId,
|
||||
previousUserState = requireNotNull(authDiskSource.userState),
|
||||
|
@ -978,6 +1001,10 @@ class AuthRepositoryImpl(
|
|||
userId: String,
|
||||
): Boolean = authDiskSource.getUserBiometricUnlockKey(userId = userId) != null
|
||||
|
||||
private fun isUserLoggedIn(
|
||||
userId: String,
|
||||
): Boolean = authDiskSource.getAccountTokens(userId = userId)?.isLoggedIn == true
|
||||
|
||||
private fun getVaultUnlockType(
|
||||
userId: String,
|
||||
): VaultUnlockType =
|
||||
|
|
|
@ -61,10 +61,7 @@ val AuthDiskSource.userOrganizationsListFlow: Flow<List<UserOrganizations>>
|
|||
val AuthDiskSource.userSwitchingChangesFlow: Flow<UserSwitchingData>
|
||||
get() {
|
||||
var lastActiveUserId: String? = null
|
||||
return this
|
||||
.userStateFlow
|
||||
.map { it?.activeUserId }
|
||||
.distinctUntilChanged()
|
||||
return activeUserIdChangesFlow
|
||||
.map { activeUserId ->
|
||||
val previousActiveUserId = lastActiveUserId
|
||||
lastActiveUserId = activeUserId
|
||||
|
@ -74,3 +71,12 @@ val AuthDiskSource.userSwitchingChangesFlow: Flow<UserSwitchingData>
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [Flow] that emits every time the active user ID is changed.
|
||||
*/
|
||||
val AuthDiskSource.activeUserIdChangesFlow: Flow<String?>
|
||||
get() = this
|
||||
.userStateFlow
|
||||
.map { it?.activeUserId }
|
||||
.distinctUntilChanged()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
|
@ -19,9 +18,7 @@ fun GetTokenResponseJson.Success.toUserState(
|
|||
environmentUrlData: EnvironmentUrlDataJson,
|
||||
): UserStateJson {
|
||||
val accessToken = this.accessToken
|
||||
|
||||
@Suppress("UnsafeCallOnNullableType")
|
||||
val jwtTokenData = parseJwtTokenDataOrNull(jwtToken = accessToken)!!
|
||||
val jwtTokenData = requireNotNull(parseJwtTokenDataOrNull(jwtToken = accessToken))
|
||||
val userId = jwtTokenData.userId
|
||||
|
||||
val account = AccountJson(
|
||||
|
@ -45,10 +42,6 @@ fun GetTokenResponseJson.Success.toUserState(
|
|||
kdfParallelism = this.kdfParallelism,
|
||||
userDecryptionOptions = this.userDecryptionOptions,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = accessToken,
|
||||
refreshToken = this.refreshToken,
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = environmentUrlData,
|
||||
),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
|
||||
|
@ -27,10 +26,6 @@ fun RefreshTokenResponseJson.toUserStateJson(
|
|||
name = jwtTokenData.name,
|
||||
hasPremium = jwtTokenData.hasPremium,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = accessToken,
|
||||
refreshToken = this.refreshToken,
|
||||
),
|
||||
)
|
||||
|
||||
// Update the existing UserState.
|
||||
|
|
|
@ -43,12 +43,14 @@ fun UserStateJson.toUpdatedUserStateJson(
|
|||
/**
|
||||
* Converts the given [UserStateJson] to a [UserState] using the given [vaultState].
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun UserStateJson.toUserState(
|
||||
vaultState: List<VaultUnlockData>,
|
||||
userOrganizationsList: List<UserOrganizations>,
|
||||
hasPendingAccountAddition: Boolean,
|
||||
isBiometricsEnabledProvider: (userId: String) -> Boolean,
|
||||
vaultUnlockTypeProvider: (userId: String) -> VaultUnlockType,
|
||||
isLoggedInProvider: (userId: String) -> Boolean,
|
||||
): UserState =
|
||||
UserState(
|
||||
activeUserId = this.activeUserId,
|
||||
|
@ -71,7 +73,7 @@ fun UserStateJson.toUserState(
|
|||
.environmentUrlData
|
||||
.toEnvironmentUrlsOrDefault(),
|
||||
isPremium = accountJson.profile.hasPremium == true,
|
||||
isLoggedIn = accountJson.isLoggedIn,
|
||||
isLoggedIn = isLoggedInProvider(userId),
|
||||
isVaultUnlocked = vaultUnlocked && !needsPasswordReset,
|
||||
needsPasswordReset = needsPasswordReset,
|
||||
organizations = userOrganizationsList
|
||||
|
|
|
@ -100,8 +100,6 @@ class PushManagerImpl @Inject constructor(
|
|||
|
||||
private val activeUserId: String?
|
||||
get() = authDiskSource.userState?.activeUserId
|
||||
private val isLoggedIn: Boolean
|
||||
get() = authDiskSource.userState?.activeAccount?.isLoggedIn == true
|
||||
|
||||
init {
|
||||
authDiskSource
|
||||
|
@ -153,7 +151,7 @@ class PushManagerImpl @Inject constructor(
|
|||
-> {
|
||||
val payload: NotificationPayload.SyncCipherNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
if (!isLoggedIn || !payload.userMatchesNotification(userId)) return
|
||||
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
|
||||
mutableSyncCipherUpsertSharedFlow.tryEmit(
|
||||
SyncCipherUpsertData(
|
||||
cipherId = payload.id,
|
||||
|
@ -170,7 +168,7 @@ class PushManagerImpl @Inject constructor(
|
|||
-> {
|
||||
val payload: NotificationPayload.SyncCipherNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
if (!isLoggedIn || !payload.userMatchesNotification(userId)) return
|
||||
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
|
||||
mutableSyncCipherDeleteSharedFlow.tryEmit(
|
||||
SyncCipherDeleteData(payload.id),
|
||||
)
|
||||
|
@ -188,7 +186,7 @@ class PushManagerImpl @Inject constructor(
|
|||
-> {
|
||||
val payload: NotificationPayload.SyncFolderNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
if (!isLoggedIn || !payload.userMatchesNotification(userId)) return
|
||||
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
|
||||
mutableSyncFolderUpsertSharedFlow.tryEmit(
|
||||
SyncFolderUpsertData(
|
||||
folderId = payload.id,
|
||||
|
@ -201,7 +199,7 @@ class PushManagerImpl @Inject constructor(
|
|||
NotificationType.SYNC_FOLDER_DELETE -> {
|
||||
val payload: NotificationPayload.SyncFolderNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
if (!isLoggedIn || !payload.userMatchesNotification(userId)) return
|
||||
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
|
||||
|
||||
mutableSyncFolderDeleteSharedFlow.tryEmit(
|
||||
SyncFolderDeleteData(payload.id),
|
||||
|
@ -209,7 +207,7 @@ class PushManagerImpl @Inject constructor(
|
|||
}
|
||||
|
||||
NotificationType.SYNC_ORG_KEYS -> {
|
||||
if (!isLoggedIn) return
|
||||
if (!isLoggedIn(userId)) return
|
||||
mutableSyncOrgKeysSharedFlow.tryEmit(Unit)
|
||||
}
|
||||
|
||||
|
@ -218,7 +216,7 @@ class PushManagerImpl @Inject constructor(
|
|||
-> {
|
||||
val payload: NotificationPayload.SyncSendNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
if (!isLoggedIn || !payload.userMatchesNotification(userId)) return
|
||||
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
|
||||
mutableSyncSendUpsertSharedFlow.tryEmit(
|
||||
SyncSendUpsertData(
|
||||
sendId = payload.id,
|
||||
|
@ -231,7 +229,7 @@ class PushManagerImpl @Inject constructor(
|
|||
NotificationType.SYNC_SEND_DELETE -> {
|
||||
val payload: NotificationPayload.SyncSendNotification =
|
||||
json.decodeFromJsonElement(notification.payload)
|
||||
if (!isLoggedIn || !payload.userMatchesNotification(userId)) return
|
||||
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
|
||||
mutableSyncSendDeleteSharedFlow.tryEmit(
|
||||
SyncSendDeleteData(payload.id),
|
||||
)
|
||||
|
@ -242,8 +240,8 @@ class PushManagerImpl @Inject constructor(
|
|||
override fun registerPushTokenIfNecessary(token: String) {
|
||||
pushDiskSource.registeredPushToken = token
|
||||
|
||||
if (!isLoggedIn) return
|
||||
val userId = activeUserId ?: return
|
||||
if (!isLoggedIn(userId)) return
|
||||
ioScope.launch {
|
||||
registerPushTokenIfNecessaryInternal(
|
||||
userId = userId,
|
||||
|
@ -254,8 +252,8 @@ class PushManagerImpl @Inject constructor(
|
|||
|
||||
@Suppress("ReturnCount")
|
||||
override fun registerStoredPushTokenIfNecessary() {
|
||||
if (!isLoggedIn) return
|
||||
val userId = activeUserId ?: return
|
||||
if (!isLoggedIn(userId)) return
|
||||
|
||||
// If the last registered token is from less than a day before, skip this for now
|
||||
val lastRegistration = pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toInstant()
|
||||
|
@ -305,6 +303,10 @@ class PushManagerImpl @Inject constructor(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun isLoggedIn(
|
||||
userId: String,
|
||||
): Boolean = authDiskSource.getAccountTokens(userId)?.isLoggedIn == true
|
||||
}
|
||||
|
||||
private fun NotificationPayload.userMatchesNotification(userId: String?): Boolean {
|
||||
|
|
|
@ -20,6 +20,7 @@ import io.mockk.every
|
|||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
|
@ -48,7 +49,7 @@ class AuthDiskSourceTest {
|
|||
|
||||
@Test
|
||||
fun `initialization should kick off a legacy migration if necessary`() {
|
||||
every { legacySecureStorageMigrator.migrateIfNecessary() }
|
||||
verify(exactly = 1) { legacySecureStorageMigrator.migrateIfNecessary() }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -150,6 +151,9 @@ class AuthDiskSourceTest {
|
|||
assertNull(authDiskSource.userState)
|
||||
assertNull(awaitItem())
|
||||
|
||||
// Extra emission from migration logic
|
||||
assertNull(awaitItem())
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
authDiskSource.userState = USER_STATE
|
||||
assertEquals(USER_STATE, awaitItem())
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.junit.jupiter.api.extension.ExtendWith
|
|||
@ExtendWith(MainDispatcherExtension::class)
|
||||
class UserLogoutManagerTest {
|
||||
private val authDiskSource: AuthDiskSource = mockk {
|
||||
every { storeAccountTokens(userId = any(), accountTokens = null) } just runs
|
||||
every { userState = any() } just runs
|
||||
every { clearData(any()) } just runs
|
||||
}
|
||||
|
@ -121,11 +122,10 @@ class UserLogoutManagerTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softLogout should clear most data associated with the given user and remove token data from the account in the user state`() {
|
||||
fun `softLogout should clear most data associated with the given user and remove token data in the authDiskSource`() {
|
||||
val userId = USER_ID_1
|
||||
val vaultTimeoutInMinutes = 360
|
||||
val vaultTimeoutAction = VaultTimeoutAction.LOCK
|
||||
every { authDiskSource.userState } returns SINGLE_USER_STATE_1
|
||||
every {
|
||||
settingsDiskSource.getVaultTimeoutInMinutes(userId = userId)
|
||||
} returns vaultTimeoutInMinutes
|
||||
|
@ -135,22 +135,7 @@ class UserLogoutManagerTest {
|
|||
|
||||
userLogoutManager.softLogout(userId = userId)
|
||||
|
||||
val updatedAccount = ACCOUNT_1
|
||||
.copy(
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = null,
|
||||
refreshToken = null,
|
||||
),
|
||||
)
|
||||
val updatedUserState = SINGLE_USER_STATE_1
|
||||
.copy(
|
||||
accounts = SINGLE_USER_STATE_1
|
||||
.accounts
|
||||
.toMutableMap().apply {
|
||||
set(userId, updatedAccount)
|
||||
},
|
||||
)
|
||||
verify { authDiskSource.userState = updatedUserState }
|
||||
verify { authDiskSource.storeAccountTokens(userId = USER_ID_1, accountTokens = null) }
|
||||
assertDataCleared(userId = userId)
|
||||
|
||||
verify {
|
||||
|
|
|
@ -238,46 +238,49 @@ class AuthRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `authStateFlow should react to user state changes`() {
|
||||
assertEquals(
|
||||
AuthState.Unauthenticated,
|
||||
repository.authStateFlow.value,
|
||||
)
|
||||
fun `authStateFlow should react to user state changes and account token changes`() = runTest {
|
||||
repository.authStateFlow.test {
|
||||
assertEquals(AuthState.Unauthenticated, awaitItem())
|
||||
|
||||
// Update the active user updates the state
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
AuthState.Authenticated(ACCESS_TOKEN),
|
||||
repository.authStateFlow.value,
|
||||
)
|
||||
// Store the tokens, nothing happens yet since there is technically no active user yet
|
||||
fakeAuthDiskSource.storeAccountTokens(
|
||||
userId = USER_ID_1,
|
||||
accountTokens = ACCOUNT_TOKENS_1,
|
||||
)
|
||||
expectNoEvents()
|
||||
// Update the active user, we are now authenticated
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), awaitItem())
|
||||
|
||||
// Updating the non-active user does not update the state
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
assertEquals(
|
||||
AuthState.Authenticated(ACCESS_TOKEN),
|
||||
repository.authStateFlow.value,
|
||||
)
|
||||
// Adding a tokens for the non-active user does not update the state
|
||||
fakeAuthDiskSource.storeAccountTokens(
|
||||
userId = USER_ID_2,
|
||||
accountTokens = ACCOUNT_TOKENS_2,
|
||||
)
|
||||
expectNoEvents()
|
||||
// Adding a non-active user does not update the state
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
expectNoEvents()
|
||||
|
||||
// Clearing the tokens of the active state results in the Unauthenticated state
|
||||
val updatedAccount = ACCOUNT_1.copy(
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = null,
|
||||
refreshToken = null,
|
||||
),
|
||||
)
|
||||
val updatedState = MULTI_USER_STATE.copy(
|
||||
accounts = MULTI_USER_STATE
|
||||
.accounts
|
||||
.toMutableMap()
|
||||
.apply {
|
||||
set(USER_ID_1, updatedAccount)
|
||||
},
|
||||
)
|
||||
fakeAuthDiskSource.userState = updatedState
|
||||
assertEquals(
|
||||
AuthState.Unauthenticated,
|
||||
repository.authStateFlow.value,
|
||||
)
|
||||
// Changing the active users tokens causes an update
|
||||
val newAccessToken = "new_access_token"
|
||||
fakeAuthDiskSource.storeAccountTokens(
|
||||
userId = USER_ID_1,
|
||||
accountTokens = ACCOUNT_TOKENS_1.copy(accessToken = newAccessToken),
|
||||
)
|
||||
assertEquals(AuthState.Authenticated(newAccessToken), awaitItem())
|
||||
|
||||
// Change the active user causes an update
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE.copy(activeUserId = USER_ID_2)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN_2), awaitItem())
|
||||
|
||||
// Clearing the tokens of the active state results in the Unauthenticated state
|
||||
fakeAuthDiskSource.storeAccountTokens(
|
||||
userId = USER_ID_2,
|
||||
accountTokens = null,
|
||||
)
|
||||
assertEquals(AuthState.Unauthenticated, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -297,6 +300,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -319,6 +323,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isLoggedInProvider = { false },
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -332,6 +337,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isLoggedInProvider = { false },
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -357,6 +363,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -535,6 +542,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
)
|
||||
val finalUserState = SINGLE_USER_STATE_2.toUserState(
|
||||
vaultState = VAULT_UNLOCK_DATA,
|
||||
|
@ -542,6 +550,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
)
|
||||
val kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams()
|
||||
coEvery {
|
||||
|
@ -648,7 +657,10 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `refreshTokenSynchronously returns failure and logs out on failure`() = runTest {
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
fakeAuthDiskSource.storeAccountTokens(
|
||||
userId = USER_ID_1,
|
||||
accountTokens = ACCOUNT_TOKENS_1,
|
||||
)
|
||||
coEvery {
|
||||
identityService.refreshTokenSynchronously(REFRESH_TOKEN)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
@ -662,6 +674,10 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `refreshTokenSynchronously returns success and update user state on success`() = runTest {
|
||||
fakeAuthDiskSource.storeAccountTokens(
|
||||
userId = USER_ID_1,
|
||||
accountTokens = ACCOUNT_TOKENS_1,
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
coEvery {
|
||||
identityService.refreshTokenSynchronously(REFRESH_TOKEN)
|
||||
|
@ -2674,6 +2690,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
|
@ -2704,6 +2721,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
|
@ -2732,6 +2750,7 @@ class AuthRepositoryTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
)
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
assertEquals(
|
||||
|
@ -3043,6 +3062,7 @@ class AuthRepositoryTest {
|
|||
@Test
|
||||
fun `syncOrgKeysFlow emissions should refresh access token and sync`() {
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
fakeAuthDiskSource.storeAccountTokens(userId = USER_ID_1, accountTokens = ACCOUNT_TOKENS_1)
|
||||
coEvery {
|
||||
identityService.refreshTokenSynchronously(REFRESH_TOKEN)
|
||||
} returns REFRESH_TOKEN_RESPONSE_JSON.asSuccess()
|
||||
|
@ -3210,10 +3230,6 @@ class AuthRepositoryTest {
|
|||
kdfParallelism = 4,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN,
|
||||
refreshToken = REFRESH_TOKEN,
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = null,
|
||||
),
|
||||
|
@ -3235,10 +3251,6 @@ class AuthRepositoryTest {
|
|||
kdfParallelism = null,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN_2,
|
||||
refreshToken = "refreshToken",
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = null,
|
||||
),
|
||||
|
@ -3262,6 +3274,14 @@ class AuthRepositoryTest {
|
|||
USER_ID_2 to ACCOUNT_2,
|
||||
),
|
||||
)
|
||||
private val ACCOUNT_TOKENS_1: AccountTokensJson = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN,
|
||||
refreshToken = REFRESH_TOKEN,
|
||||
)
|
||||
private val ACCOUNT_TOKENS_2: AccountTokensJson = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN_2,
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
private val USER_ORGANIZATIONS = listOf(
|
||||
UserOrganizations(
|
||||
userId = USER_ID_1,
|
||||
|
|
|
@ -14,6 +14,7 @@ import io.mockk.every
|
|||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AuthDiskSourceExtensionsTest {
|
||||
|
@ -192,6 +193,24 @@ class AuthDiskSourceExtensionsTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `activeUserIdChangesFlow should emit changes when active user changes`() = runTest {
|
||||
authDiskSource.activeUserIdChangesFlow.test {
|
||||
assertNull(awaitItem())
|
||||
authDiskSource.userState = MOCK_USER_STATE
|
||||
assertEquals(MOCK_USER_ID, awaitItem())
|
||||
authDiskSource.userState = MOCK_USER_STATE.copy(
|
||||
accounts = mapOf(
|
||||
MOCK_USER_ID to MOCK_ACCOUNT,
|
||||
"mockId-2" to mockk(),
|
||||
),
|
||||
)
|
||||
expectNoEvents()
|
||||
authDiskSource.userState = null
|
||||
assertNull(awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val MOCK_USER_ID: String = "mockId-1"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
|
@ -55,7 +54,6 @@ class GetTokenResponseExtensionsTest {
|
|||
}
|
||||
|
||||
private const val ACCESS_TOKEN_1 = "accessToken1"
|
||||
private const val ACCESS_TOKEN_2 = "accessToken2"
|
||||
private const val USER_ID_1 = "2a135b23-e1fb-42c9-bec3-573857bc8181"
|
||||
private const val USER_ID_2 = "b9d32ec0-6497-4582-9798-b350f53bfa02"
|
||||
|
||||
|
@ -103,10 +101,6 @@ private val ACCOUNT_1 = AccountJson(
|
|||
kdfParallelism = 4,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN_1,
|
||||
refreshToken = "refreshToken",
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
),
|
||||
|
@ -128,10 +122,6 @@ private val ACCOUNT_2 = AccountJson(
|
|||
kdfParallelism = null,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN_2,
|
||||
refreshToken = "refreshToken",
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
|
@ -54,9 +53,7 @@ class RefreshTokenResponseJsonTest {
|
|||
}
|
||||
}
|
||||
|
||||
private const val ACCESS_TOKEN = "accessToken"
|
||||
private const val ACCESS_TOKEN_UPDATED = "updatedAccessToken"
|
||||
private const val REFRESH_TOKEN = "refreshToken"
|
||||
private const val REFRESH_TOKEN_UPDATED = "updatedRefreshToken"
|
||||
private const val USER_ID_1 = "2a135b23-e1fb-42c9-bec3-573857bc8181"
|
||||
private const val USER_ID_2 = "b9d32ec0-6497-4582-9798-b350f53bfa02"
|
||||
|
@ -95,10 +92,6 @@ private val ACCOUNT_1 = AccountJson(
|
|||
kdfParallelism = 4,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN,
|
||||
refreshToken = REFRESH_TOKEN,
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
),
|
||||
|
@ -112,10 +105,6 @@ private val ACCOUNT_1_UPDATED = ACCOUNT_1.copy(
|
|||
name = JWT_TOKEN_DATA.name,
|
||||
hasPremium = JWT_TOKEN_DATA.hasPremium,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = ACCESS_TOKEN_UPDATED,
|
||||
refreshToken = REFRESH_TOKEN_UPDATED,
|
||||
),
|
||||
)
|
||||
|
||||
private val ACCOUNT_2 = AccountJson(
|
||||
|
@ -135,10 +124,6 @@ private val ACCOUNT_2 = AccountJson(
|
|||
kdfParallelism = null,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountTokensJson(
|
||||
accessToken = "accessToken2",
|
||||
refreshToken = "refreshToken2",
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
),
|
||||
|
|
|
@ -163,6 +163,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasPendingAccountAddition = false,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isLoggedInProvider = { true },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -234,6 +235,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasPendingAccountAddition = true,
|
||||
isBiometricsEnabledProvider = { true },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isLoggedInProvider = { false },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.manager
|
|||
import app.cash.turbine.test
|
||||
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.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
|
@ -27,7 +28,6 @@ import com.x8bit.bitwarden.data.platform.util.asFailure
|
|||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
@ -78,10 +78,12 @@ class PushManagerTest {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns false
|
||||
}
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
val accountTokens = AccountTokensJson(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
authDiskSource.storeAccountTokens(userId, accountTokens)
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to mockk<AccountJson>()))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -159,10 +161,12 @@ 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))
|
||||
val accountTokens = AccountTokensJson(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
authDiskSource.storeAccountTokens(userId, accountTokens)
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to mockk<AccountJson>()))
|
||||
|
||||
pushManager.logoutFlow.test {
|
||||
pushManager.onMessageReceived(LOGOUT_NOTIFICATION_JSON)
|
||||
|
@ -178,10 +182,9 @@ class PushManagerTest {
|
|||
@BeforeEach
|
||||
fun setUp() {
|
||||
val userId = "any user ID"
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns false
|
||||
}
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
authDiskSource.storeAccountTokens(userId, null)
|
||||
authDiskSource.userState =
|
||||
UserStateJson(userId, mapOf(userId to mockk<AccountJson>()))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -242,9 +245,12 @@ class PushManagerTest {
|
|||
@BeforeEach
|
||||
fun setUp() {
|
||||
val userId = "078966a2-93c2-4618-ae2a-0a2394c88d37"
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
val accountTokens = AccountTokensJson(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
authDiskSource.storeAccountTokens(userId, accountTokens)
|
||||
val account = mockk<AccountJson>()
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
}
|
||||
|
||||
|
@ -410,9 +416,12 @@ class PushManagerTest {
|
|||
@BeforeEach
|
||||
fun setUp() {
|
||||
val userId = "bad user ID"
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
val accountTokens = AccountTokensJson(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
authDiskSource.storeAccountTokens(userId, accountTokens)
|
||||
val account = mockk<AccountJson>()
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
}
|
||||
|
||||
|
@ -550,9 +559,12 @@ class PushManagerTest {
|
|||
@BeforeEach
|
||||
fun setUp() {
|
||||
val userId = "any user ID"
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
val accountTokens = AccountTokensJson(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
authDiskSource.storeAccountTokens(userId, accountTokens)
|
||||
val account = mockk<AccountJson>()
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
}
|
||||
|
||||
|
@ -620,9 +632,8 @@ class PushManagerTest {
|
|||
@BeforeEach
|
||||
fun setUp() {
|
||||
val userId = "any user ID"
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns false
|
||||
}
|
||||
authDiskSource.storeAccountTokens(userId = userId, accountTokens = null)
|
||||
val account = mockk<AccountJson>()
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
}
|
||||
|
||||
|
@ -677,9 +688,12 @@ class PushManagerTest {
|
|||
@BeforeEach
|
||||
fun setUp() {
|
||||
pushDiskSource.storeCurrentPushToken(userId, existingToken)
|
||||
val account = mockk<AccountJson> {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
val accountTokens = AccountTokensJson(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
authDiskSource.storeAccountTokens(userId, accountTokens)
|
||||
val account = mockk<AccountJson>()
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to account))
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue