mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 03:49:36 +03:00
BIT-1547: Hook up remaining push notification sync handling (#848)
This commit is contained in:
parent
d2ffd7bf01
commit
8489bd1476
9 changed files with 1163 additions and 95 deletions
|
@ -54,6 +54,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsList
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsListFlow
|
import com.x8bit.bitwarden.data.auth.repository.util.userOrganizationsListFlow
|
||||||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
|
@ -71,7 +72,9 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -95,6 +98,7 @@ class AuthRepositoryImpl(
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
private val userLogoutManager: UserLogoutManager,
|
private val userLogoutManager: UserLogoutManager,
|
||||||
|
private val pushManager: PushManager,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() },
|
private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() },
|
||||||
) : AuthRepository {
|
) : AuthRepository {
|
||||||
|
@ -117,7 +121,9 @@ class AuthRepositoryImpl(
|
||||||
* use of [Dispatchers.Unconfined] allows for this to happen synchronously whenever any of
|
* use of [Dispatchers.Unconfined] allows for this to happen synchronously whenever any of
|
||||||
* these flows changes.
|
* these flows changes.
|
||||||
*/
|
*/
|
||||||
private val collectionScope = CoroutineScope(dispatcherManager.unconfined)
|
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||||
|
|
||||||
|
private val ioScope = CoroutineScope(dispatcherManager.io)
|
||||||
|
|
||||||
override var twoFactorResponse: TwoFactorRequired? = null
|
override var twoFactorResponse: TwoFactorRequired? = null
|
||||||
|
|
||||||
|
@ -136,7 +142,7 @@ class AuthRepositoryImpl(
|
||||||
?: AuthState.Unauthenticated
|
?: AuthState.Unauthenticated
|
||||||
}
|
}
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = collectionScope,
|
scope = unconfinedScope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
initialValue = AuthState.Uninitialized,
|
initialValue = AuthState.Uninitialized,
|
||||||
)
|
)
|
||||||
|
@ -169,7 +175,7 @@ class AuthRepositoryImpl(
|
||||||
!mutableHasPendingAccountDeletionStateFlow.value
|
!mutableHasPendingAccountDeletionStateFlow.value
|
||||||
}
|
}
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = collectionScope,
|
scope = unconfinedScope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
initialValue = authDiskSource
|
initialValue = authDiskSource
|
||||||
.userState
|
.userState
|
||||||
|
@ -199,6 +205,22 @@ class AuthRepositoryImpl(
|
||||||
override var hasPendingAccountAddition: Boolean
|
override var hasPendingAccountAddition: Boolean
|
||||||
by mutableHasPendingAccountAdditionStateFlow::value
|
by mutableHasPendingAccountAdditionStateFlow::value
|
||||||
|
|
||||||
|
init {
|
||||||
|
pushManager
|
||||||
|
.syncOrgKeysFlow
|
||||||
|
.onEach {
|
||||||
|
val userId = activeUserId ?: return@onEach
|
||||||
|
refreshAccessTokenSynchronously(userId)
|
||||||
|
vaultRepository.sync()
|
||||||
|
}
|
||||||
|
.launchIn(ioScope)
|
||||||
|
|
||||||
|
pushManager
|
||||||
|
.logoutFlow
|
||||||
|
.onEach { logout() }
|
||||||
|
.launchIn(unconfinedScope)
|
||||||
|
}
|
||||||
|
|
||||||
override fun clearPendingAccountDeletion() {
|
override fun clearPendingAccountDeletion() {
|
||||||
mutableHasPendingAccountDeletionStateFlow.value = false
|
mutableHasPendingAccountDeletionStateFlow.value = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
|
@ -49,6 +50,7 @@ object AuthRepositoryModule {
|
||||||
settingsRepository: SettingsRepository,
|
settingsRepository: SettingsRepository,
|
||||||
vaultRepository: VaultRepository,
|
vaultRepository: VaultRepository,
|
||||||
userLogoutManager: UserLogoutManager,
|
userLogoutManager: UserLogoutManager,
|
||||||
|
pushManager: PushManager,
|
||||||
): AuthRepository = AuthRepositoryImpl(
|
): AuthRepository = AuthRepositoryImpl(
|
||||||
accountsService = accountsService,
|
accountsService = accountsService,
|
||||||
authRequestsService = authRequestsService,
|
authRequestsService = authRequestsService,
|
||||||
|
@ -65,5 +67,6 @@ object AuthRepositoryModule {
|
||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
vaultRepository = vaultRepository,
|
vaultRepository = vaultRepository,
|
||||||
userLogoutManager = userLogoutManager,
|
userLogoutManager = userLogoutManager,
|
||||||
|
pushManager = pushManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,8 @@ class PushManagerImpl @Inject constructor(
|
||||||
SyncCipherUpsertData(
|
SyncCipherUpsertData(
|
||||||
cipherId = payload.id,
|
cipherId = payload.id,
|
||||||
revisionDate = payload.revisionDate,
|
revisionDate = payload.revisionDate,
|
||||||
|
organizationId = payload.organizationId,
|
||||||
|
collectionIds = payload.collectionIds,
|
||||||
isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE,
|
isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@ sealed class NotificationPayload {
|
||||||
@SerialName("id") val id: String,
|
@SerialName("id") val id: String,
|
||||||
@SerialName("userId") override val userId: String?,
|
@SerialName("userId") override val userId: String?,
|
||||||
@SerialName("organizationId") val organizationId: String?,
|
@SerialName("organizationId") val organizationId: String?,
|
||||||
@SerialName("collectionIds") val collectionIds: List<String>,
|
@SerialName("collectionIds") val collectionIds: List<String>?,
|
||||||
@Contextual
|
@Contextual
|
||||||
@SerialName("revisionDate") val revisionDate: ZonedDateTime,
|
@SerialName("revisionDate") val revisionDate: ZonedDateTime,
|
||||||
) : NotificationPayload()
|
) : NotificationPayload()
|
||||||
|
|
|
@ -13,5 +13,7 @@ import java.time.ZonedDateTime
|
||||||
data class SyncCipherUpsertData(
|
data class SyncCipherUpsertData(
|
||||||
val cipherId: String,
|
val cipherId: String,
|
||||||
val revisionDate: ZonedDateTime,
|
val revisionDate: ZonedDateTime,
|
||||||
|
val organizationId: String?,
|
||||||
|
val collectionIds: List<String>?,
|
||||||
val isUpdate: Boolean,
|
val isUpdate: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,8 +19,11 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherDeleteData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendDeleteData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendUpsertData
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendUpsertData
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
@ -91,11 +94,13 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
@ -236,16 +241,36 @@ class VaultRepositoryImpl(
|
||||||
}
|
}
|
||||||
.launchIn(unconfinedScope)
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
|
pushManager
|
||||||
|
.fullSyncFlow
|
||||||
|
.onEach { syncIfNecessary() }
|
||||||
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
|
pushManager
|
||||||
|
.syncCipherDeleteFlow
|
||||||
|
.onEach(::deleteCipher)
|
||||||
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
pushManager
|
pushManager
|
||||||
.syncCipherUpsertFlow
|
.syncCipherUpsertFlow
|
||||||
.onEach(::syncCipherIfNecessary)
|
.onEach(::syncCipherIfNecessary)
|
||||||
.launchIn(ioScope)
|
.launchIn(ioScope)
|
||||||
|
|
||||||
|
pushManager
|
||||||
|
.syncSendDeleteFlow
|
||||||
|
.onEach(::deleteSend)
|
||||||
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
pushManager
|
pushManager
|
||||||
.syncSendUpsertFlow
|
.syncSendUpsertFlow
|
||||||
.onEach(::syncSendIfNecessary)
|
.onEach(::syncSendIfNecessary)
|
||||||
.launchIn(ioScope)
|
.launchIn(ioScope)
|
||||||
|
|
||||||
|
pushManager
|
||||||
|
.syncFolderDeleteFlow
|
||||||
|
.onEach(::deleteFolder)
|
||||||
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
pushManager
|
pushManager
|
||||||
.syncFolderUpsertFlow
|
.syncFolderUpsertFlow
|
||||||
.onEach(::syncFolderIfNecessary)
|
.onEach(::syncFolderIfNecessary)
|
||||||
|
@ -1231,19 +1256,76 @@ class VaultRepositoryImpl(
|
||||||
.onEach { mutableSendDataStateFlow.value = it }
|
.onEach { mutableSendDataStateFlow.value = it }
|
||||||
|
|
||||||
//region Push notification helpers
|
//region Push notification helpers
|
||||||
|
/**
|
||||||
|
* Deletes the cipher specified by [syncCipherDeleteData] from disk.
|
||||||
|
*/
|
||||||
|
private suspend fun deleteCipher(syncCipherDeleteData: SyncCipherDeleteData) {
|
||||||
|
val userId = activeUserId ?: return
|
||||||
|
|
||||||
|
val cipherId = syncCipherDeleteData.cipherId
|
||||||
|
vaultDiskSource.deleteCipher(
|
||||||
|
userId = userId,
|
||||||
|
cipherId = cipherId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs an individual cipher contained in [syncCipherUpsertData] to disk if certain criteria
|
* Syncs an individual cipher contained in [syncCipherUpsertData] to disk if certain criteria
|
||||||
* are met. If the resource cannot be found cloud-side, and it was updated, delete it from disk
|
* are met. If the resource cannot be found cloud-side, and it was updated, delete it from disk
|
||||||
* for now.
|
* for now.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("ReturnCount")
|
||||||
private suspend fun syncCipherIfNecessary(syncCipherUpsertData: SyncCipherUpsertData) {
|
private suspend fun syncCipherIfNecessary(syncCipherUpsertData: SyncCipherUpsertData) {
|
||||||
val userId = activeUserId ?: return
|
val userId = activeUserId ?: return
|
||||||
|
|
||||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
|
||||||
// handled as part of BIT-1547.
|
|
||||||
|
|
||||||
val cipherId = syncCipherUpsertData.cipherId
|
val cipherId = syncCipherUpsertData.cipherId
|
||||||
|
val organizationId = syncCipherUpsertData.organizationId
|
||||||
|
val collectionIds = syncCipherUpsertData.collectionIds
|
||||||
|
val revisionDate = syncCipherUpsertData.revisionDate
|
||||||
val isUpdate = syncCipherUpsertData.isUpdate
|
val isUpdate = syncCipherUpsertData.isUpdate
|
||||||
|
|
||||||
|
val localCipher = ciphersStateFlow
|
||||||
|
.mapNotNull { it.data }
|
||||||
|
.first()
|
||||||
|
.find { it.id == cipherId }
|
||||||
|
|
||||||
|
// Return if local cipher is more recent
|
||||||
|
if (localCipher != null &&
|
||||||
|
localCipher.revisionDate.epochSecond > revisionDate.toEpochSecond()
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldUpdate: Boolean
|
||||||
|
val shouldCheckCollections: Boolean
|
||||||
|
|
||||||
|
when {
|
||||||
|
isUpdate -> {
|
||||||
|
shouldUpdate = localCipher != null
|
||||||
|
shouldCheckCollections = true
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionIds == null || organizationId == null -> {
|
||||||
|
shouldUpdate = localCipher == null
|
||||||
|
shouldCheckCollections = false
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
shouldUpdate = false
|
||||||
|
shouldCheckCollections = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldUpdate && shouldCheckCollections && organizationId != null) {
|
||||||
|
// Check if there are any collections in common
|
||||||
|
shouldUpdate = collectionsStateFlow
|
||||||
|
.mapNotNull { it.data }
|
||||||
|
.first()
|
||||||
|
.mapNotNull { it.id }
|
||||||
|
.any { collectionIds?.contains(it) == true } == true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldUpdate) return
|
||||||
|
|
||||||
ciphersService
|
ciphersService
|
||||||
.getCipher(cipherId)
|
.getCipher(cipherId)
|
||||||
.fold(
|
.fold(
|
||||||
|
@ -1259,6 +1341,19 @@ class VaultRepositoryImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the send specified by [syncSendDeleteData] from disk.
|
||||||
|
*/
|
||||||
|
private suspend fun deleteSend(syncSendDeleteData: SyncSendDeleteData) {
|
||||||
|
val userId = activeUserId ?: return
|
||||||
|
|
||||||
|
val sendId = syncSendDeleteData.sendId
|
||||||
|
vaultDiskSource.deleteSend(
|
||||||
|
userId = userId,
|
||||||
|
sendId = sendId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs an individual send contained in [syncSendUpsertData] to disk if certain criteria are
|
* Syncs an individual send contained in [syncSendUpsertData] to disk if certain criteria are
|
||||||
* met. If the resource cannot be found cloud-side, and it was updated, delete it from disk for
|
* met. If the resource cannot be found cloud-side, and it was updated, delete it from disk for
|
||||||
|
@ -1266,12 +1361,22 @@ class VaultRepositoryImpl(
|
||||||
*/
|
*/
|
||||||
private suspend fun syncSendIfNecessary(syncSendUpsertData: SyncSendUpsertData) {
|
private suspend fun syncSendIfNecessary(syncSendUpsertData: SyncSendUpsertData) {
|
||||||
val userId = activeUserId ?: return
|
val userId = activeUserId ?: return
|
||||||
|
|
||||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
|
||||||
// handled as part of BIT-1547.
|
|
||||||
|
|
||||||
val sendId = syncSendUpsertData.sendId
|
val sendId = syncSendUpsertData.sendId
|
||||||
val isUpdate = syncSendUpsertData.isUpdate
|
val isUpdate = syncSendUpsertData.isUpdate
|
||||||
|
val revisionDate = syncSendUpsertData.revisionDate
|
||||||
|
|
||||||
|
val localSend = sendDataStateFlow
|
||||||
|
.mapNotNull { it.data }
|
||||||
|
.first()
|
||||||
|
.sendViewList
|
||||||
|
.find { it.id == sendId }
|
||||||
|
val isValidCreate = !isUpdate && localSend == null
|
||||||
|
val isValidUpdate = isUpdate &&
|
||||||
|
localSend != null &&
|
||||||
|
localSend.revisionDate.epochSecond < revisionDate.toEpochSecond()
|
||||||
|
|
||||||
|
if (!isValidCreate && !isValidUpdate) return
|
||||||
|
|
||||||
sendsService
|
sendsService
|
||||||
.getSend(sendId)
|
.getSend(sendId)
|
||||||
.fold(
|
.fold(
|
||||||
|
@ -1287,17 +1392,44 @@ class VaultRepositoryImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the folder specified by [syncFolderDeleteData] from disk.
|
||||||
|
*/
|
||||||
|
private suspend fun deleteFolder(syncFolderDeleteData: SyncFolderDeleteData) {
|
||||||
|
val userId = activeUserId ?: return
|
||||||
|
|
||||||
|
val folderId = syncFolderDeleteData.folderId
|
||||||
|
clearFolderIdFromCiphers(
|
||||||
|
folderId = folderId,
|
||||||
|
userId = userId,
|
||||||
|
)
|
||||||
|
vaultDiskSource.deleteFolder(
|
||||||
|
folderId = folderId,
|
||||||
|
userId = userId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs an individual folder contained in [syncFolderUpsertData] to disk if certain criteria
|
* Syncs an individual folder contained in [syncFolderUpsertData] to disk if certain criteria
|
||||||
* are met.
|
* are met.
|
||||||
*/
|
*/
|
||||||
private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) {
|
private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) {
|
||||||
val userId = activeUserId ?: return
|
val userId = activeUserId ?: return
|
||||||
|
|
||||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
|
||||||
// handled as part of BIT-1547.
|
|
||||||
|
|
||||||
val folderId = syncFolderUpsertData.folderId
|
val folderId = syncFolderUpsertData.folderId
|
||||||
|
val isUpdate = syncFolderUpsertData.isUpdate
|
||||||
|
val revisionDate = syncFolderUpsertData.revisionDate
|
||||||
|
|
||||||
|
val localFolder = foldersStateFlow
|
||||||
|
.mapNotNull { it.data }
|
||||||
|
.first()
|
||||||
|
.find { it.id == folderId }
|
||||||
|
val isValidCreate = !isUpdate && localFolder == null
|
||||||
|
val isValidUpdate = isUpdate &&
|
||||||
|
localFolder != null &&
|
||||||
|
localFolder.revisionDate.epochSecond < revisionDate.toEpochSecond()
|
||||||
|
|
||||||
|
if (!isValidCreate && !isValidUpdate) return
|
||||||
|
|
||||||
folderService
|
folderService
|
||||||
.getFolder(folderId)
|
.getFolder(folderId)
|
||||||
.onSuccess { vaultDiskSource.saveFolder(userId, it) }
|
.onSuccess { vaultDiskSource.saveFolder(userId, it) }
|
||||||
|
|
|
@ -64,10 +64,12 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.toUserStateJson
|
import com.x8bit.bitwarden.data.auth.repository.util.toUserStateJson
|
||||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
|
||||||
|
@ -172,6 +174,13 @@ class AuthRepositoryTest {
|
||||||
every { logout(any()) } just runs
|
every { logout(any()) } just runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val mutableLogoutFlow = bufferedMutableSharedFlow<Unit>()
|
||||||
|
private val mutableSyncOrgKeysFlow = bufferedMutableSharedFlow<Unit>()
|
||||||
|
private val pushManager: PushManager = mockk {
|
||||||
|
every { logoutFlow } returns mutableLogoutFlow
|
||||||
|
every { syncOrgKeysFlow } returns mutableSyncOrgKeysFlow
|
||||||
|
}
|
||||||
|
|
||||||
private var elapsedRealtimeMillis = 123456789L
|
private var elapsedRealtimeMillis = 123456789L
|
||||||
|
|
||||||
private val repository = AuthRepositoryImpl(
|
private val repository = AuthRepositoryImpl(
|
||||||
|
@ -190,6 +199,7 @@ class AuthRepositoryTest {
|
||||||
vaultRepository = vaultRepository,
|
vaultRepository = vaultRepository,
|
||||||
userLogoutManager = userLogoutManager,
|
userLogoutManager = userLogoutManager,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
|
pushManager = pushManager,
|
||||||
elapsedRealtimeMillisProvider = { elapsedRealtimeMillis },
|
elapsedRealtimeMillisProvider = { elapsedRealtimeMillis },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2770,6 +2780,43 @@ class AuthRepositoryTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `logOutFlow emission for action account should call logout on the UserLogoutManager and clear the user's in memory vault data`() {
|
||||||
|
val userId = USER_ID_1
|
||||||
|
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||||
|
|
||||||
|
mutableLogoutFlow.tryEmit(Unit)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
userLogoutManager.logout(userId = userId)
|
||||||
|
vaultRepository.clearUnlockedData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `syncOrgKeysFlow emissions should refresh access token and sync`() {
|
||||||
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||||
|
coEvery {
|
||||||
|
identityService.refreshTokenSynchronously(REFRESH_TOKEN)
|
||||||
|
} returns REFRESH_TOKEN_RESPONSE_JSON.asSuccess()
|
||||||
|
every {
|
||||||
|
REFRESH_TOKEN_RESPONSE_JSON.toUserStateJson(
|
||||||
|
userId = USER_ID_1,
|
||||||
|
previousUserState = SINGLE_USER_STATE_1,
|
||||||
|
)
|
||||||
|
} returns SINGLE_USER_STATE_1
|
||||||
|
|
||||||
|
coEvery { vaultRepository.sync() } just runs
|
||||||
|
|
||||||
|
mutableSyncOrgKeysFlow.tryEmit(Unit)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
identityService.refreshTokenSynchronously(REFRESH_TOKEN)
|
||||||
|
vaultRepository.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val UNIQUE_APP_ID = "testUniqueAppId"
|
private const val UNIQUE_APP_ID = "testUniqueAppId"
|
||||||
private const val EMAIL = "test@bitwarden.com"
|
private const val EMAIL = "test@bitwarden.com"
|
||||||
|
|
|
@ -184,6 +184,8 @@ class PushManagerTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
SyncCipherUpsertData(
|
SyncCipherUpsertData(
|
||||||
cipherId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321",
|
cipherId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321",
|
||||||
|
organizationId = "6a41d965-ed95-4eae-98c3-5f1ec609c2c1",
|
||||||
|
collectionIds = listOf(),
|
||||||
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"),
|
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"),
|
||||||
isUpdate = false,
|
isUpdate = false,
|
||||||
),
|
),
|
||||||
|
@ -214,6 +216,8 @@ class PushManagerTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
SyncCipherUpsertData(
|
SyncCipherUpsertData(
|
||||||
cipherId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321",
|
cipherId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321",
|
||||||
|
organizationId = "6a41d965-ed95-4eae-98c3-5f1ec609c2c1",
|
||||||
|
collectionIds = listOf(),
|
||||||
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"),
|
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"),
|
||||||
isUpdate = true,
|
isUpdate = true,
|
||||||
),
|
),
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue