From 982d56b4298dfd225e0255ff3a7648d8352abbfd Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 3 Jun 2026 11:04:42 -0500 Subject: [PATCH] PM-38280: Update collection API to V2 --- .../data/vault/manager/CipherManager.kt | 1 - .../data/vault/manager/CipherManagerImpl.kt | 24 +-- .../VaultMoveToOrganizationViewModel.kt | 1 - .../datasource/sdk/model/CipherViewUtil.kt | 3 +- .../data/vault/manager/CipherManagerTest.kt | 95 ++++----- .../VaultMoveToOrganizationViewModelTest.kt | 2 - .../com/bitwarden/network/api/CiphersApi.kt | 5 +- .../UpdateCipherCollectionsResponseJson.kt | 15 ++ .../network/service/CiphersService.kt | 3 +- .../network/service/CiphersServiceImpl.kt | 3 +- .../network/service/CiphersServiceTest.kt | 185 +++++++++++++++++- 11 files changed, 254 insertions(+), 83 deletions(-) create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/UpdateCipherCollectionsResponseJson.kt diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt index 4935f04b7e6..6ecb35a4d07 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt @@ -130,7 +130,6 @@ interface CipherManager { */ suspend fun updateCipherCollections( cipherId: String, - cipherView: CipherView, collectionIds: List, ): ShareCipherResult diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt index c9e4b6e849f..5d3118696be 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt @@ -442,7 +442,6 @@ class CipherManagerImpl( override suspend fun updateCipherCollections( cipherId: String, - cipherView: CipherView, collectionIds: List, ): ShareCipherResult { val userId = activeUserId ?: return ShareCipherResult.Error(error = NoActiveUserException()) @@ -451,17 +450,18 @@ class CipherManagerImpl( cipherId = cipherId, body = UpdateCipherCollectionsJsonRequest(collectionIds = collectionIds), ) - .flatMap { - vaultSdkSource.encryptCipher( - userId = userId, - cipherView = cipherView.copy(collectionIds = collectionIds), - ) - } - .onSuccess { encryptionContext -> - vaultDiskSource.saveCipher( - userId = userId, - cipher = encryptionContext.toEncryptedNetworkCipherResponse(), - ) + .onSuccess { response -> + response + .cipher + ?.let { + // Save the updated cipher to disk. + vaultDiskSource.saveCipher(userId = userId, cipher = it) + } + ?: run { + // The user no longer has any collection access to the cipher after + // the update, so remove it from disk. + vaultDiskSource.deleteCipher(userId = userId, cipherId = cipherId) + } } .fold( onSuccess = { ShareCipherResult.Success }, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt index 6de9d0cb093..c1559b2b44e 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt @@ -309,7 +309,6 @@ class VaultMoveToOrganizationViewModel @Inject constructor( VaultMoveToOrganizationAction.Internal.ShareCipherResultReceive( shareCipherResult = vaultRepository.updateCipherCollections( cipherId = state.vaultItemId, - cipherView = cipherView, collectionIds = collectionIds, ), message = BitwardenString.item_updated.asText(), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt index 43fff632670..3f9a6a28d4e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt @@ -49,6 +49,7 @@ fun createMockCipherView( totp: String? = "mockTotp-$number", organizationId: String? = "mockOrganizationId-$number", folderId: String? = "mockId-$number", + collectionIds: List = listOf("mockId-$number"), notes: String? = "mockNotes-$number", password: String? = "mockPassword-$number", clock: Clock = FIXED_CLOCK, @@ -75,7 +76,7 @@ fun createMockCipherView( id = "mockId-$number", organizationId = organizationId, folderId = folderId, - collectionIds = listOf("mockId-$number"), + collectionIds = collectionIds, key = "mockKey-$number", name = "mockName-$number", notes = notes, diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt index 15709f0bba3..efcf65f06a4 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt @@ -18,6 +18,7 @@ import com.bitwarden.network.model.ShareCipherJsonRequest import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.network.model.UnarchiveCipherResponseJson import com.bitwarden.network.model.UpdateCipherCollectionsJsonRequest +import com.bitwarden.network.model.UpdateCipherCollectionsResponseJson import com.bitwarden.network.model.UpdateCipherResponseJson import com.bitwarden.network.model.createMockAttachment import com.bitwarden.network.model.createMockAttachmentResponse @@ -1511,7 +1512,6 @@ class CipherManagerTest { val result = cipherManager.updateCipherCollections( cipherId = "cipherId", - cipherView = mockk(), collectionIds = emptyList(), ) @@ -1520,109 +1520,96 @@ class CipherManagerTest { @Test @Suppress("MaxLineLength") - fun `updateCipherCollections with cipherService updateCipherCollections success should return ShareCipherResultSuccess`() = + fun `updateCipherCollections with cipherService updateCipherCollections failure should return Error`() = runTest { fakeAuthDiskSource.userState = MOCK_USER_STATE val userId = "mockId-1" - coEvery { - vaultSdkSource.encryptCipher( - userId = userId, - cipherView = createMockCipherView(number = 1), - ) - } returns createMockEncryptionContext( - number = 1, - cipher = createMockSdkCipher(number = 1, clock = clock), - ).asSuccess() + val cipherId = "mockCipherId-1" + val error = Throwable("Fail") coEvery { ciphersService.updateCipherCollections( - cipherId = "mockId-1", + cipherId = cipherId, body = UpdateCipherCollectionsJsonRequest( collectionIds = listOf("mockId-1"), ), ) - } returns Unit.asSuccess() - coEvery { vaultDiskSource.saveCipher(userId, any()) } just runs + } returns error.asFailure() val result = cipherManager.updateCipherCollections( - cipherId = "mockId-1", - cipherView = createMockCipherView(number = 1).copy( - collectionIds = listOf("mockId-1"), - ), + cipherId = cipherId, collectionIds = listOf("mockId-1"), ) - assertEquals(ShareCipherResult.Success, result) + assertEquals(ShareCipherResult.Error(error), result) + coVerify(exactly = 0) { + vaultDiskSource.deleteCipher(userId = userId, cipherId = any()) + vaultDiskSource.saveCipher(userId = userId, cipher = any()) + } } @Test @Suppress("MaxLineLength") - fun `updateCipherCollections with updateCipherCollections shareCipher failure should return ShareCipherResultError`() = + fun `updateCipherCollections with cipherService updateCipherCollections success and null cipher should delete the cipher and return Success`() = runTest { fakeAuthDiskSource.userState = MOCK_USER_STATE val userId = "mockId-1" - coEvery { - vaultSdkSource.encryptCipher( - userId = userId, - cipherView = createMockCipherView(number = 1), - ) - } returns createMockEncryptionContext( - number = 1, - cipher = createMockSdkCipher(number = 1, clock = clock), - ).asSuccess() - val error = Throwable("Fail") + val cipherId = "mockCipherId-1" + val response = UpdateCipherCollectionsResponseJson(cipher = null) coEvery { ciphersService.updateCipherCollections( - cipherId = "mockId-1", + cipherId = cipherId, body = UpdateCipherCollectionsJsonRequest( collectionIds = listOf("mockId-1"), ), ) - } returns error.asFailure() - coEvery { vaultDiskSource.saveCipher(userId, any()) } just runs + } returns response.asSuccess() + coEvery { vaultDiskSource.deleteCipher(userId = userId, cipherId = cipherId) } just runs val result = cipherManager.updateCipherCollections( - cipherId = "mockId-1", - cipherView = createMockCipherView(number = 1).copy( - collectionIds = listOf("mockId-1"), - ), + cipherId = cipherId, collectionIds = listOf("mockId-1"), ) - assertEquals(ShareCipherResult.Error(error = error), result) + assertEquals(ShareCipherResult.Success, result) + coVerify(exactly = 0) { + vaultDiskSource.saveCipher(userId = userId, cipher = any()) + } + coVerify(exactly = 1) { + vaultDiskSource.deleteCipher(userId = userId, cipherId = cipherId) + } } @Test @Suppress("MaxLineLength") - fun `updateCipherCollections with updateCipherCollections encryptCipher failure should return ShareCipherResultError`() = + fun `updateCipherCollections with cipherService updateCipherCollections success and valid cipher should save the cipher and return Success`() = runTest { fakeAuthDiskSource.userState = MOCK_USER_STATE val userId = "mockId-1" - val error = Throwable("Fail") - coEvery { - vaultSdkSource.encryptCipher( - userId = userId, - cipherView = createMockCipherView(number = 1), - ) - } returns error.asFailure() + val cipherId = "mockCipherId-1" + val cipher = mockk() + val response = UpdateCipherCollectionsResponseJson(cipher = cipher) coEvery { ciphersService.updateCipherCollections( - cipherId = "mockId-1", + cipherId = cipherId, body = UpdateCipherCollectionsJsonRequest( collectionIds = listOf("mockId-1"), ), ) - } returns Unit.asSuccess() - coEvery { vaultDiskSource.saveCipher(userId, any()) } just runs + } returns response.asSuccess() + coEvery { vaultDiskSource.saveCipher(userId = userId, cipher = cipher) } just runs val result = cipherManager.updateCipherCollections( - cipherId = "mockId-1", - cipherView = createMockCipherView(number = 1).copy( - collectionIds = listOf("mockId-1"), - ), + cipherId = cipherId, collectionIds = listOf("mockId-1"), ) - assertEquals(ShareCipherResult.Error(error = error), result) + assertEquals(ShareCipherResult.Success, result) + coVerify(exactly = 0) { + vaultDiskSource.deleteCipher(userId = userId, cipherId = any()) + } + coVerify(exactly = 1) { + vaultDiskSource.saveCipher(userId = userId, cipher = cipher) + } } @Test diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt index e2086bcbd0d..60bb978155b 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt @@ -513,7 +513,6 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { coEvery { vaultRepository.updateCipherCollections( cipherId = "mockCipherId", - cipherView = createMockCipherView(number = 1), collectionIds = listOf("mockId-1"), ) } returns ShareCipherResult.Success @@ -533,7 +532,6 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { coVerify { vaultRepository.updateCipherCollections( cipherId = "mockCipherId", - cipherView = createMockCipherView(number = 1), collectionIds = listOf("mockId-1"), ) } diff --git a/network/src/main/kotlin/com/bitwarden/network/api/CiphersApi.kt b/network/src/main/kotlin/com/bitwarden/network/api/CiphersApi.kt index 94f72379657..2b70b3c79bf 100644 --- a/network/src/main/kotlin/com/bitwarden/network/api/CiphersApi.kt +++ b/network/src/main/kotlin/com/bitwarden/network/api/CiphersApi.kt @@ -11,6 +11,7 @@ import com.bitwarden.network.model.NetworkResult import com.bitwarden.network.model.ShareCipherJsonRequest import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.network.model.UpdateCipherCollectionsJsonRequest +import com.bitwarden.network.model.UpdateCipherCollectionsResponseJson import okhttp3.MultipartBody import retrofit2.http.Body import retrofit2.http.DELETE @@ -115,11 +116,11 @@ internal interface CiphersApi { /** * Updates a cipher's collections. */ - @PUT("ciphers/{cipherId}/collections") + @PUT("ciphers/{cipherId}/collections_v2") suspend fun updateCipherCollections( @Path("cipherId") cipherId: String, @Body body: UpdateCipherCollectionsJsonRequest, - ): NetworkResult + ): NetworkResult /** * Hard deletes a cipher. diff --git a/network/src/main/kotlin/com/bitwarden/network/model/UpdateCipherCollectionsResponseJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/UpdateCipherCollectionsResponseJson.kt new file mode 100644 index 00000000000..7a6384191d8 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/UpdateCipherCollectionsResponseJson.kt @@ -0,0 +1,15 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents API response model for updating a cipher's collections. + * + * @property cipher The updated cipher, or `null` if the user no longer has access. + */ +@Serializable +data class UpdateCipherCollectionsResponseJson( + @SerialName("cipher") + val cipher: SyncResponseJson.Cipher?, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/service/CiphersService.kt b/network/src/main/kotlin/com/bitwarden/network/service/CiphersService.kt index 80d1aca687f..ce552d26f8d 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/CiphersService.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/CiphersService.kt @@ -16,6 +16,7 @@ import com.bitwarden.network.model.ShareCipherJsonRequest import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.network.model.UnarchiveCipherResponseJson import com.bitwarden.network.model.UpdateCipherCollectionsJsonRequest +import com.bitwarden.network.model.UpdateCipherCollectionsResponseJson import com.bitwarden.network.model.UpdateCipherResponseJson import java.io.File @@ -101,7 +102,7 @@ interface CiphersService { suspend fun updateCipherCollections( cipherId: String, body: UpdateCipherCollectionsJsonRequest, - ): Result + ): Result /** * Attempt to hard delete a cipher. diff --git a/network/src/main/kotlin/com/bitwarden/network/service/CiphersServiceImpl.kt b/network/src/main/kotlin/com/bitwarden/network/service/CiphersServiceImpl.kt index 4c0d3407a33..47aa2cc7d26 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/CiphersServiceImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/CiphersServiceImpl.kt @@ -20,6 +20,7 @@ import com.bitwarden.network.model.ShareCipherJsonRequest import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.network.model.UnarchiveCipherResponseJson import com.bitwarden.network.model.UpdateCipherCollectionsJsonRequest +import com.bitwarden.network.model.UpdateCipherCollectionsResponseJson import com.bitwarden.network.model.UpdateCipherResponseJson import com.bitwarden.network.model.toBitwardenError import com.bitwarden.network.util.NetworkErrorCode @@ -235,7 +236,7 @@ internal class CiphersServiceImpl( override suspend fun updateCipherCollections( cipherId: String, body: UpdateCipherCollectionsJsonRequest, - ): Result = + ): Result = ciphersApi .updateCipherCollections( cipherId = cipherId, diff --git a/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt index 1c68399eed9..018da38e63e 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt @@ -16,6 +16,7 @@ import com.bitwarden.network.model.ImportCiphersResponseJson import com.bitwarden.network.model.ShareCipherJsonRequest import com.bitwarden.network.model.UnarchiveCipherResponseJson import com.bitwarden.network.model.UpdateCipherCollectionsJsonRequest +import com.bitwarden.network.model.UpdateCipherCollectionsResponseJson import com.bitwarden.network.model.UpdateCipherResponseJson import com.bitwarden.network.model.createMockAttachment import com.bitwarden.network.model.createMockAttachmentInfo @@ -419,7 +420,12 @@ class CiphersServiceTest : BaseServiceTest() { @Test fun `updateCipherCollections should execute the updateCipherCollections API`() = runTest { - server.enqueue(MockResponse().setResponseCode(200)) + server.enqueue( + MockResponse() + .setBody(UPDATE_CIPHER_COLLECTION_SUCCESS_JSON) + .setResponseCode(200), + ) + val response = UpdateCipherCollectionsResponseJson(cipher = createMockCipher(number = 1)) val result = ciphersService.updateCipherCollections( cipherId = "mockId-1", @@ -428,7 +434,7 @@ class CiphersServiceTest : BaseServiceTest() { ), ) assertEquals( - Unit, + response, result.getOrThrow(), ) } @@ -613,7 +619,7 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """ "mockCollectionId-1" ], "name": "mockName-1", - "id": "mockId-1" + "id": "mockId-1", "fields": [ { "linkedId": 100, @@ -661,7 +667,7 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """ "expirationDate": "mockExpirationDate-1", "dateOfBirth": "mockDateOfBirth-1", "issueDate": "mockIssueDate-1", - "licenseClass": "mockLicenseClass-1", + "licenseClass": "mockLicenseClass-1" }, "passport": { "surname": "mockSurname-1", @@ -676,7 +682,7 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """ "issuingCountry": "mockIssuingCountry-1", "issuingAuthority": "mockIssuingAuthority-1", "issueDate": "mockIssueDate-1", - "expirationDate": "mockExpirationDate-1", + "expirationDate": "mockExpirationDate-1" }, "encryptedFor": "mockEncryptedFor-1", "archivedDate": "2023-10-27T12:00:00.00Z" @@ -782,7 +788,7 @@ private const val CREATE_RESTORE_UPDATE_CIPHER_SUCCESS_JSON = """ "mockCollectionId-1" ], "name": "mockName-1", - "id": "mockId-1" + "id": "mockId-1", "fields": [ { "linkedId": 100, @@ -830,7 +836,7 @@ private const val CREATE_RESTORE_UPDATE_CIPHER_SUCCESS_JSON = """ "expirationDate": "mockExpirationDate-1", "dateOfBirth": "mockDateOfBirth-1", "issueDate": "mockIssueDate-1", - "licenseClass": "mockLicenseClass-1", + "licenseClass": "mockLicenseClass-1" }, "passport": { "surname": "mockSurname-1", @@ -845,7 +851,7 @@ private const val CREATE_RESTORE_UPDATE_CIPHER_SUCCESS_JSON = """ "issuingCountry": "mockIssuingCountry-1", "issuingAuthority": "mockIssuingAuthority-1", "issueDate": "mockIssueDate-1", - "expirationDate": "mockExpirationDate-1", + "expirationDate": "mockExpirationDate-1" }, "encryptedFor": "mockEncryptedFor-1", "archivedDate": "2023-10-27T12:00:00.00Z" @@ -875,3 +881,166 @@ private const val GET_CIPHER_ATTACHMENT_SUCCESS_JSON = """ "key": "mockKey-1" } """ + +private const val UPDATE_CIPHER_COLLECTION_SUCCESS_JSON = """ +{ + "cipher": { + "notes": "mockNotes-1", + "attachments": [ + { + "fileName": "mockFileName-1", + "size": 1, + "sizeName": "mockSizeName-1", + "id": "mockId-1", + "url": "mockUrl-1", + "key": "mockKey-1" + } + ], + "organizationUseTotp": false, + "reprompt": 0, + "edit": false, + "passwordHistory": [ + { + "password": "mockPassword-1", + "lastUsedDate": "2023-10-27T12:00:00.00Z" + } + ], + "permissions": { + "delete": true, + "restore": true + }, + "revisionDate": "2023-10-27T12:00:00.00Z", + "type": 1, + "login": { + "uris": [ + { + "match": 1, + "uri": "mockUri-1", + "uriChecksum": "mockUriChecksum-1" + } + ], + "totp": "mockTotp-1", + "password": "mockPassword-1", + "passwordRevisionDate": "2023-10-27T12:00:00.00Z", + "autofillOnPageLoad": false, + "uri": "mockUri-1", + "username": "mockUsername-1", + "fido2Credentials": [ + { + "credentialId": "mockCredentialId-1", + "keyType": "mockKeyType-1", + "keyAlgorithm": "mockKeyAlgorithm-1", + "keyCurve": "mockKeyCurve-1", + "keyValue": "mockKeyValue-1", + "rpId": "mockRpId-1", + "rpName": "mockRpName-1", + "userHandle": "mockUserHandle-1", + "userName": "mockUserName-1", + "userDisplayName": "mockUserDisplayName-1", + "counter": "mockCounter-1", + "discoverable": "mockDiscoverable-1", + "creationDate": "2023-10-27T12:00:00.00Z" + } + ] + }, + "creationDate": "2023-10-27T12:00:00.00Z", + "secureNote": { + "type": 0 + }, + "folderId": "mockFolderId-1", + "organizationId": "mockOrganizationId-1", + "deletedDate": "2023-10-27T12:00:00.00Z", + "identity": { + "passportNumber": "mockPassportNumber-1", + "lastName": "mockLastName-1", + "address3": "mockAddress3-1", + "address2": "mockAddress2-1", + "city": "mockCity-1", + "country": "mockCountry-1", + "address1": "mockAddress1-1", + "postalCode": "mockPostalCode-1", + "title": "mockTitle-1", + "ssn": "mockSsn-1", + "firstName": "mockFirstName-1", + "phone": "mockPhone-1", + "middleName": "mockMiddleName-1", + "company": "mockCompany-1", + "licenseNumber": "mockLicenseNumber-1", + "state": "mockState-1", + "email": "mockEmail-1", + "username": "mockUsername-1" + }, + "collectionIds": [ + "mockCollectionId-1" + ], + "name": "mockName-1", + "id": "mockId-1", + "fields": [ + { + "linkedId": 100, + "name": "mockName-1", + "type": 1, + "value": "mockValue-1" + } + ], + "viewPassword": false, + "favorite": false, + "card": { + "number": "mockNumber-1", + "expMonth": "mockExpMonth-1", + "code": "mockCode-1", + "expYear": "mockExpirationYear-1", + "cardholderName": "mockCardholderName-1", + "brand": "mockBrand-1" + }, + "key": "mockKey-1", + "sshKey": { + "publicKey": "mockPublicKey-1", + "privateKey": "mockPrivateKey-1", + "keyFingerprint": "mockKeyFingerprint-1" + }, + "bankAccount": { + "bankName": "mockBankName-1", + "nameOnAccount": "mockNameOnAccount-1", + "accountType": "mockAccountType-1", + "accountNumber": "mockAccountNumber-1", + "routingNumber": "mockRoutingNumber-1", + "branchNumber": "mockBranchNumber-1", + "pin": "mockPin-1", + "swiftCode": "mockSwiftCode-1", + "iban": "mockIban-1", + "bankContactPhone": "mockBankContactPhone-1" + }, + "driversLicense": { + "firstName": "mockFirstName-1", + "middleName": "mockMiddleName-1", + "lastName": "mockLastName-1", + "licenseNumber": "mockLicenseNumber-1", + "issuingCountry": "mockIssuingCountry-1", + "issuingState": "mockIssuingState-1", + "issuingAuthority": "mockIssuingAuthority-1", + "expirationDate": "mockExpirationDate-1", + "dateOfBirth": "mockDateOfBirth-1", + "issueDate": "mockIssueDate-1", + "licenseClass": "mockLicenseClass-1" + }, + "passport": { + "surname": "mockSurname-1", + "givenName": "mockGivenName-1", + "dateOfBirth": "mockDateOfBirth-1", + "birthPlace": "mockBirthPlace-1", + "sex": "mockSex-1", + "nationality": "mockNationality-1", + "passportNumber": "mockPassportNumber-1", + "passportType": "mockPassportType-1", + "nationalIdentificationNumber": "mockNationalIdentificationNumber-1", + "issuingCountry": "mockIssuingCountry-1", + "issuingAuthority": "mockIssuingAuthority-1", + "issueDate": "mockIssueDate-1", + "expirationDate": "mockExpirationDate-1" + }, + "encryptedFor": "mockEncryptedFor-1", + "archivedDate": "2023-10-27T12:00:00.00Z" + } +} +"""