mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
[PM-15054] Add API for importing ciphers
Add an API for importing ciphers, including folders and folder relationships.
This commit is contained in:
parent
d418444dc0
commit
8b7f2f99b8
6 changed files with 138 additions and 0 deletions
|
@ -5,6 +5,8 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonReq
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
|
||||
|
@ -149,4 +151,9 @@ interface CiphersApi {
|
|||
*/
|
||||
@GET("ciphers/has-unassigned-ciphers")
|
||||
suspend fun hasUnassignedCiphers(): NetworkResult<Boolean>
|
||||
|
||||
@POST("ciphers/import")
|
||||
suspend fun importCiphers(
|
||||
@Body body: ImportCiphersJsonRequest,
|
||||
): NetworkResult<ImportCiphersResponseJson>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents an import ciphers request.
|
||||
*
|
||||
* @property folders A list of folders to import.
|
||||
* @property ciphers A list of ciphers to import.
|
||||
* @property folderRelationships A map of cipher folder relationships to import. Key correlates to
|
||||
* the index of the cipher in the ciphers list. Value correlates to the index of the folder in the
|
||||
* folders list.
|
||||
*/
|
||||
@Serializable
|
||||
data class ImportCiphersJsonRequest(
|
||||
@SerialName("folders")
|
||||
val folders: List<FolderWithIdJsonRequest>,
|
||||
@SerialName("ciphers")
|
||||
val ciphers: List<CipherJsonRequest>,
|
||||
@SerialName("folderRelationships")
|
||||
val folderRelationships: Map<Int, Int>,
|
||||
) {
|
||||
/**
|
||||
* Represents a folder request with an optional [id] if the folder already exists.
|
||||
*
|
||||
* @property name The name of the folder.
|
||||
* @property id The ID of the folder, if it already exists. Null otherwise.
|
||||
**/
|
||||
@Serializable
|
||||
data class FolderWithIdJsonRequest(
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
@SerialName("id")
|
||||
val id: String?,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The response body for importing ciphers.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class ImportCiphersResponseJson {
|
||||
|
||||
/**
|
||||
* Models a successful json response.
|
||||
*/
|
||||
@Serializable
|
||||
object Success : ImportCiphersResponseJson()
|
||||
|
||||
/**
|
||||
* Represents the json body of an invalid request.
|
||||
*
|
||||
* @param validationErrors a map where each value is a list of error messages for each key.
|
||||
* The values in the array should be used for display to the user, since the keys tend to come
|
||||
* back as nonsense. (eg: empty string key)
|
||||
*/
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("message")
|
||||
private val invalidMessage: String? = null,
|
||||
|
||||
@SerialName("Message")
|
||||
private val errorMessage: String? = null,
|
||||
|
||||
@SerialName("validationErrors")
|
||||
val validationErrors: Map<String, List<String>>?,
|
||||
) : ImportCiphersResponseJson() {
|
||||
/**
|
||||
* A generic error message.
|
||||
*/
|
||||
val message: String? get() = invalidMessage ?: errorMessage
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonReq
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
|
||||
|
@ -118,4 +120,9 @@ interface CiphersService {
|
|||
* Returns a boolean indicating if the active user has unassigned ciphers.
|
||||
*/
|
||||
suspend fun hasUnassignedCiphers(): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Attempt to import ciphers.
|
||||
*/
|
||||
suspend fun importCiphers(request: ImportCiphersJsonRequest): Result<ImportCiphersResponseJson>
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRes
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
|
||||
|
@ -216,6 +218,23 @@ class CiphersServiceImpl(
|
|||
.hasUnassignedCiphers()
|
||||
.toResult()
|
||||
|
||||
override suspend fun importCiphers(
|
||||
request: ImportCiphersJsonRequest,
|
||||
): Result<ImportCiphersResponseJson> =
|
||||
ciphersApi
|
||||
.importCiphers(body = request)
|
||||
.toResult()
|
||||
.map { ImportCiphersResponseJson.Success }
|
||||
.recoverCatching { throwable ->
|
||||
throwable
|
||||
.toBitwardenError()
|
||||
.parseErrorBodyOrNull<ImportCiphersResponseJson.Invalid>(
|
||||
code = 400,
|
||||
json = json,
|
||||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
private fun createMultipartBodyBuilder(
|
||||
encryptedFile: File,
|
||||
filename: String?,
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ImportCiphersJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
|
@ -321,6 +322,32 @@ class CiphersServiceTest : BaseServiceTest() {
|
|||
val result = ciphersService.hasUnassignedCiphers()
|
||||
assertTrue(result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importCiphers should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(""))
|
||||
val result = ciphersService.importCiphers(
|
||||
request = ImportCiphersJsonRequest(
|
||||
ciphers = listOf(createMockCipherJsonRequest(number = 1)),
|
||||
folders = emptyList(),
|
||||
folderRelationships = emptyMap(),
|
||||
),
|
||||
)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importCiphers should return an error when the response is an error`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(400))
|
||||
val result = ciphersService.importCiphers(
|
||||
request = ImportCiphersJsonRequest(
|
||||
ciphers = listOf(createMockCipherJsonRequest(number = 1)),
|
||||
folders = emptyList(),
|
||||
folderRelationships = emptyMap(),
|
||||
),
|
||||
)
|
||||
assertTrue(result.isFailure)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMockUri(
|
||||
|
|
Loading…
Reference in a new issue