[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