From 9b5449f657844b3f154d706e8f50ca911784e850 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Fri, 3 May 2024 16:14:49 -0400 Subject: [PATCH] Enforce Base32 key values (#82) --- .../feature/edititem/EditItemScreen.kt | 2 ++ .../feature/edititem/EditItemViewModel.kt | 17 +++++++++++++++-- .../manualcodeentry/ManualCodeEntryScreen.kt | 5 ++++- .../manualcodeentry/ManualCodeEntryViewModel.kt | 13 +++++++++++++ .../feature/qrcodescan/QrCodeScanViewModel.kt | 9 +-------- .../ui/platform/base/util/StringExtensions.kt | 8 ++++++++ .../components/field/BitwardenPasswordField.kt | 5 +++++ app/src/main/res/values/strings.xml | 6 +++++- 8 files changed, 53 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt index fe8dce7869..a326412c87 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -255,6 +256,7 @@ fun EditItemContent( value = viewState.itemData.totpCode, onValueChange = onTotpCodeTextChange, singleLine = true, + capitalization = KeyboardCapitalization.Characters, ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt index 8cc67ffc66..d92c34b9e2 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt @@ -1,6 +1,8 @@ package com.bitwarden.authenticator.ui.authenticator.feature.edititem import android.os.Parcelable +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.toUpperCase import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.bitwarden.authenticator.R @@ -18,6 +20,7 @@ import com.bitwarden.authenticator.ui.platform.base.BaseViewModel import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.base.util.asText import com.bitwarden.authenticator.ui.platform.base.util.concat +import com.bitwarden.authenticator.ui.platform.base.util.isBase32 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -93,7 +96,17 @@ class EditItemViewModel @Inject constructor( it.copy( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), - message = R.string.validation_field_required.asText(R.string.secret_key), + message = R.string.validation_field_required.asText(R.string.key), + ) + ) + } + return@onContent + } else if (!content.itemData.totpCode.isBase32()) { + mutableStateFlow.update { + it.copy( + dialog = EditItemState.DialogState.Generic( + title = R.string.an_error_has_occurred.asText(), + message = R.string.key_is_invalid.asText() ) ) } @@ -320,7 +333,7 @@ class EditItemViewModel @Inject constructor( itemData = EditItemData( refreshPeriod = AuthenticatorRefreshPeriodOption.fromSeconds(period) ?: AuthenticatorRefreshPeriodOption.THIRTY, - totpCode = key, + totpCode = key.toUpperCase(Locale.current), type = type, username = accountName, issuer = issuer, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt index baeea08db6..968b5a97fd 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -40,6 +41,7 @@ import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicD import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenLoadingDialog import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState +import com.bitwarden.authenticator.ui.platform.components.field.BitwardenPasswordField import com.bitwarden.authenticator.ui.platform.components.field.BitwardenTextField import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager @@ -177,7 +179,7 @@ fun ManualCodeEntryScreen( .padding(horizontal = 16.dp), ) Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( + BitwardenPasswordField( singleLine = false, label = stringResource(id = R.string.key), value = state.code, @@ -188,6 +190,7 @@ fun ManualCodeEntryScreen( ) } }, + capitalization = KeyboardCapitalization.Characters, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt index bf37490c5c..dc20111036 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt @@ -11,6 +11,7 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.CreateIte import com.bitwarden.authenticator.ui.platform.base.BaseViewModel import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.base.util.asText +import com.bitwarden.authenticator.ui.platform.base.util.isBase32 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -82,6 +83,17 @@ class ManualCodeEntryViewModel @Inject constructor( return } + if (!state.code.isBase32()) { + mutableStateFlow.update { + it.copy( + dialog = ManualCodeEntryState.DialogState.Error( + message = R.string.key_is_invalid.asText() + ) + ) + } + return + } + if (state.issuer.isBlank()) { mutableStateFlow.update { it.copy( @@ -92,6 +104,7 @@ class ManualCodeEntryViewModel @Inject constructor( } return } + viewModelScope.launch { val result = authenticatorRepository.createItem( AuthenticatorItemEntity( diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt index 9ab99385c7..b8b84bfe9e 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanViewModel.kt @@ -5,6 +5,7 @@ import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRe import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult import com.bitwarden.authenticator.ui.platform.base.BaseViewModel import com.bitwarden.authenticator.ui.platform.base.util.Text +import com.bitwarden.authenticator.ui.platform.base.util.isBase32 import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -160,11 +161,3 @@ sealed class QrCodeScanAction { */ data object CameraSetupErrorReceive : QrCodeScanAction() } - -/** - * Checks if a string is using base32 digits. - */ -private fun String.isBase32(): Boolean { - val regex = ("^[A-Z2-7]+=*$").toRegex() - return regex.matches(this) -} diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt index 53c8ea9ead..481f765421 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/base/util/StringExtensions.kt @@ -72,3 +72,11 @@ fun String.removeDiacritics(): String = Normalizer.normalize(this, Normalizer.Form.NFKD), "", ) + +/** + * Checks if a string is using base32 digits. + */ +fun String.isBase32(): Boolean { + val regex = ("^[A-Z2-7]+=*$").toRegex() + return regex.matches(this) +} diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt index 6140482c8a..0f75ac961b 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation @@ -67,6 +68,7 @@ fun BitwardenPasswordField( autoFocus: Boolean = false, keyboardType: KeyboardType = KeyboardType.Password, imeAction: ImeAction = ImeAction.Default, + capitalization: KeyboardCapitalization = KeyboardCapitalization.None, ) { val focusRequester = remember { FocusRequester() } OutlinedTextField( @@ -83,6 +85,7 @@ fun BitwardenPasswordField( singleLine = singleLine, readOnly = readOnly, keyboardOptions = KeyboardOptions( + capitalization = capitalization, keyboardType = keyboardType, imeAction = imeAction, ), @@ -156,6 +159,7 @@ fun BitwardenPasswordField( autoFocus: Boolean = false, keyboardType: KeyboardType = KeyboardType.Password, imeAction: ImeAction = ImeAction.Default, + capitalization: KeyboardCapitalization = KeyboardCapitalization.None, ) { var showPassword by rememberSaveable { mutableStateOf(initialShowPassword) } BitwardenPasswordField( @@ -172,6 +176,7 @@ fun BitwardenPasswordField( autoFocus = autoFocus, keyboardType = keyboardType, imeAction = imeAction, + capitalization = capitalization, ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14f2739e72..45f2892b64 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,7 +44,6 @@ Number of digits Save The %1$s field is required. - Secret key %d seconds Saving Item saved @@ -108,4 +107,9 @@ Key is required. Name is required. Submit crash logs + There was a problem importing your vault. + File Source + Import + Vault import successful + Key is invalid.