Skip to content

Commit

Permalink
feat: backup password validation [WPB-4374] (#2370)
Browse files Browse the repository at this point in the history
  • Loading branch information
Garzas committed Oct 26, 2023
1 parent a7f109a commit a60a8f8
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 10 deletions.
Expand Up @@ -20,14 +20,15 @@

package com.wire.android.ui.home.settings.backup

import com.wire.kalium.logic.feature.auth.ValidatePasswordResult
import okio.Path

data class BackupAndRestoreState(
val backupRestoreProgress: BackupRestoreProgress,
val restoreFileValidation: RestoreFileValidation,
val restorePasswordValidation: PasswordValidation,
val backupCreationProgress: BackupCreationProgress,
val backupCreationPasswordValidation: PasswordValidation
val passwordValidation: ValidatePasswordResult = ValidatePasswordResult.Valid
) {

data class CreatedBackup(val path: Path, val assetName: String, val assetSize: Long, val isEncrypted: Boolean)
Expand All @@ -37,7 +38,7 @@ data class BackupAndRestoreState(
restoreFileValidation = RestoreFileValidation.Initial,
backupCreationProgress = BackupCreationProgress.InProgress(),
restorePasswordValidation = PasswordValidation.NotVerified,
backupCreationPasswordValidation = PasswordValidation.Valid,
passwordValidation = ValidatePasswordResult.Valid,
)
}
}
Expand Down
Expand Up @@ -33,6 +33,8 @@ import com.wire.android.appLogger
import com.wire.android.util.FileManager
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.data.asset.KaliumFileSystem
import com.wire.kalium.logic.feature.auth.ValidatePasswordResult
import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase
import com.wire.kalium.logic.feature.backup.CreateBackupResult
import com.wire.kalium.logic.feature.backup.CreateBackupUseCase
import com.wire.kalium.logic.feature.backup.RestoreBackupResult
Expand All @@ -58,6 +60,7 @@ class BackupAndRestoreViewModel
private val importBackup: RestoreBackupUseCase,
private val createBackupFile: CreateBackupUseCase,
private val verifyBackup: VerifyBackupUseCase,
private val validatePassword: ValidatePasswordUseCase,
private val kaliumFileSystem: KaliumFileSystem,
private val fileManager: FileManager,
private val dispatcher: DispatcherProvider,
Expand Down Expand Up @@ -228,7 +231,13 @@ class BackupAndRestoreViewModel
}

fun validateBackupCreationPassword(backupPassword: TextFieldValue) {
// TODO: modify in case the password requirements change
state = state.copy(
passwordValidation = if (backupPassword.text.isEmpty()) {
ValidatePasswordResult.Valid
} else {
validatePassword(backupPassword.text)
}
)
}

