Presented in a "slow refactor" series, and including extra tests, these patches support the following workflow to be able to work more ergonomically with lines and fields of password files. Basically, the first part is set up to split out "what are you trying to pull out of gpg?" and "how are you trying to display that to the user?". Once that split's been made, then it's a simple matter of adding --line, and --field support for "filtering", and then there's a few improvements made to allow --line or --field to be simply printed, copied to clipboard, etc. The QR-Code patch has a dependency on the previous "YES_TTY" patch which I submitted previously. Hopefully it's clear about the ergonomics around this. The specific use case I was targeting was multi-value credentials (ie: S3 username, password, access-key, bucket-id, etc) which I wanted to be able to pull out the appropriate fields in a scriptable fashion. In order to do that, it was easier to fix the jumble of clipboard/qrcode/etc. before trying to add a new feature to it. `--clip` still maintains the ability to specify a line number for convenience and backwards compatibility, but the addition of `--line` is critical to segregate out `--clip` and `--qrcode` as two distinct output types. That is to say: `--clip=2` and `--line=2 --clip` are equivalent, but `--line=2 --qrcode` and `--field=foo --clip` seem like incredibly important use cases (and are supported by this patch series).
$ printf "hello\ntarget: world\n" | ./src/password-store.sh insert -m test Enter contents of test and press Ctrl+D when finished: [master fff1234] Add given password for test to store. 1 file changed, 0 insertions(+), 0 deletions(-) rewrite test.gpg (100%) $ ./src/password-store.sh show test hello target: world $ ./src/password-store.sh show test --line=2 target: world $ ./src/password-store.sh show test --field=target world$ ./src/password-store.sh show test --field=target --clip && echo $( xclip -selection c -o ) world$ ./src/password-store.sh show test --field=target --qrcode | cat██████████████████████████████████████████████████████████ ████ ▄▄▄▄▄ █▀ █ ▄█ ▄▄▄▄▄ ████ ████ █ █ █▄ █▀▄█ █ █ ████ ████ █▄▄▄█ █ ██▀ █ █▄▄▄█ ████ ████▄▄▄▄▄▄▄█ ▀ ▀ █▄▄▄▄▄▄▄████ ████▄▄▀▄▀▀▄ █▄ ███ ▄▄█ ▀████ ████ █ ▀▀▄▀▀▀█▀▄▄███ ██▄████ ███████▄█▄▄▄ █▀▀▄█▄▀ █ ████ ████ ▄▄▄▄▄ █▄▄ ▀▀ █ ▀▄█▄█████ ████ █ █ █▀█▀ ██▄ ▄▀▀▀ ████ ████ █▄▄▄█ █▀█▄█▄▀▀▀ █▄█▄████ ████▄▄▄▄▄▄▄█▄▄▄▄▄██▄▄▄█▄█████ █████████████████████████████ █████████████████████████████
From 6a958febd32f75dfd923c8b77248afb26c7ea423 Mon Sep 17 00:00:00 2001 From: Robert Ames <[email protected]> Date: Mon, 22 Jul 2019 15:48:04 -0700 Subject: [PATCH 1/4] Lift password extraction / gpg usage out of two code paths Consistently extract the base64 encoded password text before further processing (this makes it easier and more consistent to operate on extracted text from gpg). Tests still passing, no functionality changes, just a refactor. --- src/password-store.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index 66fe4f1..d504a1e 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -381,12 +381,12 @@ cmd_show() { local passfile="$PREFIX/$path.gpg" check_sneaky_paths "$path" if [[ -f $passfile ]]; then + pass_b64="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $? if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then - pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $? - echo "$pass" | $BASE64 -d + echo "$pass_b64" | $BASE64 -d else [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." - pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n +${selected_line} | head -n 1)" || exit $? + pass="$( echo "$pass_b64" | $BASE64 -d | sed -n ${selected_line}p )" || exit $? [[ -n $pass ]] || die "There is no password to put on the clipboard at line ${selected_line}." if [[ $clip -eq 1 ]]; then clip "$pass" "$path" -- 2.17.1
From fcb8bac854a0c5fd51eeeaf94c8f955dcca40d5f Mon Sep 17 00:00:00 2001 From: Robert Ames <[email protected]> Date: Mon, 22 Jul 2019 16:13:33 -0700 Subject: [PATCH 2/4] Push down default printing behavior to a password output block By splitting password filtering from password output styles, it makes it more clear what is the behaviour of the password output (and enables more types of filtering and more types of output). Tests still passing (although --qrcode and --clip do not have tests as they interact with windowing system). --- src/password-store.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index d504a1e..cc2c14f 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -382,17 +382,24 @@ cmd_show() { check_sneaky_paths "$path" if [[ -f $passfile ]]; then pass_b64="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $? - if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then - echo "$pass_b64" | $BASE64 -d - else + pass=$( echo "$pass_b64" | $BASE64 -d ) # default to full contents of password file + # password file filtering styles + if [[ $clip -eq 1 ]]; then [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." pass="$( echo "$pass_b64" | $BASE64 -d | sed -n ${selected_line}p )" || exit $? - [[ -n $pass ]] || die "There is no password to put on the clipboard at line ${selected_line}." + [[ -n $pass ]] || die "There is no password to show on line '${selected_line}'." + fi + # password output styles + if [[ -n $pass ]]; then if [[ $clip -eq 1 ]]; then clip "$pass" "$path" elif [[ $qrcode -eq 1 ]]; then qrcode "$pass" "$path" + else + echo "$pass" fi + else + [[ -n $pass ]] || die "There is no password stored." fi elif [[ -d $PREFIX/$path ]]; then if [[ -z $path ]]; then -- 2.17.1
From 67aedb3bfe08e96ae44be112e9096b6c89ffbed2 Mon Sep 17 00:00:00 2001 From: Robert Ames <[email protected]> Date: Mon, 22 Jul 2019 16:38:52 -0700 Subject: [PATCH 3/4] Add robustness for --qrcode generation, including pipeline cases --qrcode defaults to opening a window when a GUI and viewer is detected. This patch adds robustness for falling back to text/terminal/UTF-8 display of the QR-Code when an appropriate viewer is not found, and additionally, defaults to text/terminal/UTF-8 display when in a pipeline situation (which allows basic test cases for QR-Code generation, one of which is implemented / included). --- src/password-store.sh | 16 +++++++++++++++- tests/t0020-show-tests.sh | 11 +++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/password-store.sh b/src/password-store.sh index cc2c14f..48c1c91 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -196,7 +196,16 @@ clip() { } qrcode() { - if [[ -n $DISPLAY || -n $WAYLAND_DISPLAY ]]; then + if [[ -z $YES_TTY ]]; then + # in a pipeline (NO_TTY), default to utf8 text output + # (this is slightly unexpected, as an argument could be + # made that `$pass show foo -q > code.png` should work, + # but instead it works as `$pass show foo -q > code.txt`, + # which has a semblance of a chance of being testable + # and is still compatible with `xdg-open code.txt`) + echo -n "$1" | qrencode -t utf8 + return + elif [[ -n $DISPLAY || -n $WAYLAND_DISPLAY ]]; then if type feh >/dev/null 2>&1; then echo -n "$1" | qrencode --size 10 -o - | feh -x --title "pass: $2" -g +200+200 - return @@ -206,8 +215,13 @@ qrcode() { elif type display >/dev/null 2>&1; then echo -n "$1" | qrencode --size 10 -o - | display -title "pass: $2" -geometry +200+200 - return + else + # in a GUI context, but no suitable viewers found + echo -n "$1" | qrencode -t utf8 + return fi fi + # failsafe echo -n "$1" | qrencode -t utf8 } diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh index c3671b2..17bf0e0 100755 --- a/tests/t0020-show-tests.sh +++ b/tests/t0020-show-tests.sh @@ -27,4 +27,15 @@ test_expect_success 'Test "show" command with simple tree' ' "$PASS" rm tree/of/creds2 ' +# slightly hacky qr-code test, but assume 15 lines in qrencode output, +# and in absence of relying on a specific md5-command, assume that +# the particular encoded password has "71 spaces" which will give a +# rough check that the qr-code is correct / readable. +test_expect_success 'Test "show" command with qrcode in a pipeline' ' + "$PASS" insert -e cred3 <<<"blah" && + [[ $("$PASS" show cred3 --qrcode | wc -l) == "15" ]] && + [[ $("$PASS" show cred3 --qrcode | sed "s/[^ ]//g" | wc -c) == "71" ]] && + "$PASS" rm cred3 +' + test_done -- 2.17.1
From 3bbd8dd640d1f49cdaf5dbf9003e9715d01372f9 Mon Sep 17 00:00:00 2001 From: Robert Ames <[email protected]> Date: Mon, 22 Jul 2019 22:57:18 -0700 Subject: [PATCH 4/4] Add --field/-f and --line/-l support By convention, the author recommends storing passwords on the first line with subsequent lines either as free-form text, or generally using a "colon-suffix" scheme for field-names. This convention is reflected in at least the `pass` iOS app which detects lines of the form `field: value` and shows them with a header/value presentation. Building on some of the previous refactorings, segregate out `--line/-l` as a separate mechanism for simply printing the contents of a particular line number, and also supporting `--field/-f` to do a simple search for `field: ...` and output ONLY the value to the right of the field name. --- src/password-store.sh | 17 ++++++++++++----- tests/t0020-show-tests.sh | 24 +++++++++++++++++++++++- tests/t0100-insert-tests.sh | 8 ++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index 48c1c91..0ce36a5 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -296,7 +296,7 @@ cmd_usage() { List passwords. $PROGRAM find pass-names... List passwords that match pass-names. - $PROGRAM [show] [--clip[=line-number],-c[line-number]] pass-name + $PROGRAM [show] [--clip[=line-number],-c [line-number]] [--field=name,-f name] [--line=line-number,-lline-number] pass-name Show existing password and optionally put it on the clipboard. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. $PROGRAM grep [GREPOPTIONS] search-string @@ -378,17 +378,19 @@ cmd_init() { } cmd_show() { - local opts selected_line clip=0 qrcode=0 - opts="$($GETOPT -o q::c:: -l qrcode::,clip:: -n "$PROGRAM" -- "$@")" + local opts selected_line clip=0 qrcode=0 field=0 + opts="$($GETOPT -o q::c::f::l:: -l qrcode::,clip::,field::,line:: -n "$PROGRAM" -- "$@")" local err=$? eval set -- "$opts" while true; do case $1 in -q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;; -c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;; + -f|--field) field=1; selected_field="${2:-1}"; shift 2 ;; + -l|--line) line=1; selected_line="${2:-1}"; shift 2 ;; --) shift; break ;; esac done - [[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]" + [[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--line=number],[-lnumber] [--field=name],[-fname] [--qrcode[=line-number],-q[line-number]] [pass-name]" local pass local path="$1" @@ -398,11 +400,16 @@ cmd_show() { pass_b64="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $? pass=$( echo "$pass_b64" | $BASE64 -d ) # default to full contents of password file # password file filtering styles - if [[ $clip -eq 1 ]]; then + if [[ $clip -eq 1 || $line -eq 1 ]]; then [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location '$selected_line' is not a number." pass="$( echo "$pass_b64" | $BASE64 -d | sed -n ${selected_line}p )" || exit $? [[ -n $pass ]] || die "There is no password to show on line '${selected_line}'." fi + if [[ $field -eq 1 ]]; then + [[ $selected_field =~ ^[0-9a-z]+$ ]] || die "Clip field '$selected_field' not found." + pass="$( echo "$pass_b64" | $BASE64 -d | grep """^$selected_field: """ | sed """s/^$selected_field: //g""" )" || exit $? + [[ -n $pass ]] || die "There is no password to show for field '${selected_field}'." + fi # password output styles if [[ -n $pass ]]; then if [[ $clip -eq 1 ]]; then diff --git a/tests/t0020-show-tests.sh b/tests/t0020-show-tests.sh index 17bf0e0..8f96e6d 100755 --- a/tests/t0020-show-tests.sh +++ b/tests/t0020-show-tests.sh @@ -22,7 +22,7 @@ test_expect_success 'Test "show" of nonexistant password' ' test_expect_success 'Test "show" command with simple tree' ' "$PASS" insert -e tree/of/creds1 <<<"blah" && "$PASS" insert -e tree/of/creds2 <<<"blah" && - [[ $("$PASS" show | grep "tree.of" | wc -l | sed "s/ *//g") == "2" ]] && + [[ $("$PASS" show | grep "tree.of" | wc -l) == "2" ]] && "$PASS" rm tree/of/creds1 && "$PASS" rm tree/of/creds2 ' @@ -38,4 +38,26 @@ test_expect_success 'Test "show" command with qrcode in a pipeline' ' "$PASS" rm cred3 ' +test_expect_success 'Test "show" command advertises field support' ' + [[ $("$PASS" --help | grep "show.*field" | wc -l) == "1" ]] && + [[ $("$PASS" show --help 2> >( grep "Usage: .*--field" | wc -l ) ) == "1" ]] +' + +test_expect_success 'Test "show" command with field operator' ' + printf "hello\ntarget: world\n" | "$PASS" insert -m some_cred && + [[ $("$PASS" show --field=target some_cred) == "world" ]] && + "$PASS" rm some_cred +' + +test_expect_success 'Test "show" command advertises line support' ' + [[ $("$PASS" --help | grep "show.*line" | wc -l) == "1" ]] && + [[ $("$PASS" show --help 2> >( grep "Usage: .*--line" | wc -l ) ) == "1" ]] +' + +test_expect_success 'Test "show" command with line operator' ' + printf "hello\ntarget: world\n" | "$PASS" insert -m some_cred && + [[ $("$PASS" show --line=2 some_cred) == "target: world" ]] && + "$PASS" rm some_cred +' + test_done diff --git a/tests/t0100-insert-tests.sh b/tests/t0100-insert-tests.sh index d8101ab..b36f32f 100755 --- a/tests/t0100-insert-tests.sh +++ b/tests/t0100-insert-tests.sh @@ -10,4 +10,12 @@ test_expect_success 'Test "insert" command' ' [[ $("$PASS" show cred1) == "Hello world" ]] ' +test_expect_success 'Test "insert" multiline command' ' + "$PASS" init $KEY1 && + printf "Hello world" | "$PASS" insert -e cred3 && + printf "Hello\nworld\n" | "$PASS" insert -m cred4 && + [[ $("$PASS" show cred3 | wc -l | sed "s/ //g" ) == "1" ]] + [[ $("$PASS" show cred4 | wc -l | sed "s/ //g" ) == "2" ]] +' + test_done -- 2.17.1
_______________________________________________ Password-Store mailing list [email protected] https://lists.zx2c4.com/mailman/listinfo/password-store
