mirror of
https://github.com/bitwarden/android.git
synced 2026-06-08 16:17:05 -05:00
BIT-474, BIT-518, BIT-519, BIT-521: Expose flows from the vault database tables (#414)
This commit is contained in:
committed by
Álison Fernandes
parent
8a2a205247
commit
abe2354c15
@@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
@@ -22,6 +23,21 @@ interface VaultRepository {
|
||||
*/
|
||||
val vaultDataStateFlow: StateFlow<DataState<VaultData>>
|
||||
|
||||
/**
|
||||
* Flow that represents all ciphers for the active user.
|
||||
*/
|
||||
val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
|
||||
|
||||
/**
|
||||
* Flow that represents all collections for the active user.
|
||||
*/
|
||||
val collectionsStateFlow: StateFlow<DataState<List<CollectionView>>>
|
||||
|
||||
/**
|
||||
* Flow that represents all folders for the active user.
|
||||
*/
|
||||
val foldersStateFlow: StateFlow<DataState<List<FolderView>>>
|
||||
|
||||
/**
|
||||
* Flow that represents the current vault state.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
@@ -13,10 +14,12 @@ import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionE
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.map
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.updateToPendingOrLoading
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.platform.util.zip
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
@@ -36,14 +39,19 @@ import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toVaultUnlockResult
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -53,9 +61,10 @@ import kotlinx.coroutines.withContext
|
||||
* Default implementation of [VaultRepository].
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class VaultRepositoryImpl constructor(
|
||||
class VaultRepositoryImpl(
|
||||
private val syncService: SyncService,
|
||||
private val ciphersService: CiphersService,
|
||||
private val vaultDiskSource: VaultDiskSource,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val dispatcherManager: DispatcherManager,
|
||||
@@ -67,6 +76,8 @@ class VaultRepositoryImpl constructor(
|
||||
|
||||
private var willSyncAfterUnlock = false
|
||||
|
||||
private val activeUserId: String? get() = authDiskSource.userState?.activeUserId
|
||||
|
||||
private val vaultDataMutableStateFlow =
|
||||
MutableStateFlow<DataState<VaultData>>(DataState.Loading)
|
||||
|
||||
@@ -79,19 +90,65 @@ class VaultRepositoryImpl constructor(
|
||||
override val vaultDataStateFlow: StateFlow<DataState<VaultData>>
|
||||
get() = vaultDataMutableStateFlow.asStateFlow()
|
||||
|
||||
override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
|
||||
get() = mutableCiphersStateFlow.asStateFlow()
|
||||
|
||||
override val foldersStateFlow: StateFlow<DataState<List<FolderView>>>
|
||||
get() = mutableFoldersStateFlow.asStateFlow()
|
||||
|
||||
override val collectionsStateFlow: StateFlow<DataState<List<CollectionView>>>
|
||||
get() = mutableCollectionsStateFlow.asStateFlow()
|
||||
|
||||
override val vaultStateFlow: StateFlow<VaultState>
|
||||
get() = vaultMutableStateFlow.asStateFlow()
|
||||
|
||||
override val sendDataStateFlow: StateFlow<DataState<SendData>>
|
||||
get() = sendDataMutableStateFlow.asStateFlow()
|
||||
|
||||
private val mutableCiphersStateFlow =
|
||||
MutableStateFlow<DataState<List<CipherView>>>(DataState.Loading)
|
||||
|
||||
private val mutableFoldersStateFlow =
|
||||
MutableStateFlow<DataState<List<FolderView>>>(DataState.Loading)
|
||||
|
||||
private val mutableCollectionsStateFlow =
|
||||
MutableStateFlow<DataState<List<CollectionView>>>(DataState.Loading)
|
||||
|
||||
init {
|
||||
// Setup ciphers MutableStateFlow
|
||||
mutableCiphersStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
observeVaultDiskCiphers(activeUserId)
|
||||
}
|
||||
.launchIn(scope)
|
||||
// Setup folders MutableStateFlow
|
||||
mutableFoldersStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
observeVaultDiskFolders(activeUserId)
|
||||
}
|
||||
.launchIn(scope)
|
||||
// Setup collections MutableStateFlow
|
||||
mutableCollectionsStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
observeVaultDiskCollections(activeUserId)
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun clearUnlockedData() {
|
||||
mutableCiphersStateFlow.update { DataState.Loading }
|
||||
mutableFoldersStateFlow.update { DataState.Loading }
|
||||
mutableCollectionsStateFlow.update { DataState.Loading }
|
||||
vaultDataMutableStateFlow.update { DataState.Loading }
|
||||
sendDataMutableStateFlow.update { DataState.Loading }
|
||||
}
|
||||
|
||||
override fun sync() {
|
||||
if (!syncJob.isCompleted || willSyncAfterUnlock) return
|
||||
val userId = activeUserId ?: return
|
||||
mutableCiphersStateFlow.updateToPendingOrLoading()
|
||||
mutableFoldersStateFlow.updateToPendingOrLoading()
|
||||
mutableCollectionsStateFlow.updateToPendingOrLoading()
|
||||
vaultDataMutableStateFlow.updateToPendingOrLoading()
|
||||
sendDataMutableStateFlow.updateToPendingOrLoading()
|
||||
syncJob = scope.launch {
|
||||
@@ -108,10 +165,28 @@ class VaultRepositoryImpl constructor(
|
||||
|
||||
unlockVaultForOrganizationsIfNecessary(syncResponse = syncResponse)
|
||||
storeKeys(syncResponse = syncResponse)
|
||||
decryptSyncResponseAndUpdateVaultDataState(syncResponse = syncResponse)
|
||||
decryptSyncResponseAndUpdateVaultDataState(
|
||||
userId = userId,
|
||||
syncResponse = syncResponse,
|
||||
)
|
||||
decryptSendsAndUpdateSendDataState(sendList = syncResponse.sends)
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
mutableCiphersStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
mutableFoldersStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
mutableCollectionsStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
vaultDataMutableStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
@@ -371,8 +446,13 @@ class VaultRepositoryImpl constructor(
|
||||
}
|
||||
|
||||
private suspend fun decryptSyncResponseAndUpdateVaultDataState(
|
||||
userId: String,
|
||||
syncResponse: SyncResponseJson,
|
||||
) = withContext(dispatcherManager.default) {
|
||||
val deferred = async {
|
||||
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
|
||||
}
|
||||
|
||||
// Allow decryption of various types in parallel.
|
||||
val newState = zip(
|
||||
{
|
||||
@@ -414,7 +494,56 @@ class VaultRepositoryImpl constructor(
|
||||
onFailure = { DataState.Error(error = it) },
|
||||
)
|
||||
vaultDataMutableStateFlow.update { newState }
|
||||
deferred.await()
|
||||
}
|
||||
|
||||
private fun observeVaultDiskCiphers(
|
||||
userId: String,
|
||||
): Flow<DataState<List<CipherView>>> =
|
||||
vaultDiskSource
|
||||
.getCiphers(userId = userId)
|
||||
.onStart { mutableCiphersStateFlow.value = DataState.Loading }
|
||||
.map {
|
||||
vaultSdkSource
|
||||
.decryptCipherList(cipherList = it.toEncryptedSdkCipherList())
|
||||
.fold(
|
||||
onSuccess = { ciphers -> DataState.Loaded(ciphers) },
|
||||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.onEach { mutableCiphersStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskFolders(
|
||||
userId: String,
|
||||
): Flow<DataState<List<FolderView>>> =
|
||||
vaultDiskSource
|
||||
.getFolders(userId = userId)
|
||||
.onStart { mutableFoldersStateFlow.value = DataState.Loading }
|
||||
.map {
|
||||
vaultSdkSource
|
||||
.decryptFolderList(folderList = it.toEncryptedSdkFolderList())
|
||||
.fold(
|
||||
onSuccess = { folders -> DataState.Loaded(folders) },
|
||||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.onEach { mutableFoldersStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskCollections(
|
||||
userId: String,
|
||||
): Flow<DataState<List<CollectionView>>> =
|
||||
vaultDiskSource
|
||||
.getCollections(userId = userId)
|
||||
.onStart { mutableCollectionsStateFlow.value = DataState.Loading }
|
||||
.map {
|
||||
vaultSdkSource
|
||||
.decryptCollectionList(collectionList = it.toEncryptedSdkCollectionList())
|
||||
.fold(
|
||||
onSuccess = { collections -> DataState.Loaded(collections) },
|
||||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.onEach { mutableCollectionsStateFlow.value = it }
|
||||
}
|
||||
|
||||
private fun <T> Throwable.toNetworkOrErrorState(data: T?): DataState<T> =
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
@@ -25,12 +26,14 @@ object VaultRepositoryModule {
|
||||
fun providesVaultRepository(
|
||||
syncService: SyncService,
|
||||
ciphersService: CiphersService,
|
||||
vaultDiskSource: VaultDiskSource,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): VaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
ciphersService = ciphersService,
|
||||
vaultDiskSource = vaultDiskSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
|
||||
Reference in New Issue
Block a user