Remove the send password (#579)

This commit is contained in:
David Perez
2024-06-20 17:08:07 +01:00
committed by Álison Fernandes
parent 7a6088a23d
commit 3f7fe90c5c
15 changed files with 375 additions and 13 deletions
@@ -35,4 +35,10 @@ interface SendsApi {
*/
@DELETE("sends/{sendId}")
suspend fun deleteSend(@Path("sendId") sendId: String): Result<Unit>
/**
* Deletes a send.
*/
@PUT("sends/{sendId}/remove-password")
suspend fun removeSendPassword(@Path("sendId") sendId: String): Result<SyncResponseJson.Send>
}
@@ -24,9 +24,16 @@ interface SendsService {
): Result<UpdateSendResponseJson>
/**
* Attempt to delete a cipher.
* Attempt to delete a send.
*/
suspend fun deleteSend(
sendId: String,
): Result<Unit>
/**
* Attempt to remove password protection from a send.
*/
suspend fun removeSendPassword(
sendId: String,
): Result<UpdateSendResponseJson>
}
@@ -40,4 +40,18 @@ class SendsServiceImpl(
override suspend fun deleteSend(sendId: String): Result<Unit> =
sendsApi.deleteSend(sendId = sendId)
override suspend fun removeSendPassword(sendId: String): Result<UpdateSendResponseJson> =
sendsApi
.removeSendPassword(sendId = sendId)
.map { UpdateSendResponseJson.Success(send = it) }
.recoverCatching { throwable ->
throwable
.toBitwardenError()
.parseErrorBodyOrNull<UpdateSendResponseJson.Invalid>(
code = 400,
json = json,
)
?: throw throwable
}
}
@@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
@@ -151,4 +152,9 @@ interface VaultRepository : VaultLockManager {
sendId: String,
sendView: SendView,
): UpdateSendResult
/**
* Attempt to remove the password from a send.
*/
suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult
}
@@ -30,6 +30,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
@@ -443,6 +444,34 @@ class VaultRepositoryImpl(
)
}
override suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult {
val userId = requireNotNull(activeUserId)
return sendsService
.removeSendPassword(sendId = sendId)
.fold(
onSuccess = { response ->
when (response) {
is UpdateSendResponseJson.Invalid -> {
RemovePasswordSendResult.Error(errorMessage = response.message)
}
is UpdateSendResponseJson.Success -> {
vaultDiskSource.saveSend(userId = userId, send = response.send)
vaultSdkSource
.decryptSend(
userId = userId,
send = response.send.toEncryptedSdkSend(),
)
.getOrNull()
?.let { RemovePasswordSendResult.Success(sendView = it) }
?: RemovePasswordSendResult.Error(errorMessage = null)
}
}
},
onFailure = { RemovePasswordSendResult.Error(errorMessage = null) },
)
}
private fun storeProfileData(
syncResponse: SyncResponseJson,
) {
@@ -0,0 +1,21 @@
package com.x8bit.bitwarden.data.vault.repository.model
import com.bitwarden.core.SendView
/**
* Models result of removing the password protection from a send.
*/
sealed class RemovePasswordSendResult {
/**
* Send has had the password protection successfully removed and contains the decrypted
* [SendView].
*/
data class Success(val sendView: SendView) : RemovePasswordSendResult()
/**
* Generic error while removing the password protection from a send. The optional
* [errorMessage] may be displayed directly in the UI when present.
*/
data class Error(val errorMessage: String?) : RemovePasswordSendResult()
}
@@ -32,7 +32,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
/**
* Displays new send UX.
@@ -92,7 +92,7 @@ fun AddSendScreen(
)
if (!state.isAddMode) {
BitwardenOverflowActionItem(
menuItemDataList = persistentListOf(
menuItemDataList = listOfNotNull(
OverflowMenuItemData(
text = stringResource(id = R.string.remove_password),
onClick = remember(viewModel) {
@@ -102,7 +102,8 @@ fun AddSendScreen(
)
}
},
),
)
.takeIf { state.hasPassword },
OverflowMenuItemData(
text = stringResource(id = R.string.copy_link),
onClick = remember(viewModel) {
@@ -121,7 +122,8 @@ fun AddSendScreen(
{ viewModel.trySendAction(AddSendAction.DeleteClick) }
},
),
),
)
.toPersistentList(),
)
}
},
@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
@@ -70,6 +71,7 @@ class AddSendViewModel @Inject constructor(
.plusWeeks(1),
expirationDate = null,
sendUrl = null,
hasPassword = false,
),
selectedType = AddSendState.ViewState.Content.SendType.Text(
input = "",
@@ -139,6 +141,10 @@ class AddSendViewModel @Inject constructor(
private fun handleInternalAction(action: AddSendAction.Internal): Unit = when (action) {
is AddSendAction.Internal.CreateSendResultReceive -> handleCreateSendResultReceive(action)
is AddSendAction.Internal.UpdateSendResultReceive -> handleUpdateSendResultReceive(action)
is AddSendAction.Internal.RemovePasswordResultReceive -> handleRemovePasswordResultReceive(
action,
)
is AddSendAction.Internal.UserStateReceive -> handleUserStateReceive(action)
is AddSendAction.Internal.SendDataReceive -> handleSendDataReceive(action)
}
@@ -200,6 +206,32 @@ class AddSendViewModel @Inject constructor(
}
}
private fun handleRemovePasswordResultReceive(
action: AddSendAction.Internal.RemovePasswordResultReceive,
) {
when (val result = action.result) {
is RemovePasswordSendResult.Error -> {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = result
.errorMessage
?.asText()
?: R.string.generic_error_message.asText(),
),
)
}
}
is RemovePasswordSendResult.Success -> {
updateCommonContent { it.copy(hasPassword = false) }
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(AddSendEvent.ShowToast(message = R.string.send_password_removed.asText()))
}
}
}
private fun handleUserStateReceive(action: AddSendAction.Internal.UserStateReceive) {
mutableStateFlow.update {
it.copy(isPremiumUser = action.userState?.activeAccount?.isPremium == true)
@@ -288,8 +320,22 @@ class AddSendViewModel @Inject constructor(
}
private fun handleRemovePasswordClick() {
// TODO Add remove password support (BIT-1435)
sendEvent(AddSendEvent.ShowToast("Not yet implemented".asText()))
when (val addSendType = state.addSendType) {
AddSendType.AddItem -> Unit
is AddSendType.EditItem -> {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Loading(
message = R.string.removing_send_password.asText(),
),
)
}
viewModelScope.launch {
val result = vaultRepo.removePasswordSend(addSendType.sendItemId)
sendAction(AddSendAction.Internal.RemovePasswordResultReceive(result))
}
}
}
}
private fun handleShareLinkClick() {
@@ -516,6 +562,12 @@ data class AddSendState(
*/
val isAddMode: Boolean get() = addSendType is AddSendType.AddItem
/**
* Helper to determine if the currently displayed send has a password already set.
*/
val hasPassword: Boolean
get() = (viewState as? ViewState.Content)?.common?.hasPassword == true
/**
* Represents the specific view states for the [AddSendScreen].
*/
@@ -566,6 +618,7 @@ data class AddSendState(
val deletionDate: ZonedDateTime,
val expirationDate: ZonedDateTime?,
val sendUrl: String?,
val hasPassword: Boolean,
) : Parcelable {
val dateFormatPattern: String get() = "M/d/yyyy"
@@ -767,6 +820,11 @@ sealed class AddSendAction {
*/
data class UpdateSendResultReceive(val result: UpdateSendResult) : Internal()
/**
* Indicates a result for removing the password from a send has been received.
*/
data class RemovePasswordResultReceive(val result: RemovePasswordSendResult) : Internal()
/**
* Indicates that the send item data has been received.
*/
@@ -28,6 +28,7 @@ fun SendView.toViewState(
deletionDate = ZonedDateTime.ofInstant(this.deletionDate, clock.zone),
expirationDate = this.expirationDate?.let { ZonedDateTime.ofInstant(it, clock.zone) },
sendUrl = this.toSendUrl(baseWebSendUrl),
hasPassword = this.hasPassword,
),
selectedType = when (type) {
SendType.TEXT -> {