Skip to content

Commit 5aa8369

Browse files
authored
[PM-15863] Request master password before revealing private SSH key (#4481)
1 parent 35e8cec commit 5aa8369

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,16 @@ class VaultItemViewModel @Inject constructor(
804804
action: VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked,
805805
) {
806806
onSshKeyContent { content, sshKey ->
807+
if (content.common.requiresReprompt) {
808+
updateDialogState(
809+
VaultItemState.DialogState.MasterPasswordDialog(
810+
action = PasswordRepromptAction.ViewPrivateKeyClicked(
811+
isVisible = action.isVisible,
812+
),
813+
),
814+
)
815+
return@onSshKeyContent
816+
}
807817
mutableStateFlow.update { currentState ->
808818
currentState.copy(
809819
viewState = content.copy(
@@ -2231,4 +2241,18 @@ sealed class PasswordRepromptAction : Parcelable {
22312241
override val vaultItemAction: VaultItemAction
22322242
get() = VaultItemAction.Common.RestoreVaultItemClick
22332243
}
2244+
2245+
/**
2246+
* Indicates that we should launch the
2247+
* [VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked] upon password validation.
2248+
*/
2249+
@Parcelize
2250+
data class ViewPrivateKeyClicked(
2251+
val isVisible: Boolean,
2252+
) : PasswordRepromptAction() {
2253+
override val vaultItemAction: VaultItemAction
2254+
get() = VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
2255+
isVisible = isVisible,
2256+
)
2257+
}
22342258
}

app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,11 +2490,58 @@ class VaultItemViewModelTest : BaseViewModelTest() {
24902490

24912491
@Suppress("MaxLineLength")
24922492
@Test
2493-
fun `on PrivateKeyVisibilityClick should show password dialog when re-prompt is required`() =
2493+
fun `on PrivateKeyVisibilityClick should show private key when re-prompt is not required`() =
24942494
runTest {
24952495
val sshKeyViewState = createViewState(
24962496
common = DEFAULT_COMMON.copy(requiresReprompt = false),
2497+
type = DEFAULT_SSH_KEY_TYPE,
24972498
)
2499+
val sshKeyState = DEFAULT_STATE.copy(viewState = sshKeyViewState)
2500+
every {
2501+
mockCipherView.toViewState(
2502+
previousState = null,
2503+
isPremiumUser = true,
2504+
hasMasterPassword = true,
2505+
totpCodeItemData = null,
2506+
canDelete = true,
2507+
canAssignToCollections = true,
2508+
)
2509+
} returns sshKeyViewState
2510+
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
2511+
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
2512+
mutableCollectionsStateFlow.value = DataState.Loaded(emptyList())
2513+
2514+
assertEquals(sshKeyState, viewModel.stateFlow.value)
2515+
viewModel.trySendAction(
2516+
VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
2517+
isVisible = true,
2518+
),
2519+
)
2520+
assertEquals(
2521+
sshKeyState.copy(
2522+
viewState = sshKeyViewState.copy(
2523+
common = DEFAULT_COMMON.copy(requiresReprompt = false),
2524+
type = DEFAULT_SSH_KEY_TYPE.copy(showPrivateKey = true),
2525+
),
2526+
),
2527+
viewModel.stateFlow.value,
2528+
)
2529+
verify(exactly = 1) {
2530+
mockCipherView.toViewState(
2531+
previousState = null,
2532+
isPremiumUser = true,
2533+
hasMasterPassword = true,
2534+
totpCodeItemData = null,
2535+
canDelete = true,
2536+
canAssignToCollections = true,
2537+
)
2538+
}
2539+
}
2540+
2541+
@Suppress("MaxLineLength")
2542+
@Test
2543+
fun `on PrivateKeyVisibilityClick should show password dialog when re-prompt is required`() =
2544+
runTest {
24982545
val sshKeyState = DEFAULT_STATE.copy(viewState = SSH_KEY_VIEW_STATE)
24992546
every {
25002547
mockCipherView.toViewState(
@@ -2518,15 +2565,22 @@ class VaultItemViewModelTest : BaseViewModelTest() {
25182565
)
25192566
assertEquals(
25202567
sshKeyState.copy(
2521-
viewState = sshKeyViewState.copy(
2522-
common = DEFAULT_COMMON,
2523-
type = DEFAULT_SSH_KEY_TYPE.copy(
2524-
showPrivateKey = true,
2525-
),
2568+
dialog = VaultItemState.DialogState.MasterPasswordDialog(
2569+
PasswordRepromptAction.ViewPrivateKeyClicked(isVisible = true),
25262570
),
25272571
),
25282572
viewModel.stateFlow.value,
25292573
)
2574+
verify(exactly = 1) {
2575+
mockCipherView.toViewState(
2576+
previousState = null,
2577+
isPremiumUser = true,
2578+
hasMasterPassword = true,
2579+
totpCodeItemData = null,
2580+
canDelete = true,
2581+
canAssignToCollections = true,
2582+
)
2583+
}
25302584
}
25312585

25322586
@Test

0 commit comments

Comments
 (0)