From 3e31a8b74f2bb680bd41420ce4dce1acd6d5d1ee Mon Sep 17 00:00:00 2001 From: Jonathan Lestrelin Date: Thu, 16 Sep 2021 03:17:06 +0200 Subject: [PATCH 1/4] Add support for using rbw. --- README.md | 10 +++++++--- gnome-pass-search-provider.py | 36 +++++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0f6b28a..8990919 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -A search provider for GNOME Shell that adds support for searching in zx2c4/[pass](https://www.passwordstore.org/). +A search provider for GNOME Shell that adds support for searching passwords in zx2c4/[pass](https://www.passwordstore.org/) or in the [rbw](https://github.com/doy/rbw) Bitwarden/Vaultwarden client. Names of passwords will show up in GNOME Shell searches, choosing one will copy the corresponding content to the clipboard. -Supports OTP, fields and can use GPaste. +Supports OTP, fields (pass only) and can use GPaste (pass or rbw). ![Sreencapture](misc/screencapture.gif) @@ -29,7 +29,7 @@ dnf install gnome-pass-search-provider ## Manual -Ensure that python>=3.5 as well as the dbus, gobject, fuzzywuzzy Python modules are installed. They should all be packaged under python-name or python3-name depending on your distribution. +Ensure that python>=3.7 as well as the dbus, gobject and fuzzywuzzy Python modules are installed. They should all be packaged under python-name or python3-name depending on your distribution. Clone this repository and run the installation script as root: ``` @@ -62,6 +62,10 @@ To copy the pin start the search with `:pin` and for the username with `:user`. The [pass-otp](https://github.com/tadfisher/pass-otp) extension is supported. Searches starting with `otp` will copy the otp token to the clipboard. +# Bitwarden/Vaultwarden + +If [rbw](https://github.com/doy/rbw) is installed, it can be used instead of pass by prefixing a search with `bw`. Non prefixed searches will still go through pass if present. + # Environment variables If you are configuring `pass` through environment variables, such as `PASSWORD_STORE_DIR`, make sure to set them in a way that will propagate to the search provider executable, not just in your shell. diff --git a/gnome-pass-search-provider.py b/gnome-pass-search-provider.py index 8524d9c..3230772 100755 --- a/gnome-pass-search-provider.py +++ b/gnome-pass-search-provider.py @@ -51,6 +51,7 @@ class SearchPassService(dbus.service.Object): bus_name = "org.gnome.Pass.SearchProvider" _object_path = "/" + bus_name.replace(".", "/") + use_bw = False def __init__(self): self.session_bus = dbus.SessionBus() @@ -66,7 +67,14 @@ class SearchPassService(dbus.service.Object): @dbus.service.method(in_signature="as", out_signature="as", **sbn) def GetInitialResultSet(self, terms): - return self.get_result_set(terms) + try: + if terms[0] == "bw": + self.use_bw = True + return self.get_bw_result_set(terms) + except NameError: + pass + self.use_bw = False + return self.get_pass_result_set(terms) @dbus.service.method(in_signature="as", out_signature="aa{sv}", **sbn) def GetResultMetas(self, ids): @@ -81,13 +89,31 @@ class SearchPassService(dbus.service.Object): @dbus.service.method(in_signature="asas", out_signature="as", **sbn) def GetSubsearchResultSet(self, previous_results, new_terms): - return self.get_result_set(new_terms) + if self.use_bw: + return self.get_bw_result_set(new_terms) + else: + return self.get_pass_result_set(new_terms) @dbus.service.method(in_signature="asu", terms="as", timestamp="u", **sbn) def LaunchSearch(self, terms, timestamp): pass - def get_result_set(self, terms): + def get_bw_result_set(self, terms): + name = "".join(terms[1:]) + + password_list = subprocess.check_output( + ["rbw", "list"], stderr=subprocess.STDOUT, universal_newlines=True + ).split("\n")[:-1] + + results = [ + e[0] + for e in process.extract( + name, password_list, limit=5, scorer=fuzz.partial_ratio + ) + ] + return results + + def get_pass_result_set(self, terms): if terms[0] == "otp": field = terms[0] elif terms[0].startswith(":"): @@ -180,7 +206,9 @@ class SearchPassService(dbus.service.Object): def send_password_to_clipboard(self, name): field = None - if name.startswith("otp "): + if self.use_bw: + base_args = ["rbw", "get"] + elif name.startswith("otp "): base_args = ["pass", "otp", "code"] name = name[4:] else: From 1d936552cea2a498a09917cd2b867b56a792a225 Mon Sep 17 00:00:00 2001 From: Jonathan Lestrelin Date: Fri, 17 Sep 2021 20:26:28 +0200 Subject: [PATCH 2/4] Improve/simplify error handling. --- gnome-pass-search-provider.py | 98 +++++++++++++++-------------------- 1 file changed, 42 insertions(+), 56 deletions(-) diff --git a/gnome-pass-search-provider.py b/gnome-pass-search-provider.py index 3230772..733e0eb 100755 --- a/gnome-pass-search-provider.py +++ b/gnome-pass-search-provider.py @@ -149,60 +149,37 @@ class SearchPassService(dbus.service.Object): return results def send_password_to_gpaste(self, base_args, name, field=None): + gpaste = self.session_bus.get_object( + "org.gnome.GPaste.Daemon", "/org/gnome/GPaste" + ) + + output = subprocess.check_output( + base_args + [name], stderr=subprocess.STDOUT, universal_newlines=True + ) + if field is not None: + match = re.search( + fr"^{field}:\s*(?P.+?)$", output, flags=re.I | re.M + ) + if match: + password = match.group("value") + else: + raise RuntimeError(f"The field {field} was not found in the pass file.") + else: + password = output.split("\n", 1)[0] try: - gpaste = self.session_bus.get_object( - "org.gnome.GPaste.Daemon", "/org/gnome/GPaste" - ) - - output = subprocess.check_output( - base_args + [name], stderr=subprocess.STDOUT, universal_newlines=True - ) - if field is not None: - match = re.search( - fr"^{field}:\s*(?P.+?)$", output, flags=re.I | re.M - ) - if match: - password = match.group("value") - else: - raise RuntimeError( - f"The field {field} was not found in " + "the pass file." - ) - else: - password = output.split("\n", 1)[0] - try: - gpaste.AddPassword(name, password, dbus_interface="org.gnome.GPaste1") - except dbus.DBusException: - gpaste.AddPassword(name, password, dbus_interface="org.gnome.GPaste2") - - if "otp" in base_args: - self.notify("Copied OTP password to clipboard:", body=f"{name}") - elif field is not None: - self.notify( - f"Copied field {field} to clipboard:", body=f"{name}" - ) - else: - self.notify("Copied password to clipboard:", body=f"{name}") - except subprocess.CalledProcessError as e: - self.notify("Failed to copy password!", body=e.output, error=True) - except RuntimeError as e: - self.notify("Failed to copy field!", body=e.output, error=True) + gpaste.AddPassword(name, password, dbus_interface="org.gnome.GPaste1") + except dbus.DBusException: + gpaste.AddPassword(name, password, dbus_interface="org.gnome.GPaste2") def send_password_to_native_clipboard(self, base_args, name, field=None): - if field is not None: - self.notify( - "Cannot copy field values.", - body="This feature requires GPaste.", - error=True, - ) - return + if field is not None or self.use_bw: + raise RuntimeError("This feature requires GPaste.") - pass_cmd = subprocess.run(base_args + ["-c", name]) - if pass_cmd.returncode: - self.notify("Failed to copy password!", error=True) - elif "otp" in base_args: - self.notify("Copied OTP password to clipboard:", body=f"{name}") - else: - self.notify("Copied password to clipboard:", body=f"{name}") + result = subprocess.run(base_args + ["-c", name]) + if result.returncode: + raise RuntimeError( + f"Error while running pass: got return code {result.returncode}." + ) def send_password_to_clipboard(self, name): field = None @@ -216,13 +193,22 @@ class SearchPassService(dbus.service.Object): if name.startswith(":"): field, name = name.split(" ", 1) field = field[1:] - try: - self.send_password_to_gpaste(base_args, name, field) - except dbus.DBusException: - # We couldn't join GPaste over D-Bus, - # use pass native clipboard copy - self.send_password_to_native_clipboard(base_args, name, field) + try: + self.send_password_to_gpaste(base_args, name, field) + except dbus.DBusException: + # We couldn't join GPaste over D-Bus, use native clipboard + self.send_password_to_native_clipboard(base_args, name, field) + if "otp" in base_args: + self.notify("Copied OTP password to clipboard:", body=f"{name}") + elif field is not None: + self.notify( + f"Copied field {field} to clipboard:", body=f"{name}" + ) + else: + self.notify("Copied password to clipboard:", body=f"{name}") + except (subprocess.CalledProcessError, FileNotFoundError, RuntimeError) as e: + self.notify("Failed to copy password or field!", body=e.output, error=True) def notify(self, message, body="", error=False): try: From 54a85ff9d498bad004cb598d2bb1b48ab894c32a Mon Sep 17 00:00:00 2001 From: Jonathan Lestrelin Date: Tue, 28 Sep 2021 02:36:34 +0200 Subject: [PATCH 3/4] Fix showing exception. Improve README a little bit. --- README.md | 5 +++-- gnome-pass-search-provider.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8990919..284b619 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A search provider for GNOME Shell that adds support for searching passwords in z Names of passwords will show up in GNOME Shell searches, choosing one will copy the corresponding content to the clipboard. -Supports OTP, fields (pass only) and can use GPaste (pass or rbw). +Can use the [GPaste](https://github.com/Keruspe/GPaste) clipboard manager, supports OTP and fields (pass only, requires GPaste). ![Sreencapture](misc/screencapture.gif) @@ -33,7 +33,8 @@ Ensure that python>=3.7 as well as the dbus, gobject and fuzzywuzzy Python modul Clone this repository and run the installation script as root: ``` -git clone git@github.com:jle64/gnome-shell-pass-search-provider.git +git clone https://github.com/jle64/gnome-pass-search-provider.git +cd gnome-pass-search-provider sudo ./install.sh ``` diff --git a/gnome-pass-search-provider.py b/gnome-pass-search-provider.py index 733e0eb..4f66769 100755 --- a/gnome-pass-search-provider.py +++ b/gnome-pass-search-provider.py @@ -208,7 +208,7 @@ class SearchPassService(dbus.service.Object): else: self.notify("Copied password to clipboard:", body=f"{name}") except (subprocess.CalledProcessError, FileNotFoundError, RuntimeError) as e: - self.notify("Failed to copy password or field!", body=e.output, error=True) + self.notify("Failed to copy password or field!", body=str(e), error=True) def notify(self, message, body="", error=False): try: From 72af8bf43b9cfcbb639eef78d27d7bbf74678727 Mon Sep 17 00:00:00 2001 From: Jonathan Lestrelin Date: Tue, 28 Sep 2021 02:42:56 +0200 Subject: [PATCH 4/4] Add note about bitwarden/vaultwarden in specfile. --- gnome-pass-search-provider.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnome-pass-search-provider.spec b/gnome-pass-search-provider.spec index bfc25e3..2c4a001 100644 --- a/gnome-pass-search-provider.spec +++ b/gnome-pass-search-provider.spec @@ -16,7 +16,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-build %global debug_package %{nil} %description -A Gnome Shell search provider for zx2c4/pass (passwordstore.org) that sends passwords to clipboard (or GPaste). +A Gnome Shell passwords search provider for zx2c4/pass (passwordstore.org) and Bitwarden/Vaultwarden that sends passwords to clipboard (or GPaste). %prep %setup -q -n %{name}-%{version}