mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
BIT-2102: Require all password fields for vault export (#1206)
This commit is contained in:
parent
8960210bb0
commit
d7e3b74c25
3 changed files with 107 additions and 10 deletions
|
@ -37,7 +37,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.x8bit.bitwarden.R
|
||||
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.asText
|
||||
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.dialog.BasicDialogState
|
||||
|
@ -127,7 +126,7 @@ fun ExportVaultScreen(
|
|||
is ExportVaultState.DialogState.Error -> {
|
||||
BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = dialog.title ?: R.string.an_error_has_occurred.asText(),
|
||||
title = dialog.title,
|
||||
message = dialog.message,
|
||||
),
|
||||
onDismissRequest = remember(viewModel) {
|
||||
|
|
|
@ -118,15 +118,30 @@ class ExportVaultViewModel @Inject constructor(
|
|||
/**
|
||||
* Verify the master password after confirming exporting the vault.
|
||||
*/
|
||||
@Suppress("ReturnCount")
|
||||
private fun handleConfirmExportVaultClicked() {
|
||||
// Display an error alert if the user hasn't entered a password.
|
||||
if (mutableStateFlow.value.passwordInput.isBlank()) {
|
||||
updateStateWithError(
|
||||
R.string.validation_field_required.asText(
|
||||
message = R.string.validation_field_required.asText(
|
||||
R.string.master_password.asText(),
|
||||
),
|
||||
)
|
||||
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.
|
||||
|
@ -335,7 +350,7 @@ class ExportVaultViewModel @Inject constructor(
|
|||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = message,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -115,7 +115,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.validation_field_required.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
|
||||
fun `ConfirmExportVaultClicked invalid password should show an error`() = runTest {
|
||||
val password = "password"
|
||||
|
@ -149,7 +232,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.invalid_master_password.asText(),
|
||||
),
|
||||
passwordInput = password,
|
||||
|
@ -181,7 +264,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
passwordInput = password,
|
||||
|
@ -278,7 +361,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.export_vault_failure.asText(),
|
||||
),
|
||||
),
|
||||
|
@ -401,7 +484,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.export_vault_failure.asText(),
|
||||
),
|
||||
),
|
||||
|
@ -433,7 +516,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
|||
DEFAULT_STATE.copy(
|
||||
exportData = exportData,
|
||||
dialogState = ExportVaultState.DialogState.Error(
|
||||
title = null,
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.export_vault_failure.asText(),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue