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

Reply via email to