BIT-1575: Update cipher collections functionality (#904)

This commit is contained in:
Ramsey Smith
2024-01-31 12:04:28 -07:00
committed by Álison Fernandes
parent bb0c91ee5a
commit 2d0353d744
11 changed files with 297 additions and 30 deletions

View File

@@ -6,6 +6,7 @@ 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.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.DELETE
@@ -71,6 +72,15 @@ interface CiphersApi {
@Body body: ShareCipherJsonRequest,
): Result<SyncResponseJson.Cipher>
/**
* Updates a cipher's collections.
*/
@PUT("ciphers/{cipherId}/collections")
suspend fun updateCipherCollections(
@Path("cipherId") cipherId: String,
@Body body: UpdateCipherCollectionsJsonRequest,
): Result<Unit>
/**
* Hard deletes a cipher.
*/

View File

@@ -0,0 +1,15 @@
package com.x8bit.bitwarden.data.vault.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents an update cipher collections request.
*
* @property collectionIds A list of collection ids associated with the cipher.
*/
@Serializable
data class UpdateCipherCollectionsJsonRequest(
@SerialName("CollectionIds")
val collectionIds: List<String>,
)

View File

@@ -6,6 +6,7 @@ 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.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
/**
@@ -57,6 +58,14 @@ interface CiphersService {
body: ShareCipherJsonRequest,
): Result<SyncResponseJson.Cipher>
/**
* Attempt to update a cipher's collections.
*/
suspend fun updateCipherCollections(
cipherId: String,
body: UpdateCipherCollectionsJsonRequest,
): Result<Unit>
/**
* Attempt to hard delete a cipher.
*/

View File

@@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrg
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
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
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
@@ -118,6 +119,15 @@ class CiphersServiceImpl(
body = body,
)
override suspend fun updateCipherCollections(
cipherId: String,
body: UpdateCipherCollectionsJsonRequest,
): Result<Unit> =
ciphersApi.updateCipherCollections(
cipherId = cipherId,
body = body,
)
override suspend fun hardDeleteCipher(cipherId: String): Result<Unit> =
ciphersApi.hardDeleteCipher(cipherId = cipherId)

View File

@@ -257,6 +257,15 @@ interface VaultRepository : VaultLockManager {
collectionIds: List<String>,
): ShareCipherResult
/**
* Attempt to update a cipher with the given collectionIds.
*/
suspend fun updateCipherCollections(
cipherId: String,
cipherView: CipherView,
collectionIds: List<String>,
): ShareCipherResult
/**
* Attempt to create a send. The [fileUri] _must_ be present when the given [SendView] has a
* [SendView.type] of [SendType.FILE].

View File

@@ -38,6 +38,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonReq
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
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
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateFolderResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
@@ -753,7 +754,7 @@ class VaultRepositoryImpl(
cipherView: CipherView,
collectionIds: List<String>,
): ShareCipherResult {
val userId = activeUserId ?: return ShareCipherResult.Error(null)
val userId = activeUserId ?: return ShareCipherResult.Error
return vaultSdkSource
.encryptCipher(
userId = userId,
@@ -768,12 +769,40 @@ class VaultRepositoryImpl(
),
)
}
.onSuccess { vaultDiskSource.saveCipher(userId = userId, cipher = it) }
.fold(
onFailure = { ShareCipherResult.Error(errorMessage = null) },
onSuccess = {
vaultDiskSource.saveCipher(userId = userId, cipher = it)
ShareCipherResult.Success
},
onFailure = { ShareCipherResult.Error },
onSuccess = { ShareCipherResult.Success },
)
}
override suspend fun updateCipherCollections(
cipherId: String,
cipherView: CipherView,
collectionIds: List<String>,
): ShareCipherResult {
val userId = activeUserId ?: return ShareCipherResult.Error
return ciphersService
.updateCipherCollections(
cipherId = cipherId,
body = UpdateCipherCollectionsJsonRequest(collectionIds = collectionIds),
)
.flatMap {
vaultSdkSource
.encryptCipher(
userId = userId,
cipherView = cipherView.copy(collectionIds = collectionIds),
)
}
.onSuccess { cipher ->
vaultDiskSource.saveCipher(
userId = userId,
cipher = cipher.toEncryptedNetworkCipherResponse(),
)
}
.fold(
onSuccess = { ShareCipherResult.Success },
onFailure = { ShareCipherResult.Error },
)
}

View File

@@ -10,8 +10,7 @@ sealed class ShareCipherResult {
data object Success : ShareCipherResult()
/**
* Generic error while sharing cipher. The optional [errorMessage] may be displayed directly in
* the UI when present.
* Generic error while sharing cipher.
*/
data class Error(val errorMessage: String?) : ShareCipherResult()
data object Error : ShareCipherResult()
}

View File

@@ -60,14 +60,14 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
) { cipherViewState, collectionsState, userState ->
VaultMoveToOrganizationAction.Internal.VaultDataReceive(
vaultData = combineDataStates(
dataState1 = cipherViewState.map { Unit },
dataState1 = cipherViewState,
dataState2 = collectionsState,
dataState3 = DataState.Loaded(userState).map { Unit },
) { _, collectionsData, _ ->
dataState3 = DataState.Loaded(userState),
) { cipherData, collectionsData, userData ->
Triple(
first = cipherViewState.data,
first = cipherData,
second = collectionsData,
third = userState,
third = userData,
)
},
)
@@ -270,6 +270,12 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
cipherView: CipherView,
contentState: VaultMoveToOrganizationState.ViewState.Content,
) {
val collectionIds = contentState
.selectedOrganization
.collections
.filter { it.isSelected }
.map { it.id }
mutableStateFlow.update {
it.copy(
dialogState = VaultMoveToOrganizationState.DialogState.Loading(
@@ -280,17 +286,21 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
viewModelScope.launch {
trySendAction(
VaultMoveToOrganizationAction.Internal.ShareCipherResultReceive(
vaultRepository.shareCipher(
cipherId = mutableStateFlow.value.vaultItemId,
cipherView = cipherView.copy(
organizationId = contentState.selectedOrganizationId,
),
collectionIds = contentState
.selectedOrganization
.collections
.filter { it.isSelected }
.map { it.id },
),
if (state.onlyShowCollections) {
vaultRepository.updateCipherCollections(
cipherId = mutableStateFlow.value.vaultItemId,
cipherView = cipherView,
collectionIds = collectionIds,
)
} else {
vaultRepository.shareCipher(
cipherId = mutableStateFlow.value.vaultItemId,
cipherView = cipherView.copy(
organizationId = contentState.selectedOrganizationId,
),
collectionIds = collectionIds,
)
},
),
)
}