Mercurial > cgi-bin > hgweb.cgi > PassMan
view src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt @ 9:72619175004e
Fix issues found in testing.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sat, 01 Oct 2022 09:57:23 -0700 |
parents | 698c4a3d758d |
children | cbe4c797c9a6 |
line wrap: on
line source
package name.blackcap.passman import org.apache.commons.cli.* import java.sql.Types import java.util.* import kotlin.properties.Delegates import kotlin.system.exitProcess class UpdateSubcommand(): Subcommand() { private lateinit var commandLine: CommandLine private lateinit var db: Database private lateinit var nameIn: String private var id by Delegates.notNull<Long>() private var length by Delegates.notNull<Int>() private val fields = StringBuilder() private val fieldValues = mutableListOf<Any?>() private companion object { const val GENERATE = "generate" const val HELP = "help" const val LENGTH = "length" const val SYMBOLS = "symbols" const val VERBOSE = "verbose" const val NULL_SPECIFIED = "." val NULLABLE_FIELDS = setOf<String>("notes") val SENSITIVE_FIELDS = setOf<String>("password") } override fun run(args: Array<String>) { parseArguments(args) checkDatabase() try { update() } finally { cleanUp() } } private fun parseArguments(args: Array<String>) { val options = Options().apply { addOption("g", GENERATE, false, "Use password generator.") addOption("h", HELP, false, "Print this help message.") addOption("l", LENGTH, true, "Length of generated password.") addOption("s", SYMBOLS, false, "Use symbol characters in generated password.") addOption("v", VERBOSE, false, "Print the generated password.") } try { commandLine = DefaultParser().parse(options, args) } catch (e: ParseException) { die(e.message ?: "syntax error", 2) } if (commandLine.hasOption(HELP)) { HelpFormatter().printHelp("$SHORTNAME update [options] name", options) exitProcess(0) } checkArguments() db = Database.open() nameIn = commandLine.args[0] id = db.makeKey(nameIn) length = commandLine.getOptionValue(LENGTH)?.let { rawLength -> try { rawLength.toInt() } catch (e: NumberFormatException) { die("${see(rawLength)} - invalid length") -1 /* will never happen */ } } ?: DEFAULT_GENERATED_LENGTH } private fun checkArguments(): Unit { var bad = false if (!commandLine.hasOption(GENERATE)) { for (option in listOf<String>(LENGTH, SYMBOLS, VERBOSE)) { if (commandLine.hasOption(option)) { error("--$option requires --$GENERATE") bad = true } } } if (bad) { exitProcess(2); } if (commandLine.args.isEmpty()) { die("expecting site name", 2) } if (commandLine.args.size > 1) { die("unexpected trailing arguments", 2) } } private fun checkDatabase(): Unit { db.connection.prepareStatement("select count(*) from passwords where id = ?").use { it.setLong(1, id) val result = it.executeQuery() result.next() val count = result.getInt(1) if (count < 1) { die("no record matches " + see(nameIn)) } } } private fun update(): Unit { updateOne("username") if (commandLine.hasOption(GENERATE)) { generatePassword() } else { updateOne("password") } updateOne("notes") if (fieldValues.isEmpty()) { error("no values changed") return } db.connection.prepareStatement("update passwords set modified = ?, $fields where id = ?").use { stmt -> stmt.setLong(1, System.currentTimeMillis()) fieldValues.indices.forEach { fieldIndex -> val fieldValue = fieldValues[fieldIndex] val columnIndex = fieldIndex + 2 when (fieldValue) { is String -> stmt.setEncryptedString(columnIndex, fieldValue, db.encryption) is CharArray -> stmt.setEncrypted(columnIndex, fieldValue, db.encryption) null -> stmt.setNull(columnIndex, Types.BLOB) else -> throw RuntimeException("this shouldn't happen") } } stmt.setLong(fieldValues.size + 2, id) stmt.execute() } } private fun cleanUp(): Unit { fieldValues.forEach { if (it is CharArray) { it.clear() } } } private fun updateOne(name: String): Unit { val prompt = name.replaceFirstChar { it.titlecase(Locale.getDefault()) } + ": " val value: Any? = if (name in SENSITIVE_FIELDS) { updatePassword() } else { val rawValue = readLine(prompt) if (name in NULLABLE_FIELDS && rawValue == NULL_SPECIFIED) { null } else { rawValue } } val noChange = when (value) { is String -> value.isEmpty() is CharArray -> value.isEmpty() else -> false } if (noChange) { return } addOne(name, value) } private fun addOne(name: String, value: Any?) { if (fields.isNotEmpty()) { fields.append(", ") } fields.append(name) fields.append(" = ?") fieldValues.add(value) } private fun generatePassword(): Unit { val newPassword = generate(length, commandLine.hasOption(SYMBOLS)) if (commandLine.hasOption(VERBOSE)) { printPassword(newPassword) } addOne("password", newPassword) } private fun updatePassword(): CharArray { while (true) { val pw1 = getPassword("Password: ") if (pw1.isEmpty()) { return pw1 } val pw2 = getPassword("Verification: ") if (pw1 contentEquals pw2) { return pw1 } error("mismatch, try again") } } }