From 19fc4de4843a3604e94d45d4405de1c243f42701 Mon Sep 17 00:00:00 2001 From: cryzed Date: Wed, 1 Nov 2017 17:57:30 +0100 Subject: [PATCH 01/12] Add qute-pass userscript --- misc/userscripts/qute-pass | 130 +++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100755 misc/userscripts/qute-pass diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass new file mode 100755 index 000000000..12d7c5b40 --- /dev/null +++ b/misc/userscripts/qute-pass @@ -0,0 +1,130 @@ +#!/usr/bin/env python + +# Insert login information using pass and a dmenu-provider (e.g. dmenu, rofi -dmenu, ...). +# A short demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif. +# +# The domain of the site has to appear as a segment in the pass path, for example: "github.com/cryzed" or +# "websites/github.com". How the username and password are determined is freely configurable using the CLI arguments. +# The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner: +# [USERNAME][PASSWORD], which is compatible with almost all login forms. +# +# Dependencies: tldextract (Python 3 module), pass +# For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts +# +# Chris Braun (cryzed), 2017 + +import argparse +import enum +import fnmatch +import functools +import os +import re +import shlex +import subprocess +import sys + +import tldextract + +PASSWORD_STORE_PATH = os.path.expanduser('~/.password-store') +DMENU_INVOCATION = 'rofi -dmenu' +ENCODING = 'UTF-8' + +argument_parser = argparse.ArgumentParser() +argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL')) +argument_parser.add_argument('--password-store', '-p', default=PASSWORD_STORE_PATH, + help='Path to your pass password-store') +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', + help='The target for the username regular expression') +argument_parser.add_argument('--password-pattern', '-P', default=r'(.*)', + help='Regular expression that matches the password') +argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu', + help='Invocation used to execute a dmenu-provider') + +stderr = functools.partial(print, file=sys.stderr) + + +class ExitCodes(enum.IntEnum): + SUCCESS = 0 + COULD_NOT_DETERMINE_URL = 1 + COULD_NOT_DETERMINE_DOMAIN = 2 + NO_PASS_CANDIDATES = 3 + COULD_NOT_MATCH_USERNAME = 4 + COULD_NOT_MATCH_PASSWORD = 5 + + +def qute_command(command): + with open(os.environ['QUTE_FIFO'], 'w') as fifo: + fifo.write(command + '\n') + fifo.flush() + + +def find_pass_candidates(domain, password_store_path=PASSWORD_STORE_PATH): + candidates = [] + for path, directories, file_names in os.walk(password_store_path): + if directories or domain not in path.split(os.path.sep): + continue + + # Strip password store path prefix to get the relative pass path + pass_path = path[len(password_store_path) + 1:] + secrets = fnmatch.filter(file_names, '*.gpg') + candidates.extend(os.path.join(pass_path, os.path.splitext(secret)[0]) for secret in secrets) + return candidates + + +def pass_(path): + process = subprocess.run(['pass', path], stdout=subprocess.PIPE, encoding=ENCODING) + return process.stdout.strip() + + +def dmenu(items, invocation=DMENU_INVOCATION): + command = shlex.split(invocation) + process = subprocess.run(command, input='\n'.join(items), stdout=subprocess.PIPE, encoding=ENCODING) + return process.stdout.strip() + + +def main(arguments): + # Domain wasn't overriden using CLI argument or found in qutebrowser environment variable + if not arguments.url: + stderr('Could not determine URL!') + return ExitCodes.COULD_NOT_DETERMINE_URL + + domain = tldextract.extract(arguments.url).registered_domain + if not domain: + stderr('Could not determine domain from URL: {!r}!'.format(arguments.url)) + return ExitCodes.COULD_NOT_DETERMINE_DOMAIN + + # Expand potential ~ in paths, since this script won't be called from a shell that does it for us + candidates = find_pass_candidates(domain, os.path.expanduser(arguments.password_store)) + if not candidates: + stderr('No candidates for domain {!r} found!'.format(domain)) + return ExitCodes.NO_PASS_CANDIDATES + + selection = candidates[0] if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation) + secret = pass_(selection) + + # Match username + target = selection if arguments.username_target == 'path' else secret + match = re.match(arguments.username_pattern, target) + if not match: + stderr('Failed to match username pattern on {}!'.format(arguments.username_target)) + return ExitCodes.COULD_NOT_MATCH_USERNAME + username = match.group(1) + + # Match password + match = re.match(arguments.password_pattern, secret) + if not match: + stderr('Failed to match password pattern on secret!') + return ExitCodes.COULD_NOT_MATCH_PASSWORD + password = match.group(1) + + # Enter username and password using fake-key and (which seems to work almost universally) and switch back into + # insert-mode, so the form can be directly submitted by hitting enter afterwards + qute_command('fake-key {} ;; fake-key ;; fake-key {} ;; enter-mode insert'.format(username, password)) + return ExitCodes.SUCCESS + + +if __name__ == '__main__': + arguments = argument_parser.parse_args() + sys.exit(main(arguments)) From c97b416cb1f51aa7a6aefc8d38193a2ee52c192c Mon Sep 17 00:00:00 2001 From: cryzed Date: Wed, 1 Nov 2017 18:19:46 +0100 Subject: [PATCH 02/12] Rename qute-pass to qute-passmenu --- misc/userscripts/{qute-pass => qute-passmenu} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename misc/userscripts/{qute-pass => qute-passmenu} (100%) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-passmenu similarity index 100% rename from misc/userscripts/qute-pass rename to misc/userscripts/qute-passmenu From 09d55cb271f8e7cfeb58f0229006aea4931b71f5 Mon Sep 17 00:00:00 2001 From: cryzed Date: Wed, 1 Nov 2017 19:01:17 +0100 Subject: [PATCH 03/12] Add support for only inserting the username or password --- misc/userscripts/qute-passmenu | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/misc/userscripts/qute-passmenu b/misc/userscripts/qute-passmenu index 12d7c5b40..9c77f61a8 100755 --- a/misc/userscripts/qute-passmenu +++ b/misc/userscripts/qute-passmenu @@ -41,6 +41,9 @@ argument_parser.add_argument('--password-pattern', '-P', default=r'(.*)', help='Regular expression that matches the password') argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu', help='Invocation used to execute a dmenu-provider') +group = argument_parser.add_mutually_exclusive_group() +group.add_argument('--username-only', '-uo', action='store_true', help='Only insert username') +group.add_argument('--password-only', '-po', action='store_true', help='Only insert password') stderr = functools.partial(print, file=sys.stderr) @@ -119,9 +122,15 @@ def main(arguments): return ExitCodes.COULD_NOT_MATCH_PASSWORD password = match.group(1) - # Enter username and password using fake-key and (which seems to work almost universally) and switch back into - # insert-mode, so the form can be directly submitted by hitting enter afterwards - qute_command('fake-key {} ;; fake-key ;; fake-key {} ;; enter-mode insert'.format(username, password)) + if arguments.username_only: + qute_command('fake-key {} ;; enter-mode insert'.format(username)) + elif arguments.password_only: + qute_command('fake-key {} ;; enter-mode insert'.format(password)) + else: + # Enter username and password using fake-key and (which seems to work almost universally) and switch back + # into insert-mode, so the form can be directly submitted by hitting enter afterwards + qute_command('fake-key {} ;; fake-key ;; fake-key {} ;; enter-mode insert'.format(username, password)) + return ExitCodes.SUCCESS From 6d37e4671a2dc256c8dd1da900aa55ba4b4c1d99 Mon Sep 17 00:00:00 2001 From: cryzed Date: Wed, 1 Nov 2017 19:08:49 +0100 Subject: [PATCH 04/12] Add support for not automatically entering insert mode --- misc/userscripts/qute-passmenu | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/misc/userscripts/qute-passmenu b/misc/userscripts/qute-passmenu index 9c77f61a8..6f3b22e8b 100755 --- a/misc/userscripts/qute-passmenu +++ b/misc/userscripts/qute-passmenu @@ -41,6 +41,8 @@ argument_parser.add_argument('--password-pattern', '-P', default=r'(.*)', help='Regular expression that matches the password') argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu', help='Invocation used to execute a dmenu-provider') +argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false', + help="Don't automatically enter insert mode") group = argument_parser.add_mutually_exclusive_group() group.add_argument('--username-only', '-uo', action='store_true', help='Only insert username') group.add_argument('--password-only', '-po', action='store_true', help='Only insert password') @@ -122,14 +124,15 @@ def main(arguments): return ExitCodes.COULD_NOT_MATCH_PASSWORD password = match.group(1) + insert_mode = ';; enter-mode insert' if arguments.insert_mode else '' if arguments.username_only: - qute_command('fake-key {} ;; enter-mode insert'.format(username)) + qute_command('fake-key {}{}'.format(username, insert_mode)) elif arguments.password_only: - qute_command('fake-key {} ;; enter-mode insert'.format(password)) + qute_command('fake-key {}{}'.format(password, insert_mode)) else: # Enter username and password using fake-key and (which seems to work almost universally) and switch back # into insert-mode, so the form can be directly submitted by hitting enter afterwards - qute_command('fake-key {} ;; fake-key ;; fake-key {} ;; enter-mode insert'.format(username, password)) + qute_command('fake-key {} ;; fake-key ;; fake-key {}{}'.format(username, password, insert_mode)) return ExitCodes.SUCCESS From 0e3c42db698d97c3526b02b7f9897f3ca379a577 Mon Sep 17 00:00:00 2001 From: cryzed Date: Wed, 1 Nov 2017 20:40:59 +0100 Subject: [PATCH 05/12] Rename qute-passmenu to qute-pass again --- misc/userscripts/{qute-passmenu => qute-pass} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename misc/userscripts/{qute-passmenu => qute-pass} (100%) diff --git a/misc/userscripts/qute-passmenu b/misc/userscripts/qute-pass similarity index 100% rename from misc/userscripts/qute-passmenu rename to misc/userscripts/qute-pass From 16fefc1c7bdee9c7fa05c320133e0af241ad60fe Mon Sep 17 00:00:00 2001 From: cryzed Date: Thu, 2 Nov 2017 14:06:17 +0100 Subject: [PATCH 06/12] Make changes suggested here: https://github.com/qutebrowser/qutebrowser/pull/3232/files/0e3c42db698d97c3526b02b7f9897f3ca379a577 --- misc/userscripts/qute-pass | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 6f3b22e8b..19dae9d4a 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Insert login information using pass and a dmenu-provider (e.g. dmenu, rofi -dmenu, ...). # A short demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif. @@ -25,13 +25,9 @@ import sys import tldextract -PASSWORD_STORE_PATH = os.path.expanduser('~/.password-store') -DMENU_INVOCATION = 'rofi -dmenu' -ENCODING = 'UTF-8' - argument_parser = argparse.ArgumentParser() -argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL')) -argument_parser.add_argument('--password-store', '-p', default=PASSWORD_STORE_PATH, +argument_parser.add_argument('url', nargs='?', default=os.environ['QUTE_URL']) +argument_parser.add_argument('--password-store', '-p', default=os.path.expanduser('~/.password-store'), help='Path to your pass password-store') argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)', help='Regular expression that matches the username') @@ -43,9 +39,11 @@ argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu', help='Invocation used to execute a dmenu-provider') argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false', help="Don't automatically enter insert mode") +argument_parser.add_argument('--io-encoding', '-i', default='UTF-8', + help='Encoding used to communicate with subprocesses') group = argument_parser.add_mutually_exclusive_group() -group.add_argument('--username-only', '-uo', action='store_true', help='Only insert username') -group.add_argument('--password-only', '-po', action='store_true', help='Only insert password') +group.add_argument('--username-only', '-e', action='store_true', help='Only insert username') +group.add_argument('--password-only', '-w', action='store_true', help='Only insert password') stderr = functools.partial(print, file=sys.stderr) @@ -65,7 +63,7 @@ def qute_command(command): fifo.flush() -def find_pass_candidates(domain, password_store_path=PASSWORD_STORE_PATH): +def find_pass_candidates(domain, password_store_path): candidates = [] for path, directories, file_names in os.walk(password_store_path): if directories or domain not in path.split(os.path.sep): @@ -78,15 +76,15 @@ def find_pass_candidates(domain, password_store_path=PASSWORD_STORE_PATH): return candidates -def pass_(path): - process = subprocess.run(['pass', path], stdout=subprocess.PIPE, encoding=ENCODING) - return process.stdout.strip() +def pass_(path, encoding): + process = subprocess.run(['pass', path], stdout=subprocess.PIPE) + return process.stdout.decode(encoding).strip() -def dmenu(items, invocation=DMENU_INVOCATION): +def dmenu(items, invocation, encoding): command = shlex.split(invocation) - process = subprocess.run(command, input='\n'.join(items), stdout=subprocess.PIPE, encoding=ENCODING) - return process.stdout.strip() + process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE) + return process.stdout.decode(encoding).strip() def main(arguments): @@ -106,8 +104,9 @@ def main(arguments): stderr('No candidates for domain {!r} found!'.format(domain)) return ExitCodes.NO_PASS_CANDIDATES - selection = candidates[0] if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation) - secret = pass_(selection) + selection = candidates[0] if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation, + arguments.io_encoding) + secret = pass_(selection, arguments.io_encoding) # Match username target = selection if arguments.username_target == 'path' else secret From 78eb7b5da876e730c7ee21a4413f599af584d300 Mon Sep 17 00:00:00 2001 From: cryzed Date: Fri, 3 Nov 2017 02:43:33 +0100 Subject: [PATCH 07/12] Select pass candidates for the fully-qualified domain name first, then for the registered domain and finally the IPv4 address if that is what the URL was --- misc/userscripts/qute-pass | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 19dae9d4a..6e6997ea5 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -50,11 +50,10 @@ stderr = functools.partial(print, file=sys.stderr) class ExitCodes(enum.IntEnum): SUCCESS = 0 - COULD_NOT_DETERMINE_URL = 1 - COULD_NOT_DETERMINE_DOMAIN = 2 - NO_PASS_CANDIDATES = 3 - COULD_NOT_MATCH_USERNAME = 4 - COULD_NOT_MATCH_PASSWORD = 5 + # 1 is reserved for general script errors + NO_PASS_CANDIDATES = 2 + COULD_NOT_MATCH_USERNAME = 3 + COULD_NOT_MATCH_PASSWORD = 4 def qute_command(command): @@ -88,20 +87,19 @@ def dmenu(items, invocation, encoding): def main(arguments): - # Domain wasn't overriden using CLI argument or found in qutebrowser environment variable - if not arguments.url: - stderr('Could not determine URL!') - return ExitCodes.COULD_NOT_DETERMINE_URL - - domain = tldextract.extract(arguments.url).registered_domain - if not domain: - stderr('Could not determine domain from URL: {!r}!'.format(arguments.url)) - return ExitCodes.COULD_NOT_DETERMINE_DOMAIN + domain = tldextract.extract(arguments.url) # Expand potential ~ in paths, since this script won't be called from a shell that does it for us - candidates = find_pass_candidates(domain, os.path.expanduser(arguments.password_store)) - if not candidates: - stderr('No candidates for domain {!r} found!'.format(domain)) + password_store_path = os.path.expanduser(arguments.password_store) + + # Try to find candidates using targets in the following order: fully qualified domain (including subdomains), + # registered domain and finally the IPv4 address if that's what the URL was + for target in filter(None, [domain.fqdn, domain.registered_domain, domain.ipv4]): + candidates = find_pass_candidates(target, password_store_path) + if candidates: + break + else: + stderr('No pass candidates for URL {!r} found!'.format(arguments.url)) return ExitCodes.NO_PASS_CANDIDATES selection = candidates[0] if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation, From ee6b4bc7ee3b1b53b1702a82fac415e72e6de7ec Mon Sep 17 00:00:00 2001 From: cryzed Date: Fri, 3 Nov 2017 11:25:35 +0100 Subject: [PATCH 08/12] Add option to merge pass candidates for the fully-qualified and registered domain name --- misc/userscripts/qute-pass | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 6e6997ea5..22b1b4826 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -41,6 +41,8 @@ argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', actio help="Don't automatically enter insert mode") argument_parser.add_argument('--io-encoding', '-i', default='UTF-8', help='Encoding used to communicate with subprocesses') +argument_parser.add_argument('--merge-candidates', '-m', action='store_true', + help='Merge pass candidates for fully-qualified and registered domain name') group = argument_parser.add_mutually_exclusive_group() group.add_argument('--username-only', '-e', action='store_true', help='Only insert username') group.add_argument('--password-only', '-w', action='store_true', help='Only insert password') @@ -50,7 +52,7 @@ stderr = functools.partial(print, file=sys.stderr) class ExitCodes(enum.IntEnum): SUCCESS = 0 - # 1 is reserved for general script errors + # 1 is automatically used if Python throws an exception NO_PASS_CANDIDATES = 2 COULD_NOT_MATCH_USERNAME = 3 COULD_NOT_MATCH_PASSWORD = 4 @@ -87,23 +89,29 @@ def dmenu(items, invocation, encoding): def main(arguments): - domain = tldextract.extract(arguments.url) + extract_result = tldextract.extract(arguments.url) # Expand potential ~ in paths, since this script won't be called from a shell that does it for us password_store_path = os.path.expanduser(arguments.password_store) - # Try to find candidates using targets in the following order: fully qualified domain (including subdomains), - # registered domain and finally the IPv4 address if that's what the URL was - for target in filter(None, [domain.fqdn, domain.registered_domain, domain.ipv4]): - candidates = find_pass_candidates(target, password_store_path) - if candidates: + # Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains), + # the registered domain name and finally: the IPv4 address if that's what the URL represents + candidates = set() + for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4]): + target_candidates = find_pass_candidates(target, password_store_path) + if not target_candidates: + continue + + candidates.update(target_candidates) + if not arguments.merge_candidates: break else: - stderr('No pass candidates for URL {!r} found!'.format(arguments.url)) - return ExitCodes.NO_PASS_CANDIDATES + if not candidates: + stderr('No pass candidates for URL {!r} found!'.format(arguments.url)) + return ExitCodes.NO_PASS_CANDIDATES - selection = candidates[0] if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation, - arguments.io_encoding) + selection = candidates.pop() if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation, + arguments.io_encoding) secret = pass_(selection, arguments.io_encoding) # Match username @@ -127,8 +135,8 @@ def main(arguments): elif arguments.password_only: qute_command('fake-key {}{}'.format(password, insert_mode)) else: - # Enter username and password using fake-key and (which seems to work almost universally) and switch back - # into insert-mode, so the form can be directly submitted by hitting enter afterwards + # Enter username and password using fake-key and (which seems to work almost universally), then switch + # back into insert-mode, so the form can be directly submitted by hitting enter afterwards qute_command('fake-key {} ;; fake-key ;; fake-key {}{}'.format(username, password, insert_mode)) return ExitCodes.SUCCESS From a96e4490eef20dcfd3e02b5bb84432e0df50c3c6 Mon Sep 17 00:00:00 2001 From: cryzed Date: Fri, 3 Nov 2017 11:32:32 +0100 Subject: [PATCH 09/12] Add qutebrowser license header and warning about login details in qute's debug log --- misc/userscripts/qute-pass | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 22b1b4826..142c62f38 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -1,5 +1,23 @@ #!/usr/bin/env python3 +# Copyright 2014-2017 Chris Braun (cryzed) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + + # Insert login information using pass and a dmenu-provider (e.g. dmenu, rofi -dmenu, ...). # A short demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif. # @@ -11,7 +29,8 @@ # Dependencies: tldextract (Python 3 module), pass # For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts # -# Chris Braun (cryzed), 2017 +# Warning: The login details are viewable in plaintext in qutebrowser's debug log (qute://log) + import argparse import enum From 22dcd775dadaf460cb6f4038e7782e8c9e598957 Mon Sep 17 00:00:00 2001 From: cryzed Date: Fri, 3 Nov 2017 11:57:23 +0100 Subject: [PATCH 10/12] Improve warning message and adjust copyright --- misc/userscripts/qute-pass | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 142c62f38..5894a3361 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2014-2017 Chris Braun (cryzed) +# Copyright 2017 Chris Braun (cryzed) # # This file is part of qutebrowser. # @@ -27,10 +27,10 @@ # [USERNAME][PASSWORD], which is compatible with almost all login forms. # # Dependencies: tldextract (Python 3 module), pass -# For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts +# For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts. # -# Warning: The login details are viewable in plaintext in qutebrowser's debug log (qute://log) - +# WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if +# you decide to submit a crash report! import argparse import enum From 4ec2e5485a5c1f8ac2431e9d6ada355a19de70b1 Mon Sep 17 00:00:00 2001 From: cryzed Date: Fri, 3 Nov 2017 13:14:29 +0100 Subject: [PATCH 11/12] Sort candidates alphabetically --- misc/userscripts/qute-pass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 5894a3361..659ed32b8 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -129,7 +129,7 @@ def main(arguments): stderr('No pass candidates for URL {!r} found!'.format(arguments.url)) return ExitCodes.NO_PASS_CANDIDATES - selection = candidates.pop() if len(candidates) == 1 else dmenu(candidates, arguments.dmenu_invocation, + selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation, arguments.io_encoding) secret = pass_(selection, arguments.io_encoding) From 600d2a543df68000c6cd00b48645fcb6406caa42 Mon Sep 17 00:00:00 2001 From: cryzed Date: Fri, 3 Nov 2017 13:54:43 +0100 Subject: [PATCH 12/12] Exit successfully when the user makes no selection --- misc/userscripts/qute-pass | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 659ed32b8..1592d6349 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -131,6 +131,10 @@ def main(arguments): selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation, arguments.io_encoding) + # Nothing was selected, simply return + if not selection: + return ExitCodes.SUCCESS + secret = pass_(selection, arguments.io_encoding) # Match username