Add password_fill userscript
Add a configurable userscript that fills login forms (i.e. the fiels "Username" and "Password) of websites using a configurable backend where the actual passwords are stored. The only backend yet is using the password store "pass".
This commit is contained in:
parent
fd63b21261
commit
d73491b0c8
276
misc/userscripts/password_fill
Executable file
276
misc/userscripts/password_fill
Executable file
@ -0,0 +1,276 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
help() {
|
||||||
|
blink=$'\e[1;31m' reset=$'\e[0m'
|
||||||
|
cat <<EOF
|
||||||
|
This script can only be used as a userscript for qutebrowser
|
||||||
|
2015, Thorsten Wißmann <edu _at_ thorsten-wissmann _dot_ de>
|
||||||
|
In case of questions or suggestions, do not hesitate to send me an E-Mail or to
|
||||||
|
directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode.
|
||||||
|
|
||||||
|
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
|
||||||
|
WARNING: the passwords are stored in qutebrowser's
|
||||||
|
debug log reachable via the url qute:log
|
||||||
|
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
|
||||||
|
|
||||||
|
Usage: run as a userscript form qutebrowser, e.g.:
|
||||||
|
spawn --userscript ~/.config/qutebrowser/password_fill
|
||||||
|
|
||||||
|
Behaviour:
|
||||||
|
It will try to find a username/password entry in the configured backend
|
||||||
|
(currently only pass) for the current website and will load that pair of
|
||||||
|
username and password to any form on the current page that has some password
|
||||||
|
entry field.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
|
shopt -s nocasematch
|
||||||
|
|
||||||
|
if [ -z "$QUTE_FIFO" ] ; then
|
||||||
|
help
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
error() {
|
||||||
|
local msg="$*"
|
||||||
|
echo "message-error '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||||
|
}
|
||||||
|
msg() {
|
||||||
|
local msg="$*"
|
||||||
|
echo "message-info '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||||
|
}
|
||||||
|
die() {
|
||||||
|
error "$*"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ======================================================= #
|
||||||
|
# CONFIGURATION
|
||||||
|
# ======================================================= #
|
||||||
|
# The configuration file is per default located in
|
||||||
|
# ~/.config/qutebrowser/password_fill_rc and is a bash script that is loaded
|
||||||
|
# later in the present script. So basically you can replace all of the
|
||||||
|
# following definitions and make them fit your needs.
|
||||||
|
|
||||||
|
# The following simplifies a URL to the domain (e.g. "wiki.qutebrowser.org")
|
||||||
|
# which is later used to search the correct entries in the password backend. If
|
||||||
|
# you e.g. don't want the "www." to be removed or if you want to distinguish
|
||||||
|
# between different paths on the same domain.
|
||||||
|
|
||||||
|
simplify_url() {
|
||||||
|
simple_url="${1##*://}" # remove protocoll specification
|
||||||
|
simple_url="${simple_url%%\?*}" # remove GET parameters
|
||||||
|
simple_url="${simple_url%%/*}" # remove directory path
|
||||||
|
simple_url="${simple_url##www.}" # remove www. subdomain
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Backend implementations tell, how the actual password store is accessed.
|
||||||
|
# Right now, there is only one fully functional password backend, namely for
|
||||||
|
# the program "pass".
|
||||||
|
# A password backend consists of three actions:
|
||||||
|
# - init() initializes backend-specific things and does sanity checks.
|
||||||
|
# - query_entries() is called with a simplified url and is expected to fill
|
||||||
|
# the bash array $files with the names of matching password entries. There
|
||||||
|
# are no requirements how these names should look like.
|
||||||
|
# - open_entry() is called with some specific entry of the $files array and is
|
||||||
|
# expected to write the username of that entry to the $username variable and
|
||||||
|
# the corresponding password to $password
|
||||||
|
|
||||||
|
reset_backend() {
|
||||||
|
init() { true ; }
|
||||||
|
query_entries() { true ; }
|
||||||
|
open_entry() { true ; }
|
||||||
|
}
|
||||||
|
|
||||||
|
# choose_entry() is expected to choose one entry from the array $files and
|
||||||
|
# write it to the variable $file.
|
||||||
|
choose_entry() {
|
||||||
|
choose_entry_random
|
||||||
|
}
|
||||||
|
|
||||||
|
# The default implementation chooses a random entry from the array. So if there
|
||||||
|
# are multiple matching entries, multiple calls to this userscript will
|
||||||
|
# eventually pick the "correct" entry. I.e. if this userscript is bound to
|
||||||
|
# "zl", the user has to press "zl" until the correct username shows up in the
|
||||||
|
# login form.
|
||||||
|
choose_entry_random() {
|
||||||
|
local nr=${#files[@]}
|
||||||
|
file="${files[$((RANDOM % nr))]}"
|
||||||
|
# Warn user, that there might be other matching password entries
|
||||||
|
if [ "$nr" -gt 1 ] ; then
|
||||||
|
msg "Picked $file out of $nr entries: ${files[*]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# another implementation would be to ask the user via some menu (like rofi or
|
||||||
|
# dmenu or zenity or even qutebrowser completion in future?) which entry to
|
||||||
|
# pick
|
||||||
|
MENU_COMMAND=( head -n 1 )
|
||||||
|
choose_entry_menu() {
|
||||||
|
local nr=${#files[@]}
|
||||||
|
if [ "$nr" -eq 1 ] ; then
|
||||||
|
file="${files[0]}"
|
||||||
|
else
|
||||||
|
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_entry_rofi() {
|
||||||
|
MENU_COMMAND=( rofi -p "qutebrowser> " -dmenu
|
||||||
|
-mesg $'Pick a password entry for <b>'"${QUTE_URL//&/&}"'</b>' )
|
||||||
|
choose_entry_menu || true
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_entry_zenity() {
|
||||||
|
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
|
||||||
|
--text "Pick the password entry:"
|
||||||
|
--column "Name" )
|
||||||
|
choose_entry_menu || true
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_entry_zenity_radio() {
|
||||||
|
zenity_helper() {
|
||||||
|
awk '{ print $0 ; print $0 }' \
|
||||||
|
| zenity --list --radiolist \
|
||||||
|
--title "Qutebrowser password fill" \
|
||||||
|
--text "Pick the password entry:" \
|
||||||
|
--column " " --column "Name"
|
||||||
|
}
|
||||||
|
MENU_COMMAND=( zenity_helper )
|
||||||
|
choose_entry_menu || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# =======================================================
|
||||||
|
# backend: PASS
|
||||||
|
|
||||||
|
# configuration options:
|
||||||
|
match_filename=1 # whether allowing entry match by filepath
|
||||||
|
match_line=1 # whether allowing entry match by URL-Pattern in file
|
||||||
|
match_line_pattern='^url: .*' # applied using grep -iE
|
||||||
|
user_pattern='^(user|username): '
|
||||||
|
|
||||||
|
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
|
||||||
|
GPG="gpg"
|
||||||
|
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
|
||||||
|
which gpg2 &>/dev/null && GPG="gpg2"
|
||||||
|
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
|
||||||
|
|
||||||
|
pass_backend() {
|
||||||
|
init() {
|
||||||
|
PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
|
||||||
|
if ! [ -d "$PREFIX" ] ; then
|
||||||
|
die "Can not open password store dir »$PREFIX«"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
query_entries() {
|
||||||
|
local url="$1"
|
||||||
|
|
||||||
|
if ((match_line)) ; then
|
||||||
|
# add entries with matching URL-tag
|
||||||
|
while read -r -d "" passfile ; do
|
||||||
|
if $GPG "${GPG_OPTS}" -d "$passfile" \
|
||||||
|
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
||||||
|
then
|
||||||
|
passfile="${passfile#$PREFIX}"
|
||||||
|
passfile="${passfile#/}"
|
||||||
|
files+=( "${passfile%.gpg}" )
|
||||||
|
fi
|
||||||
|
done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
|
||||||
|
fi
|
||||||
|
if ((match_filename)) ; then
|
||||||
|
# add entries wth matching filepath
|
||||||
|
while read -r passfile ; do
|
||||||
|
passfile="${passfile#$PREFIX}"
|
||||||
|
passfile="${passfile#/}"
|
||||||
|
files+=( "${passfile%.gpg}" )
|
||||||
|
done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
open_entry() {
|
||||||
|
local path="$PREFIX/${1}.gpg"
|
||||||
|
password=""
|
||||||
|
local firstline=1
|
||||||
|
while read -r line ; do
|
||||||
|
if ((firstline)) ; then
|
||||||
|
password="$line"
|
||||||
|
firstline=0
|
||||||
|
else
|
||||||
|
if [[ $line =~ $user_pattern ]] ; then
|
||||||
|
# remove the matching prefix "user: " from the beginning of the line
|
||||||
|
username=${line#${BASH_REMATCH[0]}}
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# =======================================================
|
||||||
|
|
||||||
|
# load some sane default backend
|
||||||
|
reset_backend
|
||||||
|
pass_backend
|
||||||
|
# load configuration
|
||||||
|
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
||||||
|
PWFILL_CONFIG=${PFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
||||||
|
if [ -f "$CONFIG_FILE" ] ; then
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
init
|
||||||
|
|
||||||
|
simplify_url "$QUTE_URL"
|
||||||
|
query_entries "${simple_url}"
|
||||||
|
if [ 0 -eq "${#files[@]}" ] ; then
|
||||||
|
die "No entry found for »$simple_url«"
|
||||||
|
fi
|
||||||
|
choose_entry
|
||||||
|
if [ -z "$file" ] ; then
|
||||||
|
# choose_entry didn't want any of these entries
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
open_entry "$file"
|
||||||
|
#username="$(date)"
|
||||||
|
#password="XYZ"
|
||||||
|
|
||||||
|
[ -n "$username" ] || die "Username not set in entry $file"
|
||||||
|
[ -n "$password" ] || die "Password not set in entry $file"
|
||||||
|
|
||||||
|
js() {
|
||||||
|
cat <<EOF
|
||||||
|
function hasPasswordField(form) {
|
||||||
|
var inputs = form.getElementsByTagName("input");
|
||||||
|
for (var j = 0; j < inputs.length; j++) {
|
||||||
|
var input = inputs[j];
|
||||||
|
if (input.type == "password") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
function loadData2Form (form) {
|
||||||
|
var inputs = form.getElementsByTagName("input");
|
||||||
|
for (var j = 0; j < inputs.length; j++) {
|
||||||
|
var input = inputs[j];
|
||||||
|
if (input.type == "text" || input.type == "email") {
|
||||||
|
input.value = "$username";
|
||||||
|
}
|
||||||
|
if (input.type == "password") {
|
||||||
|
input.value = "$password";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var forms = document.getElementsByTagName("form");
|
||||||
|
for (i = 0; i < forms.length; i++) {
|
||||||
|
if (hasPasswordField(forms[i])) {
|
||||||
|
loadData2Form(forms[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
printjs() {
|
||||||
|
js | sed 's,//.*$,,' | tr '\n' ' '
|
||||||
|
}
|
||||||
|
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
Loading…
Reference in New Issue
Block a user