[PM-12739] adjusted generator length to not be lower than minimum length (#4016)

This commit is contained in:
aj-rosado 2024-10-03 10:30:54 +02:00 committed by GitHub
parent 0c83a1099f
commit e2e5042be5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 70 additions and 16 deletions

View file

@ -507,7 +507,7 @@ private fun ColumnScope.PasswordTypeContent(
BitwardenSlider( BitwardenSlider(
value = passwordTypeState.length, value = passwordTypeState.length,
onValueChange = passwordHandlers.onPasswordSliderLengthChange, onValueChange = passwordHandlers.onPasswordSliderLengthChange,
range = passwordTypeState.minLength..passwordTypeState.maxLength, range = passwordTypeState.computedMinimumLength..passwordTypeState.maxLength,
sliderTag = "PasswordLengthSlider", sliderTag = "PasswordLengthSlider",
valueTag = "PasswordLengthLabel", valueTag = "PasswordLengthLabel",
) )

View file

@ -578,7 +578,7 @@ class GeneratorViewModel @Inject constructor(
uppercase = password.useCapitals, uppercase = password.useCapitals,
numbers = password.useNumbers, numbers = password.useNumbers,
special = password.useSpecialChars, special = password.useSpecialChars,
length = password.length.toUByte(), length = max(password.computedMinimumLength, password.length).toUByte(),
avoidAmbiguous = password.avoidAmbiguousChars, avoidAmbiguous = password.avoidAmbiguousChars,
minLowercase = null, minLowercase = null,
minUppercase = null, minUppercase = null,
@ -829,7 +829,7 @@ class GeneratorViewModel @Inject constructor(
updatePasswordType { currentPasswordType -> updatePasswordType { currentPasswordType ->
currentPasswordType.copy( currentPasswordType.copy(
length = max(adjustedLength, currentPasswordType.minimumLength), length = max(adjustedLength, currentPasswordType.computedMinimumLength),
isUserInteracting = action.isUserInteracting, isUserInteracting = action.isUserInteracting,
) )
} }
@ -1477,7 +1477,10 @@ class GeneratorViewModel @Inject constructor(
private fun updatePasswordLength() { private fun updatePasswordLength() {
updatePasswordType { currentPasswordType -> updatePasswordType { currentPasswordType ->
currentPasswordType.copy( currentPasswordType.copy(
length = max(currentPasswordType.length, currentPasswordType.minimumLength), length = max(
currentPasswordType.length,
currentPasswordType.computedMinimumLength,
),
) )
} }
} }
@ -1853,6 +1856,22 @@ data class GeneratorState(
override val displayStringResId: Int override val displayStringResId: Int
get() = PasscodeTypeOption.PASSWORD.labelRes get() = PasscodeTypeOption.PASSWORD.labelRes
/**
* The computed minimum length for the generated Password
* based on what characters must be included.
*/
val computedMinimumLength: Int
get() {
val minLowercase = if (useLowercase) 1 else 0
val minUppercase = if (useCapitals) 1 else 0
val minimumNumbers = if (useNumbers) max(1, minNumbers) else 0
val minimumSpecial = if (useSpecialChars) max(1, minSpecial) else 0
return max(
minLength,
minLowercase + minUppercase + minimumNumbers + minimumSpecial,
)
}
@Suppress("UndocumentedPublicClass") @Suppress("UndocumentedPublicClass")
companion object { companion object {
private const val DEFAULT_PASSWORD_LENGTH: Int = 14 private const val DEFAULT_PASSWORD_LENGTH: Int = 14
@ -2625,18 +2644,6 @@ private fun Password.enforceAtLeastOneToggleOn(): Password =
this this
} }
/**
* The computed minimum length for the generated Password based on what characters must be included.
*/
private val Password.minimumLength: Int
get() {
val minLowercase = if (useLowercase) 1 else 0
val minUppercase = if (useCapitals) 1 else 0
val minimumNumbers = if (useNumbers) max(1, minNumbers) else 0
val minimumSpecial = if (useSpecialChars) max(1, minSpecial) else 0
return minLowercase + minUppercase + minimumNumbers + minimumSpecial
}
private val PasscodeGenerationOptions?.passcodeType: Passcode.PasscodeType private val PasscodeGenerationOptions?.passcodeType: Passcode.PasscodeType
get() = when (this?.type) { get() = when (this?.type) {
PasscodeGenerationOptions.PasscodeType.PASSWORD -> Password() PasscodeGenerationOptions.PasscodeType.PASSWORD -> Password()

View file

@ -2304,6 +2304,53 @@ class GeneratorViewModelTest : BaseViewModelTest() {
assertEquals(expectedState, viewModel.stateFlow.value) assertEquals(expectedState, viewModel.stateFlow.value)
} }
} }
@Test
fun `Password minimumLength should be at least as long as the sum of the minimums`() {
val password =
GeneratorState.MainType.Passcode.PasscodeType.Password(
length = 14,
minLength = 10,
useCapitals = true,
capitalsEnabled = false,
useLowercase = true,
lowercaseEnabled = false,
useNumbers = true,
numbersEnabled = false,
useSpecialChars = true,
specialCharsEnabled = false,
minNumbers = 9,
minNumbersAllowed = 3,
minSpecial = 9,
minSpecialAllowed = 3,
avoidAmbiguousChars = false,
)
// 9 numbers + 9 special + 1 lowercase + 1 uppercase
assertEquals(20, password.computedMinimumLength)
}
@Test
fun `Password minimumLength should use minLength if higher than sum of the minimums`() {
val password =
GeneratorState.MainType.Passcode.PasscodeType.Password(
length = 14,
minLength = 10,
useCapitals = true,
capitalsEnabled = false,
useLowercase = true,
lowercaseEnabled = false,
useNumbers = true,
numbersEnabled = false,
useSpecialChars = true,
specialCharsEnabled = false,
minNumbers = 1,
minNumbersAllowed = 3,
minSpecial = 1,
minSpecialAllowed = 3,
avoidAmbiguousChars = false,
)
assertEquals(10, password.computedMinimumLength)
}
//region Helper Functions //region Helper Functions
@Suppress("LongParameterList") @Suppress("LongParameterList")