mirror of
https://github.com/bitwarden/android.git
synced 2026-06-06 14:28:45 -05:00
Add Domains database (#784)
This commit is contained in:
committed by
Álison Fernandes
parent
5fa49c8b53
commit
52acc2fa47
@@ -34,6 +34,11 @@ interface VaultDiskSource {
|
||||
*/
|
||||
fun getCollections(userId: String): Flow<List<SyncResponseJson.Collection>>
|
||||
|
||||
/**
|
||||
* Retrieves all domains from the data source for a given [userId].
|
||||
*/
|
||||
fun getDomains(userId: String): Flow<SyncResponseJson.Domains>
|
||||
|
||||
/**
|
||||
* Saves a folder to the data source for the given [userId].
|
||||
*/
|
||||
|
||||
@@ -3,10 +3,12 @@ package com.x8bit.bitwarden.data.vault.datasource.disk
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
@@ -16,6 +18,7 @@ import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@@ -26,6 +29,7 @@ import kotlinx.serialization.json.Json
|
||||
class VaultDiskSourceImpl(
|
||||
private val ciphersDao: CiphersDao,
|
||||
private val collectionsDao: CollectionsDao,
|
||||
private val domainsDao: DomainsDao,
|
||||
private val foldersDao: FoldersDao,
|
||||
private val sendsDao: SendsDao,
|
||||
private val json: Json,
|
||||
@@ -103,6 +107,13 @@ class VaultDiskSourceImpl(
|
||||
},
|
||||
)
|
||||
|
||||
override fun getDomains(userId: String): Flow<SyncResponseJson.Domains> =
|
||||
domainsDao
|
||||
.getDomains(userId)
|
||||
.map { entity ->
|
||||
json.decodeFromString<SyncResponseJson.Domains>(entity.domainsJson)
|
||||
}
|
||||
|
||||
override suspend fun saveFolder(userId: String, folder: SyncResponseJson.Folder) {
|
||||
foldersDao.insertFolder(
|
||||
folder = FolderEntity(
|
||||
@@ -198,6 +209,14 @@ class VaultDiskSourceImpl(
|
||||
},
|
||||
)
|
||||
}
|
||||
launch {
|
||||
domainsDao.insertDomains(
|
||||
domains = DomainsEntity(
|
||||
userId = userId,
|
||||
domainsJson = json.encodeToString(vault.domains),
|
||||
),
|
||||
)
|
||||
}
|
||||
val deferredFolders = async {
|
||||
foldersDao.replaceAllFolders(
|
||||
userId = userId,
|
||||
@@ -245,11 +264,13 @@ class VaultDiskSourceImpl(
|
||||
coroutineScope {
|
||||
val deferredCiphers = async { ciphersDao.deleteAllCiphers(userId = userId) }
|
||||
val deferredCollections = async { collectionsDao.deleteAllCollections(userId = userId) }
|
||||
val deferredDomains = async { domainsDao.deleteDomains(userId = userId) }
|
||||
val deferredFolders = async { foldersDao.deleteAllFolders(userId = userId) }
|
||||
val deferredSends = async { sendsDao.deleteAllSends(userId = userId) }
|
||||
awaitAll(
|
||||
deferredCiphers,
|
||||
deferredCollections,
|
||||
deferredDomains,
|
||||
deferredFolders,
|
||||
deferredSends,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.x8bit.bitwarden.data.vault.datasource.disk.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Provides methods for inserting, retrieving, and deleting domains from the database using the
|
||||
* [DomainsEntity].
|
||||
*/
|
||||
@Dao
|
||||
interface DomainsDao {
|
||||
/**
|
||||
* Deletes the stored domains associated with the given [userId].
|
||||
*/
|
||||
@Query("DELETE FROM domains WHERE user_id = :userId")
|
||||
suspend fun deleteDomains(userId: String)
|
||||
|
||||
/**
|
||||
* Retrieves domains from the database for a given [userId].
|
||||
*/
|
||||
@Query("SELECT * FROM domains WHERE user_id = :userId")
|
||||
fun getDomains(
|
||||
userId: String,
|
||||
): Flow<DomainsEntity>
|
||||
|
||||
/**
|
||||
* Inserts domains into the database.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertDomains(domains: DomainsEntity)
|
||||
}
|
||||
@@ -6,10 +6,12 @@ import androidx.room.TypeConverters
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTypeConverter
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||
|
||||
@@ -20,10 +22,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||
entities = [
|
||||
CipherEntity::class,
|
||||
CollectionEntity::class,
|
||||
DomainsEntity::class,
|
||||
FolderEntity::class,
|
||||
SendEntity::class,
|
||||
],
|
||||
version = 2,
|
||||
version = 3,
|
||||
)
|
||||
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
||||
abstract class VaultDatabase : RoomDatabase() {
|
||||
@@ -38,6 +41,11 @@ abstract class VaultDatabase : RoomDatabase() {
|
||||
*/
|
||||
abstract fun collectionDao(): CollectionsDao
|
||||
|
||||
/**
|
||||
* Provides the DAO for accessing domains data.
|
||||
*/
|
||||
abstract fun domainsDao(): DomainsDao
|
||||
|
||||
/**
|
||||
* Provides the DAO for accessing folder data.
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTypeConverter
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.DomainsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase
|
||||
@@ -45,6 +46,10 @@ class VaultDiskModule {
|
||||
@Singleton
|
||||
fun provideCollectionDao(database: VaultDatabase): CollectionsDao = database.collectionDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDomainsDao(database: VaultDatabase): DomainsDao = database.domainsDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFolderDao(database: VaultDatabase): FoldersDao = database.folderDao()
|
||||
@@ -58,12 +63,14 @@ class VaultDiskModule {
|
||||
fun provideVaultDiskSource(
|
||||
ciphersDao: CiphersDao,
|
||||
collectionsDao: CollectionsDao,
|
||||
domainsDao: DomainsDao,
|
||||
foldersDao: FoldersDao,
|
||||
sendsDao: SendsDao,
|
||||
json: Json,
|
||||
): VaultDiskSource = VaultDiskSourceImpl(
|
||||
ciphersDao = ciphersDao,
|
||||
collectionsDao = collectionsDao,
|
||||
domainsDao = domainsDao,
|
||||
foldersDao = foldersDao,
|
||||
sendsDao = sendsDao,
|
||||
json = json,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.x8bit.bitwarden.data.vault.datasource.disk.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* Entity representing a set of domains in the database.
|
||||
*/
|
||||
@Entity(tableName = "domains")
|
||||
data class DomainsEntity(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
@ColumnInfo(name = "user_id")
|
||||
val userId: String,
|
||||
|
||||
@ColumnInfo(name = "domains_json")
|
||||
val domainsJson: String,
|
||||
)
|
||||
@@ -17,6 +17,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
@@ -69,6 +70,14 @@ interface VaultRepository : VaultLockManager {
|
||||
*/
|
||||
val collectionsStateFlow: StateFlow<DataState<List<CollectionView>>>
|
||||
|
||||
/**
|
||||
* Flow that represents all domains for the active user.
|
||||
*
|
||||
* Note that the [StateFlow.value] will return the last known value but the [StateFlow] itself
|
||||
* must be collected in order to trigger state changes.
|
||||
*/
|
||||
val domainsStateFlow: StateFlow<DataState<DomainsData>>
|
||||
|
||||
/**
|
||||
* Flow that represents all folders for the active user.
|
||||
*
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
@@ -56,6 +57,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherResponse
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend
|
||||
@@ -136,6 +138,9 @@ class VaultRepositoryImpl(
|
||||
private val mutableCollectionsStateFlow =
|
||||
MutableStateFlow<DataState<List<CollectionView>>>(DataState.Loading)
|
||||
|
||||
private val mutableDomainsStateFlow =
|
||||
MutableStateFlow<DataState<DomainsData>>(DataState.Loading)
|
||||
|
||||
override var vaultFilterType: VaultFilterType = VaultFilterType.AllVaults
|
||||
|
||||
override val vaultDataStateFlow: StateFlow<DataState<VaultData>> =
|
||||
@@ -171,6 +176,9 @@ class VaultRepositoryImpl(
|
||||
override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
|
||||
get() = mutableCiphersStateFlow.asStateFlow()
|
||||
|
||||
override val domainsStateFlow: StateFlow<DataState<DomainsData>>
|
||||
get() = mutableDomainsStateFlow.asStateFlow()
|
||||
|
||||
override val foldersStateFlow: StateFlow<DataState<List<FolderView>>>
|
||||
get() = mutableFoldersStateFlow.asStateFlow()
|
||||
|
||||
@@ -187,6 +195,12 @@ class VaultRepositoryImpl(
|
||||
observeVaultDiskCiphers(activeUserId)
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
// Setup domains MutableStateFlow
|
||||
mutableDomainsStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
observeVaultDiskDomains(activeUserId)
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
// Setup folders MutableStateFlow
|
||||
mutableFoldersStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
@@ -209,6 +223,7 @@ class VaultRepositoryImpl(
|
||||
|
||||
override fun clearUnlockedData() {
|
||||
mutableCiphersStateFlow.update { DataState.Loading }
|
||||
mutableDomainsStateFlow.update { DataState.Loading }
|
||||
mutableFoldersStateFlow.update { DataState.Loading }
|
||||
mutableCollectionsStateFlow.update { DataState.Loading }
|
||||
mutableSendDataStateFlow.update { DataState.Loading }
|
||||
@@ -224,6 +239,7 @@ class VaultRepositoryImpl(
|
||||
val userId = activeUserId ?: return
|
||||
if (!syncJob.isCompleted || isVaultUnlocking(userId)) return
|
||||
mutableCiphersStateFlow.updateToPendingOrLoading()
|
||||
mutableDomainsStateFlow.updateToPendingOrLoading()
|
||||
mutableFoldersStateFlow.updateToPendingOrLoading()
|
||||
mutableCollectionsStateFlow.updateToPendingOrLoading()
|
||||
mutableSendDataStateFlow.updateToPendingOrLoading()
|
||||
@@ -250,6 +266,11 @@ class VaultRepositoryImpl(
|
||||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
mutableDomainsStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
mutableFoldersStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
@@ -993,6 +1014,19 @@ class VaultRepositoryImpl(
|
||||
}
|
||||
.onEach { mutableCiphersStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskDomains(
|
||||
userId: String,
|
||||
): Flow<DataState<DomainsData>> =
|
||||
vaultDiskSource
|
||||
.getDomains(userId = userId)
|
||||
.onStart { mutableDomainsStateFlow.value = DataState.Loading }
|
||||
.map {
|
||||
DataState.Loaded(
|
||||
data = it.toDomainsData(),
|
||||
)
|
||||
}
|
||||
.onEach { mutableDomainsStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskFolders(
|
||||
userId: String,
|
||||
): Flow<DataState<List<FolderView>>> =
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Model for equivalent domain details.
|
||||
*
|
||||
* @param equivalentDomains A list of equivalent domains to compare URIs to.
|
||||
* @param globalEquivalentDomains A list of global equivalent domains to compare URIs to.
|
||||
*/
|
||||
data class DomainsData(
|
||||
val equivalentDomains: List<List<String>>,
|
||||
val globalEquivalentDomains: List<GlobalEquivalentDomain>,
|
||||
) {
|
||||
/**
|
||||
* Model for a group of domains that should be matched together.
|
||||
*
|
||||
* @property isExcluded If the global equivalent domain should be excluded.
|
||||
* @property domains A list of domains that should all match a URI.
|
||||
* @property type The domain type identifier.
|
||||
*/
|
||||
data class GlobalEquivalentDomain(
|
||||
val isExcluded: Boolean,
|
||||
val domains: List<String>,
|
||||
val type: Int,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson.Domains
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
|
||||
/**
|
||||
* Map the API [Domains] model to the internal [DomainsData] model.
|
||||
*/
|
||||
fun Domains.toDomainsData(): DomainsData {
|
||||
val globalEquivalentDomains = this
|
||||
.globalEquivalentDomains
|
||||
?.map { it.toInternalModel() }
|
||||
.orEmpty()
|
||||
|
||||
return DomainsData(
|
||||
equivalentDomains = this.equivalentDomains.orEmpty(),
|
||||
globalEquivalentDomains = globalEquivalentDomains,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the API [Domains.GlobalEquivalentDomain] model to the internal
|
||||
* [DomainsData.GlobalEquivalentDomain] model.
|
||||
*/
|
||||
private fun Domains.GlobalEquivalentDomain.toInternalModel(): DomainsData.GlobalEquivalentDomain =
|
||||
DomainsData.GlobalEquivalentDomain(
|
||||
domains = this.domains.orEmpty(),
|
||||
isExcluded = this.isExcluded,
|
||||
type = this.type,
|
||||
)
|
||||
Reference in New Issue
Block a user