summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt69
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt138
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt31
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt48
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt180
5 files changed, 50 insertions, 416 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt
index ab51cc4e..a21e9a6a 100644
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt
@@ -14,6 +14,7 @@ import android.widget.EditText
import android.widget.Toast
import androidx.annotation.IdRes
import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
@@ -23,11 +24,12 @@ import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.databinding.FragmentPwgenBinding
+import dev.msfjarvis.aps.passgen.random.MaxIterationsExceededException
+import dev.msfjarvis.aps.passgen.random.NoCharactersIncludedException
+import dev.msfjarvis.aps.passgen.random.PasswordGenerator
+import dev.msfjarvis.aps.passgen.random.PasswordLengthTooShortException
+import dev.msfjarvis.aps.passgen.random.PasswordOption
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
-import dev.msfjarvis.aps.util.pwgen.PasswordGenerator
-import dev.msfjarvis.aps.util.pwgen.PasswordGenerator.generate
-import dev.msfjarvis.aps.util.pwgen.PasswordGenerator.setPrefs
-import dev.msfjarvis.aps.util.pwgen.PasswordOption
import dev.msfjarvis.aps.util.settings.PreferenceKeys
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -96,10 +98,23 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
}
private fun generate(passwordField: AppCompatTextView) {
- setPreferences()
+ val passwordOptions = getSelectedOptions()
+ val passwordLength = getLength()
+ setPrefs(requireContext(), passwordOptions, passwordLength)
passwordField.text =
- runCatching { generate(requireContext().applicationContext) }.getOrElse { e ->
- Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
+ runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) }.getOrElse {
+ exception ->
+ val errorText =
+ when (exception) {
+ is MaxIterationsExceededException ->
+ requireContext().getString(R.string.pwgen_max_iterations_exceeded)
+ is NoCharactersIncludedException ->
+ requireContext().getString(R.string.pwgen_no_chars_error)
+ is PasswordLengthTooShortException ->
+ requireContext().getString(R.string.pwgen_length_too_short_error)
+ else -> requireContext().getString(R.string.pwgen_some_error_occurred)
+ }
+ Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show()
""
}
}
@@ -108,18 +123,34 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
return requireDialog().findViewById<CheckBox>(id).isChecked
}
- private fun setPreferences() {
- val preferences =
- listOfNotNull(
- PasswordOption.NoDigits.takeIf { !isChecked(R.id.numerals) },
- PasswordOption.AtLeastOneSymbol.takeIf { isChecked(R.id.symbols) },
- PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
- PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
- PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
- PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
- )
+ private fun getSelectedOptions(): List<PasswordOption> {
+ return listOfNotNull(
+ PasswordOption.NoDigits.takeIf { !isChecked(R.id.numerals) },
+ PasswordOption.AtLeastOneSymbol.takeIf { isChecked(R.id.symbols) },
+ PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
+ PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
+ PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
+ PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
+ )
+ }
+
+ private fun getLength(): Int {
val lengthText = requireDialog().findViewById<EditText>(R.id.lengthNumber).text.toString()
- val length = lengthText.toIntOrNull()?.takeIf { it >= 0 } ?: PasswordGenerator.DEFAULT_LENGTH
- setPrefs(requireActivity().applicationContext, preferences, length)
+ return lengthText.toIntOrNull()?.takeIf { it >= 0 } ?: PasswordGenerator.DEFAULT_LENGTH
+ }
+
+ /**
+ * Enables the [PasswordOption]s in [options] and sets [targetLength] as the length for generated
+ * passwords.
+ */
+ private fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
+ ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
+ for (possibleOption in PasswordOption.values()) putBoolean(
+ possibleOption.key,
+ possibleOption in options
+ )
+ putInt("length", targetLength)
+ }
+ return true
}
}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt
deleted file mode 100644
index bd21ea0a..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.util.pwgen
-
-import android.content.Context
-import androidx.core.content.edit
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.util.extensions.clearFlag
-import dev.msfjarvis.aps.util.extensions.hasFlag
-import dev.msfjarvis.aps.util.settings.PreferenceKeys
-
-enum class PasswordOption(val key: String) {
- NoDigits("0"),
- NoUppercaseLetters("A"),
- NoAmbiguousCharacters("B"),
- FullyRandom("s"),
- AtLeastOneSymbol("y"),
- NoLowercaseLetters("L")
-}
-
-object PasswordGenerator {
-
- const val DEFAULT_LENGTH = 16
-
- const val DIGITS = 0x0001
- const val UPPERS = 0x0002
- const val SYMBOLS = 0x0004
- const val NO_AMBIGUOUS = 0x0008
- const val LOWERS = 0x0020
-
- const val DIGITS_STR = "0123456789"
- const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
- const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
- const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
-
- /**
- * Enables the [PasswordOption] s in [options] and sets [targetLength] as the length for generated
- * passwords.
- */
- fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
- ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
- for (possibleOption in PasswordOption.values()) putBoolean(
- possibleOption.key,
- possibleOption in options
- )
- putInt("length", targetLength)
- }
- return true
- }
-
- fun isValidPassword(password: String, pwFlags: Int): Boolean {
- if (pwFlags hasFlag DIGITS && password.none { it in DIGITS_STR }) return false
- if (pwFlags hasFlag UPPERS && password.none { it in UPPERS_STR }) return false
- if (pwFlags hasFlag LOWERS && password.none { it in LOWERS_STR }) return false
- if (pwFlags hasFlag SYMBOLS && password.none { it in SYMBOLS_STR }) return false
- if (pwFlags hasFlag NO_AMBIGUOUS && password.any { it in AMBIGUOUS_STR }) return false
- return true
- }
-
- /** Generates a password using the preferences set by [setPrefs]. */
- @Throws(PasswordGeneratorException::class)
- fun generate(ctx: Context): String {
- val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
- var numCharacterCategories = 0
-
- var phonemes = true
- var pwgenFlags = DIGITS or UPPERS or LOWERS
-
- for (option in PasswordOption.values()) {
- if (prefs.getBoolean(option.key, false)) {
- when (option) {
- PasswordOption.NoDigits -> pwgenFlags = pwgenFlags.clearFlag(DIGITS)
- PasswordOption.NoUppercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(UPPERS)
- PasswordOption.NoLowercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(LOWERS)
- PasswordOption.NoAmbiguousCharacters -> pwgenFlags = pwgenFlags or NO_AMBIGUOUS
- PasswordOption.FullyRandom -> phonemes = false
- PasswordOption.AtLeastOneSymbol -> {
- numCharacterCategories++
- pwgenFlags = pwgenFlags or SYMBOLS
- }
- }
- } else {
- // The No* options are false, so the respective character category will be included.
- when (option) {
- PasswordOption.NoDigits,
- PasswordOption.NoUppercaseLetters,
- PasswordOption.NoLowercaseLetters -> {
- numCharacterCategories++
- }
- PasswordOption.NoAmbiguousCharacters,
- PasswordOption.FullyRandom,
- // Since AtLeastOneSymbol is not negated, it is counted in the if branch.
- PasswordOption.AtLeastOneSymbol -> {}
- }
- }
- }
-
- val length = prefs.getInt(PreferenceKeys.LENGTH, DEFAULT_LENGTH)
- if (pwgenFlags.clearFlag(NO_AMBIGUOUS) == 0) {
- throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_no_chars_error))
- }
- if (length < numCharacterCategories) {
- throw PasswordGeneratorException(
- ctx.resources.getString(R.string.pwgen_length_too_short_error)
- )
- }
- if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) {
- phonemes = false
- pwgenFlags = pwgenFlags.clearFlag(NO_AMBIGUOUS)
- }
- // Experiments show that phonemes may require more than 1000 iterations to generate a valid
- // password if the length is not at least 6.
- if (length < 6) {
- phonemes = false
- }
-
- var password: String?
- var iterations = 0
- do {
- if (iterations++ > 1000)
- throw PasswordGeneratorException(
- ctx.resources.getString(R.string.pwgen_max_iterations_exceeded)
- )
- password =
- if (phonemes) {
- RandomPhonemesGenerator.generate(length, pwgenFlags)
- } else {
- RandomPasswordGenerator.generate(length, pwgenFlags)
- }
- } while (password == null)
- return password
- }
-
- class PasswordGeneratorException(string: String) : Exception(string)
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt
deleted file mode 100644
index 8ef490cc..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.util.pwgen
-
-import java.security.SecureRandom
-
-private val secureRandom = SecureRandom()
-
-/** Returns a number between 0 (inclusive) and [exclusiveBound](exclusive). */
-fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
-
-/** Returns `true` and `false` with probablity 50% each. */
-fun secureRandomBoolean() = secureRandom.nextBoolean()
-
-/**
- * Returns `true` with probability [percentTrue]% and `false` with probability `(100 - [percentTrue]
- * )`%.
- */
-fun secureRandomBiasedBoolean(percentTrue: Int): Boolean {
- require(1 <= percentTrue) { "Probability for returning `true` must be at least 1%" }
- require(percentTrue <= 99) { "Probability for returning `true` must be at most 99%" }
- return secureRandomNumber(100) < percentTrue
-}
-
-fun <T> Array<T>.secureRandomElement() = this[secureRandomNumber(size)]
-
-fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
-
-fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt
deleted file mode 100644
index c1a8aeb1..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.util.pwgen
-
-import dev.msfjarvis.aps.util.extensions.hasFlag
-
-object RandomPasswordGenerator {
-
- /**
- * Generates a random password of length [targetLength], taking the following flags in [pwFlags]
- * into account, or fails to do so and returns null:
- *
- * - [PasswordGenerator.DIGITS]: If set, the password will contain at least one digit; if not set,
- * the password will not contain any digits.
- * - [PasswordGenerator.UPPERS]: If set, the password will contain at least one uppercase letter;
- * if not set, the password will not contain any uppercase letters.
- * - [PasswordGenerator.LOWERS]: If set, the password will contain at least one lowercase letter;
- * if not set, the password will not contain any lowercase letters.
- * - [PasswordGenerator.SYMBOLS]: If set, the password will contain at least one symbol; if not
- * set, the password will not contain any symbols.
- * - [PasswordGenerator.NO_AMBIGUOUS]: If set, the password will not contain any ambiguous
- * characters.
- */
- fun generate(targetLength: Int, pwFlags: Int): String? {
- val bank =
- listOfNotNull(
- PasswordGenerator.DIGITS_STR.takeIf { pwFlags hasFlag PasswordGenerator.DIGITS },
- PasswordGenerator.UPPERS_STR.takeIf { pwFlags hasFlag PasswordGenerator.UPPERS },
- PasswordGenerator.LOWERS_STR.takeIf { pwFlags hasFlag PasswordGenerator.LOWERS },
- PasswordGenerator.SYMBOLS_STR.takeIf { pwFlags hasFlag PasswordGenerator.SYMBOLS },
- )
- .joinToString("")
-
- var password = ""
- while (password.length < targetLength) {
- val candidate = bank.secureRandomCharacter()
- if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
- candidate in PasswordGenerator.AMBIGUOUS_STR
- ) {
- continue
- }
- password += candidate
- }
- return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt
deleted file mode 100644
index 5a5f5f21..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.util.pwgen
-
-import dev.msfjarvis.aps.util.extensions.hasFlag
-import java.util.Locale
-
-object RandomPhonemesGenerator {
-
- private const val CONSONANT = 0x0001
- private const val VOWEL = 0x0002
- private const val DIPHTHONG = 0x0004
- private const val NOT_FIRST = 0x0008
-
- private val elements =
- arrayOf(
- Element("a", VOWEL),
- Element("ae", VOWEL or DIPHTHONG),
- Element("ah", VOWEL or DIPHTHONG),
- Element("ai", VOWEL or DIPHTHONG),
- Element("b", CONSONANT),
- Element("c", CONSONANT),
- Element("ch", CONSONANT or DIPHTHONG),
- Element("d", CONSONANT),
- Element("e", VOWEL),
- Element("ee", VOWEL or DIPHTHONG),
- Element("ei", VOWEL or DIPHTHONG),
- Element("f", CONSONANT),
- Element("g", CONSONANT),
- Element("gh", CONSONANT or DIPHTHONG or NOT_FIRST),
- Element("h", CONSONANT),
- Element("i", VOWEL),
- Element("ie", VOWEL or DIPHTHONG),
- Element("j", CONSONANT),
- Element("k", CONSONANT),
- Element("l", CONSONANT),
- Element("m", CONSONANT),
- Element("n", CONSONANT),
- Element("ng", CONSONANT or DIPHTHONG or NOT_FIRST),
- Element("o", VOWEL),
- Element("oh", VOWEL or DIPHTHONG),
- Element("oo", VOWEL or DIPHTHONG),
- Element("p", CONSONANT),
- Element("ph", CONSONANT or DIPHTHONG),
- Element("qu", CONSONANT or DIPHTHONG),
- Element("r", CONSONANT),
- Element("s", CONSONANT),
- Element("sh", CONSONANT or DIPHTHONG),
- Element("t", CONSONANT),
- Element("th", CONSONANT or DIPHTHONG),
- Element("u", VOWEL),
- Element("v", CONSONANT),
- Element("w", CONSONANT),
- Element("x", CONSONANT),
- Element("y", CONSONANT),
- Element("z", CONSONANT)
- )
-
- private class Element(str: String, val flags: Int) {
-
- val upperCase = str.uppercase(Locale.ROOT)
- val lowerCase = str.lowercase(Locale.ROOT)
- val length = str.length
- val isAmbiguous = str.any { it in PasswordGenerator.AMBIGUOUS_STR }
- }
-
- /**
- * Generates a random human-readable password of length [targetLength], taking the following flags
- * in [pwFlags] into account, or fails to do so and returns null:
- *
- * - [PasswordGenerator.DIGITS]: If set, the password will contain at least one digit; if not set,
- * the password will not contain any digits.
- * - [PasswordGenerator.UPPERS]: If set, the password will contain at least one uppercase letter;
- * if not set, the password will not contain any uppercase letters.
- * - [PasswordGenerator.LOWERS]: If set, the password will contain at least one lowercase letter;
- * if not set and [PasswordGenerator.UPPERS] is set, the password will not contain any lowercase
- * characters; if both are not set, an exception is thrown.
- * - [PasswordGenerator.SYMBOLS]: If set, the password will contain at least one symbol; if not
- * set, the password will not contain any symbols.
- * - [PasswordGenerator.NO_AMBIGUOUS]: If set, the password will not contain any ambiguous
- * characters.
- */
- fun generate(targetLength: Int, pwFlags: Int): String? {
- require(pwFlags hasFlag PasswordGenerator.UPPERS || pwFlags hasFlag PasswordGenerator.LOWERS)
-
- var password = ""
-
- var isStartOfPart = true
- var nextBasicType = if (secureRandomBoolean()) VOWEL else CONSONANT
- var previousFlags = 0
-
- while (password.length < targetLength) {
- // First part: Add a single letter or pronounceable pair of letters in varying case.
-
- val candidate = elements.secureRandomElement()
-
- // Reroll if the candidate does not fulfill the current requirements.
- if (!candidate.flags.hasFlag(nextBasicType) ||
- (isStartOfPart && candidate.flags hasFlag NOT_FIRST) ||
- // Don't let a diphthong that starts with a vowel follow a vowel.
- (previousFlags hasFlag VOWEL &&
- candidate.flags hasFlag VOWEL &&
- candidate.flags hasFlag DIPHTHONG) ||
- // Don't add multi-character candidates if we would go over the targetLength.
- (password.length + candidate.length > targetLength) ||
- (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && candidate.isAmbiguous)
- ) {
- continue
- }
-
- // At this point the candidate could be appended to the password, but we still have
- // to determine the case. If no upper case characters are required, we don't add
- // any.
- val useUpperIfBothCasesAllowed =
- (isStartOfPart || candidate.flags hasFlag CONSONANT) && secureRandomBiasedBoolean(20)
- password +=
- if (pwFlags hasFlag PasswordGenerator.UPPERS &&
- (!(pwFlags hasFlag PasswordGenerator.LOWERS) || useUpperIfBothCasesAllowed)
- ) {
- candidate.upperCase
- } else {
- candidate.lowerCase
- }
-
- // We ensured above that we will not go above the target length.
- check(password.length <= targetLength)
- if (password.length == targetLength) break
-
- // Second part: Add digits and symbols with a certain probability (if requested) if
- // they would not directly follow the first character in a pronounceable part.
-
- if (!isStartOfPart &&
- pwFlags hasFlag PasswordGenerator.DIGITS &&
- secureRandomBiasedBoolean(30)
- ) {
- var randomDigit: Char
- do {
- randomDigit = secureRandomNumber(10).toString(10).first()
- } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
- randomDigit in PasswordGenerator.AMBIGUOUS_STR)
-
- password += randomDigit
- // Begin a new pronounceable part after every digit.
- isStartOfPart = true
- nextBasicType = if (secureRandomBoolean()) VOWEL else CONSONANT
- previousFlags = 0
- continue
- }
-
- if (!isStartOfPart &&
- pwFlags hasFlag PasswordGenerator.SYMBOLS &&
- secureRandomBiasedBoolean(20)
- ) {
- var randomSymbol: Char
- do {
- randomSymbol = PasswordGenerator.SYMBOLS_STR.secureRandomCharacter()
- } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
- randomSymbol in PasswordGenerator.AMBIGUOUS_STR)
- password += randomSymbol
- // Continue the password generation as if nothing was added.
- }
-
- // Third part: Determine the basic type of the next character depending on the letter
- // we just added.
- nextBasicType =
- when {
- candidate.flags.hasFlag(CONSONANT) -> VOWEL
- previousFlags.hasFlag(VOWEL) ||
- candidate.flags.hasFlag(DIPHTHONG) ||
- secureRandomBiasedBoolean(60) -> CONSONANT
- else -> VOWEL
- }
- previousFlags = candidate.flags
- isStartOfPart = false
- }
- return password.takeIf { PasswordGenerator.isValidPassword(it, pwFlags) }
- }
-}