Module: Pwss::CommandSemantics
- Defined in:
- lib/pwss/cli/command_semantics.rb
Overview
what we are supposed to do with each command
Constant Summary collapse
- VERSION =
Pwss::VERSION
- MAN =
TODO: make the list of entries read from code
<<EOS NAME pwss -- A command-line password manager SYNOPSYS pwss command [options] [args] DESCRIPTION PWSS is a password manager, in the spirit of pass and pws. Features: * PWSS manages password *files*: - A password file can store different entries (password and other sensitive information) - The user can manage different password files (e.g., work, personal) * Entries in a password file can be of different types. Each type stores different information. Use the 'describe' command for more info about the available types and their fields. * Password files can be encrypted * Encrypted password files can be decrypted, for instance, to batch process entries, to migrate to another tool, or to manually edit entries * Entries are human-readable (and editable), when the password file is not encrypted * A console allows to decrypt a file once and perform multiple queries EXAMPLES pwss help # get syntax of each command # scenario pwss init -f a.enc # generate an encrypted safe a.enc pwss add -f a.enc # add an entry (pwss will generate a random 16-char password) pwss get -f a.enc my secret account # find an entry pwss console -f a.enc # decrypt a.enc and enter the pwss console to operate on a.enc VERSION This is version #{VERSION} LICENSE MIT SEE ALSO pwss man pwss help https://github.com/avillafiorita/pwss EOS
- DEFAULT_BASENAME =
the default filename YOU SHOULDN’T BE USING THESE CONTANSTS. USE ‘default_filename` INSTEAD
File.join(Dir.home, ".pwss.yaml")
- DEFAULT_FILENAME =
DEFAULT_BASENAME + ".gpg"
- @@cache =
cache is the content of the file last operated on is it used
nil
Class Method Summary collapse
- .add_entry(opts, argv) ⇒ Object
-
.all_safes ⇒ Object
return all the default safes we look for.
-
.ambiguous_default ⇒ Object
return true if the default basename appears with different extensions.
- .console(opts, argv = []) ⇒ Object
- .decrypt(opts, argv = []) ⇒ Object
- .default(opts, argv = []) ⇒ Object
-
.default_filename ⇒ Object
return the default filename.
- .describe(opts = nil, argv = []) ⇒ Object
- .destroy(opts, argv) ⇒ Object
- .encrypt(opts, argv = []) ⇒ Object
-
.existing_safes ⇒ Object
return the existing safes.
- .get(opts, argv) ⇒ Object
- .help(opts = nil, argv = []) ⇒ Object
- .init(opts, argv = []) ⇒ Object
- .list(opts, argv = []) ⇒ Object
- .man(opts = nil, argv = []) ⇒ Object
-
.no_default ⇒ Object
return true if none of the default files exist.
- .open(opts, argv = []) ⇒ Object
-
.reps(all_commands, argv) ⇒ Object
read-eval-print step.
- .update(opts, argv) ⇒ Object
- .version(opts = nil, argv = []) ⇒ Object
Class Method Details
.add_entry(opts, argv) ⇒ Object
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/pwss/cli/command_semantics.rb', line 196 def self.add_entry opts, argv waiting = opts[:wait] type = opts[:type] || "entry" strategy = opts[:ask] ? "ask" : (opts[:method] || "random") length = opts[:length] safe = use_safe opts[:filename] # the title can be specified in the argument arguments = Hash.new arguments["title"] = argv.join(" ") if argv != [] arguments[:strategy] = strategy arguments[:length] = length new_entry = eval("Pwss::" + type.capitalize).new new_entry.ask arguments puts "Adding entry '#{new_entry.entry["title"]}' of type '#{type}' to #{safe.filename}" safe.add new_entry.entry safe.save puts "Entry added" # make password available in the clipboard, if there is a password to make available if new_entry.entry["password"] Pwss::Password.to_clipboard "password", new_entry.entry["password"], waiting end end |
.all_safes ⇒ Object
return all the default safes we look for
100 101 102 |
# File 'lib/pwss/cli/command_semantics.rb', line 100 def self.all_safes [".enc", ".gpg", ""].map { |ext| DEFAULT_BASENAME + ext } end |
.ambiguous_default ⇒ Object
return true if the default basename appears with different extensions.
for instance: if the DEFAULT_BASENAME appears both with .gpg and .enc (or plain and encrypted).
This is potentially a problem, since all operations are performed on a different file from the one the user believes it is operating on.
90 91 92 |
# File 'lib/pwss/cli/command_semantics.rb', line 90 def self.ambiguous_default [".enc", ".gpg", ""].map { |ext| File.exist?(DEFAULT_BASENAME + ext) }.count(true) > 1 end |
.console(opts, argv = []) ⇒ Object
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/pwss/cli/command_semantics.rb', line 317 def self.console opts, argv = [] all_commands = Pwss::CommandSyntax.commands all_commands.delete(:console) open opts, argv # open and cache the file i = 0 while true string = Readline.readline('pwss:%03d> ' % i, true) string.gsub!(/^pwss /, "") # as a courtesy, remove any leading pwss string if string == "exit" or string == "quit" or string == "." then exit 0 end reps all_commands, string.split(' ') i = i + 1 end end |
.decrypt(opts, argv = []) ⇒ Object
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/pwss/cli/command_semantics.rb', line 297 def self.decrypt opts, argv = [] # filename: passed from options, cached one or, in order, .gpg, .enc, plain (but plain will fail) filename = opts[:filename] || (@@cache ? @@cache.filename : default_filename) if not File.exist?(filename) raise "Error: file #{filename} does not exist." end if not Pwss::FileOps.encrypted? filename raise "Error: #{filename} does not end with '.gpg' or '.enc' (and I assume it to be in plain text)" end safe = use_safe filename safe.toggle_encryption safe.save puts "A plain text copy now lives in #{safe.filename}" puts "You might want to check everything is ok and delete the plain file: #{filename}" puts "If you do nothing, the next pwss command will run on #{default_filename}" end |
.default(opts, argv = []) ⇒ Object
340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/pwss/cli/command_semantics.rb', line 340 def self.default opts, argv = [] if @@cache @@cache.filename elsif self.no_default puts "No default password file found." puts "Use -f if you have a password file stored somewhere else." puts "pwss init will create #{default_filename}." elsif self.ambiguous_default puts "Operating on #{default_filename}." puts "Warning: #{existing_safes.join(", ")} exist." else puts "Operating on #{default_filename}" end end |
.default_filename ⇒ Object
return the default filename
this is obtained by looking for plain text or encryped versions of the DEFAULT_BASENAME, with the following priority: .enc, .gpg, plain text.
If no file is found (like it might be the case when running the init command), use GPG
73 74 75 76 77 78 79 |
# File 'lib/pwss/cli/command_semantics.rb', line 73 def self.default_filename [".enc", ".gpg", ""].each do |ext| filename = DEFAULT_BASENAME + ext return filename if File.exist?(filename) end return DEFAULT_FILENAME end |
.describe(opts = nil, argv = []) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/pwss/cli/command_semantics.rb', line 137 def self.describe opts = nil, argv = [] if opts[:type] types = [("Pwss::" + opts[:type].capitalize).to_sym] else types = [Pwss::Entry] + ObjectSpace.each_object(Class).select { |klass| klass < Pwss::Entry } end types.each do |type| t = eval("#{type}.new") puts "#{type.to_s.gsub("Pwss::", "").downcase}:\n #{t.fields.join(", ")}\n\n" end end |
.destroy(opts, argv) ⇒ Object
255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/pwss/cli/command_semantics.rb', line 255 def self.destroy opts, argv safe = use_safe opts[:filename] string = argv.join(" ") entries_with_idx = safe.match string id = Pwss::Safe.choose_entry entries_with_idx, true if id != -1 then safe.destroy id safe.save end end |
.encrypt(opts, argv = []) ⇒ Object
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/pwss/cli/command_semantics.rb', line 267 def self.encrypt opts, argv = [] # filename: use the one passed from the cli or the cached one or .pwss.yaml DEFAULT_*BASE*NAME filename = opts[:filename] || (@@cache ? @@cache.filename : DEFAULT_BASENAME) encryption = opts[:symmetric] ? :enc : :gpg if not File.exist?(filename) raise "Error: file #{filename} does not exist." end if Pwss::FileOps.encrypted? filename raise "Error: #{filename} ends with '.gpg' or '.enc' (and I assume these files to be encrypted)" end if encryption == :enc then password = Pwss::Password.ask_password_twice if password == "" then raise "Error: Please specify a non-empty password." end else password = nil # it will be asked by GPG end safe = use_safe filename safe.toggle_encryption :password => password, :schema => encryption safe.save puts "An encrypted copy now lives in #{safe.filename}" puts "You might want to check everything is ok and delete the plain file: #{filename}" puts "If you do nothing, the next pwss command will run on #{default_filename}" end |
.existing_safes ⇒ Object
return the existing safes
105 106 107 |
# File 'lib/pwss/cli/command_semantics.rb', line 105 def self.existing_safes [".enc", ".gpg", ""].map { |ext| DEFAULT_BASENAME + ext }.select { |x| File.exist?(x) } end |
.get(opts, argv) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/pwss/cli/command_semantics.rb', line 170 def self.get opts, argv waiting = opts[:wait] stdout_opt = opts[:stdout] field_name = opts[:field] || "password" id = opts[:id] safe = use_safe opts[:filename] show = opts[:show] if not id string = argv.join(" ") entries_with_idx = safe.match string id = Pwss::Safe.choose_entry entries_with_idx end if id != -1 and safe.get(id) then field_value = safe.get_field id, field_name if stdout_opt then printf("%s", field_value) else puts (show ? safe.get(id).to_yaml : safe.get_pruned(id).to_yaml ) Pwss::Password.to_clipboard(field_name, field_value, waiting) end end end |
.help(opts = nil, argv = []) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/pwss/cli/command_semantics.rb', line 121 def self.help opts = nil, argv = [] all_commands = Pwss::CommandSyntax.commands if argv != [] argv.map { |x| puts all_commands[x.to_sym][0] } else puts "pwss command [options] [args]" puts "" puts "Available commands:" puts "" all_commands.keys.each do |key| puts " " + all_commands[key][0]. end end end |
.init(opts, argv = []) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/pwss/cli/command_semantics.rb', line 149 def self.init opts, argv = [] filename = opts[:filename] || @@cache.filename || DEFAULT_FILENAME if File.exist?(filename) raise "Error: file #{filename} already exists." end safe = Pwss::Safe.new filename safe.save puts "New safe created in #{filename}" end |
.list(opts, argv = []) ⇒ Object
162 163 164 165 166 167 168 |
# File 'lib/pwss/cli/command_semantics.rb', line 162 def self.list opts, argv = [] safe = use_safe opts[:filename] clean = opts[:clean] cleaned_entries = safe.prune(["created_at", "updated_at"]).map { |x| Pwss::Fields.to_clean_hash x } puts cleaned_entries.to_yaml end |
.man(opts = nil, argv = []) ⇒ Object
117 118 119 |
# File 'lib/pwss/cli/command_semantics.rb', line 117 def self.man opts = nil, argv = [] puts MAN end |
.no_default ⇒ Object
return true if none of the default files exist
95 96 97 |
# File 'lib/pwss/cli/command_semantics.rb', line 95 def self.no_default [".enc", ".gpg", ""].map { |ext| File.exist?(DEFAULT_BASENAME + ext) }.count(true) == 0 end |
.open(opts, argv = []) ⇒ Object
334 335 336 337 338 |
# File 'lib/pwss/cli/command_semantics.rb', line 334 def self.open opts, argv = [] filename = opts[:filename] || default_filename @@cache = load_safe filename puts "Loaded #{filename}" end |
.reps(all_commands, argv) ⇒ Object
read-eval-print step
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/pwss/cli/command_semantics.rb', line 356 def self.reps all_commands, argv if argv == [] Pwss::CommandSemantics.help exit 0 else command = argv[0] syntax_and_semantics = all_commands[command.to_sym] if syntax_and_semantics opts = syntax_and_semantics[0] function = syntax_and_semantics[1] begin parser = Slop::Parser.new(opts) result = parser.parse(argv[1..-1]) = result.to_hash arguments = result.arguments eval "Pwss::CommandSemantics::#{function}(options, arguments)" rescue Slop::Error => e puts "pwss: #{e}" rescue Exception => e puts e end else puts "pwss: '#{command}' is not a pwss command. See 'pwss help'" end end end |
.update(opts, argv) ⇒ Object
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/pwss/cli/command_semantics.rb', line 224 def self.update opts, argv field = (opts.to_hash[:password] or opts.to_hash[:method] or opts.to_hash[:ask]) ? "password" : opts.to_hash[:field] strategy = opts.to_hash[:ask] ? "ask" : (opts.to_hash[:method] || "random") length = opts.to_hash[:length] waiting = opts.to_hash[:wait] string = argv.join(" ") # the entry we are looking for if not field then raise "Error: please specify a field to update (use --field, -p, or --ask)" end safe = use_safe opts[:filename] entries_with_idx = safe.match string id = Pwss::Safe.choose_entry entries_with_idx, true if id != -1 then old_value = safe.get_field id, field new_value = Pwss::Fields.ask field, { strategy: strategy, length: length } printf "Updating #{field} field of '#{safe.entries[id]["title"]}' in #{safe.filename} ..." safe.update id, field, new_value safe.save puts "... done" puts "The old value of #{field} is: #{old_value}" # make the field available in the clipboard, just in case it is needed if field == "password" Pwss::Password.to_clipboard "password", new_value, waiting end end end |
.version(opts = nil, argv = []) ⇒ Object
113 114 115 |
# File 'lib/pwss/cli/command_semantics.rb', line 113 def self.version opts = nil, argv = [] puts "pwss version #{VERSION}" end |