mirror of
https://github.com/bitwarden/android.git
synced 2024-11-29 14:28:55 +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.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) {
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue