Skip to content

Commit 0a39883

Browse files
[PM-18210] Cipher key encryption error handling (#5611)
Co-authored-by: Patrick Honkonen <[email protected]> Co-authored-by: Patrick Honkonen <[email protected]>
1 parent aab8198 commit 0a39883

File tree

20 files changed

+1247
-84
lines changed

20 files changed

+1247
-84
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,6 @@ class VaultRepositoryImpl(
11031103
)
11041104
.fold(
11051105
onSuccess = { result ->
1106-
// TODO (PM-18210): Display decryption result failures
11071106
DataState.Loaded(
11081107
result.copy(successes = result.successes.sortAlphabetically()),
11091108
)

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import com.bitwarden.network.model.SyncResponseJson
1414
import com.bitwarden.network.model.UriMatchTypeJson
1515
import com.bitwarden.vault.Attachment
1616
import com.bitwarden.vault.Card
17+
import com.bitwarden.vault.CardListView
1718
import com.bitwarden.vault.Cipher
1819
import com.bitwarden.vault.CipherListView
20+
import com.bitwarden.vault.CipherListViewType
1921
import com.bitwarden.vault.CipherPermissions
2022
import com.bitwarden.vault.CipherRepromptType
2123
import com.bitwarden.vault.CipherType
@@ -26,6 +28,7 @@ import com.bitwarden.vault.Field
2628
import com.bitwarden.vault.FieldType
2729
import com.bitwarden.vault.Identity
2830
import com.bitwarden.vault.Login
31+
import com.bitwarden.vault.LoginListView
2932
import com.bitwarden.vault.LoginUri
3033
import com.bitwarden.vault.PasswordHistory
3134
import com.bitwarden.vault.SecureNote
@@ -651,3 +654,54 @@ fun EncryptionContext.toEncryptedNetworkCipher(): CipherJsonRequest =
651654
*/
652655
fun EncryptionContext.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
653656
cipher.toEncryptedNetworkCipherResponse(encryptedFor = encryptedFor)
657+
658+
/**
659+
* Converts a Bitwarden SDK [Cipher] object to a corresponding
660+
* [CipherListView] object with modified field to represent a decryption error instance.
661+
* This allows reuse of existing logic for filtering and grouping ciphers to construct
662+
* the sections in the vault list.
663+
*/
664+
fun Cipher.toFailureCipherListView(): CipherListView =
665+
CipherListView(
666+
id = id,
667+
organizationId = organizationId,
668+
folderId = folderId,
669+
collectionIds = collectionIds,
670+
key = key,
671+
name = name,
672+
subtitle = "",
673+
type = when (type) {
674+
CipherType.LOGIN -> CipherListViewType.Login(
675+
v1 = LoginListView(
676+
fido2Credentials = null,
677+
hasFido2 = false,
678+
username = null,
679+
totp = null,
680+
uris = null,
681+
),
682+
)
683+
684+
CipherType.SECURE_NOTE -> CipherListViewType.SecureNote
685+
CipherType.CARD -> CipherListViewType.Card(
686+
CardListView(
687+
brand = null,
688+
),
689+
)
690+
691+
CipherType.IDENTITY -> CipherListViewType.Identity
692+
CipherType.SSH_KEY -> CipherListViewType.SshKey
693+
},
694+
favorite = favorite,
695+
reprompt = reprompt,
696+
organizationUseTotp = organizationUseTotp,
697+
edit = edit,
698+
permissions = permissions,
699+
viewPassword = viewPassword,
700+
attachments = 0.toUInt(),
701+
hasOldAttachments = false,
702+
localData = null,
703+
creationDate = creationDate,
704+
deletedDate = deletedDate,
705+
revisionDate = revisionDate,
706+
copyableFields = emptyList(),
707+
)

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ fun VaultItemListingContent(
205205
BitwardenListItem(
206206
startIcon = it.iconData,
207207
startIconTestTag = it.iconTestTag,
208-
label = it.title,
208+
label = it.title.invoke(),
209209
labelTestTag = it.titleTestTag,
210210
secondSupportingLabel = it.secondSubtitle,
211211
secondSupportingLabelTestTag = it.secondSubtitleTestTag,

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,13 @@ fun VaultItemListingScreen(
319319
)
320320
}
321321
},
322+
onShareCipherDecryptionErrorClick = remember(viewModel) {
323+
{
324+
viewModel.trySendAction(
325+
VaultItemListingsAction.ShareCipherDecryptionErrorClick(it),
326+
)
327+
}
328+
},
322329
)
323330

324331
val vaultItemListingHandlers = remember(viewModel) {
@@ -350,6 +357,7 @@ private fun VaultItemListingDialogs(
350357
onDismissUserVerification: () -> Unit,
351358
onVaultItemTypeSelected: (CreateVaultItemType) -> Unit,
352359
onTrustPrivilegedAppClick: (selectedCipherId: String?) -> Unit,
360+
onShareCipherDecryptionErrorClick: (selectedCipherId: String) -> Unit,
353361
) {
354362
when (dialogState) {
355363
is VaultItemListingState.DialogState.Error -> BitwardenBasicDialog(
@@ -363,6 +371,20 @@ private fun VaultItemListingDialogs(
363371
text = dialogState.message(),
364372
)
365373

374+
is VaultItemListingState.DialogState.CipherDecryptionError -> {
375+
BitwardenTwoButtonDialog(
376+
title = dialogState.title(),
377+
message = dialogState.message(),
378+
confirmButtonText = stringResource(BitwardenString.copy_error_report),
379+
dismissButtonText = stringResource(BitwardenString.close),
380+
onConfirmClick = {
381+
onShareCipherDecryptionErrorClick(dialogState.selectedCipherId)
382+
},
383+
onDismissClick = onDismissRequest,
384+
onDismissRequest = onDismissRequest,
385+
)
386+
}
387+
366388
is VaultItemListingState.DialogState.CredentialManagerOperationFail -> BitwardenBasicDialog(
367389
title = dialogState.title(),
368390
message = dialogState.message(),

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ class VaultItemListingViewModel @Inject constructor(
344344
}
345345

346346
is VaultItemListingsAction.Internal -> handleInternalAction(action)
347+
348+
is VaultItemListingsAction.ShareCipherDecryptionErrorClick -> {
349+
handleShareCipherDecryptionErrorClick(action)
350+
}
347351
}
348352
}
349353

@@ -635,6 +639,16 @@ class VaultItemListingViewModel @Inject constructor(
635639
sendEvent(VaultItemListingEvent.ShowShareSheet(action.sendUrl))
636640
}
637641

642+
private fun handleShareCipherDecryptionErrorClick(
643+
action: VaultItemListingsAction.ShareCipherDecryptionErrorClick,
644+
) {
645+
sendEvent(
646+
event = VaultItemListingEvent.ShowShareSheet(
647+
content = action.selectedCipherId,
648+
),
649+
)
650+
}
651+
638652
private fun handleRemoveSendPasswordClick(
639653
action: ListingItemOverflowAction.SendAction.RemovePasswordClick,
640654
) {
@@ -929,10 +943,30 @@ class VaultItemListingViewModel @Inject constructor(
929943
sendType = itemType.type.toSendItemType(),
930944
)
931945
}
946+
947+
VaultItemListingState.DisplayItem.ItemType.DecryptionError -> {
948+
showCipherDecryptionErrorItemClick(itemId = action.id)
949+
return
950+
}
932951
}
952+
933953
sendEvent(event)
934954
}
935955

956+
private fun showCipherDecryptionErrorItemClick(itemId: String) {
957+
mutableStateFlow.update {
958+
it.copy(
959+
dialogState = VaultItemListingState.DialogState.CipherDecryptionError(
960+
title = BitwardenString.decryption_error.asText(),
961+
message = BitwardenString
962+
.bitwarden_could_not_decrypt_this_vault_item_description_long
963+
.asText(),
964+
selectedCipherId = itemId,
965+
),
966+
)
967+
}
968+
}
969+
936970
private fun handleItemClickForProviderCreateCredentialRequest(
937971
action: VaultItemListingsAction.ItemClick,
938972
createCredentialRequest: CreateCredentialRequest,
@@ -2701,6 +2735,16 @@ data class VaultItemListingState(
27012735
val throwable: Throwable? = null,
27022736
) : DialogState()
27032737

2738+
/**
2739+
* Represents a dialog indicating that a cipher decryption error occurred.
2740+
*/
2741+
@Parcelize
2742+
data class CipherDecryptionError(
2743+
val title: Text,
2744+
val message: Text,
2745+
val selectedCipherId: String,
2746+
) : DialogState()
2747+
27042748
/**
27052749
* Represents a dialog indicating that a CredentialManager operation encountered an error.
27062750
*/
@@ -2883,7 +2927,7 @@ data class VaultItemListingState(
28832927
*/
28842928
data class DisplayItem(
28852929
val id: String,
2886-
val title: String,
2930+
val title: Text,
28872931
val titleTestTag: String,
28882932
val secondSubtitle: String?,
28892933
val secondSubtitleTestTag: String?,
@@ -2912,6 +2956,11 @@ data class VaultItemListingState(
29122956
* Indicates the item type is a vault item.
29132957
*/
29142958
data class Vault(val type: CipherType) : ItemType()
2959+
2960+
/**
2961+
* Indicates the item type is a decryption error.
2962+
*/
2963+
object DecryptionError : ItemType()
29152964
}
29162965
}
29172966

@@ -3336,6 +3385,13 @@ sealed class VaultItemListingsAction {
33363385
*/
33373386
data object LockClick : VaultItemListingsAction()
33383387

3388+
/**
3389+
* Click to share cipher decryption error details.
3390+
*/
3391+
data class ShareCipherDecryptionErrorClick(
3392+
val selectedCipherId: String,
3393+
) : VaultItemListingsAction()
3394+
33393395
/**
33403396
* Click the refresh button.
33413397
*/

0 commit comments

Comments
 (0)