[Assword] assword
Jameson Graef Rollins
jrollins at finestructure.net
Sun Jan 11 22:03:15 EST 2015
On Sat, Jan 10 2015, Yuri D'Elia <wavexx at thregr.org> wrote:
> Sorry, resending this due to a typo in the patch, and also to include
> Daniel in the conversation:
Hey, Yuri. I'm replying on the assword list.
> I was looking for a minimal password manager and found assword to be
> just about right.
>
> What I didn't like however was the inability to set the key/file through
> command line flags. It's very inconvenient for anything except a single
> db. I decided to switch the code to the python's builtin 'argparse'
> using the attached patch.
>
> The argument parsing is much more robust, while still allowing the old
> environment variables to work.
>
> As a change, I renamed the variable ASSWORD_PASSWORD to ASSWORD_SIZE
> (and the related --size argument), since the old name didn't make sense
> to me.
>
> I also removed the ASSWORD_DUMP_PASSWORDS envvar, since it was clearly
> there due to the old way or parsing arguments. You can now do "dump
> --show-passwords" instead.
>
> Is there a chance to integrate these changes into the next versions?
Daniel and I were explicitly trying to keep the interface as minimal as
possible, including no command line options (which Daniel for some
reason has a strong aversion to). That said, you're not the first
person to request them:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=720900
I think it's probably reasonable to support proper command line options
for the current environment-specified-only options. I guess that means
we might want to move to argparse as well, although I really don't like
the way argparse does subcommand interfaces.
So I concede that we will consider this for the next release.
Thanks so much for the patch.
jamie.
> From c861e0597b97303178814b6dcc5e00ac7fc4b51f Mon Sep 17 00:00:00 2001
> From: Yuri D'Elia <yuri.delia at eurac.edu>
> Date: Sat, 10 Jan 2015 21:24:12 +0100
> Subject: [PATCH] Use argparse and allow to set alternate keydb/file with
> flags.
>
> ---
> assword | 271 ++++++++++++++++++++++++++++++----------------------------------
> 1 file changed, 128 insertions(+), 143 deletions(-)
>
> diff --git a/assword b/assword
> index 3ec5487..a13fa35 100755
> --- a/assword
> +++ b/assword
> @@ -8,89 +8,13 @@ import gpgme
> import assword
> import subprocess
> import pkg_resources
> -
> -############################################################
> -
> -PROG = os.path.basename(sys.argv[0])
> -
> -def version():
> - print PROG, pkg_resources.get_distribution('assword').version
> -
> -def usage():
> - print "Usage:", PROG, "<command> [<args>...]"
> - print """
> -
> -The password database is stored as a single json object, OpenPGP
> -encrypted and signed, and written to local disk (see ASSWORD_DB). The
> -file will be created upon addition of the first entry. Database
> -entries are keyed by 'context'. During retrieval of passwords, the
> -database is decrypted and read into memory. Contexts are search by
> -sub-string match.
> -
> -Commands:
> -
> - add [<context>] Add a new entry. If context is '-' read from stdin.
> - If not specified, user will be prompted for
> - context. If the context already exists, an error
> - will be thrown. See ASSWORD_PASSWORD for
> - information on passwords.
> -
> - replace [<context>]
> - Replace password for existing entry. If context
> - is '-' read from stdin. If not specified, user
> - will be prompted for context. If the context
> - does not exist an error will be thrown. See
> - ASSWORD_PASSWORD for information on passwords.
> -
> - dump [<string>] Dump search results as json. If string not specified all
> - entries are returned. Passwords will not be displayed
> - unless ASSWORD_DUMP_PASSWORDS is set.
> -
> - gui [<string>] GUI interface, good for X11 window manager integration.
> - Upon invocation the user will be prompted to decrypt the
> - database, after which a graphical search prompt will be
> - presented. If an additional string is provided, it will
> - be added as the initial search string. All matching results
> - for the query will be presented to the user. When a result
> - is selected, the password will be retrieved according to the
> - method specified by ASSWORD_XPASTE. If no match is found,
> - the user has the opportunity to generate and store a new
> - password, which is then delivered via ASSWORD_XPASTE.
> -
> - remove <context> Delete an entry from the database.
> -
> - version Report the version of this program.
> -
> - help This help.
> -
> -Environment:
> -
> - ASSWORD_DB Path to assword database file. Default: ~/.assword/db
> -
> - ASSWORD_KEYFILE File containing OpenPGP key ID of database encryption
> - recipient. Default: ~/.assword/keyid
> -
> - ASSWORD_KEYID OpenPGP key ID of database encryption recipient. This
> - overrides ASSWORD_KEYFILE if set.
> -
> - ASSWORD_PASSWORD For new entries, entropy of auto-generated password
> - in bytes (actual generated password will be longer
> - due to base64 encoding). If set to 'prompt' user
> - will be prompted for for password. Default: %d
> -
> - ASSWORD_DUMP_PASSWORDS Include passwords in dump when set.
> -
> - ASSWORD_XPASTE Method for password retrieval. Options are: 'xdo', which
> - attempts to type the password into the window that had
> - focus on launch, or 'xclip' which inserts the password in
> - the X clipboard. Default: xdo
> -"""%(assword.DEFAULT_NEW_PASSWORD_OCTETS)
> +import argparse
>
> ############################################################
>
> ASSWORD_DIR = os.path.join(os.path.expanduser('~'),'.assword')
> -
> -DBPATH = os.getenv('ASSWORD_DB', os.path.join(ASSWORD_DIR, 'db'))
> +DBPATH = os.path.join(ASSWORD_DIR, 'db')
> +KEYID = os.path.join(ASSWORD_DIR, 'keyid')
>
> ############################################################
>
> @@ -107,9 +31,9 @@ def xclip(text):
> # 20 gpg/key error
> ############################################################
>
> -def open_db(keyid=None):
> +def open_db(dbpath, keyid=None):
> try:
> - db = assword.Database(DBPATH, keyid)
> + db = assword.Database(dbpath, keyid)
> except assword.DatabaseError as e:
> print >>sys.stderr, 'Assword database error: %s' % e.msg
> sys.exit(10)
> @@ -117,9 +41,9 @@ def open_db(keyid=None):
> print >>sys.stderr, "WARNING: could not validate OpenPGP signature on db file."
> return db
>
> -def get_keyid():
> - keyid = os.getenv('ASSWORD_KEYID')
> - keyfile = os.getenv('ASSWORD_KEYFILE', os.path.join(ASSWORD_DIR, 'keyid'))
> +def get_keyid(args):
> + keyid = args.keyid
> + keyfile = args.keyfile
>
> if not keyid and os.path.exists(keyfile):
> with open(keyfile, 'r') as f:
> @@ -129,7 +53,7 @@ def get_keyid():
> if not keyid:
> print >>sys.stderr, "OpenPGP key ID of encryption target not specified."
> print >>sys.stderr, "Please provide key ID in ASSWORD_KEYID environment variable,"
> - print >>sys.stderr, "or specify key ID now to save in ~/.assword/keyid file."
> + print >>sys.stderr, "or specify key ID now to save in {db} file.".format(db=KEYID)
> keyid = raw_input('OpenPGP key ID: ')
> if keyid == '':
> keyid = None
> @@ -156,31 +80,29 @@ def get_keyid():
> return keyid
>
> def retrieve_context(args):
> - try:
> - # get context as argument
> - context = args[0]
> - # or from stdin
> - if context == '-':
> - context = sys.stdin.read()
> # prompt for context if not specified
> - except IndexError:
> + if args.context is None:
> try:
> context = raw_input('context: ')
> except KeyboardInterrupt:
> sys.exit(-1)
> + else:
> + # get context as argument
> + context = args.context or ''
> + # or from stdin
> + if context == '-':
> + context = sys.stdin.read()
> if context == '':
> sys.exit("Can not add empty string context.")
> return context
>
> -def retrieve_password():
> +def retrieve_password(args):
> # get password from prompt if requested
> - if os.getenv('ASSWORD_PASSWORD') is None:
> - return None
> - elif os.getenv('ASSWORD_PASSWORD') != 'prompt':
> + if args.size != 'prompt':
> try:
> - octets = int(os.getenv('ASSWORD_PASSWORD'))
> + octets = int(args.size)
> except ValueError:
> - sys.exit("ASSWORD_PASSWORD environment variable is neither int or 'prompt'.")
> + sys.exit("password size is neither int or 'prompt'.")
> print >>sys.stderr, "Auto-generating password..."
> return octets
> try:
> @@ -198,13 +120,13 @@ def retrieve_password():
> # Add a password to the database.
> # First argument is potentially a context.
> def add(args):
> - keyid = get_keyid()
> + keyid = get_keyid(args)
> context = retrieve_context(args)
> - db = open_db(keyid)
> + db = open_db(args.db, keyid)
> if context in db:
> print >>sys.stderr, "Entry already exists with context: '%s'" % (context)
> sys.exit(1)
> - password = retrieve_password()
> + password = retrieve_password(args)
> try:
> db.add(context.strip(), password)
> db.save()
> @@ -216,13 +138,13 @@ def add(args):
> # Replace a password in the database.
> # First argument is context to replace.
> def replace(args):
> - keyid = get_keyid()
> + keyid = get_keyid(args)
> context = retrieve_context(args)
> - db = open_db(keyid)
> + db = open_db(args.db, keyid)
> if context not in db:
> print >>sys.stderr, "Context not found: '%s'" % (context)
> sys.exit(1)
> - password = retrieve_password()
> + password = retrieve_password(args)
> try:
> db.replace(context.strip(), password)
> db.save()
> @@ -232,25 +154,26 @@ def replace(args):
> print >>sys.stderr, "New entry writen."
>
> def dump(args):
> - query = ' '.join(args)
> + query = args.string or ''
> if not os.path.exists(DBPATH):
> print >>sys.stderr, """Assword database does not exist.
> To add an entry to the database use 'assword add'.
> See 'assword help' for more information."""
> sys.exit(10)
> - db = open_db()
> + db = open_db(args.db)
> results = db.search(query)
> output = {}
> for context in results:
> output[context] = {}
> output[context]['date'] = results[context]['date']
> - if os.getenv('ASSWORD_DUMP_PASSWORDS'):
> + if args.show_passwords:
> output[context]['password'] = results[context]['password']
> print json.dumps(output, sort_keys=True, indent=2)
>
> # The X GUI
> -def gui(args, method='xdo'):
> - query = ' '.join(args)
> +def gui(args):
> + query = args.string or ''
> + method = args.xpaste
> if method == 'xdo':
> try:
> import xdo
> @@ -268,8 +191,8 @@ def gui(args, method='xdo'):
> print >>sys.stderr, "Unknown X paste method:", method
> sys.exit(1)
> # do it
> - keyid = get_keyid()
> - db = open_db(keyid)
> + keyid = get_keyid(args)
> + db = open_db(args.db, keyid)
> result = assword.Gui(db, query=query).returnValue()
> # type the password in the saved window
> if result:
> @@ -281,13 +204,9 @@ def gui(args, method='xdo'):
> xclip(result['password'])
>
> def remove(args):
> - keyid = get_keyid()
> - try:
> - context = args[0]
> - except IndexError:
> - print >>sys.stderr, "Must specify index to remove."
> - sys.exit(1)
> - db = open_db(keyid)
> + keyid = get_keyid(args)
> + context = args.context or ''
> + db = open_db(args.db, keyid)
> if context not in db:
> print >>sys.stderr, "No entry with context: '%s'" % (context)
> sys.exit(1)
> @@ -309,30 +228,96 @@ def remove(args):
> ############################################################
> # main
>
> -if len(sys.argv) < 2:
> - print >>sys.stderr, "Command not specified."
> - usage()
> - sys.exit(1)
> +ap = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
> + version=pkg_resources.get_distribution('assword').version,
> + description="""
> +The password database is stored as a single json object, OpenPGP
> +encrypted and signed, and written to local disk (see ASSWORD_DB). The
> +file will be created upon addition of the first entry. Database
> +entries are keyed by 'context'. During retrieval of passwords, the
> +database is decrypted and read into memory. Contexts are search by
> +sub-string match.
> +""", epilog="""environment:
> + ASSWORD_DB Path to assword database file. Default: {db}
>
> -cmd = sys.argv[1]
> + ASSWORD_KEYFILE File containing OpenPGP key ID of database encryption
> + recipient. Default: {keyid}
>
> -if cmd == 'add':
> - add(sys.argv[2:])
> -elif cmd == 'replace':
> - replace(sys.argv[2:])
> -elif cmd == 'dump':
> - dump(sys.argv[2:])
> -elif cmd == 'gui':
> - method = os.getenv('ASSWORD_XPASTE', 'xdo')
> - gui(sys.argv[2:], method=method)
> -elif cmd == 'remove':
> - remove(sys.argv[2:])
> -elif cmd == 'version' or cmd == '--version':
> - version()
> -elif cmd == 'help' or cmd == '--help':
> - usage()
> -else:
> - print >>sys.stderr, "Unknown command:", cmd
> - print >>sys.stderr
> - usage()
> - sys.exit(1)
> + ASSWORD_KEYID OpenPGP key ID of database encryption recipient. This
> + overrides ASSWORD_KEYFILE if set.
> +
> + ASSWORD_SIZE For new entries, entropy of auto-generated password
> + in bytes (actual generated password will be longer
> + due to base64 encoding). If set to 'prompt' user
> + will be prompted for for password. Default: {size}
> +
> + ASSWORD_XPASTE Method for password retrieval. Options are: 'xdo', which
> + attempts to type the password into the window that had
> + focus on launch, or 'xclip' which inserts the password in
> + the X clipboard. Default: xdo
> +""".format(db=DBPATH, keyid=KEYID, size=assword.DEFAULT_NEW_PASSWORD_OCTETS))
> +
> +ap.add_argument('--db', default=os.getenv('ASSWORD_DB', DBPATH),
> + help="Path to assword database file.")
> +ap.add_argument('--keyfile', default=os.getenv('ASSWORD_KEYFILE', KEYID),
> + help="File containing OpenPGP key ID of database encryption recipient.")
> +ap.add_argument('--keyid', default=os.getenv('ASSWORD_KEYID'),
> + help="OpenPGP key ID of database encryption recipient.")
> +ap.add_argument('--size', default=os.getenv('ASSWORD_SIZE', assword.DEFAULT_NEW_PASSWORD_OCTETS),
> + help="For new entries, entropy of auto-generated password "
> + "in bytes (actual generated password will be longer "
> + "due to base64 encoding). If set to 'prompt' user "
> + "will be prompted for for password.")
> +
> +sp = ap.add_subparsers()
> +ap_help = sp.add_parser('help', help='Show command usage.')
> +ap_help.set_defaults(func=lambda _: ap.print_help())
> +ap_version = sp.add_parser('version', help='Show command version.')
> +ap_version.set_defaults(func=lambda _: ap.print_version())
> +
> +ap_add = sp.add_parser('add', help='Add a new entry.')
> +ap_add.set_defaults(func=add)
> +ap_add.add_argument('context', nargs='?',
> + help="If context is '-' read from stdin. "
> + "If not specified, user will be prompted for "
> + "context. If the context already exists, an error "
> + "will be thrown.")
> +
> +ap_replace = sp.add_parser('replace', help='Replace password for existing entry.')
> +ap_replace.set_defaults(func=replace)
> +ap_replace.add_argument('context', nargs='?',
> + help="If context is '-' read from stdin. "
> + "If not specified, user will be prompted for "
> + "context. If the context already exists, an error "
> + "will be thrown.")
> +
> +ap_dump = sp.add_parser('dump', help='Dump search results as json.')
> +ap_dump.set_defaults(func=dump)
> +ap_dump.add_argument('--show-passwords', action='store_true', default=False,
> + help="Display passwords when dumping output.")
> +ap_dump.add_argument('string', nargs='?',
> + help="If string is not specified all entries are returned. "
> + "Passwords will not be displayed unless --show-passwords is "
> + "also specified.")
> +
> +ap_gui = sp.add_parser('gui', help='GUI interface.')
> +ap_gui.set_defaults(func=gui)
> +ap_gui.add_argument('--xpaste', default=os.getenv('ASSWORD_XPASTE', 'xdo'),
> + choices=['xdo', 'xclip'], help="Method for password retrieval.")
> +ap_gui.add_argument('string', nargs='?',
> + help="Upon invocation the user will be prompted to decrypt the "
> + "database, after which a graphical search prompt will be "
> + "esented. If an additional string is provided, it will "
> + "e added as the initial search string. All matching results "
> + "or the query will be presented to the user. When a result "
> + "s selected, the password will be retrieved according to the "
> + "ethod specified by ASSWORD_XPASTE. If no match is found, "
> + "he user has the opportunity to generate and store a new "
> + "assword, which is then delivered via ASSWORD_XPASTE.")
> +
> +ap_remove = sp.add_parser('remove', help='Delete an entry from the database.')
> +ap_remove.set_defaults(func=remove)
> +ap_remove.add_argument('context')
> +
> +args = ap.parse_args()
> +args.func(args)
> --
> 2.1.4
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 818 bytes
Desc: not available
URL: <https://lists.mayfirst.org/mailman/private/assword/attachments/20150111/a2b4217d/attachment.sig>
More information about the Assword
mailing list