PM-14433: Null domain data (#4243)

This commit is contained in:
David Perez 2024-11-06 10:59:20 -06:00 committed by GitHub
parent 5a4b8d64ab
commit 25097cbae1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 268 additions and 18 deletions

View file

@ -0,0 +1,250 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "f28200334a5c94feed1d9712e04ff01b",
"entities": [
{
"tableName": "ciphers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cipherType",
"columnName": "cipher_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cipherJson",
"columnName": "cipher_json",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_ciphers_user_id",
"unique": false,
"columnNames": [
"user_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_ciphers_user_id` ON `${TABLE_NAME}` (`user_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "collections",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "organizationId",
"columnName": "organization_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "shouldHidePasswords",
"columnName": "should_hide_passwords",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "externalId",
"columnName": "external_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isReadOnly",
"columnName": "read_only",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collections_user_id",
"unique": false,
"columnNames": [
"user_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collections_user_id` ON `${TABLE_NAME}` (`user_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "domains",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT, PRIMARY KEY(`user_id`))",
"fields": [
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "domainsJson",
"columnName": "domains_json",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"user_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "folders",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `name` TEXT, `revision_date` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "revisionDate",
"columnName": "revision_date",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_folders_user_id",
"unique": false,
"columnNames": [
"user_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_folders_user_id` ON `${TABLE_NAME}` (`user_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "sends",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `send_type` TEXT NOT NULL, `send_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sendType",
"columnName": "send_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sendJson",
"columnName": "send_json",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_sends_user_id",
"unique": false,
"columnNames": [
"user_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_sends_user_id` ON `${TABLE_NAME}` (`user_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f28200334a5c94feed1d9712e04ff01b')"
]
}
}

View file

@ -37,7 +37,7 @@ interface VaultDiskSource {
/** /**
* Retrieves all domains from the data source for a given [userId]. * Retrieves all domains from the data source for a given [userId].
*/ */
fun getDomains(userId: String): Flow<SyncResponseJson.Domains> fun getDomains(userId: String): Flow<SyncResponseJson.Domains?>
/** /**
* Deletes a folder from the data source for the given [userId] and [folderId]. * Deletes a folder from the data source for the given [userId] and [folderId].

View file

@ -17,7 +17,6 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
@ -120,13 +119,12 @@ class VaultDiskSourceImpl(
}, },
) )
override fun getDomains(userId: String): Flow<SyncResponseJson.Domains> = override fun getDomains(userId: String): Flow<SyncResponseJson.Domains?> =
domainsDao domainsDao
.getDomains(userId) .getDomains(userId)
.filterNotNull()
.map { entity -> .map { entity ->
withContext(dispatcherManager.default) { withContext(dispatcherManager.default) {
json.decodeFromString<SyncResponseJson.Domains>(entity.domainsJson) entity?.domainsJson?.let { json.decodeFromString<SyncResponseJson.Domains>(it) }
} }
} }
@ -239,7 +237,7 @@ class VaultDiskSourceImpl(
domainsDao.insertDomains( domainsDao.insertDomains(
domains = DomainsEntity( domains = DomainsEntity(
userId = userId, userId = userId,
domainsJson = json.encodeToString(vault.domains), domainsJson = vault.domains?.let { json.encodeToString(it) },
), ),
) )
} }

View file

@ -26,7 +26,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
FolderEntity::class, FolderEntity::class,
SendEntity::class, SendEntity::class,
], ],
version = 3, version = 4,
exportSchema = true, exportSchema = true,
) )
@TypeConverters(ZonedDateTimeTypeConverter::class) @TypeConverters(ZonedDateTimeTypeConverter::class)

View file

@ -14,5 +14,5 @@ data class DomainsEntity(
val userId: String, val userId: String,
@ColumnInfo(name = "domains_json") @ColumnInfo(name = "domains_json")
val domainsJson: String, val domainsJson: String?,
) )

View file

@ -39,7 +39,7 @@ data class SyncResponseJson(
val policies: List<Policy>?, val policies: List<Policy>?,
@SerialName("domains") @SerialName("domains")
val domains: Domains, val domains: Domains?,
@SerialName("sends") @SerialName("sends")
val sends: List<Send>?, val sends: List<Send>?,

View file

@ -6,14 +6,14 @@ import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
/** /**
* Map the API [Domains] model to the internal [DomainsData] model. * Map the API [Domains] model to the internal [DomainsData] model.
*/ */
fun Domains.toDomainsData(): DomainsData { fun Domains?.toDomainsData(): DomainsData {
val globalEquivalentDomains = this val globalEquivalentDomains = this
.globalEquivalentDomains ?.globalEquivalentDomains
?.map { it.toInternalModel() } ?.map { it.toInternalModel() }
.orEmpty() .orEmpty()
return DomainsData( return DomainsData(
equivalentDomains = this.equivalentDomains.orEmpty(), equivalentDomains = this?.equivalentDomains.orEmpty(),
globalEquivalentDomains = globalEquivalentDomains, globalEquivalentDomains = globalEquivalentDomains,
) )
} }

View file

@ -248,10 +248,13 @@ class VaultDiskSourceTest {
// We cannot compare the JSON strings directly because of formatting differences // We cannot compare the JSON strings directly because of formatting differences
// So we split that off into its own assertion. // So we split that off into its own assertion.
assertEquals( assertEquals(
DOMAINS_ENTITY.copy(domainsJson = ""), DOMAINS_ENTITY.copy(domainsJson = null),
storedDomainsEntity.copy(domainsJson = ""), storedDomainsEntity.copy(domainsJson = null),
)
assertJsonEquals(
requireNotNull(DOMAINS_ENTITY.domainsJson),
requireNotNull(storedDomainsEntity.domainsJson),
) )
assertJsonEquals(DOMAINS_ENTITY.domainsJson, storedDomainsEntity.domainsJson)
// Verify the folders dao is updated // Verify the folders dao is updated
assertEquals(listOf(FOLDER_ENTITY), foldersDao.storedFolders) assertEquals(listOf(FOLDER_ENTITY), foldersDao.storedFolders)

View file

@ -3,7 +3,6 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.dao
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
class FakeDomainsDao : DomainsDao { class FakeDomainsDao : DomainsDao {
var storedDomains: DomainsEntity? = null var storedDomains: DomainsEntity? = null
@ -18,9 +17,9 @@ class FakeDomainsDao : DomainsDao {
deleteDomainsCalled = true deleteDomainsCalled = true
} }
override fun getDomains(userId: String): Flow<DomainsEntity> { override fun getDomains(userId: String): Flow<DomainsEntity?> {
getDomainsCalled = true getDomainsCalled = true
return mutableDomainsFlow.filterNotNull() return mutableDomainsFlow
} }
override suspend fun insertDomains(domains: DomainsEntity) { override suspend fun insertDomains(domains: DomainsEntity) {