BIT-2102: Require all password fields for vault export (#1206)

This commit is contained in:
Caleb Derosier 2024-04-02 08:35:39 -06:00 committed by Álison Fernandes
parent 8960210bb0
commit d7e3b74c25
3 changed files with 107 additions and 10 deletions

View file

@ -37,7 +37,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.auth.feature.createaccount.PasswordStrengthIndicator import com.x8bit.bitwarden.ui.auth.feature.createaccount.PasswordStrengthIndicator
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledTonalButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledTonalButton
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
@ -127,7 +126,7 @@ fun ExportVaultScreen(
is ExportVaultState.DialogState.Error -> { is ExportVaultState.DialogState.Error -> {
BitwardenBasicDialog( BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown( visibilityState = BasicDialogState.Shown(
title = dialog.title ?: R.string.an_error_has_occurred.asText(), title = dialog.title,
message = dialog.message, message = dialog.message,
), ),
onDismissRequest = remember(viewModel) { onDismissRequest = remember(viewModel) {

View file

@ -118,15 +118,30 @@ class ExportVaultViewModel @Inject constructor(
/** /**
* Verify the master password after confirming exporting the vault. * Verify the master password after confirming exporting the vault.
*/ */
@Suppress("ReturnCount")
private fun handleConfirmExportVaultClicked() { private fun handleConfirmExportVaultClicked() {
// Display an error alert if the user hasn't entered a password. // Display an error alert if the user hasn't entered a password.
if (mutableStateFlow.value.passwordInput.isBlank()) { if (mutableStateFlow.value.passwordInput.isBlank()) {
updateStateWithError( updateStateWithError(
R.string.validation_field_required.asText( message = R.string.validation_field_required.asText(
R.string.master_password.asText(), R.string.master_password.asText(),
), ),
) )
return return
} else if (state.exportFormat == ExportVaultFormat.JSON_ENCRYPTED) {
if (state.filePasswordInput.isBlank()) {
updateStateWithError(
message = R.string.validation_field_required
.asText(R.string.file_password.asText()),
)
return
} else if (state.confirmFilePasswordInput.isBlank()) {
updateStateWithError(
message = R.string.validation_field_required
.asText(R.string.confirm_file_password.asText()),
)
return
}
} }
// Otherwise, validate the password. // Otherwise, validate the password.
@ -335,7 +350,7 @@ class ExportVaultViewModel @Inject constructor(
mutableStateFlow.update { mutableStateFlow.update {
it.copy( it.copy(
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = message, message = message,
), ),
) )

View file

@ -115,7 +115,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
assertEquals( assertEquals(
DEFAULT_STATE.copy( DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = R.string.validation_field_required.asText( message = R.string.validation_field_required.asText(
R.string.master_password.asText(), R.string.master_password.asText(),
), ),
@ -132,6 +132,89 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
} }
} }
@Suppress("MaxLineLength")
@Test
fun `ConfirmExportVaultClicked blank file password should show an error when export type is JSON_ENCRYPTED`() =
runTest {
val password = "password"
val viewModel = createViewModel()
viewModel.trySendAction(
ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.JSON_ENCRYPTED),
)
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
assertEquals(
DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.validation_field_required.asText(
R.string.file_password.asText(),
),
),
exportFormat = ExportVaultFormat.JSON_ENCRYPTED,
passwordInput = password,
),
viewModel.stateFlow.value,
)
viewModel.trySendAction(ExportVaultAction.DialogDismiss)
assertEquals(
DEFAULT_STATE.copy(
exportFormat = ExportVaultFormat.JSON_ENCRYPTED,
passwordInput = password,
),
viewModel.stateFlow.value,
)
}
@Suppress("MaxLineLength")
@Test
fun `ConfirmExportVaultClicked blank confirm file password should show an error when export type is JSON_ENCRYPTED`() =
runTest {
val password = "password"
coEvery {
authRepository.getPasswordStrength(
email = EMAIL_ADDRESS,
password = password,
)
} returns PasswordStrengthResult.Success(
passwordStrength = PasswordStrength.LEVEL_4,
)
val viewModel = createViewModel()
viewModel.trySendAction(
ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.JSON_ENCRYPTED),
)
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
viewModel.trySendAction(ExportVaultAction.FilePasswordInputChange(password))
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
assertEquals(
DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.validation_field_required.asText(
R.string.confirm_file_password.asText(),
),
),
exportFormat = ExportVaultFormat.JSON_ENCRYPTED,
filePasswordInput = password,
passwordInput = password,
passwordStrengthState = PasswordStrengthState.STRONG,
),
viewModel.stateFlow.value,
)
viewModel.trySendAction(ExportVaultAction.DialogDismiss)
assertEquals(
DEFAULT_STATE.copy(
exportFormat = ExportVaultFormat.JSON_ENCRYPTED,
filePasswordInput = password,
passwordInput = password,
passwordStrengthState = PasswordStrengthState.STRONG,
),
viewModel.stateFlow.value,
)
}
@Test @Test
fun `ConfirmExportVaultClicked invalid password should show an error`() = runTest { fun `ConfirmExportVaultClicked invalid password should show an error`() = runTest {
val password = "password" val password = "password"
@ -149,7 +232,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
assertEquals( assertEquals(
DEFAULT_STATE.copy( DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = R.string.invalid_master_password.asText(), message = R.string.invalid_master_password.asText(),
), ),
passwordInput = password, passwordInput = password,
@ -181,7 +264,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
assertEquals( assertEquals(
DEFAULT_STATE.copy( DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(), message = R.string.generic_error_message.asText(),
), ),
passwordInput = password, passwordInput = password,
@ -278,7 +361,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
assertEquals( assertEquals(
DEFAULT_STATE.copy( DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = R.string.export_vault_failure.asText(), message = R.string.export_vault_failure.asText(),
), ),
), ),
@ -401,7 +484,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
assertEquals( assertEquals(
DEFAULT_STATE.copy( DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = R.string.export_vault_failure.asText(), message = R.string.export_vault_failure.asText(),
), ),
), ),
@ -433,7 +516,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
DEFAULT_STATE.copy( DEFAULT_STATE.copy(
exportData = exportData, exportData = exportData,
dialogState = ExportVaultState.DialogState.Error( dialogState = ExportVaultState.DialogState.Error(
title = null, title = R.string.an_error_has_occurred.asText(),
message = R.string.export_vault_failure.asText(), message = R.string.export_vault_failure.asText(),
), ),
), ),