diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/CiphersApi.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/CiphersApi.kt index f5a6b27006..ae9541d858 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/CiphersApi.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/CiphersApi.kt @@ -4,6 +4,8 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import retrofit2.http.Body import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path /** * Defines raw calls under the /ciphers API with authentication applied. @@ -15,4 +17,13 @@ interface CiphersApi { */ @POST("ciphers") suspend fun createCipher(@Body body: CipherJsonRequest): Result + + /** + * Updates a cipher. + */ + @PUT("ciphers/{cipherId}") + suspend fun updateCipher( + @Path("cipherId") cipherId: String, + @Body body: CipherJsonRequest, + ): Result } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersService.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersService.kt index 74eae02efb..c67468b0b3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersService.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersService.kt @@ -11,4 +11,12 @@ interface CiphersService { * Attempt to create a cipher. */ suspend fun createCipher(body: CipherJsonRequest): Result + + /** + * Attempt to update a cipher. + */ + suspend fun updateCipher( + cipherId: String, + body: CipherJsonRequest, + ): Result } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceImpl.kt index 1d0bc059b8..5ee36c7135 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceImpl.kt @@ -9,4 +9,13 @@ class CiphersServiceImpl constructor( ) : CiphersService { override suspend fun createCipher(body: CipherJsonRequest): Result = ciphersApi.createCipher(body = body) + + override suspend fun updateCipher( + cipherId: String, + body: CipherJsonRequest, + ): Result = + ciphersApi.updateCipher( + cipherId = cipherId, + body = body, + ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt index 6b088f080c..832693666f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt @@ -6,6 +6,7 @@ import com.bitwarden.core.Kdf import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.SendData +import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultState import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult @@ -86,4 +87,12 @@ interface VaultRepository { * Attempt to create a cipher. */ suspend fun createCipher(cipherView: CipherView): CreateCipherResult + + /** + * Attempt to update a cipher. + */ + suspend fun updateCipher( + cipherId: String, + cipherView: CipherView, + ): UpdateCipherResult } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt index e559a546e8..c3fa55d3bc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.SendData +import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultState import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult @@ -256,6 +257,26 @@ class VaultRepositoryImpl constructor( }, ) + override suspend fun updateCipher( + cipherId: String, + cipherView: CipherView, + ): UpdateCipherResult = + vaultSdkSource + .encryptCipher(cipherView = cipherView) + .flatMap { cipher -> + ciphersService.updateCipher( + cipherId = cipherId, + body = cipher.toEncryptedNetworkCipher(), + ) + } + .fold( + onFailure = { UpdateCipherResult.Error }, + onSuccess = { + sync() + UpdateCipherResult.Success + }, + ) + // TODO: This is temporary. Eventually this needs to be based on the presence of various // user keys but this will likely require SDK updates to support this (BIT-1190). private fun setVaultToUnlocked(userId: String) { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt new file mode 100644 index 0000000000..9e8f99ee8c --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt @@ -0,0 +1,17 @@ +package com.x8bit.bitwarden.data.vault.repository.model + +/** + * Models result of updating a cipher. + */ +sealed class UpdateCipherResult { + + /** + * Cipher updated successfully. + */ + data object Success : UpdateCipherResult() + + /** + * Generic error while updating cipher. + */ + data object Error : UpdateCipherResult() +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt index 819c981a9e..5e0a2e4a9d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt @@ -19,7 +19,7 @@ class CiphersServiceTest : BaseServiceTest() { @Test fun `createCipher should return the correct response`() = runTest { - server.enqueue(MockResponse().setBody(CREATE_CIPHER_SUCCESS_JSON)) + server.enqueue(MockResponse().setBody(CREATE_UPDATE_CIPHER_SUCCESS_JSON)) val result = ciphersService.createCipher( body = createMockCipherJsonRequest(number = 1), ) @@ -28,9 +28,22 @@ class CiphersServiceTest : BaseServiceTest() { result.getOrThrow(), ) } + + @Test + fun `updateCipher should return the correct response`() = runTest { + server.enqueue(MockResponse().setBody(CREATE_UPDATE_CIPHER_SUCCESS_JSON)) + val result = ciphersService.updateCipher( + cipherId = "cipher-id-1", + body = createMockCipherJsonRequest(number = 1), + ) + assertEquals( + createMockCipher(number = 1), + result.getOrThrow(), + ) + } } -private const val CREATE_CIPHER_SUCCESS_JSON = """ +private const val CREATE_UPDATE_CIPHER_SUCCESS_JSON = """ { "notes": "mockNotes-1", "attachments": [ diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt index cd8ce3b099..e8290638a0 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt @@ -31,6 +31,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSend import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.SendData +import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultState import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult @@ -1484,6 +1485,83 @@ class VaultRepositoryTest { ) } + @Test + fun `updateCipher with encryptCipher failure should return UpdateCipherResult failure`() = + runTest { + val cipherId = "cipherId1234" + val mockCipherView = createMockCipherView(number = 1) + coEvery { + vaultSdkSource.encryptCipher(cipherView = mockCipherView) + } returns IllegalStateException().asFailure() + + val result = vaultRepository.updateCipher( + cipherId = cipherId, + cipherView = mockCipherView, + ) + + assertEquals(UpdateCipherResult.Error, result) + } + + @Test + @Suppress("MaxLineLength") + fun `updateCipher with ciphersService updateCipher failure should return UpdateCipherResult failure`() = + runTest { + val cipherId = "cipherId1234" + val mockCipherView = createMockCipherView(number = 1) + coEvery { + vaultSdkSource.encryptCipher(cipherView = mockCipherView) + } returns createMockSdkCipher(number = 1).asSuccess() + coEvery { + ciphersService.updateCipher( + cipherId = cipherId, + body = createMockCipherJsonRequest(number = 1), + ) + } returns IllegalStateException().asFailure() + + val result = vaultRepository.updateCipher( + cipherId = cipherId, + cipherView = mockCipherView, + ) + + assertEquals(UpdateCipherResult.Error, result) + } + + @Test + @Suppress("MaxLineLength") + fun `updateCipher with ciphersService updateCipher success should return UpdateCipherResult success`() = + runTest { + val cipherId = "cipherId1234" + val mockCipherView = createMockCipherView(number = 1) + coEvery { + vaultSdkSource.encryptCipher(cipherView = mockCipherView) + } returns createMockSdkCipher(number = 1).asSuccess() + coEvery { + ciphersService.updateCipher( + cipherId = cipherId, + body = createMockCipherJsonRequest(number = 1), + ) + } returns createMockCipher(number = 1).asSuccess() + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherView(1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(1))) + } returns listOf(createMockSendView(1)).asSuccess() + + val result = vaultRepository.updateCipher( + cipherId = cipherId, + cipherView = mockCipherView, + ) + + assertEquals(UpdateCipherResult.Success, result) + } + //region Helper functions /**