dotfiles/pass/.local/bin/rofi-pass
Marty Oehme c646cb1f70
pass: Add toggling of password reveal state
Previously, the password could only be revealed -- once revealed it
could not be obfuscated anymore.

Now the obfuscated/deobfuscated state can be toggled by hitting the
corresponding keymapping repeatedly (default Alt+s in entry menu).
2020-09-12 14:47:30 +02:00

307 lines
8.2 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# selector wrapper
# uses rofi if found, or dmenu if found, complains if no selector available
# passes along any options given to main script
rofi_opts=("$@")
_rofi() {
if type rofi 1>/dev/null 2>/dev/null; then
rofi -dmenu -no-auto-select -i "${rofi_opts[@]}" "$@" -p "Entry"
elif type dmenu 1>/dev/null 2>/dev/null; then
dmenu -i "${rofi_opts[@]}" "$@" -p "Entry"
else
printf "%s: 📦 %s must be installed for %s function.\n" "critical" "rofi/dmenu" "this" >&2
notify-send "📦 rofi/dmenu" --urgency="critical" "must be installed for this function."
exit 1
fi
}
# parse, see https://unix.stackexchange.com/a/331965/8541
_parse_config() {
(grep -E "^$2=" -m 1 "$1" 2>/dev/null || printf "VAR=__UNDEFINED__\n") | head -n1 | cut -d '=' -f 2-
}
# read config file
get_config() {
local locations=(
"$RP_CONFIGURATION_FILE"
"${XDG_CONFIG_HOME:-$HOME/.config}/rofi-pass/rofi-pass.conf"
"$HOME/.rofi-pass.conf"
"/etc/rofi-pass.conf"
)
# return the first config file with a valid path
for config in "${locations[@]}"; do
if [[ -n "$config" && -f "$config" ]]; then
# see if the config has been given a value
local val
val="$(_parse_config "$config" "$1")"
break
fi
done
# if there was a config file but no value
# or there was no config file at all
if [ "$val" = "__UNDEFINED__" ] || [ -z "$val" ]; then
val="$2"
fi
printf -- "%s" "$val"
}
set_defaults() {
# The location of the rofi-pass config file
# RP_CONFIGURATION_FILE="~/.config/rofi-pass/rofi-pass.conf"
# set options, leaving already set environment variables intact
# try to read any settings from config files
KEY_AUTOFILL="${RP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}"
KEY_ENTRY_OPEN="${RP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}"
KEY_FILL_USER="${RP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}"
KEY_CLIP_USER="${RP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}"
KEY_FILL_PASS="${RP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}"
KEY_CLIP_PASS="${RP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}"
KEY_ENTRYMENU_FILL="${RP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}"
KEY_ENTRYMENU_CLIP="${RP_KEY_ENTRYMENU_CLIP:-$(get_config KEY_ENTRYMENU_CLIP Alt+Return)}"
KEY_ENTRYMENU_SHOWFIELD="${KEY_ENTRYMENU_SHOWFIELD:-$(get_config KEY_ENTRYMENU_SHOWFIELD Alt+s)}"
KEY_ENTRYMENU_QUIT="${RP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}"
AUTOFILL_BACKEND="${RP_AUTOFILL_BACKEND:-$(get_config AUTOFILL_BACKEND xdotool)}"
AUTOFILL_CHAIN="${RP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}"
AUTOFILL_DELAY="${RP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}"
PASS_USERNAME_FIELD="${RP_PASS_USERNAME_FIELD:-$(get_config PASS_USERNAME_FIELD 'username user login')}"
}
# exit on escape pressed
# rofi returns exit code 1 on esc
exit_check() {
[ "$1" -eq 1 ] && exit
}
# simply return a list of all passwords in pass store
# TODO only show website names (+ folder names), and account names for multiple accounts on one site
list_passwords() {
shopt -s nullglob globstar
prefix=${PASSWORD_STORE_DIR:-~/.password-store}
password_files=("$prefix"/**/*.gpg)
password_files=("${password_files[@]#"$prefix"/}")
password_files=("${password_files[@]%.gpg}")
printf '%s\n' "${password_files[@]}"
}
# return password for argument passed
show_password() {
pass show "$1" | head -n1
}
# send password to clipboard
clip_password() {
pass show -c "$1"
}
# attempt to return the field specified
# attempts all (space separated) fields until the
# first one successfully returned
_p_get_field() {
local gp_entry="$1"
local gp_field="$2"
local clip="$3"
# return on first successfully returned key
for key in $gp_field; do
local value
value=$(_p_get_key_value "$gp_entry" "$key")
# found entry
if [ -n "$value" ]; then
if [ -n "$clip" ]; then
# copies to clipboard, removes any trailing newlines,
# and only keeps it in for 1 paste (1 loop to read in script, 1 to output)
echo "$value" | xclip -i -selection 'clipboard' -loops 2 -rmlastnl && break
else
echo "$value" && break
fi
fi
done
}
# returns the corresponding value for the key passed in
# arguments:
# $1: pass (file) entry to search through
# $2: string name of the containting key
_p_get_key_value() {
local value
value=$(list_fields "$1" | grep "$2")
# get everything after first colon, remove whitespace
echo "$value" | cut -d':' -f2- | tr -d '[:blank:]'
}
# return username for argument passed
show_username() {
_p_get_field "$1" "${PASS_USERNAME_FIELD}"
}
clip_username() {
_p_get_field "$1" "${PASS_USERNAME_FIELD}" "-c"
}
show_field() {
_p_get_field "$1" "$2"
}
clip_field() {
_p_get_field "$1" "$2" "-c"
}
list_fields() {
pass show "$1" | tail -n+2
}
# invoke the dotool to type inputs
_type() {
local tool="${AUTOFILL_BACKEND}"
local toolmode="$1"
local key="$2"
"$tool" "$toolmode" --delay "${AUTOFILL_DELAY}" "$key"
}
# automatically fill out fields
# transform special chain entries into valid dotool commands
autofill() {
local selected="${1}"
local autoentry_chain="${2}"
for part in $autoentry_chain; do
case "$part" in
":tab") _type key Tab ;;
":return") _type key Return ;;
":space") _type key space ;;
"username") _type type "$(show_username "$selected")" ;;
"password") _type type "$(show_password "$selected")" ;;
":direct") _type type "$selected" ;;
*) printf '%s' "$selected" ;;
esac
done
}
# opens a menu for the specified pass entry, containing its individual fields
entrymenu() {
local entry="$1"
local deobfuscate="$2"
local k_entrymenu_fill="${KEY_ENTRYMENU_FILL}"
local k_entrymenu_clip="${KEY_ENTRYMENU_CLIP}"
local k_entrymenu_showfield="${KEY_ENTRYMENU_SHOWFIELD}"
local k_entrymenu_quit="${KEY_ENTRYMENU_QUIT}"
local pass
if [ "$deobfuscate" = "true" ]; then
pass="$(show_password "$entry")"
else
pass="(hidden)"
fi
local field
field=$(
printf "password: %s\n%s" "$pass" "$(list_fields "$entry")" |
_rofi \
-kb-accept-entry "" \
-kb-custom-1 "$k_entrymenu_fill" \
-kb-custom-2 "$k_entrymenu_clip" \
-kb-custom-3 "$k_entrymenu_quit" \
-kb-custom-4 "$k_entrymenu_showfield" \
-mesg "$k_entrymenu_quit ᐊ | $k_entrymenu_fill: fill selection | $k_entrymenu_clip: clip selection | $k_entrymenu_showfield: reveal password"
)
exit_value=$?
exit_check "$exit_value"
# get field name
field=${field%%:*}
case "$exit_value" in
"10")
if [ "$field" = "password" ]; then
autofill "$entry" "password"
else
autofill "$(show_field "$entry" "$field")" ":direct"
fi
exit 0
;;
"11")
if [ "$field" = "password" ]; then
clip_password "$entry"
else
clip_field "$entry" "$field"
fi
exit 0
;;
"12")
main
;;
"13")
local toggle
if [ "$deobfuscate" = "true" ]; then
toggle=false
else
toggle=true
fi
entrymenu "$entry" "$toggle"
;;
esac
}
main() {
local autoentry_chain="${AUTOFILL_CHAIN}"
local k_autofill="${KEY_AUTOFILL}"
local k_fill_user="${KEY_FILL_USER}"
local k_clip_user="${KEY_CLIP_USER}"
local k_fill_pass="${KEY_FILL_PASS}"
local k_clip_pass="${KEY_CLIP_PASS}"
local k_submenu="${KEY_ENTRY_OPEN}"
entry="$(
list_passwords |
_rofi -kb-accept-entry "" \
-kb-custom-1 "$k_autofill" \
-kb-custom-2 "$k_clip_user" \
-kb-custom-3 "$k_clip_pass" \
-kb-custom-4 "$k_fill_user" \
-kb-custom-5 "$k_fill_pass" \
-kb-custom-6 "$k_submenu" \
-mesg "| $k_autofill: fill credentials | $k_submenu: open entry | $k_fill_user: fill username | $k_fill_pass: fill password | $k_clip_user: clip username | $k_clip_pass: clip password |"
)"
exit_value=$?
exit_check "$exit_value"
case "$exit_value" in
"10")
autofill "$entry" "$autoentry_chain"
exit 0
;;
"11")
clip_username "$entry"
exit 0
;;
"12")
clip_password "$entry"
exit
;;
"13")
autofill "$entry" "username"
exit
;;
"14")
autofill "$entry" "password"
exit
;;
"15")
entrymenu "$entry"
exit
;;
esac
}
set_defaults
main