mirror of
https://github.com/bitwarden/android.git
synced 2026-10-22 02:53:19 -05:00
PM-39902: feat: Update app to support blob encryption (#7173)
This commit is contained in:
1 parent
a89da00f9f
commit
4fe8d81021
13 files changed
+310
-37
No files matched your search
+272
@@ -0,0 +1,272 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "67e32a70f154b819751a225259e8222b",
|
||||
"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, `organization_id` TEXT, 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
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationId",
|
||||
"columnName": "organization_id",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"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`)"
|
||||
},
|
||||
{
|
||||
"name": "index_ciphers_user_id_organization_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id",
|
||||
"organization_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ciphers_user_id_organization_id` ON `${TABLE_NAME}` (`user_id`, `organization_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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, `default_user_collection_email` TEXT, `type` TEXT NOT NULL DEFAULT '0', 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"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isReadOnly",
|
||||
"columnName": "read_only",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canManage",
|
||||
"columnName": "manage",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultUserCollectionEmail",
|
||||
"columnName": "default_user_collection_email",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'0'"
|
||||
}
|
||||
],
|
||||
"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`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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`)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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, '67e32a70f154b819751a225259e8222b')"
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -88,7 +88,7 @@ class AuthenticatorBridgeRepositoryImpl(
|
||||
|
||||
// Vault is unlocked, query vault disk source for totp logins:
|
||||
val cipherData = vaultDiskSource
|
||||
.getTotpCiphers(userId = userId)
|
||||
.getLoginCiphers(userId = userId)
|
||||
// Filter out any deleted and archived ciphers.
|
||||
.filter { it.deletedDate == null && it.archivedDate == null }
|
||||
.mapNotNull {
|
||||
|
||||
+2
-2
@@ -43,9 +43,9 @@ interface VaultDiskSource {
|
||||
): List<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Retrieves all ciphers from the data source for a given [userId] that contain TOTP codes.
|
||||
* Retrieves all login ciphers from the data source for a given [userId].
|
||||
*/
|
||||
suspend fun getTotpCiphers(userId: String): List<SyncResponseJson.Cipher>
|
||||
suspend fun getLoginCiphers(userId: String): List<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Retrieves a cipher from the data source for a given [userId] and [cipherId].
|
||||
|
||||
+2
-11
@@ -4,7 +4,6 @@ import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.core.data.util.decodeFromStringWithErrorCallback
|
||||
import com.bitwarden.network.model.SyncResponseJson
|
||||
import com.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||
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
|
||||
@@ -53,7 +52,6 @@ class VaultDiskSourceImpl(
|
||||
CipherEntity(
|
||||
id = cipher.id,
|
||||
userId = userId,
|
||||
hasTotp = cipher.login?.totp != null,
|
||||
cipherType = json.encodeToString(cipher.type),
|
||||
cipherJson = json.encodeToString(cipher),
|
||||
organizationId = cipher.organizationId,
|
||||
@@ -120,8 +118,8 @@ class VaultDiskSourceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTotpCiphers(userId: String): List<SyncResponseJson.Cipher> {
|
||||
val entities = ciphersDao.getAllTotpCiphers(userId = userId)
|
||||
override suspend fun getLoginCiphers(userId: String): List<SyncResponseJson.Cipher> {
|
||||
val entities = ciphersDao.getAllLoginCiphers(userId = userId)
|
||||
return withContext(context = dispatcherManager.default) {
|
||||
entities
|
||||
.map { entity ->
|
||||
@@ -132,11 +130,6 @@ class VaultDiskSourceImpl(
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.filter {
|
||||
// A safety-check since after the DB migration, we will temporarily think
|
||||
// all ciphers contain a totp code
|
||||
it.login?.totp.orNullIfBlank() != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +160,6 @@ class VaultDiskSourceImpl(
|
||||
CipherEntity(
|
||||
id = cipher.id,
|
||||
userId = userId,
|
||||
hasTotp = cipher.login?.totp.orNullIfBlank() != null,
|
||||
cipherType = json.encodeToString(cipher.type),
|
||||
cipherJson = json.encodeToString(cipher),
|
||||
organizationId = cipher.organizationId,
|
||||
@@ -369,7 +361,6 @@ class VaultDiskSourceImpl(
|
||||
CipherEntity(
|
||||
id = cipher.id,
|
||||
userId = userId,
|
||||
hasTotp = cipher.login?.totp != null,
|
||||
cipherType = json.encodeToString(cipher.type),
|
||||
cipherJson = json.encodeToString(cipher),
|
||||
organizationId = cipher.organizationId,
|
||||
|
||||
+3
-3
@@ -48,10 +48,10 @@ interface CiphersDao {
|
||||
): List<CipherEntity>
|
||||
|
||||
/**
|
||||
* Retrieves all ciphers from the database for a given [userId].
|
||||
* Retrieves all login type ciphers from the database for a given [userId].
|
||||
*/
|
||||
@Query("SELECT * FROM ciphers WHERE user_id = :userId AND has_totp = 1")
|
||||
suspend fun getAllTotpCiphers(
|
||||
@Query("SELECT * FROM ciphers WHERE user_id = :userId AND cipher_type = 1")
|
||||
suspend fun getAllLoginCiphers(
|
||||
userId: String,
|
||||
): List<CipherEntity>
|
||||
|
||||
|
||||
+11
-1
@@ -2,8 +2,10 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.database
|
||||
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.DeleteColumn
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.InstantTypeConverter
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
|
||||
@@ -27,11 +29,13 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||
FolderEntity::class,
|
||||
SendEntity::class,
|
||||
],
|
||||
version = 9,
|
||||
version = 10,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 6, to = 7),
|
||||
AutoMigration(from = 7, to = 8),
|
||||
AutoMigration(from = 8, to = 9),
|
||||
AutoMigration(from = 9, to = 10, RemoveTotpAutoMigration::class),
|
||||
],
|
||||
)
|
||||
@TypeConverters(InstantTypeConverter::class)
|
||||
@@ -62,3 +66,9 @@ abstract class VaultDatabase : RoomDatabase() {
|
||||
*/
|
||||
abstract fun sendsDao(): SendsDao
|
||||
}
|
||||
|
||||
/**
|
||||
* A defined migration to remove the `has_totp` field from the `cipher` table.
|
||||
*/
|
||||
@DeleteColumn.Entries(DeleteColumn(tableName = "ciphers", columnName = "has_totp"))
|
||||
class RemoveTotpAutoMigration : AutoMigrationSpec
|
||||
-5
@@ -23,11 +23,6 @@ data class CipherEntity(
|
||||
@ColumnInfo(name = "user_id")
|
||||
val userId: String,
|
||||
|
||||
// Default to true for initial migration.
|
||||
// Subsequent syncs will populate with correct values for optimizations.
|
||||
@ColumnInfo(name = "has_totp", defaultValue = "1")
|
||||
val hasTotp: Boolean,
|
||||
|
||||
@ColumnInfo(name = "cipher_type")
|
||||
val cipherType: String,
|
||||
|
||||
|
||||
+2
-1
@@ -117,6 +117,7 @@ fun Cipher.toEncryptedNetworkCipherResponse(
|
||||
key = key,
|
||||
encryptedFor = encryptedFor,
|
||||
archivedDate = archivedDate,
|
||||
data = data,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -490,7 +491,7 @@ fun SyncResponseJson.Cipher.toEncryptedSdkCipher(): Cipher =
|
||||
deletedDate = deletedDate,
|
||||
revisionDate = revisionDate,
|
||||
archivedDate = archivedDate,
|
||||
data = null,
|
||||
data = data,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
+6
-6
@@ -143,8 +143,8 @@ class AuthenticatorBridgeRepositoryTest {
|
||||
|
||||
// Add some ciphers to vaultDiskSource for each user,
|
||||
// and setup mock decryption for them:
|
||||
coEvery { vaultDiskSource.getTotpCiphers(USER_1_ID) } returns USER_1_CIPHERS
|
||||
coEvery { vaultDiskSource.getTotpCiphers(USER_2_ID) } returns USER_2_CIPHERS
|
||||
coEvery { vaultDiskSource.getLoginCiphers(USER_1_ID) } returns USER_1_CIPHERS
|
||||
coEvery { vaultDiskSource.getLoginCiphers(USER_2_ID) } returns USER_2_CIPHERS
|
||||
mockkStatic(
|
||||
SyncResponseJson.Cipher::toEncryptedSdkCipher,
|
||||
EnvironmentUrlDataJson::toEnvironmentUrlsOrDefault,
|
||||
@@ -213,7 +213,7 @@ class AuthenticatorBridgeRepositoryTest {
|
||||
userId = USER_2_ID,
|
||||
request = InitOrgCryptoRequest(organizationKeys = USER_2_ORG_KEYS),
|
||||
)
|
||||
vaultDiskSource.getTotpCiphers(userId = USER_2_ID)
|
||||
vaultDiskSource.getLoginCiphers(userId = USER_2_ID)
|
||||
scopedVaultSdkSource.decryptCipher(
|
||||
userId = USER_2_ID,
|
||||
cipher = USER_2_ENCRYPTED_SDK_TOTP_CIPHER,
|
||||
@@ -251,7 +251,7 @@ class AuthenticatorBridgeRepositoryTest {
|
||||
userId = USER_1_ID,
|
||||
request = InitOrgCryptoRequest(organizationKeys = USER_1_ORG_KEYS),
|
||||
)
|
||||
vaultDiskSource.getTotpCiphers(userId = USER_1_ID)
|
||||
vaultDiskSource.getLoginCiphers(userId = USER_1_ID)
|
||||
scopedVaultSdkSource.decryptCipher(
|
||||
userId = USER_1_ID,
|
||||
cipher = USER_1_ENCRYPTED_SDK_TOTP_CIPHER,
|
||||
@@ -273,7 +273,7 @@ class AuthenticatorBridgeRepositoryTest {
|
||||
userId = USER_2_ID,
|
||||
request = InitOrgCryptoRequest(organizationKeys = USER_2_ORG_KEYS),
|
||||
)
|
||||
vaultDiskSource.getTotpCiphers(userId = USER_2_ID)
|
||||
vaultDiskSource.getLoginCiphers(userId = USER_2_ID)
|
||||
scopedVaultSdkSource.decryptCipher(
|
||||
userId = USER_2_ID,
|
||||
cipher = USER_2_ENCRYPTED_SDK_TOTP_CIPHER,
|
||||
@@ -341,7 +341,7 @@ class AuthenticatorBridgeRepositoryTest {
|
||||
userId = USER_2_ID,
|
||||
request = InitOrgCryptoRequest(organizationKeys = USER_2_ORG_KEYS),
|
||||
)
|
||||
vaultDiskSource.getTotpCiphers(userId = USER_2_ID)
|
||||
vaultDiskSource.getLoginCiphers(userId = USER_2_ID)
|
||||
scopedVaultSdkSource.decryptCipher(
|
||||
userId = USER_2_ID,
|
||||
cipher = USER_2_ENCRYPTED_SDK_TOTP_CIPHER,
|
||||
|
||||
+4
-5
@@ -123,17 +123,17 @@ class VaultDiskSourceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTotpCiphers should return all CiphersDao totp ciphers`() = runTest {
|
||||
fun `getLoginCiphers should return all CiphersDao login ciphers`() = runTest {
|
||||
val cipherEntities = listOf(
|
||||
CIPHER_ENTITY,
|
||||
CIPHER_ENTITY.copy(id = "otherCipherId", hasTotp = false),
|
||||
CIPHER_ENTITY.copy(id = "otherCipherId", cipherType = "2"),
|
||||
)
|
||||
val ciphers = listOf(CIPHER_1)
|
||||
|
||||
val result1 = vaultDiskSource.getTotpCiphers(USER_ID)
|
||||
val result1 = vaultDiskSource.getLoginCiphers(USER_ID)
|
||||
assertEquals(emptyList<SyncResponseJson.Cipher>(), result1)
|
||||
ciphersDao.insertCiphers(cipherEntities)
|
||||
val result2 = vaultDiskSource.getTotpCiphers(USER_ID)
|
||||
val result2 = vaultDiskSource.getLoginCiphers(USER_ID)
|
||||
assertEquals(ciphers, result2)
|
||||
}
|
||||
|
||||
@@ -638,7 +638,6 @@ private const val CIPHER_JSON = """
|
||||
private val CIPHER_ENTITY = CipherEntity(
|
||||
id = "mockId-1",
|
||||
userId = USER_ID,
|
||||
hasTotp = true,
|
||||
cipherType = "1",
|
||||
cipherJson = CIPHER_JSON,
|
||||
organizationId = "mockOrganizationId-1",
|
||||
|
||||
+2
-2
@@ -56,8 +56,8 @@ class FakeCiphersDao : CiphersDao {
|
||||
): List<CipherEntity> =
|
||||
storedCiphers.filter { it.userId == userId && it.id in cipherIds }
|
||||
|
||||
override suspend fun getAllTotpCiphers(userId: String): List<CipherEntity> =
|
||||
storedCiphers.filter { it.userId == userId && it.hasTotp }
|
||||
override suspend fun getAllLoginCiphers(userId: String): List<CipherEntity> =
|
||||
storedCiphers.filter { it.userId == userId && it.cipherType == "1" }
|
||||
|
||||
override suspend fun getCipher(userId: String, cipherId: String): CipherEntity? =
|
||||
storedCiphers.find { it.userId == userId && it.id == cipherId }
|
||||
|
||||
@@ -774,6 +774,9 @@ data class SyncResponseJson(
|
||||
@SerialName("archivedDate")
|
||||
@Contextual
|
||||
val archivedDate: Instant?,
|
||||
|
||||
@SerialName("data")
|
||||
val data: String?,
|
||||
) {
|
||||
/**
|
||||
* Represents an attachment in the vault response.
|
||||
|
||||
@@ -50,6 +50,7 @@ fun createMockCipher(
|
||||
shouldViewPassword: Boolean = false,
|
||||
key: String? = "mockKey-$number",
|
||||
encryptedFor: String? = "mockEncryptedFor-$number",
|
||||
data: String? = null,
|
||||
): SyncResponseJson.Cipher =
|
||||
SyncResponseJson.Cipher(
|
||||
id = id,
|
||||
@@ -82,6 +83,7 @@ fun createMockCipher(
|
||||
shouldViewPassword = shouldViewPassword,
|
||||
key = key,
|
||||
encryptedFor = encryptedFor,
|
||||
data = data,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user