From 25097cbae137531cb10056b2279f953e2996a20e Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 6 Nov 2024 10:59:20 -0600 Subject: [PATCH] PM-14433: Null domain data (#4243) --- .../4.json | 250 ++++++++++++++++++ .../vault/datasource/disk/VaultDiskSource.kt | 2 +- .../datasource/disk/VaultDiskSourceImpl.kt | 8 +- .../datasource/disk/database/VaultDatabase.kt | 2 +- .../datasource/disk/entity/DomainsEntity.kt | 2 +- .../network/model/SyncResponseJson.kt | 2 +- .../repository/util/DomainsExtensions.kt | 6 +- .../datasource/disk/VaultDiskSourceTest.kt | 9 +- .../datasource/disk/dao/FakeDomainsDao.kt | 5 +- 9 files changed, 268 insertions(+), 18 deletions(-) create mode 100644 app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/4.json diff --git a/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/4.json b/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/4.json new file mode 100644 index 000000000..e7afb16f4 --- /dev/null +++ b/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/4.json @@ -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')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt index 9c4329a65..13cb5a3fe 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt @@ -37,7 +37,7 @@ interface VaultDiskSource { /** * Retrieves all domains from the data source for a given [userId]. */ - fun getDomains(userId: String): Flow + fun getDomains(userId: String): Flow /** * Deletes a folder from the data source for the given [userId] and [folderId]. diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt index 544ac6b3e..a3095f093 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -120,13 +119,12 @@ class VaultDiskSourceImpl( }, ) - override fun getDomains(userId: String): Flow = + override fun getDomains(userId: String): Flow = domainsDao .getDomains(userId) - .filterNotNull() .map { entity -> withContext(dispatcherManager.default) { - json.decodeFromString(entity.domainsJson) + entity?.domainsJson?.let { json.decodeFromString(it) } } } @@ -239,7 +237,7 @@ class VaultDiskSourceImpl( domainsDao.insertDomains( domains = DomainsEntity( userId = userId, - domainsJson = json.encodeToString(vault.domains), + domainsJson = vault.domains?.let { json.encodeToString(it) }, ), ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt index 495e038d3..23511a03f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt @@ -26,7 +26,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity FolderEntity::class, SendEntity::class, ], - version = 3, + version = 4, exportSchema = true, ) @TypeConverters(ZonedDateTimeTypeConverter::class) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt index e8213b1d9..ee9414ba8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt @@ -14,5 +14,5 @@ data class DomainsEntity( val userId: String, @ColumnInfo(name = "domains_json") - val domainsJson: String, + val domainsJson: String?, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt index 5ea91d252..e3b12c777 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt @@ -39,7 +39,7 @@ data class SyncResponseJson( val policies: List?, @SerialName("domains") - val domains: Domains, + val domains: Domains?, @SerialName("sends") val sends: List?, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/DomainsExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/DomainsExtensions.kt index f889d724a..1b8ecc087 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/DomainsExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/DomainsExtensions.kt @@ -6,14 +6,14 @@ import com.x8bit.bitwarden.data.vault.repository.model.DomainsData /** * Map the API [Domains] model to the internal [DomainsData] model. */ -fun Domains.toDomainsData(): DomainsData { +fun Domains?.toDomainsData(): DomainsData { val globalEquivalentDomains = this - .globalEquivalentDomains + ?.globalEquivalentDomains ?.map { it.toInternalModel() } .orEmpty() return DomainsData( - equivalentDomains = this.equivalentDomains.orEmpty(), + equivalentDomains = this?.equivalentDomains.orEmpty(), globalEquivalentDomains = globalEquivalentDomains, ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt index 7613fe173..7efc729eb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt @@ -248,10 +248,13 @@ class VaultDiskSourceTest { // We cannot compare the JSON strings directly because of formatting differences // So we split that off into its own assertion. assertEquals( - DOMAINS_ENTITY.copy(domainsJson = ""), - storedDomainsEntity.copy(domainsJson = ""), + DOMAINS_ENTITY.copy(domainsJson = null), + storedDomainsEntity.copy(domainsJson = null), + ) + assertJsonEquals( + requireNotNull(DOMAINS_ENTITY.domainsJson), + requireNotNull(storedDomainsEntity.domainsJson), ) - assertJsonEquals(DOMAINS_ENTITY.domainsJson, storedDomainsEntity.domainsJson) // Verify the folders dao is updated assertEquals(listOf(FOLDER_ENTITY), foldersDao.storedFolders) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeDomainsDao.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeDomainsDao.kt index 675341c0c..90b69914f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeDomainsDao.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeDomainsDao.kt @@ -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.vault.datasource.disk.entity.DomainsEntity import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filterNotNull class FakeDomainsDao : DomainsDao { var storedDomains: DomainsEntity? = null @@ -18,9 +17,9 @@ class FakeDomainsDao : DomainsDao { deleteDomainsCalled = true } - override fun getDomains(userId: String): Flow { + override fun getDomains(userId: String): Flow { getDomainsCalled = true - return mutableDomainsFlow.filterNotNull() + return mutableDomainsFlow } override suspend fun insertDomains(domains: DomainsEntity) {