Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import com.x8bit.bitwarden.data.platform.repository.DebugMenuRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.manager.VaultDataManager
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
Expand Down Expand Up @@ -132,7 +133,7 @@ object PlatformManagerModule {
fun provideOrganizationEventManager(
authStateManager: AuthStateManager,
organizationManager: OrganizationManager,
vaultRepository: VaultRepository,
vaultDataManager: VaultDataManager,
authDiskSource: AuthDiskSource,
clock: Clock,
dispatcherManager: DispatcherManager,
Expand All @@ -142,7 +143,7 @@ object PlatformManagerModule {
authStateManager = authStateManager,
authDiskSource = authDiskSource,
organizationManager = organizationManager,
vaultRepository = vaultRepository,
vaultDataManager = vaultDataManager,
clock = clock,
dispatcherManager = dispatcherManager,
eventDiskSource = eventDiskSource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.x8bit.bitwarden.data.auth.manager.OrganizationManager
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.manager.VaultDataManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
Expand Down Expand Up @@ -41,7 +41,7 @@ internal class OrganizationEventManagerImpl(
private val authStateManager: AuthStateManager,
private val authDiskSource: AuthDiskSource,
private val organizationManager: OrganizationManager,
private val vaultRepository: VaultRepository,
private val vaultDataManager: VaultDataManager,
private val eventDiskSource: EventDiskSource,
private val eventService: EventService,
dispatcherManager: DispatcherManager,
Expand Down Expand Up @@ -70,7 +70,7 @@ internal class OrganizationEventManagerImpl(

ioScope.launch {
event.cipherId?.let { id ->
val cipherOrganizationId = vaultRepository
val cipherOrganizationId = vaultDataManager
.getVaultItemStateFlow(itemId = id)
.first { it.data != null }
.data
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.x8bit.bitwarden.data.vault.manager

import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.send.SendView
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherView
import com.bitwarden.vault.FolderView
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
import kotlinx.coroutines.flow.StateFlow

/**
* A helper manager for accessing and manging the vault data.
*/
interface VaultDataManager {
/**
* The [VaultFilterType] for the current user.
*
* Note that this does not affect the data provided by the repository and can be used by
* the UI for consistent filtering across screens.
*/
var vaultFilterType: VaultFilterType

/**
* Flow that represents the data for a single verification code item.
* This may emit null if any issues arise during code generation.
*/
fun getAuthCodeFlow(cipherId: String): StateFlow<DataState<VerificationCodeItem?>>

/**
* Flow that represents the data for the TOTP verification codes for ciphers items.
* This may emit an empty list if any issues arise during code generation.
*/
fun getAuthCodesFlow(): StateFlow<DataState<List<VerificationCodeItem>>>

/**
* Completely remove any persisted data from the vault.
*/
fun deleteVaultData(userId: String)

/**
* Flow that represents the data for a specific vault item as found by ID. This may emit `null`
* if the item cannot be found.
*/
fun getVaultItemStateFlow(itemId: String): StateFlow<DataState<CipherView?>>

/**
* Flow that represents the data for a specific vault folder as found by ID. This may emit
* `null` if the folder cannot be found.
*/
fun getVaultFolderStateFlow(folderId: String): StateFlow<DataState<FolderView?>>

/**
* Flow that represents the data for a specific send as found by ID. This may emit `null` if
* the Send cannot be found.
*/
fun getSendStateFlow(sendId: String): StateFlow<DataState<SendView?>>

/**
* Flow that represents the data for a specific vault list item as found by ID. This may emit
* `null` if the item cannot be found.
*/
fun getVaultListItemStateFlow(itemId: String): StateFlow<DataState<CipherListView?>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package com.x8bit.bitwarden.data.vault.manager

import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.repository.util.combineDataStates
import com.bitwarden.core.data.repository.util.map
import com.bitwarden.core.data.repository.util.mapNullable
import com.bitwarden.send.SendView
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherListViewType
import com.bitwarden.vault.CipherView
import com.bitwarden.vault.FolderView
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.autofill.util.login
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/**
* The default implementation of the [VaultDataManager].
*/
internal class VaultDataManagerImpl(
private val authDiskSource: AuthDiskSource,
private val cipherManager: CipherManager,
private val totpCodeManager: TotpCodeManager,
private val vaultDiskSource: VaultDiskSource,
private val vaultSyncManager: VaultSyncManager,
dispatcherManager: DispatcherManager,
) : VaultDataManager {
private val ioScope = CoroutineScope(context = dispatcherManager.io)
private val unconfinedScope = CoroutineScope(context = dispatcherManager.unconfined)
private val activeUserId: String? get() = authDiskSource.userState?.activeUserId

override var vaultFilterType: VaultFilterType = VaultFilterType.AllVaults

@OptIn(ExperimentalCoroutinesApi::class)
override fun getAuthCodesFlow(): StateFlow<DataState<List<VerificationCodeItem>>> {
val userId = activeUserId ?: return MutableStateFlow(
DataState.Error(error = IllegalStateException("No active user"), null),
)
return vaultSyncManager
.vaultDataStateFlow
.map { dataState ->
dataState.map { vaultData ->
vaultData
.decryptCipherListResult
.successes
.filter {
it.type is CipherListViewType.Login &&
!it.login?.totp.isNullOrBlank() &&
it.isActive
}
.toFilteredList(vaultFilterType = vaultFilterType)
}
}
.flatMapLatest { cipherDataState ->
val cipherList = cipherDataState.data ?: emptyList()
totpCodeManager
.getTotpCodesForCipherListViewsStateFlow(
userId = userId,
cipherListViews = cipherList,
)
.map { verificationCodeDataStates ->
combineDataStates(
dataState1 = verificationCodeDataStates,
dataState2 = cipherDataState,
) { verificationCodeItems, _ ->
// Just return the verification items; we are only combining the
// DataStates to know the overall state.
verificationCodeItems
}
}
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.WhileSubscribed(),
initialValue = DataState.Loading,
)
}

@OptIn(ExperimentalCoroutinesApi::class)
override fun getAuthCodeFlow(cipherId: String): StateFlow<DataState<VerificationCodeItem?>> {
val userId = activeUserId ?: return MutableStateFlow(
DataState.Error(error = IllegalStateException("No active user"), null),
)
return this.getVaultListItemStateFlow(cipherId)
.flatMapLatest { cipherDataState ->
cipherDataState
.data
?.let {
totpCodeManager
.getTotpCodeStateFlow(userId = userId, cipherListView = it)
.map { totpCodeDataState ->
combineDataStates(
dataState1 = totpCodeDataState,
dataState2 = cipherDataState,
) { _, _ ->
// We are only combining the DataStates to know the overall
// state, we map it to the appropriate value below.
}
.mapNullable { totpCodeDataState.data }
}
}
?: flowOf(DataState.Loaded(data = null))
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.WhileSubscribed(),
initialValue = DataState.Loading,
)
}

override fun deleteVaultData(userId: String) {
ioScope.launch {
vaultDiskSource.deleteVaultData(userId = userId)
}
}

override fun getVaultItemStateFlow(
itemId: String,
): StateFlow<DataState<CipherView?>> =
vaultSyncManager
.vaultDataStateFlow
.map { dataState ->
dataState.map { vaultData ->
val getCipherResult = vaultData
.decryptCipherListResult
.successes
.find { it.id == itemId }
.let { cipherManager.getCipher(cipherId = itemId) }
when (getCipherResult) {
is GetCipherResult.Success -> getCipherResult.cipherView
else -> null
}
}
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Lazily,
initialValue = DataState.Loading,
)

override fun getVaultListItemStateFlow(
itemId: String,
): StateFlow<DataState<CipherListView?>> =
vaultSyncManager
.vaultDataStateFlow
.map { dataState ->
dataState.map { vaultData ->
vaultData.decryptCipherListResult.successes.find { it.id == itemId }
}
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Lazily,
initialValue = DataState.Loading,
)

override fun getVaultFolderStateFlow(
folderId: String,
): StateFlow<DataState<FolderView?>> =
vaultSyncManager
.vaultDataStateFlow
.map { dataState ->
dataState.map { vaultData ->
vaultData.folderViewList.find { it.id == folderId }
}
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Lazily,
initialValue = DataState.Loading,
)

override fun getSendStateFlow(
sendId: String,
): StateFlow<DataState<SendView?>> =
vaultSyncManager
.sendDataStateFlow
.map { dataState ->
dataState.map { sendData ->
sendData.sendViewList.find { it.id == sendId }
}
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Lazily,
initialValue = DataState.Loading,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import com.x8bit.bitwarden.data.vault.manager.SendManager
import com.x8bit.bitwarden.data.vault.manager.SendManagerImpl
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManagerImpl
import com.x8bit.bitwarden.data.vault.manager.VaultDataManager
import com.x8bit.bitwarden.data.vault.manager.VaultDataManagerImpl
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.manager.VaultLockManagerImpl
import com.x8bit.bitwarden.data.vault.manager.VaultMigrationManager
Expand Down Expand Up @@ -228,6 +230,24 @@ object VaultManagerModule {
clock = clock,
)

@Provides
@Singleton
fun provideVaultDataManager(
authDiskSource: AuthDiskSource,
cipherManager: CipherManager,
totpCodeManager: TotpCodeManager,
vaultDiskSource: VaultDiskSource,
vaultSyncManager: VaultSyncManager,
dispatcherManager: DispatcherManager,
): VaultDataManager = VaultDataManagerImpl(
authDiskSource = authDiskSource,
cipherManager = cipherManager,
totpCodeManager = totpCodeManager,
vaultDiskSource = vaultDiskSource,
vaultSyncManager = vaultSyncManager,
dispatcherManager = dispatcherManager,
)

@Provides
@Singleton
fun provideVaultSyncManager(
Expand Down
Loading
Loading