fun cancelBackupCreation() = viewModelScope.launch(dispatcher.main()) {
Expand Down
Expand Up @@ -29,7 +29,6 @@ import androidx.compose.ui.text.input.TextFieldValue
import com.wire.android.R
import com.wire.android.ui.home.settings.backup.BackupAndRestoreState
import com.wire.android.ui.home.settings.backup.BackupCreationProgress
import com.wire.android.ui.home.settings.backup.PasswordValidation
import com.wire.android.ui.home.settings.backup.dialog.common.FailureDialog

@Composable
Expand All @@ -47,7 +46,7 @@ fun CreateBackupDialogFlow(
when (currentBackupDialogStep) {
BackUpDialogStep.SetPassword -> {
SetBackupPasswordDialog(
isBackupPasswordValid = backUpAndRestoreState.backupCreationPasswordValidation is PasswordValidation.Valid,
passwordValidation = backUpAndRestoreState.passwordValidation,
onBackupPasswordChanged = onValidateBackupPassword,
onCreateBackup = { password ->
toCreatingBackup()
Expand Down
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
Expand All @@ -42,18 +43,20 @@ import com.wire.android.ui.common.WireDialog
import com.wire.android.ui.common.WireDialogButtonProperties
import com.wire.android.ui.common.WireDialogButtonType
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.spacers.VerticalSpace
import com.wire.android.ui.common.textfield.WirePasswordTextField
import com.wire.android.ui.common.textfield.WireTextFieldState
import com.wire.android.ui.common.wireDialogPropertiesBuilder
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.permission.rememberCreateFileFlow
import com.wire.kalium.logic.feature.auth.ValidatePasswordResult
import java.util.Locale
import kotlin.math.roundToInt

@Composable
fun SetBackupPasswordDialog(
isBackupPasswordValid: Boolean,
passwordValidation: ValidatePasswordResult,
onBackupPasswordChanged: (TextFieldValue) -> Unit,
onCreateBackup: (String) -> Unit,
onDismissDialog: () -> Unit
Expand All @@ -73,12 +76,14 @@ fun SetBackupPasswordDialog(
onClick = { onCreateBackup(backupPassword.text) },
text = stringResource(id = R.string.backup_dialog_create_backup_now),
type = WireDialogButtonType.Primary,
state = if (!isBackupPasswordValid) WireButtonState.Disabled else WireButtonState.Default
state = if (passwordValidation.isValid) WireButtonState.Default else WireButtonState.Disabled
)
) {
WirePasswordTextField(
modifier = Modifier.padding(bottom = dimensions().spacing16x),
labelText = stringResource(R.string.label_textfield_optional_password).uppercase(Locale.getDefault()),
state = if (!isBackupPasswordValid) WireTextFieldState.Error("some error") else WireTextFieldState.Default,
descriptionText = stringResource(R.string.create_account_details_password_description),
state = if (passwordValidation.isValid) WireTextFieldState.Default else WireTextFieldState.Error(),
value = backupPassword,
onValueChange = {
backupPassword = it
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Expand Up @@ -996,7 +996,7 @@
<string name="label_mls_thumbprint">MLS Thumbprint</string>
<!--create and restore backup-->
<string name="backup_dialog_create_backup_set_password_title">Set password</string>
<string name="backup_dialog_create_backup_set_password_message">The backup will be compressed, and you can encrypt it with a password.</string>
<string name="backup_dialog_create_backup_set_password_message">The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.</string>
<string name="backup_dialog_create_backup_now">Back Up Now</string>
<string name="backup_dialog_create_backup_title">Creating Backup</string>
<string name="backup_dialog_create_backup_subtitle">Saving conversations...</string>
Expand Down
Expand Up @@ -21,6 +21,7 @@
package com.wire.android.ui.home.settings.home

import android.net.Uri
import androidx.compose.ui.text.input.TextFieldValue
import androidx.core.net.toUri
import com.wire.android.config.CoroutineTestExtension
import com.wire.android.config.TestDispatcherProvider
Expand All @@ -33,6 +34,8 @@ import com.wire.android.ui.home.settings.backup.PasswordValidation
import com.wire.android.ui.home.settings.backup.RestoreFileValidation
import com.wire.android.util.FileManager
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.feature.auth.ValidatePasswordResult
import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase
import com.wire.kalium.logic.feature.backup.CreateBackupResult
import com.wire.kalium.logic.feature.backup.CreateBackupUseCase
import com.wire.kalium.logic.feature.backup.RestoreBackupResult
Expand Down Expand Up @@ -103,6 +106,55 @@ class BackupAndRestoreViewModelTest {
coVerify(exactly = 1) { arrangement.createBackupFile(password = password) }
}

@Test
fun givenAnEmptyPassword_whenValidating_thenItUpdatePasswordStateToValid() = runTest(dispatcher.default()) {
// Given
val password = ""
val (arrangement, backupAndRestoreViewModel) = Arrangement()
.withInvalidPassword()
.arrange()

// When
backupAndRestoreViewModel.validateBackupCreationPassword(TextFieldValue(password))
advanceUntilIdle()

// Then
assert(backupAndRestoreViewModel.state.passwordValidation.isValid)
coVerify(exactly = 0) { arrangement.validatePassword(any()) }
}

@Test
fun givenANonEmptyPassword_whenItIsInvalid_thenItUpdatePasswordValidationState() = runTest(dispatcher.default()) {
// Given
val password = "mayTh3ForceBeWIthYou"
val (arrangement, backupAndRestoreViewModel) = Arrangement()
.withInvalidPassword()
.arrange()

// When
backupAndRestoreViewModel.validateBackupCreationPassword(TextFieldValue(password))
advanceUntilIdle()

// Then
assert(!backupAndRestoreViewModel.state.passwordValidation.isValid)
}

@Test
fun givenANonEmptyPassword_whenItIsValid_thenItUpdatePasswordValidationState() = runTest(dispatcher.default()) {
// Given
val password = "mayTh3ForceBeWIthYou_"
val (arrangement, backupAndRestoreViewModel) = Arrangement()
.withValidPassword()
.arrange()

// When
backupAndRestoreViewModel.validateBackupCreationPassword(TextFieldValue(password))
advanceUntilIdle()

// Then
assert(backupAndRestoreViewModel.state.passwordValidation.isValid)
}

@Test
fun givenANonEmptyPassword_whenCreatingABackupWithAGivenError_thenItReturnsAFailure() = runTest(dispatcher.default()) {
// Given
Expand Down Expand Up @@ -412,6 +464,9 @@ class BackupAndRestoreViewModelTest {
@MockK
private lateinit var verifyBackup: VerifyBackupUseCase

@MockK
lateinit var validatePassword: ValidatePasswordUseCase

@MockK
lateinit var fileManager: FileManager

Expand All @@ -423,7 +478,8 @@ class BackupAndRestoreViewModelTest {
verifyBackup = verifyBackup,
kaliumFileSystem = fakeKaliumFileSystem,
dispatcher = dispatcher,
fileManager = fileManager
fileManager = fileManager,
validatePassword = validatePassword
)

fun withSuccessfulCreation(password: String) = apply {
Expand Down Expand Up @@ -491,6 +547,14 @@ class BackupAndRestoreViewModelTest {
coEvery { importBackup(any(), any()) } returns error
}

fun withValidPassword() = apply {
every { validatePassword(any()) } returns ValidatePasswordResult.Valid
}

fun withInvalidPassword() = apply {
every { validatePassword(any()) } returns ValidatePasswordResult.Invalid()
}

fun arrange() = this to viewModel
}
}

0 comments on commit a60a8f8

Please sign in to comment.