Add gopass support to qute-pass via a CLI switch

The new CLI-switch --mode makes qute-pass compatible with the gopass
password store (https://github.com/gopasspw/gopass). While gopass itself is
mostly compatible with pass, it offers the possibility to mount multiple shared
password stores. qute-pass way of just traversing PASSWORD_STORE_DIR won't help
in that case.

Closes #5142
This commit is contained in:
Sebastian Schulze 2019-12-10 14:14:24 +01:00
parent 02c3f02edc
commit 5bf7c8e7d7
No known key found for this signature in database
GPG Key ID: 5BCB1D3B4D38A35A
1 changed files with 29 additions and 15 deletions

View File

@ -27,6 +27,8 @@ USAGE = """The domain of the site has to appear as a segment in the pass path, f
login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
If you use gopass with multiple mounts, use the CLI switch --mode gopass to switch to gopass mode.
Suggested bindings similar to Uzbl's `formfiller` script:
config.bind('<z><l>', 'spawn --userscript qute-pass')
@ -58,6 +60,8 @@ argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
argument_parser.add_argument('--password-store', '-p',
default=os.getenv('PASSWORD_STORE_DIR', default=os.path.expanduser('~/.password-store')),
help='Path to your pass password-store')
argument_parser.add_argument('--mode', '-M', choices=['pass', 'gopass'], default="pass",
help='Select mode [gopass] to enable gopass-specific features like mounts.')
argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)',
help='Regular expression that matches the username')
argument_parser.add_argument('--username-target', '-U', choices=['path', 'secret'], default='path',
@ -99,34 +103,44 @@ def qute_command(command):
def find_pass_candidates(domain, password_store_path):
candidates = []
for path, directories, file_names in os.walk(password_store_path, followlinks=True):
secrets = fnmatch.filter(file_names, '*.gpg')
if not secrets:
continue
# Strip password store path prefix to get the relative pass path
pass_path = path[len(password_store_path) + 1:]
split_path = pass_path.split(os.path.sep)
for secret in secrets:
secret_base = os.path.splitext(secret)[0]
if domain not in (split_path + [secret_base]):
if arguments.mode == "gopass":
all_passwords = subprocess.run(["gopass", "list", "--flat" ], stdout=subprocess.PIPE).stdout.decode("UTF-8").splitlines()
for password in all_passwords:
if domain in password:
candidates.append(password)
else:
for path, directories, file_names in os.walk(password_store_path, followlinks=True):
secrets = fnmatch.filter(file_names, '*.gpg')
if not secrets:
continue
candidates.append(os.path.join(pass_path, secret_base))
# Strip password store path prefix to get the relative pass path
pass_path = path[len(password_store_path) + 1:]
split_path = pass_path.split(os.path.sep)
for secret in secrets:
secret_base = os.path.splitext(secret)[0]
if domain not in (split_path + [secret_base]):
continue
candidates.append(os.path.join(pass_path, secret_base))
return candidates
def _run_pass(command, encoding):
process = subprocess.run(command, stdout=subprocess.PIPE)
def _run_pass(pass_arguments, encoding):
# The executable is conveniently named after it's mode [pass|gopass].
pass_command = [arguments.mode]
process = subprocess.run(pass_command + pass_arguments, stdout=subprocess.PIPE)
return process.stdout.decode(encoding).strip()
def pass_(path, encoding):
return _run_pass(['pass', path], encoding)
return _run_pass([path], encoding)
def pass_otp(path, encoding):
return _run_pass(['pass', 'otp', path], encoding)
return _run_pass(['otp', path], encoding)
def dmenu(items, invocation, encoding):