added bwmenu script
This commit is contained in:
parent
62fe628da1
commit
4e2a8d6797
1 changed files with 500 additions and 0 deletions
500
bin/bwmenu
Executable file
500
bin/bwmenu
Executable file
|
@ -0,0 +1,500 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rofi extension for BitWarden-cli
|
||||
NAME="$(basename "$0")"
|
||||
VERSION="0.4"
|
||||
DEFAULT_CLEAR=5
|
||||
BW_HASH=
|
||||
|
||||
# Options
|
||||
CLEAR=$DEFAULT_CLEAR # Clear password after N seconds (0 to disable)
|
||||
SHOW_PASSWORD=no # Show part of the password in the notification
|
||||
AUTO_LOCK=900 # 15 minutes, default for bitwarden apps
|
||||
|
||||
# Holds the available items in memory
|
||||
ITEMS=
|
||||
|
||||
# Stores which command will be used to emulate keyboard type
|
||||
AUTOTYPE_MODE=
|
||||
|
||||
# Stores which command will be used to deal with clipboards
|
||||
CLIPBOARD_MODE=
|
||||
|
||||
# Specify what happens when pressing Enter on an item.
|
||||
# Defaults to copy_password, can be changed to (auto_type all) or (auto_type password)
|
||||
ENTER_CMD=copy_password
|
||||
|
||||
# Keyboard shortcuts
|
||||
KB_SYNC="Alt+r"
|
||||
KB_URLSEARCH="Alt+u"
|
||||
KB_NAMESEARCH="Alt+n"
|
||||
KB_FOLDERSELECT="Alt+c"
|
||||
KB_TOTPCOPY="Alt+t"
|
||||
KB_LOCK="Alt+L"
|
||||
KB_TYPEALL="Alt+1"
|
||||
KB_TYPEUSER="Alt+2"
|
||||
KB_TYPEPASS="Alt+3"
|
||||
|
||||
# Item type classification
|
||||
TYPE_LOGIN=1
|
||||
TYPE_NOTE=2
|
||||
TYPE_CARD=3
|
||||
TYPE_IDENTITY=4
|
||||
|
||||
# Populated in parse_cli_arguments
|
||||
ROFI_OPTIONS=()
|
||||
DEDUP_MARK="(+)"
|
||||
|
||||
# Source helper functions
|
||||
DIR="$(dirname "$(readlink -f "$0")")"
|
||||
source "$DIR/lib-bwmenu"
|
||||
|
||||
ask_password() {
|
||||
mpw=$(printf '' | rofi -dmenu -p "Master Password" -password -lines 0) || exit $?
|
||||
echo "$mpw" | bw unlock 2>/dev/null | grep 'export' | sed -E 's/.*export BW_SESSION="(.*==)"$/\1/' || exit_error $? "Could not unlock vault"
|
||||
}
|
||||
|
||||
get_session_key() {
|
||||
if [ $AUTO_LOCK -eq 0 ]; then
|
||||
keyctl purge user bw_session &>/dev/null
|
||||
BW_HASH=$(ask_password)
|
||||
else
|
||||
if ! key_id=$(keyctl request user bw_session 2>/dev/null); then
|
||||
session=$(ask_password)
|
||||
[[ -z "$session" ]] && exit_error 1 "Could not unlock vault"
|
||||
key_id=$(echo "$session" | keyctl padd user bw_session @u)
|
||||
fi
|
||||
|
||||
if [ $AUTO_LOCK -gt 0 ]; then
|
||||
keyctl timeout "$key_id" $AUTO_LOCK
|
||||
fi
|
||||
BW_HASH=$(keyctl pipe "$key_id")
|
||||
fi
|
||||
}
|
||||
|
||||
# source the hash file to gain access to the BitWarden CLI
|
||||
# Pre fetch all the items
|
||||
load_items() {
|
||||
if ! ITEMS=$(bw list items --session "$BW_HASH" 2>/dev/null); then
|
||||
exit_error $? "Could not load items"
|
||||
fi
|
||||
}
|
||||
|
||||
exit_error() {
|
||||
local code="$1"
|
||||
local message="$2"
|
||||
|
||||
rofi -e "$message"
|
||||
exit "$code"
|
||||
}
|
||||
|
||||
# Show the Rofi menu with options
|
||||
# Reads items from stdin
|
||||
rofi_menu() {
|
||||
|
||||
actions=(
|
||||
-kb-custom-1 $KB_SYNC
|
||||
-kb-custom-2 $KB_NAMESEARCH
|
||||
-kb-custom-3 $KB_URLSEARCH
|
||||
-kb-custom-4 $KB_FOLDERSELECT
|
||||
-kb-custom-8 $KB_TOTPCOPY
|
||||
-kb-custom-9 $KB_LOCK
|
||||
)
|
||||
|
||||
msg="<b>$KB_SYNC</b>: sync | <b>$KB_URLSEARCH</b>: urls | <b>$KB_NAMESEARCH</b>: names | <b>$KB_FOLDERSELECT</b>: folders | <b>$KB_TOTPCOPY</b>: totp | <b>$KB_LOCK</b>: lock"
|
||||
|
||||
[[ ! -z "$AUTOTYPE_MODE" ]] && {
|
||||
actions+=(
|
||||
-kb-custom-5 $KB_TYPEALL
|
||||
-kb-custom-6 $KB_TYPEUSER
|
||||
-kb-custom-7 $KB_TYPEPASS
|
||||
)
|
||||
msg+="
|
||||
<b>$KB_TYPEALL</b>: Type all | <b>$KB_TYPEUSER</b>: Type user | <b>$KB_TYPEPASS</b>: Type pass"
|
||||
}
|
||||
|
||||
rofi -dmenu -p 'Name' \
|
||||
-i -no-custom \
|
||||
-mesg "$msg" \
|
||||
"${actions[@]}" \
|
||||
"${ROFI_OPTIONS[@]}"
|
||||
}
|
||||
|
||||
# Show items in a rofi menu by name of the item
|
||||
show_items() {
|
||||
if item=$(
|
||||
echo "$ITEMS" \
|
||||
| jq -r ".[] | select( has( \"login\" ) ) | \"\\(.name)\"" \
|
||||
| dedup_lines \
|
||||
| rofi_menu
|
||||
); then
|
||||
item_array="$(array_from_name "$item")"
|
||||
"${ENTER_CMD[@]}" "$item_array"
|
||||
else
|
||||
rofi_exit_code=$?
|
||||
item_array="$(array_from_name "$item")"
|
||||
on_rofi_exit "$rofi_exit_code" "$item_array"
|
||||
fi
|
||||
}
|
||||
|
||||
# Similar to show_items() but using the item's ID for deduplication
|
||||
show_full_items() {
|
||||
if item=$(
|
||||
echo "$ITEMS" \
|
||||
| jq -r ".[] | select( has( \"login\" )) | \"\\(.id): name: \\(.name), username: \\(.login.username)\"" \
|
||||
| rofi_menu
|
||||
); then
|
||||
item_id="$(echo "$item" | cut -d ':' -f 1)"
|
||||
item_array="$(array_from_id "$item_id")"
|
||||
"${ENTER_CMD[@]}" "$item_array"
|
||||
else
|
||||
rofi_exit_code=$?
|
||||
item_id="$(echo "$item" | cut -d ':' -f 1)"
|
||||
item_array="$(array_from_id "$item_id")"
|
||||
on_rofi_exit "$rofi_exit_code" "$item_array"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show items in a rofi menu by url of the item
|
||||
# if url occurs in multiple items, show the menu again with those items only
|
||||
show_urls() {
|
||||
if url=$(
|
||||
echo "$ITEMS" \
|
||||
| jq -r '.[] | select(has("login")) | .login | select(has("uris")).uris | .[].uri' \
|
||||
| rofi_menu
|
||||
); then
|
||||
item_array="$(bw list items --url "$url" --session "$BW_HASH")"
|
||||
"${ENTER_CMD[@]}" "$item_array"
|
||||
else
|
||||
rofi_exit_code="$?"
|
||||
item_array="$(bw list items --url "$url" --session "$BW_HASH")"
|
||||
on_rofi_exit "$rofi_exit_code" "$item_array"
|
||||
fi
|
||||
}
|
||||
|
||||
show_folders() {
|
||||
folders=$(bw list folders --session "$BW_HASH")
|
||||
if folder=$(echo "$folders" | jq -r '.[] | .name' | rofi_menu); then
|
||||
|
||||
folder_id=$(echo "$folders" | jq -r ".[] | select(.name == \"$folder\").id")
|
||||
|
||||
ITEMS=$(bw list items --folderid "$folder_id" --session "$BW_HASH")
|
||||
show_items
|
||||
else
|
||||
rofi_exit_code="$?"
|
||||
folder_id=$(echo "$folders" | jq -r ".[] | select(.name == \"$folder\").id")
|
||||
item_array=$(bw list items --folderid "$folder_id" --session "$BW_HASH")
|
||||
on_rofi_exit "$rofi_exit_code" "$item_array"
|
||||
fi
|
||||
}
|
||||
|
||||
# re-sync the BitWarden items with the server
|
||||
sync_bitwarden() {
|
||||
bw sync --session "$BW_HASH" &>/dev/null || exit_error 1 "Failed to sync bitwarden"
|
||||
|
||||
load_items
|
||||
show_items
|
||||
}
|
||||
|
||||
# Evaluate the rofi exit codes
|
||||
on_rofi_exit() {
|
||||
case "$1" in
|
||||
10) sync_bitwarden;;
|
||||
11) load_items; show_items;;
|
||||
12) show_urls;;
|
||||
13) show_folders;;
|
||||
17) copy_totp "$2";;
|
||||
18) lock_vault;;
|
||||
14) auto_type all "$2";;
|
||||
15) auto_type username "$2";;
|
||||
16) auto_type password "$2";;
|
||||
*) exit "$1";;
|
||||
esac
|
||||
}
|
||||
|
||||
# Auto type using xdotool/ydotool
|
||||
# $1: what to type; all, username, password
|
||||
# $2: item array
|
||||
auto_type() {
|
||||
if not_unique "$2"; then
|
||||
ITEMS="$2"
|
||||
show_full_items
|
||||
else
|
||||
sleep 0.3
|
||||
case "$1" in
|
||||
all)
|
||||
type_word "$(echo "$2" | jq -r '.[0].login.username')"
|
||||
type_tab
|
||||
type_word "$(echo "$2" | jq -r '.[0].login.password')"
|
||||
;;
|
||||
username)
|
||||
type_word "$(echo "$2" | jq -r '.[0].login.username')"
|
||||
;;
|
||||
password)
|
||||
type_word "$(echo "$2" | jq -r '.[0].login.password')"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Set $AUTOTYPE_MODE to a command that will emulate keyboard input
|
||||
select_autotype_command() {
|
||||
if [[ -z "$AUTOTYPE_MODE" ]]; then
|
||||
if [ "$XDG_SESSION_TYPE" = "wayland" ] && hash ydotool 2>/dev/null; then
|
||||
AUTOTYPE_MODE=(sudo ydotool)
|
||||
elif [ "$XDG_SESSION_TYPE" != "wayland" ] && hash xdotool 2>/dev/null; then
|
||||
AUTOTYPE_MODE=xdotool
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
type_word() {
|
||||
"${AUTOTYPE_MODE[@]}" type "$1"
|
||||
}
|
||||
|
||||
type_tab() {
|
||||
"${AUTOTYPE_MODE[@]}" key Tab
|
||||
}
|
||||
|
||||
|
||||
# Set $CLIPBOARD_MODE to a command that will put stdin into the clipboard.
|
||||
select_copy_command() {
|
||||
if [[ -z "$CLIPBOARD_MODE" ]]; then
|
||||
if [ "$XDG_SESSION_TYPE" = "wayland" ]; then
|
||||
hash wl-copy 2>/dev/null && CLIPBOARD_MODE=wayland
|
||||
elif hash xclip 2>/dev/null; then
|
||||
CLIPBOARD_MODE=xclip
|
||||
elif hash xsel 2>/dev/null; then
|
||||
CLIPBOARD_MODE=xsel
|
||||
fi
|
||||
[ -z "$CLIPBOARD_MODE" ] && exit_error 1 "No clipboard command found. Please install either xclip, xsel, or wl-clipboard."
|
||||
fi
|
||||
}
|
||||
|
||||
clipboard-set() {
|
||||
clipboard-${CLIPBOARD_MODE}-set
|
||||
}
|
||||
|
||||
clipboard-get() {
|
||||
clipboard-${CLIPBOARD_MODE}-get
|
||||
}
|
||||
|
||||
clipboard-clear() {
|
||||
clipboard-${CLIPBOARD_MODE}-clear
|
||||
}
|
||||
|
||||
clipboard-xclip-set() {
|
||||
xclip -selection clipboard -r
|
||||
}
|
||||
|
||||
clipboard-xclip-get() {
|
||||
xclip -selection clipboard -o
|
||||
}
|
||||
|
||||
clipboard-xclip-clear() {
|
||||
echo -n "" | xclip -selection clipboard -r
|
||||
}
|
||||
|
||||
clipboard-xsel-set() {
|
||||
xsel --clipboard --input
|
||||
}
|
||||
|
||||
clipboard-xsel-get() {
|
||||
xsel --clipboard
|
||||
}
|
||||
|
||||
clipboard-xsel-clear() {
|
||||
xsel --clipboard --delete
|
||||
}
|
||||
|
||||
clipboard-wayland-set() {
|
||||
wl-copy
|
||||
}
|
||||
|
||||
clipboard-wayland-get() {
|
||||
wl-paste
|
||||
}
|
||||
|
||||
clipboard-wayland-clear() {
|
||||
wl-copy --clear
|
||||
}
|
||||
|
||||
# Copy the password
|
||||
# copy to clipboard and give the user feedback that the password is copied
|
||||
# $1: json array of items
|
||||
copy_password() {
|
||||
if not_unique "$1"; then
|
||||
ITEMS="$1"
|
||||
show_full_items
|
||||
else
|
||||
pass="$(echo "$1" | jq -r '.[0].login.password')"
|
||||
|
||||
show_copy_notification "$(echo "$1" | jq -r '.[0]')"
|
||||
echo -n "$pass" | clipboard-set
|
||||
|
||||
if [[ $CLEAR -gt 0 ]]; then
|
||||
sleep "$CLEAR"
|
||||
if [[ "$(clipboard-get)" == "$pass" ]]; then
|
||||
clipboard-clear
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Copy the TOTP
|
||||
# $1: item array
|
||||
copy_totp() {
|
||||
if not_unique "$1"; then
|
||||
ITEMS="$item_array"
|
||||
show_full_items
|
||||
else
|
||||
id=$(echo "$1" | jq -r ".[0].id")
|
||||
|
||||
if ! totp=$(bw --session "$BW_HASH" get totp "$id"); then
|
||||
exit_error 1 "$totp"
|
||||
fi
|
||||
|
||||
echo -n "$totp" | clipboard-set
|
||||
notify-send "TOTP Copied"
|
||||
fi
|
||||
}
|
||||
|
||||
# Lock the vault by purging the key used to store the session hash
|
||||
lock_vault() {
|
||||
keyctl purge user bw_session &>/dev/null
|
||||
}
|
||||
|
||||
# Show notification about the password being copied.
|
||||
# $1: json item
|
||||
show_copy_notification() {
|
||||
local title
|
||||
local body=""
|
||||
local extra_options=()
|
||||
|
||||
title="<b>$(echo "$1" | jq -r '.name')</b> copied"
|
||||
|
||||
if [[ $SHOW_PASSWORD == "yes" ]]; then
|
||||
pass=$(echo "$1" | jq -r '.login.password')
|
||||
body="${pass:0:4}****"
|
||||
fi
|
||||
|
||||
if [[ $CLEAR -gt 0 ]]; then
|
||||
body="$body<br>Will be cleared in ${CLEAR} seconds."
|
||||
# Keep notification visible while the clipboard contents are active.
|
||||
extra_options+=("-t" "$((CLEAR * 1000))")
|
||||
fi
|
||||
# not sure if icon will be present everywhere, /usr/share/icons is default icon location
|
||||
notify-send "$title" "$body" "${extra_options[@]}" -i /usr/share/icons/hicolor/64x64/apps/bitwarden.png
|
||||
}
|
||||
|
||||
parse_cli_arguments() {
|
||||
# Use GNU getopt to parse command line arguments
|
||||
if ! ARGUMENTS=$(getopt -o c:C --long auto-lock:,clear:,no-clear,show-password,state-path:,help,version -- "$@"); then
|
||||
exit_error 1 "Failed to parse command-line arguments"
|
||||
fi
|
||||
eval set -- "$ARGUMENTS"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--help )
|
||||
cat <<-USAGE
|
||||
$NAME $VERSION
|
||||
|
||||
Usage:
|
||||
$NAME [options] -- [rofi options]
|
||||
|
||||
Options:
|
||||
--help
|
||||
Show this help text and exit.
|
||||
|
||||
--version
|
||||
Show version information and exit.
|
||||
|
||||
--auto-lock <SECONDS>
|
||||
Automatically lock the Vault <SECONDS> seconds after last unlock.
|
||||
Use 0 to lock immediatly.
|
||||
Use -1 to disable.
|
||||
Default: 900 (15 minutes)
|
||||
|
||||
-c <SECONDS>, --clear <SECONDS>, --clear=<SECONDS>
|
||||
Clear password from clipboard after this many seconds.
|
||||
Defaults: ${DEFAULT_CLEAR} seconds.
|
||||
|
||||
-C, --no-clear
|
||||
Don't automatically clear the password from the clipboard. This disables
|
||||
the default --clear option.
|
||||
|
||||
--show-password
|
||||
Show the first 4 characters of the copied password in the notification.
|
||||
|
||||
Quick Actions:
|
||||
When hovering over an item in the rofi menu, you can make use of Quick Actions.
|
||||
|
||||
$KB_SYNC Resync your vault
|
||||
|
||||
$KB_URLSEARCH Search through urls
|
||||
$KB_NAMESEARCH Search through names
|
||||
$KB_FOLDERSELECT Search through folders
|
||||
|
||||
$KB_TOTPCOPY Copy the TOTP
|
||||
$KB_TYPEALL Autotype the username and password [needs xdotool or ydotool]
|
||||
$KB_TYPEUSER Autotype the username [needs xdotool or ydotool]
|
||||
$KB_TYPEPASS Autotype the password [needs xdotool or ydotool]
|
||||
|
||||
$KB_LOCK Lock your vault
|
||||
|
||||
Examples:
|
||||
# Default options work well
|
||||
$NAME
|
||||
|
||||
# Immediatly lock the Vault after use
|
||||
$NAME --auto-lock 0
|
||||
|
||||
# Never lock the Vault
|
||||
$NAME --auto-lock -1
|
||||
|
||||
# Place rofi on top of screen, like a Quake console
|
||||
$NAME -- -location 2
|
||||
USAGE
|
||||
shift
|
||||
exit 0
|
||||
;;
|
||||
--version )
|
||||
echo "$NAME $VERSION"
|
||||
shift
|
||||
exit 0
|
||||
;;
|
||||
--auto-lock )
|
||||
AUTO_LOCK=$2
|
||||
shift 2
|
||||
;;
|
||||
-c | --clear )
|
||||
CLEAR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-C | --no-clear )
|
||||
CLEAR=0
|
||||
shift
|
||||
;;
|
||||
--show-password )
|
||||
SHOW_PASSWORD=yes
|
||||
shift
|
||||
;;
|
||||
-- )
|
||||
shift
|
||||
ROFI_OPTIONS=("$@")
|
||||
break
|
||||
;;
|
||||
* )
|
||||
exit_error 1 "Unknown option $1"
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
parse_cli_arguments "$@"
|
||||
|
||||
get_session_key
|
||||
select_autotype_command
|
||||
select_copy_command
|
||||
load_items
|
||||
show_items
|
Loading…
Reference in a new issue