[gopass] Add rofi-gopass menu for quick secret entry

Allows auto-filling and copying secrets to the clipboard. Works on the secrets themselves and any fields contained within the secrets. Key bindings and options can be configured through a file or the environment.
This commit is contained in:
Marty Oehme 2020-05-16 15:19:08 +00:00
parent 2c11d14cbd
commit 3f958a65d6
6 changed files with 317 additions and 266 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -1,20 +1,2 @@
root: root:
askformore: false path: gpgcli-gitcli-fs+file://~/.local/share/gopass/passwords
autoclip: true
autoprint: false
autoimport: true
autosync: false
check_recipient_hash: false
cliptimeout: 45
concurrency: 1
editrecipients: false
nocolor: false
noconfirm: true
nopager: false
notifications: true
path: gpgcli-gitcli-fs+file:///home/marty/.local/share/gopass/passwords
recipient_hash:
.gpg-id: 353145383639394133414439344537334641363830434541313341433036443341444345413339410a423038433639313143453343313539463643373831324546334641423133324534463946343433390aa69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26
safecontent: false
usesymbols: false
mounts: {}

261
gopass/.local/bin/rofi-gopass Executable file
View file

@ -0,0 +1,261 @@
#!/usr/bin/env bash
#
# Original inspiration from https://github.com/carnager/rofi-pass/
# 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
exist rofi critical "rofi-gopass" || exit 0
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=(
"$RGP_CONFIGURATION_FILE"
"${XDG_CONFIG_HOME:-$HOME/.config}/rofi-gopass/rofi-gopass.conf"
"$HOME/.rofi-gopass.conf"
"/etc/rofi-gopass.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-gopass config file
# ROFI_PASS_CONFIGURATION_FILE="~/.config/rofi-gopass"
# set options, leaving already set environment variables intact
# try to read any settings from config files
KEY_AUTOFILL="${RGP_KEY_AUTOFILL:-$(get_config KEY_AUTOFILL Return)}"
KEY_ENTRY_OPEN="${RGP_KEY_ENTRY_OPEN:-$(get_config KEY_ENTRY_OPEN Alt+Return)}"
KEY_FILL_USER="${RGP_KEY_FILL_USER:-$(get_config KEY_FILL_USER Alt+u)}"
KEY_CLIP_USER="${RGP_KEY_CLIP_USER:-$(get_config KEY_CLIP_USER Ctrl+Alt+u)}"
KEY_FILL_PASS="${RGP_KEY_FILL_PASS:-$(get_config KEY_FILL_PASS Alt+p)}"
KEY_CLIP_PASS="${RGP_KEY_CLIP_PASS:-$(get_config KEY_CLIP_PASS Ctrl+Alt+p)}"
KEY_ENTRYMENU_FILL="${RGP_KEY_ENTRYMENU_FILL:-$(get_config KEY_ENTRYMENU_FILL Return)}"
KEY_ENTRYMENU_CLIP="${RGP_KEY_ENTRYMENU_CLIP:-$(get_config KEY_ENTRYMENU_CLIP Alt+Return)}"
KEY_ENTRYMENU_QUIT="${RGP_KEY_ENTRYMENU_QUIT:-$(get_config KEY_ENTRYMENU_QUIT Alt+BackSpace)}"
AUTOFILL_BACKEND="${RGP_BACKEND:-$(get_config AUTOFILL_BACKEND xdotool)}"
AUTOFILL_CHAIN="${RGP_AUTOENTRY_CHAIN:-$(get_config AUTOFILL_CHAIN 'username :tab password')}"
AUTOFILL_DELAY="${RGP_AUTOENTRY_DELAY:-$(get_config AUTOFILL_DELAY 30)}"
GOPASS_USERNAME_FIELD="${RGP_GOPASS_USERNAME_FIELD:-$(get_config GOPASS_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 gopass store
# TODO choose password store
# TODO only show website names (+ folder names), and account names for multiple accounts on one site
list_passwords() {
gopass list --flat
}
# return password for argument passed
show_password() {
gopass show -f --password "$1"
}
# send password to clipboard
clip_password() {
gopass show -c "$1"
}
# attempt to return the field specified
# attempts all (space separated) fields until the
# first one successfully returned
_gp_get_field() {
local gp_entry="$1"
local gp_field="$2"
local clip="$3"
for key in $gp_field; do
# return on first successfully returned key
if [ -n "$clip" ]; then
gopass show -c "$gp_entry" "$key" && break
else
gopass show -f "$gp_entry" "$key" && break
fi
done
}
# return username for argument passed
show_username() {
_gp_get_field "$1" "${GOPASS_USERNAME_FIELD}"
}
clip_username() {
_gp_get_field "$1" "${GOPASS_USERNAME_FIELD}" "-c"
}
show_field() {
_gp_get_field "$1" "$2"
}
clip_field() {
_gp_get_field "$1" "$2" "-c"
}
list_fields() {
gopass show -f "$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 gopass entry, containing its individual fields
entrymenu() {
local entry="$1"
local k_entrymenu_fill="${KEY_ENTRYMENU_FILL}"
local k_entrymenu_clip="${KEY_ENTRYMENU_CLIP}"
local k_entrymenu_quit="${KEY_ENTRYMENU_QUIT}"
local pass_obfuscation="(hidden)"
local field
field=$(
printf "password: %s\n%s" "$pass_obfuscation" "$(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" \
-mesg " ᐊ $k_entrymenu_quit ᐊ | $k_entrymenu_fill: fill selection | $k_entrymenu_clip: clip selection |"
)
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
;;
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

54
gopass/README.md Normal file
View file

@ -0,0 +1,54 @@
# gopass
[gopass](https://www.gopass.pw/) - command-line password manager, [pass](https://www.passwordstore.org) compatible, git by default
The gopass module tries to make interacting with the basic necessities of gopass painless and quick -- it is focused on accessing secrets, and quickly auto-filling forms or copying secrets to the clipboard. It relies on the default gopass auto-deleting clipping, so your secrets are automatically removed after a short while.
The configuration of gopass is relatively standard, it tries to follow XDG_specifications, putting the default pass store into `~/.local/share/gopass/passwords`.
## rofi-gopass
To make accessing secrets easy, it uses the `rofi-gopass` script, which creates a small rofi menu displaying all your secrets (names only), and from which you have quick access to copy, fill, or open the individual entries. An example of the menu in action:
![rofi-gopass demonstration](.assets/gopass/rofi-menu.gif)
There are several keybindings available, to either fill or copy to clipboard the username, password, or open the full view to an individual entry. For every send to clipboard action, the clipboard will be automatically cleared after the time specified in your gopass settings. For auto-filling to work correctly, the username field will (by default) have to be highlighted.
| keybinding | function | setting name |
| -------- | -------- | ---------- |
| Return | Auto-fill username & password | KEY_AUTOFILL |
| Alt+Return | Open individual entry | KEY_OPEN_ENTRY |
| Alt+u | Auto-fill username | KEY_FILL_USER |
| Alt+p | Auto-fill password | KEY_FILL_PASS |
| Ctrl+Alt+u | Send username to clipboard | KEY_CLIP_USER |
| Ctrl+Alt+p | Send password to clipboard | KEY_CLIP_PASS |
| Return | Auto-fill selected field (from opened entry) | KEY_ENTRYMENU_FILL |
| Alt+Return | Send selected field to clipboard (from opened entry) | KEY_ENTRYMENU_CLIP |
| Alt+BackSpace | Close individual entry, return to overview | KEY_ENTRYMENU_QUIT |
These keys, as well as the additional configuration can be changed by setting the corresponding environment variable, through a configuration file, or at the top of the script file itself. The script tries to follow xdg-specification, meaning it looks for a configuration file in the following directories (in descending order):
* `XDG_CONFIG_HOME/rofi-gopass/rofi-gopass.conf`
* `~/.config/rofi-gopass/rofi-gopass.conf`
* `~/.rofi-gopass.conf`
* `/etc/rofi-gopass.conf`
or, alternatively, a custom directory if the `RGP_CONFIGURATION_FILE` variable points to a configuration file.
To use environment variables to configure any of these options or keys, prefix them with `RGP_`, so that e.g. `KEY_AUTOFILL` becomes `RGP_KEY_AUTOFILL`. Environment variables take precedence over configuration file settings.
Additional configuration options:
* `AUTOFILL_BACKEND`
:sets the auto-filling tool used, only tested with `xdotool` currently.
* `AUTOFILL_CHAIN`
:sets the chain of keys that should be sent to auto-fill an entry. Can use the following special fields: `:tab`, `:space`, `:return`, `username`, `password`.
The default chain is `username :tab password`, which will enter the username, simulate the tab-key to switch from the username to the password field, and enter the password. This can be changed to suit your needs. To, for example, log in fully automatically at the end of the sequence, change it to `username :tab password :return`, and there will be no further user input for the login required.
* `AUTOFILL_DELAY`
:sets the time for xdotool to wait in-between simulated actions, if some letters appear missing or the fields are not switched between quickly enough, it can usually be fixed by increasing this delay (though typing will also take longer)
* `GOPASS_USERNAME_FIELD`
:sets the name of the field in gopass secrets which contain the username. Usually, the default setting should be fine (it will look for `user`, then `username`, then `login`) but custom field names can be supplied. If multiple field names are given, it will use the first supplied field name a secret contains.

View file

@ -1,9 +1,3 @@
if mp.get_script_directory == nil then if mp.get_script_directory == nil then
local scriptpath = debug.getinfo(1).short_src:match('.*/') dofile(mp.find_config_file("scripts/sponsorblock/main.lua"))
local originalpath = package.path
package.path = scriptpath .. "?.lua"
require 'sponsorblock/main'
package.path = originalpath
end end

View file

@ -1,240 +0,0 @@
#!/bin/bash
# Code belongs to https://github.com/carnager/rofi-pass/
# Copyright (C) 2019 carnager
# rofi wrapper. Add custom settings here.
_rofi() {
rofi -dmenu -no-auto-select -i "$@" -theme /themes/dmenu
}
# default settings
backend=xdotool
dotool_delay=20
daemon_wait=2
autotype_delay=2
key_autotype="Return"
key_usertype="Alt+2"
key_passtype="Alt+3"
key_actions="Alt+a"
key_clipboard="Alt+1"
key_fieldtype="Return"
# read config file
get_config_file() {
configs=("$ROFI_PASS_CONFIG"
"$HOME/.config/rofi-pass/rofi-gopass.conf"
"/etc/rofi-gopass.conf")
# return the first config file with a valid path
for config in "${configs[@]}"; do
# '! -z' is needed in case ROFI_PASS_CONFIG is not set
if [[ ! -z "${config}" && -f "${config}" ]]; then
printf "%s" "$config"
return
fi
done
}
# Make sure ESC will always end the programm.
# Call this function with "exit_check $?" after each rofi call.
exit_check() {
exit_value=$1
if [[ "${exit_value}" == "1" ]]; then
exit
fi
}
clipboard() {
local entry
local key
local value
entry="${1}"
key="${2}"
value="$(gopass show "${entry}" "${key}")"
printf '%s' "${value}" | xclip -sel clip
notify-send "rofi-gopass" "Copied ${key} to clipboard\nClearing in 45 seconds."
(
sleep 45
printf '%s' "" | xclip
printf '%s' "" | xclip -selection clipboard | notify-send "rofi-gopass" "Clipboard cleared"
) &
exit
}
_ydotoold() {
if ! pgrep -x "ydotoold" >/dev/null; then
# ydotoold blocks the terminal, so we need to background it.
# Sadly this way we never know when the process finished starting up.
# Until ydotoold receives proper daemonizing we add a sleep value here.
ydotoold &
sleep "${daemon_wait}"
fi
}
_dotool() {
local mode
local key
mode="${1}"
key="${2:-null}"
case "${mode}" in
"type")
case "${backend}" in
"xdotool") xdotool type --delay "${dotool_delay}" --file - ;;
"ydotool")
_ydotoold
ydotool type --delay "${dotool_delay}" --file -
;;
esac
;;
"key")
case "${backend}" in
"xdotool") xdotool key "${key}" ;;
"ydotool")
_ydotoold
ydotool key "${key}"
;;
esac
;;
esac
}
list_passwords() {
gopass list --flat
}
autopass() {
local entry
local autotype
entry="${1}"
autotype="$(gopass show "${entry}" autotype)"
autotype="${autotype:-username :tab pass}"
for word in ${autotype}; do
case "$word" in
":tab") _dotool key Tab ;;
":space") _dotool key " " ;;
":delay") sleep "${autotype_delay}" ;;
":enter") _dotool key enter ;;
"pass") printf '%s' "$(gopass show --password "${entry}")" | _dotool type ;;
*) printf '%s' "$(gopass show "${entry}" "${word}")" | _dotool type ;;
esac
done
}
list_keys() {
# gopass has no option to only list keys, so we need to build the list ourselves.
local entry
local keys
entry="${1}"
keys="$(gopass show "${entry}")"
printf '%s\n' "${keys}" | while read -r line; do
if [[ "${line}" == *": "* ]]; then
printf '%s\n' "${line%: *}"
fi
done
}
edit_key() {
local entry
local keys
entry="${1}"
keys="$(list_keys "${entry}")"
key_name=$(printf '%s\n' "${keys}" | _rofi -mesg "Enter new key or chose existing one")
exit_check $?
value_name=$(printf '%s' "" | _rofi -mesg "Enter Value for key \"${key_name}\"")
exit_check $?
if [[ -z "${key_name}" ]]; then
printf '%s' "${value_name}" | gopass insert -a "${entry}" "${key_name}"
else
printf '%s' "${value_name}" | gopass insert "${entry}" "${key_name}"
fi
}
# For dangerous operations call this function first. You can provide a message as argument.
# Example: confirm "Are you sure you want to delete entry?"
confirm() {
local message
message="${1}"
confirm_content=(
"Yes"
"No")
confirm_menu=$(printf '%s\n' "${confirm_content[@]}" | _rofi -mesg "${message}")
exit_check $?
case "${confirm_menu}" in
"Yes") : ;;
"No") exit ;;
esac
}
custom_type() {
local entry
local keys
entry="${1}"
keys="$(list_keys "${entry}")"
key_name=$(printf '%s\n' "${keys}" | _rofi -kb-accept-entry "" -no-custom -kb-custom-1 "${key_clipboard}" -kb-custom-2 "${key_fieldtype}" -mesg "${key_clipboard}: Copy to Clipboard | ${key_fieldtype}: Type Field")
local exit_value=$?
exit_check "${exit_value}"
case "${exit_value}" in
"10") clipboard "${entry}" "${key_name}" ;;
"11")
printf '%s' "$(gopass show "${entry}" "${key_name}")" | _dotool type
exit
;;
esac
}
do_menu() {
local entry
entry="${1}"
action_menu_content=(
"< Go Back"
"---"
"Show Fields"
"Add/Edit Keys"
"Generate New Password"
"Delete Entry"
)
action_menu="$(printf '%s\n' "${action_menu_content[@]}" | _rofi -no-custom -mesg "Selected Entry: ${entry}" -p '> ')"
exit_value=$?
exit_check "${exit_value}"
case "${action_menu}" in
"< Go Back") main ;;
"Show Fields") custom_type "${entry}" ;;
"Add/Edit Keys") edit_key "${entry}" ;;
"Delete Entry")
confirm "Delete ${entry}?"
gopass rm -f "${entry}"
;;
"Generate New Password")
confirm "Generate a new password for ${entry}?"
gopass generate -f "${entry}"
;;
esac
}
main() {
entry="$(list_passwords | _rofi -kb-accept-entry "" -kb-custom-1 "${key_autotype}" -kb-custom-2 "${key_usertype}" -kb-custom-3 "${key_passtype}" -kb-custom-4 "${key_actions}" -mesg "${key_autotype}: Autotype | ${key_usertype}: Type User | ${key_passtype}: Type Pass | ${key_actions}: More Actions")"
exit_value=$?
exit_check "${exit_value}"
case "${exit_value}" in
"10")
autopass "${entry}"
exit
;;
"11")
printf '%s' "$(gopass show "${entry}" username)" | _dotool type
exit
;;
"12")
printf '%s' "$(gopass show --password "${entry}")" | _dotool type
exit
;;
esac
do_menu "${entry}"
}
main