-
Notifications
You must be signed in to change notification settings - Fork 885
[PM-18210] Cipher key encryption error handling #5611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ipher decryption failures.
…tion when clicking a failed cipher.
Great job! No new security vulnerabilities introduced in this pull request |
...rc/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
Outdated
Show resolved
Hide resolved
...rc/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
Outdated
Show resolved
Hide resolved
...tlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
Outdated
Show resolved
Hide resolved
...tlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
Outdated
Show resolved
Hide resolved
...tlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
Show resolved
Hide resolved
# Conflicts: # app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5611 +/- ##
==========================================
+ Coverage 83.86% 83.91% +0.04%
==========================================
Files 696 696
Lines 52703 52960 +257
Branches 7216 7243 +27
==========================================
+ Hits 44200 44439 +239
- Misses 5975 5988 +13
- Partials 2528 2533 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
# Conflicts: # app/src/main/res/values/strings.xml
.filter { cipherListView -> | ||
cipherListView.determineListingPredicate(itemListingType) | ||
} | ||
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds) | ||
.toFilteredList(vaultFilterType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎨 Can we extract this into an extension function. Something like,
.filter { cipherListView -> | |
cipherListView.determineListingPredicate(itemListingType) | |
} | |
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds) | |
.toFilteredList(vaultFilterType) | |
.applyFilters( | |
itemListingType = itemListingType, | |
vaultFilterType = vaultFilterType, | |
restrictItemTypesPolicyOrgIds = restrictItemTypesPolicyOrgIds, | |
) |
private fun List<CipherListView>.applyFilters(
itemListingType: VaultItemListingState.ItemListingType.Vault,
vaultFilterType: VaultFilterType,
restrictItemTypesPolicyOrgIds: List<String>,
): List<CipherListView> = this
.filter { it.determineListingPredicate(itemListingType) }
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds)
.toFilteredList(vaultFilterType)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ Minor, but I think these variable names could be improved for readability. The function is getting pretty complex. I'd like to break it up with helper/extension functions at some point, but updating the var names will at least help in the interim.
@@ -44,15 +45,36 @@ fun VaultData.toViewState( | |||
vaultFilterType: VaultFilterType, | |||
restrictItemTypesPolicyOrgIds: List<String>?, | |||
): VaultState.ViewState { | |||
|
|||
val filteredCipherViewListWithDeletedItems = | |||
val filteredAllCipherViewListWithDeletedItems = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val allCipherViews
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds ?: emptyList()) | ||
.toFilteredList(vaultFilterType) | ||
|
||
val filteredCipherViewList = filteredCipherViewListWithDeletedItems | ||
val filteredCipherViewList = filteredAllCipherViewListWithDeletedItems |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val activeCipherViews
val filteredCipherViewList = filteredAllCipherViewListWithDeletedItems | ||
.filter { it.deletedDate == null } | ||
|
||
val filteredSuccessCipherViewList = decryptCipherListResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val activeDecryptedCipherViews
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds ?: emptyList()) | ||
.toFilteredList(vaultFilterType) | ||
|
||
val filteredFailureCipherViewList = decryptCipherListResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val activeUndecryptableCipherViews
.applyRestrictItemTypesPolicy(restrictItemTypesPolicyOrgIds ?: emptyList()) | ||
.toFilteredList(vaultFilterType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to my previous comment, it would be nice to extract this common filtering logic into an ext function.
.mapNotNull { | ||
it.id | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to declare the lambda arg here or in the enclosing update { }
block.
.mapNotNull { | |
it.id | |
} | |
.mapNotNull { cipher -> cipher.id } |
@@ -233,6 +282,7 @@ private fun CipherListView.toVaultItemOrNull( | |||
extraIconList = toLabelIcons(), | |||
shouldShowMasterPasswordReprompt = hasMasterPassword && | |||
reprompt == CipherRepromptType.PASSWORD, | |||
hasDecryptionError = hasDecryptionError, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use hasDecryptionError
to control the overflow options that are assigned? That would make the item.hasDecryptionError
checks in VaultContent
unnecessary. Something like...
overflowOptions = toOverflowOptions(...).takeUnless { hasDecryptionError }
or if we don't want toOverflowOptions()
to execute...
overflowOptions = if (hasDecryptionError) {
emptyList()
} else {
toOverflowOptions()
}
and in VaultContent
we can revert
overflowOptions = if (favoriteItem.hasDecryptionError) {
persistentListOf()
} else {
favoriteItem.overflowOptions.toImmutableList()
},
label = if (favoriteItem.hasDecryptionError) { | ||
stringResource(id = BitwardenString.error_cannot_decrypt) | ||
} else { | ||
favoriteItem.name() | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make this check unnecessary too, if we perform the hasDecryptionError
check in VaultDataExtensions
instead.
label = if (isDecryptionErrorType) { | ||
stringResource(id = BitwardenString.error_cannot_decrypt) | ||
} else { | ||
it.title | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrebispo5 @david-livefront thoughts on making DisplayItem.title: Text
instead of String
?
That would allow us to assign title
in VaultItemListingDataExtensions
instead of having this if (decryptionError)
check in the Composable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No concern from me
private fun handleShareCipherDecryptionErrorClick(cipherId: String?) { | ||
sendEvent( | ||
event = VaultItemListingEvent.ShowShareSheet( | ||
content = cipherId.orEmpty(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of printing an empty string, can we print something that explicitly states the cipher does not have an ID? This should never happen, but if it does it will be easier to track down than sending an empty log.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-18210
📔 Objective
After the user navigates to their vault view
If there is a corrupt cipher:
If user selects the corrupt cipher
📸 Screenshots
Screen.Recording.2025-07-29.at.14.28.54.mov
Screen.Recording.2025-07-29.at.22.55.59.mov
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes