summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAditya Wasan <adityawasan55@gmail.com>2022-01-13 22:13:53 +0530
committerGitHub <noreply@github.com>2022-01-13 16:43:53 +0000
commitabc62c2b6b9d7106eb9f53f4994f086835602e57 (patch)
tree477942729574acfd0699f090a1e3ad8a073d1703
parentc1ef2e73418d1ddda7711520645a77b8c8e3cf56 (diff)
Refactor randomized password generator into a separate module (#1663)
-rw-r--r--app/build.gradle.kts1
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt69
-rw-r--r--app/src/main/res/values/strings.xml1
-rw-r--r--build-logic/kotlin-plugins/src/main/kotlin/com.github.android-password-store.binary-compatibility.gradle.kts1
-rw-r--r--passgen/random/.gitignore1
-rw-r--r--passgen/random/build.gradle.kts4
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGenerator.kt (renamed from app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt)82
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGeneratorException.kt10
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordOption.kt10
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomNumberGenerator.kt (renamed from app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt)14
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomPasswordGenerator.kt (renamed from app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt)6
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomPhonemesGenerator.kt (renamed from app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt)6
-rw-r--r--passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/util/Extensions.kt11
-rw-r--r--settings.gradle.kts2
14 files changed, 126 insertions, 92 deletions
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 443121cf..e171a814 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -79,6 +79,7 @@ dependencies {
implementation(projects.formatCommon)
implementation(projects.openpgpKtx)
implementation(projects.passgen.diceware)
+ implementation(projects.passgen.random)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.autofill)
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/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7b476587..c11e6f25 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -391,5 +391,6 @@
<string name="pgp_key_import_succeeded">Successfully imported PGP key</string>
<string name="pgp_key_import_succeeded_message">The key ID of the imported key is given below, please review it for correctness:\n%1$s</string>
<string name="pref_category_pgp_title">PGP settings</string>
+ <string name="pwgen_some_error_occurred">Some error occurred</string>
</resources>
diff --git a/build-logic/kotlin-plugins/src/main/kotlin/com.github.android-password-store.binary-compatibility.gradle.kts b/build-logic/kotlin-plugins/src/main/kotlin/com.github.android-password-store.binary-compatibility.gradle.kts
index 4e0ceaa3..2aec2844 100644
--- a/build-logic/kotlin-plugins/src/main/kotlin/com.github.android-password-store.binary-compatibility.gradle.kts
+++ b/build-logic/kotlin-plugins/src/main/kotlin/com.github.android-password-store.binary-compatibility.gradle.kts
@@ -15,5 +15,6 @@ apiValidation {
"crypto-pgpainless",
"format-common",
"diceware",
+ "random",
)
}
diff --git a/passgen/random/.gitignore b/passgen/random/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/passgen/random/.gitignore
@@ -0,0 +1 @@
+/build \ No newline at end of file
diff --git a/passgen/random/build.gradle.kts b/passgen/random/build.gradle.kts
new file mode 100644
index 00000000..f91458dd
--- /dev/null
+++ b/passgen/random/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ kotlin("jvm")
+ id("com.github.android-password-store.kotlin-library")
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGenerator.kt
index bd21ea0a..fcd28311 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGenerator.kt
@@ -2,56 +2,28 @@
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.util.pwgen
+package dev.msfjarvis.aps.passgen.random
-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
+import dev.msfjarvis.aps.passgen.random.util.clearFlag
+import dev.msfjarvis.aps.passgen.random.util.hasFlag
-enum class PasswordOption(val key: String) {
- NoDigits("0"),
- NoUppercaseLetters("A"),
- NoAmbiguousCharacters("B"),
- FullyRandom("s"),
- AtLeastOneSymbol("y"),
- NoLowercaseLetters("L")
-}
-
-object PasswordGenerator {
+public object PasswordGenerator {
- const val DEFAULT_LENGTH = 16
+ public const val DEFAULT_LENGTH: Int = 16
- const val DIGITS = 0x0001
- const val UPPERS = 0x0002
- const val SYMBOLS = 0x0004
- const val NO_AMBIGUOUS = 0x0008
- const val LOWERS = 0x0020
+ internal const val DIGITS = 0x0001
+ internal const val UPPERS = 0x0002
+ internal const val SYMBOLS = 0x0004
+ internal const val NO_AMBIGUOUS = 0x0008
+ internal 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
- }
+ internal const val DIGITS_STR = "0123456789"
+ internal const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ internal const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
+ internal const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+ internal const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
- fun isValidPassword(password: String, pwFlags: Int): Boolean {
+ internal 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
@@ -60,17 +32,15 @@ object PasswordGenerator {
return true
}
- /** Generates a password using the preferences set by [setPrefs]. */
+ /** Generates a password using the given [passwordOptions] and [length]. */
@Throws(PasswordGeneratorException::class)
- fun generate(ctx: Context): String {
- val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
+ public fun generate(passwordOptions: List<PasswordOption>, length: Int = DEFAULT_LENGTH): String {
var numCharacterCategories = 0
-
var phonemes = true
var pwgenFlags = DIGITS or UPPERS or LOWERS
for (option in PasswordOption.values()) {
- if (prefs.getBoolean(option.key, false)) {
+ if (option in passwordOptions) {
when (option) {
PasswordOption.NoDigits -> pwgenFlags = pwgenFlags.clearFlag(DIGITS)
PasswordOption.NoUppercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(UPPERS)
@@ -98,14 +68,11 @@ object PasswordGenerator {
}
}
- 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))
+ throw NoCharactersIncludedException()
}
if (length < numCharacterCategories) {
- throw PasswordGeneratorException(
- ctx.resources.getString(R.string.pwgen_length_too_short_error)
- )
+ throw PasswordLengthTooShortException()
}
if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) {
phonemes = false
@@ -120,10 +87,7 @@ object PasswordGenerator {
var password: String?
var iterations = 0
do {
- if (iterations++ > 1000)
- throw PasswordGeneratorException(
- ctx.resources.getString(R.string.pwgen_max_iterations_exceeded)
- )
+ if (iterations++ > 1000) throw MaxIterationsExceededException()
password =
if (phonemes) {
RandomPhonemesGenerator.generate(length, pwgenFlags)
@@ -133,6 +97,4 @@ object PasswordGenerator {
} while (password == null)
return password
}
-
- class PasswordGeneratorException(string: String) : Exception(string)
}
diff --git a/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGeneratorException.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGeneratorException.kt
new file mode 100644
index 00000000..b7d70c39
--- /dev/null
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordGeneratorException.kt
@@ -0,0 +1,10 @@
+package dev.msfjarvis.aps.passgen.random
+
+public sealed class PasswordGeneratorException(message: String? = null, cause: Throwable? = null) :
+ Throwable(message, cause)
+
+public class MaxIterationsExceededException : PasswordGeneratorException()
+
+public class NoCharactersIncludedException : PasswordGeneratorException()
+
+public class PasswordLengthTooShortException : PasswordGeneratorException()
diff --git a/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordOption.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordOption.kt
new file mode 100644
index 00000000..70d0da22
--- /dev/null
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/PasswordOption.kt
@@ -0,0 +1,10 @@
+package dev.msfjarvis.aps.passgen.random
+
+public enum class PasswordOption(public val key: String) {
+ NoDigits("0"),
+ NoUppercaseLetters("A"),
+ NoAmbiguousCharacters("B"),
+ FullyRandom("s"),
+ AtLeastOneSymbol("y"),
+ NoLowercaseLetters("L")
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomNumberGenerator.kt
index 8ef490cc..3bb34325 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomNumberGenerator.kt
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomNumberGenerator.kt
@@ -2,30 +2,30 @@
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.util.pwgen
+package dev.msfjarvis.aps.passgen.random
import java.security.SecureRandom
private val secureRandom = SecureRandom()
/** Returns a number between 0 (inclusive) and [exclusiveBound](exclusive). */
-fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
+internal fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
/** Returns `true` and `false` with probablity 50% each. */
-fun secureRandomBoolean() = secureRandom.nextBoolean()
+internal fun secureRandomBoolean() = secureRandom.nextBoolean()
/**
* Returns `true` with probability [percentTrue]% and `false` with probability `(100 - [percentTrue]
* )`%.
*/
-fun secureRandomBiasedBoolean(percentTrue: Int): Boolean {
+internal 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)]
+internal fun <T> Array<T>.secureRandomElement() = this[secureRandomNumber(size)]
-fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
+internal fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
-fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
+internal fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomPasswordGenerator.kt
index c1a8aeb1..32d3ff49 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomPasswordGenerator.kt
@@ -2,11 +2,11 @@
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.util.pwgen
+package dev.msfjarvis.aps.passgen.random
-import dev.msfjarvis.aps.util.extensions.hasFlag
+import dev.msfjarvis.aps.passgen.random.util.hasFlag
-object RandomPasswordGenerator {
+internal object RandomPasswordGenerator {
/**
* Generates a random password of length [targetLength], taking the following flags in [pwFlags]
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomPhonemesGenerator.kt
index 5a5f5f21..b9df4324 100644
--- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/RandomPhonemesGenerator.kt
@@ -2,12 +2,12 @@
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.util.pwgen
+package dev.msfjarvis.aps.passgen.random
-import dev.msfjarvis.aps.util.extensions.hasFlag
+import dev.msfjarvis.aps.passgen.random.util.hasFlag
import java.util.Locale
-object RandomPhonemesGenerator {
+internal object RandomPhonemesGenerator {
private const val CONSONANT = 0x0001
private const val VOWEL = 0x0002
diff --git a/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/util/Extensions.kt b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/util/Extensions.kt
new file mode 100644
index 00000000..3b38e22b
--- /dev/null
+++ b/passgen/random/src/main/kotlin/dev/msfjarvis/aps/passgen/random/util/Extensions.kt
@@ -0,0 +1,11 @@
+package dev.msfjarvis.aps.passgen.random.util
+
+/** Clears the given [flag] from the value of this [Int] */
+internal infix fun Int.clearFlag(flag: Int): Int {
+ return this and flag.inv()
+}
+
+/** Checks if this [Int] contains the given [flag] */
+internal infix fun Int.hasFlag(flag: Int): Boolean {
+ return this and flag == flag
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index bc0c5ab3..3b2f4d2a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -53,3 +53,5 @@ include("format-common")
include("openpgp-ktx")
include("passgen:diceware")
+
+include(":passgen:random")