diff --git a/README.md b/README.md index 179d519..0b269d8 100644 --- a/README.md +++ b/README.md @@ -4,117 +4,75 @@ A [Ulauncher](https://ulauncher.io/) extension to search your [Bitwarden](https: ## Features +- Leverage [`bw`](https://bitwarden.com/help/cli/) in [RESTful API](https://bitwarden.com/help/cli/#serve) mode (much faster!) - Quickly search through the database entries by name, and copy passwords/usernames/URLs/TOTPs to the clipboard - Works also with self hosted Bitwarden servers. -- Support vaults with a passphrase also with MFA codes. The extension does not keep the password in the memory. -It rather uses SessionID generated by the Bitwarden CLI client. ## Requirements -- Install a recent version of [Bitwarden CLI](https://github.com/bitwarden/clients/tree/master/apps/cli) -- Make sure you can execute `bw` in a terminal +- Install a recent version of [Bitwarden CLI](https://github.com/bitwarden/clients/tree/master/apps/cli) (e.g. via [3rd party apt repo](https://gitlab.com/packaging/bitwarden-cli/)) +- Install python requests module (e.g. `sudo apt-get install python3-requests`) ## Installation -Open Ulauncher preferences window -> Extensions -> "Add extension" and paste the following url: +### `bw` configuration +```bash +bw login ``` -https://github.com/kbialek/ulauncher-bitwarden -``` - -## Configuration - -- `Bitwarden Server Url` -- `User e-mail address` -- `Enable MFA login` - if you use MFA authentication with your Bitwarden account select `yes`, otherwise leave `no` -- `Inactivity lock timeout` - forces you to re-enter the passphrase after you haven't used the extension for a while. By default it's set to 300 seconds (5 minutes). If you'd rather not re-enter it, you can set the value to 0, but that's probably not a great idea. NOTE: The cached passphrase is only stored in memory, so you'll need to re-enter it if you reboot your computer or restart Ulauncher. -- `Session store command` - optional command called after successful login or unlock. Bitwarden session key is passed over stdin. -You can use it to run a command which will store session key in "some" secure location, -and later read the session key when directly calling `bw` in the cli. -It's totally up to you how and where you will store the session key. -## Usage - -Open Ulauncher and type in "bw " to start the extension. If your password database is locked with a passphrase, it'll ask you to enter it: +self hosted (e.g. [vaultwarden](https://github.com/dani-garcia/vaultwarden/)): -![Unlock Database](images/screenshots/unlock-database.png) +```bash +bw config server https://my-vault.example.com +``` -Once unlocked, search the database for "mail" logins: +### `bw serve` systemd service -![Search](images/screenshots/search1.png) +- Make sure `bw` works in a shell session (e.g. configuring server, e-mail, ...) +- Create a user systemd directory: `mkdir -p ~/.config/systemd/user` +- Create a user service in `~/.config/systemd/user/bw.service` with the following content + - Your `ExecStart` may vary (e.g. `%h/bin/bw serve` if you have it in your home directory) -Look at the `GMail` entry: - -![Entry details](images/screenshots/details1.png) +``` +[Unit] +Description=Bitwarden CLI RESTful API +After=network.target -## Exporting Session Key -The extension keeps the session key in memory. This is a problem when one wants to use `bw` directly from the -command line. Vault must be unlocked and bw-cli creates a new session key and at this same time invalidates -the session key stored by the extension. +[Service] +ExecStart=/usr/bin/bw serve +Restart=on-failure -To overcome this problem the extension is now able to export the session key after a successful login or unlock. -Please keep in mind, that this weakens your vault's security, as the session key is easier to intercept when -it's stored outside of the extension memory. +[Install] +WantedBy=default.target +``` -### Exporting session key into a file -I do not recommend this solution because it leaves valid session key in the file until vault is explicitly locked. +### Ulauncher -To store session key in a file use the following script. -```shell script -#!/bin/bash +- Open Ulauncher preferences window -> Extensions -> "Add extension" and paste the following url: -BW_SESSION_FILE=$HOME/.bw-session -touch $BW_SESSION_FILE -chmod 600 $BW_SESSION_FILE -cat /dev/stdin > $BW_SESSION_FILE ``` -`Session store command` property must be set to absolute path of the script. - -Now you can use it in the command line -```shell script -export BW_SESSION=$(cat ~/.bw-session) -bw list items +https://github.com/morph027/ulauncher-bitwarden ``` -### Exporting session key into Kernel Key Management -Linux kernel comes with key management facility, that can be used to store user secrets. -For more details read [this](https://github.com/jdukes/pykeyctl/blob/master/docs/Overview.org) page. - -To store session key in the kernel memory use this script. +## Configuration -File `$HOME/bin/bw-store-session` -```shell script -#!/bin/bash +- `Bitwarden CLI serve url` -BW_SESSION_FILE=$HOME/.bw-session -touch $BW_SESSION_FILE -chmod 600 $BW_SESSION_FILE -KEY_ID=$(cat /dev/stdin | keyctl padd user bw-session @u) -keyctl timeout $KEY_ID 36000 -echo $KEY_ID > $BW_SESSION_FILE -``` -Please note that it sets **key timeout**, therefore the key will expire, which is great from security perspective. -Key ID will be stored in `$HOME/.bw-session` file. +## Usage -`Session store command` property must be set to absolute path of the script. +Open Ulauncher and type in "bw " to start the extension. If your password database is locked with a passphrase, it'll ask you to enter it: -We need one more script to read the key from the kernel memory. +![Unlock Database](images/screenshots/unlock-database.png) -File `$HOME/bin/bw-read-session` -```shell script -#!/bin/bash +Once unlocked, search the database for "mail" logins: -BW_SESSION_FILE=$HOME/.bw-session -KEY_ID=$(cat $BW_SESSION_FILE) -keyctl print $KEY_ID -``` +![Search](images/screenshots/search1.png) -Now you can easily read the session key into an environment variable -``` -export BW_SESSION=$(bw-read-session) -``` +Look at the `GMail` entry: +![Entry details](images/screenshots/details1.png) ## Inspiration and thanks -This is a fork of well crafted [ulauncher-keepassxc](https://github.com/pbkhrv/ulauncher-keepassxc) extension. Thank you @pbkhrv! +This is a fork of well crafted [ulauncher-bitwarden](https://github.com/kbialek/ulauncher-bitwarden) extension. Thank you @kbialek! diff --git a/bitwarden.py b/bitwarden.py index ce582ff..28fb552 100644 --- a/bitwarden.py +++ b/bitwarden.py @@ -1,11 +1,4 @@ -import subprocess -from datetime import datetime, timedelta -import json -from json import JSONDecodeError - - -class BitwardenCliNotFoundError(Exception): - pass +from requests import get, post class BitwardenCliError(Exception): @@ -25,169 +18,75 @@ class BitwardenClient: """ Wrapper around bitwarden-cli """ def __init__(self): - self.cli = "bw" - self.init_done = False - self.path = None - self.path_checked = False - self.server = None - self.email = None - self.session = None - self.folders = None - self.mfa_enabled = None - self.passphrase_expires_at = None - self.inactivity_lock_timeout = 0 - self.session_store_cmd = "" - - def initialize(self, server, email, mfa_enabled, inactivity_lock_timeout, session_store_cmd): - """ - Check that - - we can call the CLI - Don't call more than once. - """ - self.server = server - self.email = email - self.mfa_enabled = mfa_enabled - self.inactivity_lock_timeout = inactivity_lock_timeout - self.session_store_cmd = session_store_cmd - if not self.init_done: - self.configure_server() - if self.can_execute_cli(): - self.init_done = True - else: - raise BitwardenCliNotFoundError() - - if self.inactivity_lock_timeout and self.passphrase_expires_at is not None: - if datetime.now() > self.passphrase_expires_at: - self.lock() - - def change_server_url(self, new_server_url): - """ - Change the path to the database file and lock the database. - """ - self.logout() - self.server = new_server_url - self.passphrase_expires_at = None - self.configure_server() - - def change_email(self, new_email): - """ - Change the path to the database file and lock the database. - """ - self.logout() - self.email = new_email - self.passphrase_expires_at = None - - def change_inactivity_lock_timeout(self, secs): - """ - Change the inactivity lock timeout and immediately lock the database. - """ - self.inactivity_lock_timeout = secs - self.passphrase_expires_at = None + self.url = "http://localhost:8087" + self.status = {} + self.folders = {} + try: + self.status = get( + url="{}/status".format(self.url), + ) + self.status.raise_for_status() + except Exception as err: + raise BitwardenCliError(str(err)) from err - def change_session_store_cmd(self, cmd): + def configure(self, url): """ - Change the inactivity lock timeout and immediately lock the database. """ - self.session_store_cmd = cmd - - def configure_server(self): - self.run_cli_session("config", "server", self.server) - - def need_login(self): - try: - (err, out) = self.run_cli_session("login", "--check") - return self.handle_unlock_result(err, out) - except BitwardenVaultLockedError: - return True - - def need_mfa(self): - return self.mfa_enabled + self.url = url or self.url - def need_unlock(self): - if not self.has_session(): + def is_unlocked(self): + res = get( + url="{}/status".format(self.url), + ).json() + if res.get("data", {}).get("template", {}).get("status", "") in ["unlocked"]: return True - (err, out) = self.run_cli_session("unlock", "--check") - return self.handle_unlock_result(err, out) - - def has_session(self): - return self.session is not None - - @staticmethod - def handle_unlock_result(err, out): - if out: - try: - result = out["success"] is False - return result - except JSONDecodeError: - raise BitwardenCliError(err) - else: - return False - - def verify_and_set_passphrase(self, pp, mfa): - success = False - if self.need_login(): - success = self.login(pp, mfa) - elif self.need_unlock(): - success = self.unlock(pp) - if success: - self.run_cli_store_session() - self.list_folders() - return success - - def login(self, pp, mfa): - args = ["login", self.email, "--raw"] - if self.mfa_enabled and mfa: - args.append("--code") - args.append(mfa) - (err, out) = self.run_cli_pp(pp, *args) - if out: - self.session = out - return True - else: - self.session = None - return False - - def logout(self): - self.session = None - (err, out) = self.run_cli_session("logout") - if err: - raise BitwardenCliError(err) + return False def unlock(self, pp): - (err, out) = self.run_cli_pp(pp, "unlock", "--raw") - if out: - self.session = out - return True - else: - self.session = None - return False + try: + res = post( + url="{}/unlock".format(self.url), + json={"password": pp}, + ).json() + self.list_folders() + return res.get("success") + except Exception as err: + raise BitwardenCliError(str(err)) from err def lock(self): - self.session = None - (err, out) = self.run_cli_session("lock") - if err: - raise BitwardenCliError(err) - else: + if not self.is_unlocked: return True + try: + res = post( + url="{}/lock".format(self.url), + ).json() + return res.get("success") + except Exception as err: + raise BitwardenCliError(str(err)) from err def sync(self): - (err, out) = self.run_cli_session("sync") - if err: - raise BitwardenCliError(err) - else: - self.list_folders() - return True + try: + res = post( + url="{}/sync".format(self.url), + ).json() + return res.get("success") + except Exception as err: + raise BitwardenCliError(str(err)) from err def list_folders(self): - (err, out) = self.run_cli_session("list", "folders") - if err: - self.folders = None - return False - else: + self.folders = {} + try: + res = get( + url="{}/list/object/folders".format(self.url), + ).json() + if not res.get("success"): + return False self.folders = dict() - for item in out["data"]["data"]: + for item in res.get("data", {}).get("data", []): self.folders[item["id"]] = item["name"] return True + except Exception as err: + raise BitwardenCliError(str(err)) from err def get_folder(self, folder_id): if folder_id in self.folders: @@ -198,114 +97,38 @@ def get_folder(self, folder_id): def search(self, query): if len(query) < 2: return [] - - (err, out) = self.run_cli_session("list", "items", "--search", query) - if err: - raise BitwardenCliError(err) - else: - return out["data"]["data"] + try: + res = get( + url="{}/list/object/items".format(self.url), + params={"search": query}, + ).json() + if not res.get("success"): + raise BitwardenCliError(res.get("message")) + return res.get("data", {}).get("data", []) + except Exception as err: + raise BitwardenCliError(str(err)) from err def get_entry_details(self, entry): - attrs = dict() - - (err, out) = self.run_cli_session("get", "item", entry) - if err: - raise BitwardenCliError(err) - else: - data = out["data"] - login = data["login"] - if "fields" in data: - attrs["fields"] = data["fields"] - - attrs["username"] = login["username"] - attrs["password"] = login["password"] + attrs = {} + try: + res = get( + url="{}/object/item/{}".format(self.url, entry), + ).json() + if not res.get("success"): + raise BitwardenCliError(res.get("message")) + login = res.get("data", {}).get("login") + if "fields" in res.get("data", {}): + attrs["fields"] = res.get("data", {}).get("fields") + attrs["username"] = login.get("username") + attrs["password"] = login.get("password") if "uris" in login: - uris = login["uris"] + uris = login.get("uris") attrs["uri"] = uris[0]["uri"] if uris else "" - - if "totp" in login and login["totp"]: - (err, out) = self.run_cli_session("get", "totp", entry) - attrs["totp"] = out["data"]["data"] - return attrs - - def can_execute_cli(self): - try: - subprocess.run([self.cli], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return True - except FileNotFoundError: - return False - - def get_bw_version(self): - try: - cp = subprocess.run( - [self.cli, "--version"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - except FileNotFoundError: - raise BitwardenCliNotFoundError() - - out = cp.stdout.decode("utf-8") - return str(out).strip() - - def run_cli_session(self, *args): - session_args = ["--session", self.session] if self.session else [] - try: - cp = subprocess.run( - [self.cli, *args, "--response", *session_args], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - except FileNotFoundError: - raise BitwardenCliNotFoundError() - - if self.inactivity_lock_timeout: - self.passphrase_expires_at = datetime.now() + timedelta( - seconds=self.inactivity_lock_timeout - ) - - err = cp.stderr.decode("utf-8") - out = cp.stdout.decode("utf-8") - - err_json = None - out_json = None - - if out: - out_json = json.loads(out) - if not out_json["success"] and out_json["message"] == "You are not logged in.": - raise BitwardenVaultLockedError(out_json["message"]) - - return err_json, out_json - - def run_cli_pp(self, passphrase, *args): - try: - cp = subprocess.run( - [self.cli, *args], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - input=bytes(passphrase, "utf-8"), - ) - except FileNotFoundError: - raise BitwardenCliNotFoundError() - - if self.inactivity_lock_timeout: - self.passphrase_expires_at = datetime.now() + timedelta( - seconds=self.inactivity_lock_timeout - ) - - return cp.stderr.decode("utf-8"), cp.stdout.decode("utf-8") - - def run_cli_store_session(self): - if self.session_store_cmd == '': - return - try: - cp = subprocess.run( - [self.session_store_cmd], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - input=bytes(self.session, "utf-8"), - ) - except FileNotFoundError: - raise BitwardenCliNotFoundError() - except Exception as e: - raise BitwardenCliError(e) + if login.get("totp"): + res = get( + url="{}/object/totp/{}".format(self.url, entry), + ).json() + attrs["totp"] = res.get("data", {}).get("data", "") + return attrs + except Exception as err: + raise BitwardenCliError(str(err)) from err diff --git a/gtk_passphrase_entry.py b/gtk_passphrase_entry.py index 8a8797b..832014f 100644 --- a/gtk_passphrase_entry.py +++ b/gtk_passphrase_entry.py @@ -1,14 +1,12 @@ -import os - import gi gi.require_version("Gtk", "3.0") -from gi.repository import Gtk, GLib +from gi.repository import Gtk class GtkPassphraseEntryWindow(Gtk.Window): - def __init__(self, login_mode, mfa_enabled, verify_passphrase_fn=None): - Gtk.Window.__init__(self, title="Bitwarden Login" if login_mode else "Bitwarden Unlock") + def __init__(self, verify_passphrase_fn=None): + Gtk.Window.__init__(self, title="Bitwarden Unlock") self.set_keep_above(True) # self.set_icon_from_file(os.path.dirname(os.path.abspath(__file__)) + "/images/bitwarden.png") @@ -30,19 +28,6 @@ def __init__(self, login_mode, mfa_enabled, verify_passphrase_fn=None): self.password_entry.connect("key-press-event", self.key_pressed) vbox.pack_start(self.password_entry, True, True, 0) - self.mfa_label = Gtk.Label("Two Factor Authentication Code") - - self.mfa_entry = Gtk.Entry() - self.mfa_entry.set_text("") - self.mfa_entry.set_editable(True) - self.mfa_entry.set_visibility(True) - self.mfa_entry.props.max_width_chars = 6 - self.mfa_entry.connect("activate", self.enter_pressed) - self.mfa_entry.connect("key-press-event", self.key_pressed) - if login_mode and mfa_enabled: - vbox.pack_start(self.mfa_label, True, True, 0) - vbox.pack_start(self.mfa_entry, True, True, 0) - self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) @@ -52,10 +37,9 @@ def close_window(self): def enter_pressed(self, entry): pp = self.password_entry.get_text() - mfa = self.mfa_entry.get_text() if self.verify_passphrase_fn: self.show_verifying_passphrase() - if self.verify_passphrase_fn(pp, mfa): + if self.verify_passphrase_fn(pp): self.passphrase = pp self.close_window() else: diff --git a/main.py b/main.py index fe2bc8f..dee1df2 100644 --- a/main.py +++ b/main.py @@ -16,15 +16,9 @@ from ulauncher.api.shared.action.ExtensionCustomAction import ExtensionCustomAction from ulauncher.api.shared.action.ActionList import ActionList from ulauncher.api.shared.action.CopyToClipboardAction import CopyToClipboardAction -from bitwarden import ( - BitwardenClient, - BitwardenCliNotFoundError, - BitwardenCliError, - BitwardenVaultLockedError) +from bitwarden import BitwardenClient, BitwardenCliError, BitwardenVaultLockedError from gtk_passphrase_entry import GtkPassphraseEntryWindow -BW_CLI_MIN_VERSION = "1.20.0" - SEARCH_ICON = "images/bitwarden-search.svg" UNLOCK_ICON = "images/bitwarden-search-locked.svg" EMPTY_ICON = "images/empty.png" @@ -33,13 +27,6 @@ COPY_ICON = "images/copy.svg" NOT_FOUND_ICON = "images/not_found.svg" -BITWARDEN_CLI_NOT_FOUND_ITEM = ExtensionResultItem( - icon=ERROR_ICON, - name="Cannot find or execute bw", - description="Please make sure that bitwarden-cli is installed and accessible", - on_enter=DoNothingAction(), -) - NEED_PASSPHRASE_ITEM = ExtensionResultItem( icon=UNLOCK_ICON, name="Unlock Bitwarden", @@ -89,6 +76,7 @@ def bitwarden_cli_error_item(message): on_enter=DoNothingAction(), ) + def formatted_result_item(hidden, name, value, action): item_description = "Copy {} to clipboard".format(name) @@ -98,27 +86,27 @@ def formatted_result_item(hidden, name, value, action): item_name = "{}: {}".format(name, value) return ExtensionResultItem( - icon=COPY_ICON, - name=item_name, - description=item_description, - on_enter=action, - ) + icon=COPY_ICON, + name=item_name, + description=item_description, + on_enter=action, + ) + def custom_clipboard_actions_list(name, value): return [ ExtensionCustomAction( { "action": "show_notification", - "summary": "{} copied to clipboard.".format( - name - ), + "summary": "{} copied to clipboard.".format(name), } ), CopyToClipboardAction(value), ] + class BitwardenExtension(Extension): - """ Extension class, coordinates everything """ + """Extension class, coordinates everything""" def __init__(self): super(BitwardenExtension, self).__init__() @@ -139,55 +127,33 @@ def get_sync_keyword(self): def get_lock_keyword(self): return self.preferences["lock"] - def get_server_url(self): - return self.preferences["server-url"] - - def get_email(self): - return self.preferences["email"] - - def get_mfa_enabled(self): - return self.preferences["mfa"] == 'yes' + def get_url(self): + return self.preferences["url"] def get_max_result_items(self): return int(self.preferences["max-results"]) - def get_inactivity_lock_timeout(self): - return int(self.preferences["inactivity-lock-timeout"]) - - def get_session_store_cmd(self): - return self.preferences["session-store-cmd"] - def set_active_entry(self, keyword, entry): self.active_entry = (keyword, entry) class KeywordQueryEventListener(EventListener): - """ KeywordQueryEventListener class used to manage user input """ + """KeywordQueryEventListener class used to manage user input""" def __init__(self, bitwarden): self.bitwarden = bitwarden def on_event(self, event, extension): try: - self.bitwarden.initialize( - extension.get_server_url(), - extension.get_email(), - extension.get_mfa_enabled(), - extension.get_inactivity_lock_timeout(), - extension.get_session_store_cmd() + self.bitwarden.configure( + url=extension.get_url(), ) - - if not self.bitwarden.has_session(): - if self.bitwarden.get_bw_version() < BW_CLI_MIN_VERSION: - return RenderResultListAction([build_bitwarden_cli_version_unsupported_item(BW_CLI_MIN_VERSION)]) - else: - return RenderResultListAction([NEED_PASSPHRASE_ITEM]) + if not self.bitwarden.is_unlocked(): + return RenderResultListAction([NEED_PASSPHRASE_ITEM]) else: return self.process_keyword_query(event, extension) except BitwardenVaultLockedError: return RenderResultListAction([NEED_PASSPHRASE_ITEM]) - except BitwardenCliNotFoundError: - return RenderResultListAction([BITWARDEN_CLI_NOT_FOUND_ITEM]) except BitwardenCliError as e: return RenderResultListAction([bitwarden_cli_error_item(e.message)]) @@ -228,16 +194,20 @@ def process_keyword_query(self, event, extension): if self.bitwarden.sync(): Notify.Notification.new("Bitwarden vault synchronized.").show() else: - Notify.Notification.new("Error", "Bitwarden vault synchronization error.").show() + Notify.Notification.new( + "Error", "Bitwarden vault synchronization error." + ).show() elif query_keyword == extension.get_lock_keyword(): if self.bitwarden.lock(): Notify.Notification.new("Bitwarden vault locked.").show() else: - Notify.Notification.new("Error", "Bitwarden vault locking error.").show() + Notify.Notification.new( + "Error", "Bitwarden vault locking error." + ).show() class ItemEnterEventListener(EventListener): - """ KeywordQueryEventListener class used to manage user input """ + """KeywordQueryEventListener class used to manage user input""" def __init__(self, bitwarden): self.bitwarden = bitwarden @@ -255,19 +225,13 @@ def on_event(self, event, extension): return self.show_active_entry(entry["id"]) elif action == "show_notification": Notify.Notification.new(data.get("summary")).show() - except BitwardenCliNotFoundError: - return RenderResultListAction([BITWARDEN_CLI_NOT_FOUND_ITEM]) except BitwardenCliError as e: return RenderResultListAction([bitwarden_cli_error_item(e.message)]) def read_verify_passphrase(self, extension): - win = GtkPassphraseEntryWindow( - login_mode=self.bitwarden.need_login(), - mfa_enabled=self.bitwarden.need_mfa(), - verify_passphrase_fn=self.bitwarden.verify_and_set_passphrase - ) + win = GtkPassphraseEntryWindow(verify_passphrase_fn=self.bitwarden.unlock) win.read_passphrase() - if not self.bitwarden.need_unlock(): + if self.bitwarden.is_unlocked(): Notify.Notification.new("Bitwarden vault unlocked.").show() def show_active_entry(self, entry): @@ -278,7 +242,7 @@ def show_active_entry(self, entry): ("username", "username"), ("uri", "URL"), ("totp", "totp"), - ("fields", "Custom") + ("fields", "Custom"), ] for attr, attr_nice in attrs: val = details.get(attr, "") @@ -290,37 +254,45 @@ def show_active_entry(self, entry): ) if field["type"] == 1: - items.append(formatted_result_item(True, field["name"], field["value"], action)) + items.append( + formatted_result_item( + True, field["name"], field["value"], action + ) + ) else: - items.append(formatted_result_item(False, field["name"], field["value"], action)) + items.append( + formatted_result_item( + False, field["name"], field["value"], action + ) + ) else: action = ActionList( custom_clipboard_actions_list(attr_nice.capitalize(), val) ) if attr == "password": - items.append(formatted_result_item(True, attr_nice.capitalize(), val, action)) + items.append( + formatted_result_item(True, attr_nice.capitalize(), val, action) + ) elif attr != "fields": - items.append(formatted_result_item(False, attr_nice.capitalize(), val, action)) + items.append( + formatted_result_item( + False, attr_nice.capitalize(), val, action + ) + ) return RenderResultListAction(items) class PreferencesUpdateEventListener(EventListener): - """ Handle preferences updates """ + """Handle preferences updates""" def __init__(self, bitwarden): self.bitwarden = bitwarden def on_event(self, event, extension): if event.new_value != event.old_value: - if event.id == "server-url": - self.bitwarden.change_server_url(event.new_value) - elif event.id == "email": - self.bitwarden.change_email(event.new_value) - elif event.id == "inactivity-lock-timeout": - self.bitwarden.change_inactivity_lock_timeout(int(event.new_value)) - elif event.id == "session-store-cmd": - self.bitwarden.change_session_store_cmd(event.new_value) + if event.id == "url": + self.bitwarden.configure(url=event.new_value) if __name__ == "__main__": diff --git a/manifest.json b/manifest.json index 4fb7a2c..20f04ea 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "required_api_version": "^2.0.0", "name": "Bitwarden Search", "description": "Quickly search your Bitwarden password manager database", - "developer_name": "Krzysztof Bialek", + "developer_name": "STefan Heitmüller", "icon": "images/bitwarden-search.svg", "options": { "query_debounce": 0.2 @@ -30,43 +30,16 @@ "default_value": "bwlock" }, { - "id": "server-url", + "id": "url", "type": "input", - "name": "Bitwarden Server Url", - "default_value": "https://vault.bitwarden.com" - }, - { - "id": "email", - "type": "input", - "name": "User e-mail address", - "default_value": "" - }, - { - "id": "mfa", - "type": "select", - "options": ["yes", "no"], - "name": "Enable MFA login", - "default_value": "no" + "name": "Bitwarden CLI serve url", + "default_value": "http://localhost:8087" }, { "id": "max-results", "type": "input", "name": "Maximum number of search results to display", "default_value": "6" - }, - { - "id": "inactivity-lock-timeout", - "type": "input", - "name": "Inactivity lock timeout", - "description": "Lock database if extension hasn't been used for a certain number of seconds. Set to 0 to never lock", - "default_value": "300" - }, - { - "id": "session-store-cmd", - "type": "input", - "name": "Session store command", - "description": "Command called after successful login or unlock. SessionID is passed over stdin", - "default_value": "" } ] }