comparison src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt @ 21:ea65ab890f66

More work to support interactive feature.
author David Barts <n5jrn@me.com>
date Tue, 02 Jul 2024 11:27:39 -0700
parents 302d224bbd57
children
comparison
equal deleted inserted replaced
20:4391afcf6bd0 21:ea65ab890f66
2 2
3 import org.apache.commons.cli.* 3 import org.apache.commons.cli.*
4 import java.sql.Types 4 import java.sql.Types
5 import java.util.* 5 import java.util.*
6 import kotlin.properties.Delegates 6 import kotlin.properties.Delegates
7 import kotlin.system.exitProcess
8 7
9 class UpdateSubcommand(): Subcommand() { 8 class UpdateSubcommand(): Subcommand() {
10 private lateinit var commandLine: CommandLine 9 private lateinit var commandLine: CommandLine
11 private lateinit var db: Database 10 private lateinit var db: Database
12 private lateinit var nameIn: String 11 private lateinit var nameIn: String
26 val SENSITIVE_FIELDS = setOf<String>("password") 25 val SENSITIVE_FIELDS = setOf<String>("password")
27 } 26 }
28 27
29 override fun run(args: Array<String>) { 28 override fun run(args: Array<String>) {
30 parseArguments(args) 29 parseArguments(args)
30 if (commandLine.hasOption(HELP)) {
31 return
32 }
31 checkDatabase() 33 checkDatabase()
32 try { 34 try {
33 update() 35 update()
34 } finally { 36 } finally {
35 cleanUp() 37 cleanUp()
45 addOption("v", VERBOSE, false, "Print the generated password.") 47 addOption("v", VERBOSE, false, "Print the generated password.")
46 } 48 }
47 try { 49 try {
48 commandLine = DefaultParser().parse(options, args) 50 commandLine = DefaultParser().parse(options, args)
49 } catch (e: ParseException) { 51 } catch (e: ParseException) {
50 die(e.message ?: "syntax error", 2) 52 throw SubcommandException(message = e.message ?: "syntax error", status = 2, cause = e)
51 } 53 }
52 if (commandLine.hasOption(HELP)) { 54 if (commandLine.hasOption(HELP)) {
53 HelpFormatter().printHelp("$SHORTNAME update [options] name", options) 55 HelpFormatter().printHelp("$SHORTNAME update [options] name", options)
54 exitProcess(0) 56 return
55 } 57 }
56 checkArguments() 58 checkArguments()
57 db = Database.open() 59 db = Database.default
58 nameIn = commandLine.args[0] 60 nameIn = commandLine.args[0]
59 id = db.makeKey(nameIn) 61 id = db.makeKey(nameIn)
60 val rawLength = commandLine.getOptionValue(LENGTH) 62 val rawLength = commandLine.getOptionValue(LENGTH)
61 length = try { 63 length = try {
62 rawLength?.toInt() ?: DEFAULT_GENERATED_LENGTH 64 rawLength?.toInt() ?: DEFAULT_GENERATED_LENGTH
63 } catch (e: NumberFormatException) { 65 } catch (e: NumberFormatException) {
64 -1 66 -1
65 } 67 }
66 if (length < MIN_GENERATED_LENGTH) { 68 if (length < MIN_GENERATED_LENGTH) {
67 die("${see(rawLength)} - invalid length") 69 throw SubcommandException(message = "${see(rawLength)} - invalid length")
68 } 70 }
69 } 71 }
70 72
71 private fun checkArguments(): Unit { 73 private fun checkArguments(): Unit {
72 var bad = false 74 var bad = false
77 bad = true 79 bad = true
78 } 80 }
79 } 81 }
80 } 82 }
81 if (bad) { 83 if (bad) {
82 exitProcess(2); 84 throw SubcommandException(status = 2)
83 } 85 }
84 if (commandLine.args.isEmpty()) { 86 if (commandLine.args.isEmpty()) {
85 die("expecting site name", 2) 87 throw SubcommandException(message = "expecting site name", status = 2)
86 } 88 }
87 if (commandLine.args.size > 1) { 89 if (commandLine.args.size > 1) {
88 die("unexpected trailing arguments", 2) 90 throw SubcommandException(message = "unexpected trailing arguments", status = 2)
89 } 91 }
90 } 92 }
91 93
92 private fun checkDatabase(): Unit { 94 private fun checkDatabase(): Unit {
93 db.connection.prepareStatement("select count(*) from passwords where id = ?").use { 95 db.connection.prepareStatement("select count(*) from passwords where id = ?").use {
94 it.setLong(1, id) 96 it.setLong(1, id)
95 val result = it.executeQuery() 97 val result = it.executeQuery()
96 result.next() 98 result.next()
97 val count = result.getInt(1) 99 val count = result.getInt(1)
98 if (count < 1) { 100 if (count < 1) {
99 die("no record matches " + see(nameIn)) 101 throw SubcommandException(message = "no record matches " + see(nameIn))
100 } 102 }
101 } 103 }
102 } 104 }
103 105
104 private fun update(): Unit { 106 private fun update(): Unit {
138 } 140 }
139 } 141 }
140 } 142 }
141 143
142 private fun updateOne(name: String): Unit { 144 private fun updateOne(name: String): Unit {
143 val prompt = name.replaceFirstChar { it.titlecase(Locale.getDefault()) } + ": " 145 val prompt = name.replaceFirstChar { it.uppercase(Locale.getDefault()) } + ": "
144 val value: Any? = if (name in SENSITIVE_FIELDS) { 146 val value: Any? = if (name in SENSITIVE_FIELDS) {
145 updatePassword() 147 try {
148 getPassword(prompt)
149 } catch (e: ConsoleException) {
150 throw SubcommandException(message = e.message, cause = e)
151 }
146 } else { 152 } else {
147 val rawValue = readLine(prompt) 153 val rawValue = readLine(prompt)
148 if (name in NULLABLE_FIELDS && rawValue == NULL_SPECIFIED) { 154 if (name in NULLABLE_FIELDS && rawValue == NULL_SPECIFIED) {
149 null 155 null
150 } else { 156 } else {
179 printPassword(newPassword) 185 printPassword(newPassword)
180 } 186 }
181 addOne("password", newPassword) 187 addOne("password", newPassword)
182 } 188 }
183 189
184 private fun updatePassword(): CharArray {
185 while (true) {
186 val pw1 = getPassword("Password: ")
187 if (pw1.isEmpty()) {
188 return pw1
189 }
190 val pw2 = getPassword("Verification: ")
191 if (pw1 contentEquals pw2) {
192 return pw1
193 }
194 error("mismatch, try again")
195 }
196 }
197 } 190 }