mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-31 06:15:48 +00:00
Add xkpasswd-style password generator (#633)
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
### Added
|
||||
- Copy implicit username (password filename) by long pressing
|
||||
- Create xkpasswd style passwords
|
||||
|
||||
### Fixed
|
||||
- Can't delete folders containing a password
|
||||
|
@@ -15,6 +15,7 @@ import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import android.view.MenuItem
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import android.widget.Toast
|
||||
@@ -23,6 +24,7 @@ import androidx.biometric.BiometricManager
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
@@ -32,6 +34,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
|
||||
import com.zeapo.pwdstore.crypto.PgpActivity
|
||||
import com.zeapo.pwdstore.git.GitActivity
|
||||
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
|
||||
import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment
|
||||
import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
@@ -312,6 +315,51 @@ class UserPreference : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val prefCustomXkpwdDictionary = findPreference<Preference>("pref_key_custom_dict")
|
||||
prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener {
|
||||
callingActivity.storeCustomDictionaryPath()
|
||||
true
|
||||
}
|
||||
val dictUri = sharedPreferences.getString("pref_key_custom_dict", "")
|
||||
|
||||
if (!TextUtils.isEmpty(dictUri)) {
|
||||
setCustomDictSummary(prefCustomXkpwdDictionary, Uri.parse(dictUri))
|
||||
}
|
||||
|
||||
val prefIsCustomDict = findPreference<CheckBoxPreference>("pref_key_is_custom_dict")
|
||||
val prefCustomDictPicker = findPreference<Preference>("pref_key_custom_dict")
|
||||
val prefPwgenType = findPreference<ListPreference>("pref_key_pwgen_type")
|
||||
showHideDependentPrefs(prefPwgenType?.value, prefIsCustomDict, prefCustomDictPicker)
|
||||
|
||||
prefPwgenType?.onPreferenceChangeListener = ChangeListener() { _, newValue ->
|
||||
showHideDependentPrefs(newValue, prefIsCustomDict, prefCustomDictPicker)
|
||||
true
|
||||
}
|
||||
|
||||
prefIsCustomDict?.onPreferenceChangeListener = ChangeListener() { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
val customDictFile = File(context.filesDir, XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE)
|
||||
if (customDictFile.exists()) {
|
||||
FileUtils.deleteQuietly(customDictFile)
|
||||
}
|
||||
prefCustomDictPicker?.setSummary(R.string.xkpwgen_pref_custom_dict_picker_summary)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun showHideDependentPrefs(newValue: Any?, prefIsCustomDict: CheckBoxPreference?, prefCustomDictPicker: Preference?) {
|
||||
when (newValue as String) {
|
||||
PgpActivity.KEY_PWGEN_TYPE_CLASSIC -> {
|
||||
prefIsCustomDict?.isVisible = false
|
||||
prefCustomDictPicker?.isVisible = false
|
||||
}
|
||||
PgpActivity.KEY_PWGEN_TYPE_XKPASSWD -> {
|
||||
prefIsCustomDict?.isVisible = true
|
||||
prefCustomDictPicker?.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -396,6 +444,17 @@ class UserPreference : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick custom xkpwd dictionary from sdcard
|
||||
*/
|
||||
private fun storeCustomDictionaryPath() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
}
|
||||
startActivityForResult(intent, SET_CUSTOM_XKPWD_DICT)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun copySshKey(uri: Uri) {
|
||||
// TODO: Check if valid SSH Key before import
|
||||
@@ -516,6 +575,27 @@ class UserPreference : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
SET_CUSTOM_XKPWD_DICT -> {
|
||||
val uri: Uri = data.data ?: throw IOException("Unable to open file")
|
||||
|
||||
Toast.makeText(
|
||||
this,
|
||||
this.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
|
||||
prefs.edit().putString("pref_key_custom_dict", uri.toString()).apply()
|
||||
|
||||
val customDictPref = prefsFragment.findPreference<Preference>("pref_key_custom_dict")
|
||||
setCustomDictSummary(customDictPref, uri)
|
||||
// copy user selected file to internal storage
|
||||
val inputStream = this.contentResolver.openInputStream(uri)
|
||||
val customDictFile = File(this.filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE)
|
||||
FileUtils.copyInputStreamToFile(inputStream, customDictFile)
|
||||
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
@@ -599,6 +679,16 @@ class UserPreference : AppCompatActivity() {
|
||||
private const val SELECT_GIT_DIRECTORY = 4
|
||||
private const val EXPORT_PASSWORDS = 5
|
||||
private const val EDIT_GIT_CONFIG = 6
|
||||
private const val SET_CUSTOM_XKPWD_DICT = 7
|
||||
private const val TAG = "UserPreference"
|
||||
|
||||
/**
|
||||
* Set custom dictionary summary
|
||||
*/
|
||||
@JvmStatic
|
||||
private fun setCustomDictSummary(customDictPref: Preference?, uri: Uri) {
|
||||
val fileName = uri.path?.substring(uri.path?.lastIndexOf(":")!! + 1)
|
||||
customDictPref?.setSummary("Selected dictionary: " + fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ import com.zeapo.pwdstore.PasswordEntry
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.UserPreference
|
||||
import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment
|
||||
import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
|
||||
import com.zeapo.pwdstore.utils.Otp
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -142,8 +143,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||
setContentView(R.layout.encrypt_layout)
|
||||
|
||||
generate_password?.setOnClickListener {
|
||||
PasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "generator")
|
||||
when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) {
|
||||
KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "generator")
|
||||
KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "xkpwgenerator")
|
||||
}
|
||||
}
|
||||
|
||||
title = getString(R.string.new_password_title)
|
||||
@@ -505,8 +510,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||
private fun editPassword() {
|
||||
setContentView(R.layout.encrypt_layout)
|
||||
generate_password?.setOnClickListener {
|
||||
PasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "generator")
|
||||
when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) {
|
||||
KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "generator")
|
||||
KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "xkpwgenerator")
|
||||
}
|
||||
}
|
||||
|
||||
title = getString(R.string.edit_password_title)
|
||||
@@ -853,6 +862,9 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||
|
||||
const val TAG = "PgpActivity"
|
||||
|
||||
const val KEY_PWGEN_TYPE_CLASSIC = "classic"
|
||||
const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd"
|
||||
|
||||
private var delayTask: DelayShow? = null
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.pwgenxkpwd
|
||||
|
||||
enum class CapsType {
|
||||
lowercase, UPPERCASE, TitleCase, Sentencecase, As_iS
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.pwgenxkpwd
|
||||
|
||||
import android.content.Context
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator.PasswordGeneratorExeption
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.ArrayList
|
||||
import java.util.Locale
|
||||
|
||||
class PasswordBuilder(ctx: Context) {
|
||||
|
||||
private var numSymbols = 0
|
||||
private var isAppendSymbolsSeparator = false
|
||||
private var context = ctx
|
||||
private var numWords = 3
|
||||
private var maxWordLength = 9
|
||||
private var minWordLength = 5
|
||||
private var separator = "."
|
||||
private var capsType = CapsType.Sentencecase
|
||||
private var prependDigits = 0
|
||||
private var numDigits = 0
|
||||
private var isPrependWithSeparator = false
|
||||
private var isAppendNumberSeparator = false
|
||||
|
||||
fun setNumberOfWords(amount: Int) = apply {
|
||||
numWords = amount
|
||||
}
|
||||
|
||||
fun setMinimumWordLength(min: Int) = apply {
|
||||
minWordLength = min
|
||||
}
|
||||
|
||||
fun setMaximumWordLength(max: Int) = apply {
|
||||
maxWordLength = max
|
||||
}
|
||||
|
||||
fun setSeparator(separator: String) = apply {
|
||||
this.separator = separator
|
||||
}
|
||||
|
||||
fun setCapitalization(capitalizationScheme: CapsType) = apply {
|
||||
capsType = capitalizationScheme
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun prependNumbers(numDigits: Int, addSeparator: Boolean = true) = apply {
|
||||
prependDigits = numDigits
|
||||
isPrependWithSeparator = addSeparator
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun appendNumbers(numDigits: Int, addSeparator: Boolean = false) = apply {
|
||||
this.numDigits = numDigits
|
||||
isAppendNumberSeparator = addSeparator
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun appendSymbols(numSymbols: Int, addSeparator: Boolean = false) = apply {
|
||||
this.numSymbols = numSymbols
|
||||
isAppendSymbolsSeparator = addSeparator
|
||||
}
|
||||
|
||||
private fun generateRandomNumberSequence(totalNumbers: Int): String {
|
||||
val secureRandom = SecureRandom()
|
||||
val numbers = StringBuilder(totalNumbers)
|
||||
|
||||
for (i in 0 until totalNumbers) {
|
||||
numbers.append(secureRandom.nextInt(10))
|
||||
}
|
||||
return numbers.toString()
|
||||
}
|
||||
|
||||
private fun generateRandomSymbolSequence(numSymbols: Int): String {
|
||||
val secureRandom = SecureRandom()
|
||||
val numbers = StringBuilder(numSymbols)
|
||||
|
||||
for (i in 0 until numSymbols) {
|
||||
numbers.append(SYMBOLS[secureRandom.nextInt(SYMBOLS.length)])
|
||||
}
|
||||
return numbers.toString()
|
||||
}
|
||||
|
||||
@Throws(PasswordGenerator.PasswordGeneratorExeption::class)
|
||||
fun create(): String {
|
||||
val wordBank = ArrayList<String>()
|
||||
val secureRandom = SecureRandom()
|
||||
val password = StringBuilder()
|
||||
|
||||
if (prependDigits != 0) {
|
||||
password.append(generateRandomNumberSequence(prependDigits))
|
||||
if (isPrependWithSeparator) {
|
||||
password.append(separator)
|
||||
}
|
||||
}
|
||||
try {
|
||||
val dictionary = XkpwdDictionary(context)
|
||||
val words = dictionary.words
|
||||
for (wordLength in words.keys) {
|
||||
if (wordLength in minWordLength..maxWordLength) {
|
||||
wordBank.addAll(words[wordLength]!!)
|
||||
}
|
||||
}
|
||||
|
||||
if (wordBank.size == 0) {
|
||||
throw PasswordGeneratorExeption(context.getString(R.string.xkpwgen_builder_error, minWordLength, maxWordLength))
|
||||
}
|
||||
|
||||
for (i in 0 until numWords) {
|
||||
val randomIndex = secureRandom.nextInt(wordBank.size)
|
||||
var s = wordBank[randomIndex]
|
||||
|
||||
if (capsType != CapsType.As_iS) {
|
||||
s = s.toLowerCase(Locale.getDefault())
|
||||
when (capsType) {
|
||||
CapsType.UPPERCASE -> s = s.toUpperCase(Locale.getDefault())
|
||||
CapsType.Sentencecase -> {
|
||||
if (i == 0) {
|
||||
s = capitalize(s)
|
||||
}
|
||||
}
|
||||
CapsType.TitleCase -> {
|
||||
s = capitalize(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
password.append(s)
|
||||
wordBank.removeAt(randomIndex)
|
||||
if (i + 1 < numWords) {
|
||||
password.append(separator)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw PasswordGeneratorExeption("Failed generating password!")
|
||||
}
|
||||
if (numDigits != 0) {
|
||||
if (isAppendNumberSeparator) {
|
||||
password.append(separator)
|
||||
}
|
||||
password.append(generateRandomNumberSequence(numDigits))
|
||||
}
|
||||
if (numSymbols != 0) {
|
||||
if (isAppendSymbolsSeparator) {
|
||||
password.append(separator)
|
||||
}
|
||||
password.append(generateRandomSymbolSequence(numSymbols))
|
||||
}
|
||||
return password.toString()
|
||||
}
|
||||
|
||||
private fun capitalize(s: String): String {
|
||||
var result = s
|
||||
val lower = result.toLowerCase(Locale.getDefault())
|
||||
result = lower.substring(0, 1).toUpperCase(Locale.getDefault()) + result.substring(1)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SYMBOLS = "!@\$%^&*-_+=:|~?/.;#"
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.pwgenxkpwd
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.zeapo.pwdstore.R
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
|
||||
class XkpwdDictionary(context: Context) {
|
||||
val words: HashMap<Int, ArrayList<String>> = HashMap()
|
||||
|
||||
init {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
var lines: List<String> = listOf()
|
||||
|
||||
if (prefs.getBoolean("pref_key_is_custom_dict", false)) {
|
||||
|
||||
val uri = prefs.getString("pref_key_custom_dict", "")
|
||||
|
||||
if (!TextUtils.isEmpty(uri)) {
|
||||
val customDictFile = File(context.filesDir, XKPWD_CUSTOM_DICT_FILE)
|
||||
|
||||
if (customDictFile.exists() && customDictFile.canRead()) {
|
||||
lines = customDictFile.inputStream().bufferedReader().readLines()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.isEmpty()) {
|
||||
lines = context.getResources().openRawResource(R.raw.xkpwdict).bufferedReader().readLines()
|
||||
}
|
||||
|
||||
for (word in lines) {
|
||||
if (!word.trim { it <= ' ' }.contains(" ")) {
|
||||
val length = word.trim { it <= ' ' }.length
|
||||
|
||||
if (length > 0) {
|
||||
if (!words.containsKey(length)) {
|
||||
words[length] = ArrayList()
|
||||
}
|
||||
val strings = words[length]!!
|
||||
strings.add(word.trim { it <= ' ' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val XKPWD_CUSTOM_DICT_FILE = "custom_dict.txt"
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
package com.zeapo.pwdstore.ui.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
@@ -26,7 +25,7 @@ import com.zeapo.pwdstore.pwgen.PasswordGenerator.setPrefs
|
||||
class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
val callingActivity: Activity = requireActivity()
|
||||
val callingActivity = requireActivity()
|
||||
val inflater = callingActivity.layoutInflater
|
||||
val view = inflater.inflate(R.layout.fragment_pwgen, null)
|
||||
val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf")
|
||||
@@ -49,8 +48,8 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||
val edit = callingActivity.findViewById<EditText>(R.id.crypto_password_edit)
|
||||
edit.setText(passwordText.text)
|
||||
}
|
||||
builder.setNegativeButton(resources.getString(R.string.dialog_cancel)) { _, _ -> }
|
||||
builder.setNeutralButton(resources.getString(R.string.pwgen_generate), null)
|
||||
builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> }
|
||||
builder.setNegativeButton(resources.getString(R.string.pwgen_generate), null)
|
||||
val dialog = builder.setTitle(this.resources.getString(R.string.pwgen_title)).create()
|
||||
dialog.setOnShowListener {
|
||||
setPreferences()
|
||||
@@ -60,7 +59,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
||||
passwordText.text = ""
|
||||
}
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
|
||||
setPreferences()
|
||||
try {
|
||||
passwordText.text = generate(callingActivity.applicationContext)[0]
|
||||
|
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.ui.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.Spinner
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator
|
||||
import com.zeapo.pwdstore.pwgenxkpwd.CapsType
|
||||
import com.zeapo.pwdstore.pwgenxkpwd.PasswordBuilder
|
||||
import timber.log.Timber
|
||||
|
||||
/** A placeholder fragment containing a simple view. */
|
||||
class XkPasswordGeneratorDialogFragment : DialogFragment() {
|
||||
|
||||
private lateinit var editSeparator: AppCompatEditText
|
||||
private lateinit var editNumWords: AppCompatEditText
|
||||
private lateinit var cbSymbols: CheckBox
|
||||
private lateinit var spinnerCapsType: Spinner
|
||||
private lateinit var cbNumbers: CheckBox
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var spinnerNumbersCount: Spinner
|
||||
private lateinit var spinnerSymbolsCount: Spinner
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
val callingActivity = requireActivity()
|
||||
val inflater = callingActivity.layoutInflater
|
||||
val view = inflater.inflate(R.layout.fragment_xkpwgen, null)
|
||||
|
||||
val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf")
|
||||
|
||||
builder.setView(view)
|
||||
|
||||
prefs = callingActivity.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
|
||||
|
||||
cbNumbers = view.findViewById<CheckBox>(R.id.xknumerals)
|
||||
cbNumbers.isChecked = prefs.getBoolean(PREF_KEY_USE_NUMERALS, false)
|
||||
|
||||
spinnerNumbersCount = view.findViewById<Spinner>(R.id.xk_numbers_count)
|
||||
|
||||
val storedNumbersCount = prefs.getInt(PREF_KEY_NUMBERS_COUNT, 0)
|
||||
spinnerNumbersCount.setSelection(storedNumbersCount)
|
||||
|
||||
cbSymbols = view.findViewById<CheckBox>(R.id.xksymbols)
|
||||
cbSymbols.isChecked = prefs.getBoolean(PREF_KEY_USE_SYMBOLS, false) != false
|
||||
spinnerSymbolsCount = view.findViewById<Spinner>(R.id.xk_symbols_count)
|
||||
val symbolsCount = prefs.getInt(PREF_KEY_SYMBOLS_COUNT, 0)
|
||||
spinnerSymbolsCount.setSelection(symbolsCount)
|
||||
|
||||
val previousStoredCapStyle: String = try {
|
||||
prefs.getString(PREF_KEY_CAPITALS_STYLE, DEFAULT_CAPS_STYLE)!!
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("xkpw").e(e)
|
||||
DEFAULT_CAPS_STYLE
|
||||
}
|
||||
spinnerCapsType = view.findViewById<Spinner>(R.id.xkCapType)
|
||||
|
||||
val lastCapitalsStyleIndex: Int
|
||||
|
||||
lastCapitalsStyleIndex = try {
|
||||
CapsType.valueOf(previousStoredCapStyle).ordinal
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("xkpw").e(e)
|
||||
DEFAULT_CAPS_INDEX
|
||||
}
|
||||
spinnerCapsType.setSelection(lastCapitalsStyleIndex)
|
||||
|
||||
editNumWords = view.findViewById<AppCompatEditText>(R.id.xk_num_words)
|
||||
editNumWords.setText(prefs.getString(PREF_KEY_NUM_WORDS, DEFAULT_NUMBER_OF_WORDS))
|
||||
|
||||
editSeparator = view.findViewById<AppCompatEditText>(R.id.xk_separator)
|
||||
editSeparator.setText(prefs.getString(PREF_KEY_SEPARATOR, DEFAULT_WORD_SEPARATOR))
|
||||
|
||||
val passwordText: AppCompatTextView = view.findViewById(R.id.xkPasswordText)
|
||||
passwordText.typeface = monoTypeface
|
||||
|
||||
builder.setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ ->
|
||||
setPreferences()
|
||||
val edit = callingActivity.findViewById<EditText>(R.id.crypto_password_edit)
|
||||
edit.setText(passwordText.text)
|
||||
}
|
||||
|
||||
// flip neutral and negative buttons
|
||||
builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> }
|
||||
builder.setNegativeButton(resources.getString(R.string.pwgen_generate), null)
|
||||
|
||||
val dialog = builder.setTitle(this.resources.getString(R.string.xkpwgen_title)).create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
setPreferences()
|
||||
makeAndSetPassword(passwordText)
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
|
||||
setPreferences()
|
||||
makeAndSetPassword(passwordText)
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun makeAndSetPassword(passwordText: AppCompatTextView) {
|
||||
try {
|
||||
passwordText.text = PasswordBuilder(requireContext())
|
||||
.setNumberOfWords(Integer.valueOf(editNumWords.text.toString()))
|
||||
.setMinimumWordLength(DEFAULT_MIN_WORD_LENGTH)
|
||||
.setMaximumWordLength(DEFAULT_MAX_WORD_LENGTH)
|
||||
.setSeparator(editSeparator.text.toString())
|
||||
.appendNumbers(if (cbNumbers.isChecked) Integer.parseInt(spinnerNumbersCount.selectedItem as String) else 0)
|
||||
.appendSymbols(if (cbSymbols.isChecked) Integer.parseInt(spinnerSymbolsCount.selectedItem as String) else 0)
|
||||
.setCapitalization(CapsType.valueOf(spinnerCapsType.selectedItem.toString())).create()
|
||||
} catch (e: PasswordGenerator.PasswordGeneratorExeption) {
|
||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
||||
Timber.tag("xkpw").e(e, "failure generating xkpasswd")
|
||||
passwordText.text = FALLBACK_ERROR_PASS
|
||||
}
|
||||
}
|
||||
|
||||
private fun setPreferences() {
|
||||
prefs.edit().putBoolean(PREF_KEY_USE_NUMERALS, cbNumbers.isChecked)
|
||||
.putBoolean(PREF_KEY_USE_SYMBOLS, cbSymbols.isChecked)
|
||||
.putString(PREF_KEY_CAPITALS_STYLE, spinnerCapsType.selectedItem.toString())
|
||||
.putString(PREF_KEY_NUM_WORDS, editNumWords.text.toString())
|
||||
.putString(PREF_KEY_SEPARATOR, editSeparator.text.toString())
|
||||
.putInt(PREF_KEY_NUMBERS_COUNT, Integer.parseInt(spinnerNumbersCount.selectedItem as String) - 1)
|
||||
.putInt(PREF_KEY_SYMBOLS_COUNT, Integer.parseInt(spinnerSymbolsCount.selectedItem as String) - 1)
|
||||
.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREF_KEY_USE_NUMERALS = "pref_key_use_numerals"
|
||||
const val PREF_KEY_USE_SYMBOLS = "pref_key_use_symbols"
|
||||
const val PREF_KEY_CAPITALS_STYLE = "pref_key_capitals_style"
|
||||
const val PREF_KEY_NUM_WORDS = "pref_key_num_words"
|
||||
const val PREF_KEY_SEPARATOR = "pref_key_separator"
|
||||
const val PREF_KEY_NUMBERS_COUNT = "pref_key_xkpwgen_numbers_count"
|
||||
const val PREF_KEY_SYMBOLS_COUNT = "pref_key_symbols_count"
|
||||
val DEFAULT_CAPS_STYLE = CapsType.Sentencecase.name
|
||||
val DEFAULT_CAPS_INDEX = CapsType.Sentencecase.ordinal
|
||||
const val DEFAULT_NUMBER_OF_WORDS = "3"
|
||||
const val DEFAULT_WORD_SEPARATOR = "."
|
||||
const val DEFAULT_MIN_WORD_LENGTH = 3
|
||||
const val DEFAULT_MAX_WORD_LENGTH = 9
|
||||
const val FALLBACK_ERROR_PASS = "42"
|
||||
}
|
||||
}
|
142
app/src/main/res/layout/fragment_xkpwgen.xml
Normal file
142
app/src/main/res/layout/fragment_xkpwgen.xml
Normal file
@@ -0,0 +1,142 @@
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingBottom="20dp"
|
||||
tools:context=".MainActivityFragment">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/xkPasswordText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight=".6"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/pwgen_include"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/xknumerals"
|
||||
android:layout_width="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/xkpwgen_numbers"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/xk_numbers_count"
|
||||
android:layout_width="fill_parent"
|
||||
android:minWidth="40dp"
|
||||
android:dropDownWidth="40dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/xk_range_1_10"
|
||||
android:entryValues="@array/xk_range_1_10"
|
||||
android:spinnerMode="dropdown" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/xksymbols"
|
||||
android:layout_width="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/xkpwgen_symbols"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/xk_symbols_count"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minWidth="40dp"
|
||||
android:dropDownWidth="40dp"
|
||||
android:entries="@array/xk_range_1_10"
|
||||
android:entryValues="@array/xk_range_1_10"
|
||||
android:spinnerMode="dropdown" />
|
||||
</LinearLayout>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/xkCapType"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:entries="@array/capitalization_type_values"
|
||||
android:entryValues="@array/capitalization_type_values"
|
||||
android:spinnerMode="dropdown" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.4"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/xkpwgen_length"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/xk_num_words"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:maxLength="2"
|
||||
android:ems="10"
|
||||
android:inputType="number" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/xkpwgen_separator"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/xk_separator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:ems="10"
|
||||
android:autofillHints=""
|
||||
android:hint="@string/xkpwgen_separator_character"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
9000
app/src/main/res/raw/xkpwdict.txt
Normal file
9000
app/src/main/res/raw/xkpwdict.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -19,4 +19,33 @@
|
||||
<item>FILE_FIRST</item>
|
||||
<item>INDEPENDENT</item>
|
||||
</string-array>
|
||||
<string-array name="capitalization_type_entries">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</string-array>
|
||||
<string-array name="capitalization_type_values">
|
||||
<item>lowercase</item>
|
||||
<item>UPPERCASE</item>
|
||||
<item>TitleCase</item>
|
||||
<item>Sentencecase</item>
|
||||
</string-array>
|
||||
<string-array name="xk_range_1_10">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
<item>9</item>
|
||||
<item>10</item>
|
||||
</string-array>
|
||||
<string-array name="pwgen_provider">
|
||||
<item>classic</item>
|
||||
<item>xkpasswd</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
@@ -190,6 +190,25 @@
|
||||
<string name="pwgen_no_chars_error">No characters included</string>
|
||||
<string name="pwgen_length_too_short_error">Length too short for selected criteria</string>
|
||||
|
||||
<!-- XKPWD password generator -->
|
||||
<string name="xkpwgen_title">Xkpasswd Generator</string>
|
||||
<string name="xkpwgen_length">Total words</string>
|
||||
<string name="xkpwgen_separator">Separator</string>
|
||||
<string name="xkpwgen_custom_dict_imported">Custom wordlist: %1$s</string>
|
||||
<string name="xkpwgen_separator_character">separator character</string>
|
||||
<string name="xkpwgen_numbers">numbers:</string>
|
||||
<string name="xkpwgen_symbols">symbols:</string>
|
||||
<string name="xkpwgen_builder_error">Selected dictionary does not contain enough words of given length %1$d..%2$d</string>
|
||||
|
||||
<!-- XKPWD prefs -->
|
||||
<string name="xkpwgen_pref_gentype_title">Password generator type</string>
|
||||
<string name="xkpwgen_pref_custom_dict_title">Custom wordlist</string>
|
||||
<string name="xkpwgen_pref_custom_dict_summary_on">Using custom wordlist file</string>
|
||||
<string name="xkpwgen_pref_custom_dict_summary_off">Using built-in wordlist</string>
|
||||
<string name="xkpwgen_pref_custom_dict_picker_title">Custom worldlist file</string>
|
||||
<string name="xkpwgen_pref_custom_dict_picker_summary">Tap to pick a custom wordlist file containing one word per line</string>
|
||||
|
||||
|
||||
<!-- ssh keygen fragment -->
|
||||
<string name="ssh_keygen_length">Length</string>
|
||||
<string name="ssh_keygen_passphrase">Passphrase</string>
|
||||
@@ -285,4 +304,6 @@
|
||||
<string name="pref_search_on_start_hint">Open search bar when app is launched</string>
|
||||
<string name="pref_search_from_root">Always search from root</string>
|
||||
<string name="pref_search_from_root_hint">Search from root of store regardless of currently open directory</string>
|
||||
<string name="password_generator_category_title">Password Generator</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<androidx.preference.PreferenceCategory android:title="@string/pref_git_title">
|
||||
<androidx.preference.Preference
|
||||
android:key="git_server_info"
|
||||
@@ -48,6 +49,27 @@
|
||||
android:title="@string/pref_key_title" />
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory android:title="@string/password_generator_category_title">
|
||||
<androidx.preference.ListPreference
|
||||
android:key="pref_key_pwgen_type"
|
||||
android:title="@string/xkpwgen_pref_gentype_title"
|
||||
android:defaultValue="classic"
|
||||
android:entries="@array/pwgen_provider"
|
||||
android:entryValues="@array/pwgen_provider"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
android:persistent="true" />
|
||||
<androidx.preference.CheckBoxPreference
|
||||
android:key="pref_key_is_custom_dict"
|
||||
android:title="@string/xkpwgen_pref_custom_dict_title"
|
||||
android:summaryOn="@string/xkpwgen_pref_custom_dict_summary_on"
|
||||
android:summaryOff="@string/xkpwgen_pref_custom_dict_summary_off"/>
|
||||
<androidx.preference.Preference
|
||||
android:key="pref_key_custom_dict"
|
||||
android:title="@string/xkpwgen_pref_custom_dict_picker_title"
|
||||
android:summary="@string/xkpwgen_pref_custom_dict_picker_summary"
|
||||
android:dependency="pref_key_is_custom_dict"/>
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory android:title="@string/pref_general_title">
|
||||
<androidx.preference.EditTextPreference
|
||||
android:defaultValue="45"
|
||||
|
Reference in New Issue
Block a user