From fe84feb18427ffdac4c3845a6cdd71e4439e25cd Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:18:18 -0500 Subject: [PATCH] PM-14433: Null domain data (#4268) Co-authored-by: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com> Co-authored-by: David Perez --- .../5.json | 256 ++++++++++++++++++ .../vault/datasource/disk/VaultDiskSource.kt | 2 +- .../datasource/disk/VaultDiskSourceImpl.kt | 6 +- .../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, 274 insertions(+), 16 deletions(-) create mode 100644 app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/5.json diff --git a/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/5.json b/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/5.json new file mode 100644 index 000000000..f1136b0ff --- /dev/null +++ b/app/schemas/com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase/5.json @@ -0,0 +1,256 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "ee697e71290c92fe5b607d0b7665481b", + "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, `manage` 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 + }, + { + "fieldPath": "canManage", + "columnName": "manage", + "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, 'ee697e71290c92fe5b607d0b7665481b')" + ] + } +} \ 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 cdb1e1235..204fe787e 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 @@ -122,13 +122,13 @@ 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) } } } @@ -242,7 +242,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 23511a03f..19dcdc75d 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 = 4, + version = 5, 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 a476fa672..d8aec4b85 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 7c5719b7b..8f0724828 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) {