commit 483fc8febac23f8015aaaf8850d399403a5d1459
parent 8e82720524ef8bb1272491f11f0e88e0c16e35c9
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date: Sat, 9 Nov 2024 09:08:02 +0000
Reencrypt command
Diffstat:
5 files changed, 283 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
@@ -82,6 +82,8 @@ decrypt before `diff`.
- The new `random` command leverages password generation without touching
the password store.
+- The new `reencrypt` command re-encrypts secrets in-place.
+
## Manual
TODO
diff --git a/spec/pashage_extra_spec.sh b/spec/pashage_extra_spec.sh
@@ -739,6 +739,7 @@ Describe 'Integrated Command Functions'
The output should include ' prg gitconfig'
The output should include ' prg move '
The output should include ' prg random '
+ The output should include ' prg reencrypt '
End
End
@@ -1093,6 +1094,151 @@ Describe 'Integrated Command Functions'
End
End
+ Describe 'cmd_reencrypt'
+ usage_text() { %text
+ #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ...
+ }
+
+ It 'reencrypts a single file'
+ When call cmd_reencrypt stale
+ The status should be success
+ The error should be blank
+ The output should be blank
+ expected_file() { %text
+ #|ageRecipient:myself
+ #|age:0-password
+ }
+ The contents of file "${PREFIX}/stale.age" \
+ should equal "$(expected_file)"
+ expected_log() { %text
+ #|Re-encrypt stale
+ #|
+ #| stale.age | 1 -
+ #| 1 file changed, 1 deletion(-)
+ setup_log
+ }
+ The result of function check_git_log should be successful
+ End
+
+ It 'reencrypts a single file interactively'
+ Data 'y'
+ When call cmd_reencrypt -i stale
+ The status should be success
+ The error should be blank
+ The output should equal 'Re-encrypt stale? [y/n]'
+ expected_file() { %text
+ #|ageRecipient:myself
+ #|age:0-password
+ }
+ The contents of file "${PREFIX}/stale.age" \
+ should equal "$(expected_file)"
+ expected_log() { %text
+ #|Re-encrypt stale
+ #|
+ #| stale.age | 1 -
+ #| 1 file changed, 1 deletion(-)
+ setup_log
+ }
+ The result of function check_git_log should be successful
+ End
+
+ It 'does not reencrypt a single file when interactively refused'
+ Data 'n'
+ When call cmd_reencrypt --interactive stale
+ The status should be success
+ The error should be blank
+ The output should equal 'Re-encrypt stale? [y/n]'
+ expected_file() { %text
+ #|ageRecipient:master
+ #|ageRecipient:myself
+ #|age:0-password
+ }
+ The contents of file "${PREFIX}/stale.age" \
+ should equal "$(expected_file)"
+ The result of function check_git_log should be successful
+ End
+
+ It 'reencrypts a directory recursively'
+ When call cmd_reencrypt /
+ The status should be success
+ The error should be blank
+ The output should be blank
+ expected_file() { %text
+ #|ageRecipient:myself
+ #|age:0-password
+ }
+ The contents of file "${PREFIX}/stale.age" \
+ should equal "$(expected_file)"
+ expected_log() { %text
+ #|Re-encrypt /
+ #|
+ #| stale.age | 1 -
+ #| 1 file changed, 1 deletion(-)
+ setup_log
+ }
+ The result of function check_git_log should be successful
+ End
+
+ It 'reencrypts a directory recursively and interactively'
+ Data
+ #|n
+ #|y
+ #|n
+ End
+ When call cmd_reencrypt -i ''
+ The status should be success
+ The error should be blank
+ The output should equal 'Re-encrypt extra/subdir/file? [y/n]Re-encrypt stale? [y/n]Re-encrypt subdir/file? [y/n]'
+ expected_file() { %text
+ #|ageRecipient:myself
+ #|age:0-password
+ }
+ The contents of file "${PREFIX}/stale.age" \
+ should equal "$(expected_file)"
+ expected_log() { %text
+ #|Re-encrypt /
+ #|
+ #| stale.age | 1 -
+ #| 1 file changed, 1 deletion(-)
+ setup_log
+ }
+ The result of function check_git_log should be successful
+ End
+
+ It 'fails to reencrypt a file named like a flag without escape'
+ PROGRAM=prg
+ When run cmd_reencrypt -g
+ The status should equal 1
+ The error should equal "$(usage_text)"
+ The output should be blank
+ The result of function check_git_log should be successful
+ End
+
+ It 'fails to reencrypt a non-existent direcotry'
+ When run cmd_reencrypt -- -y/
+ The status should equal 1
+ The error should equal 'Error: -y/ is not in the password store.'
+ The output should be blank
+ The result of function check_git_log should be successful
+ End
+
+ It 'fails to reencrypt a non-existent file'
+ When run cmd_reencrypt -- -y
+ The status should equal 1
+ The error should equal 'Error: -y is not in the password store.'
+ The output should be blank
+ The result of function check_git_log should be successful
+ End
+
+ It 'rejects a path containing ..'
+ When run cmd_reencrypt fluff/../stale
+ The status should equal 1
+ The output should be blank
+ The error should include 'sneaky'
+ The result of function check_git_log should be successful
+ End
+ End
+
Describe 'cmd_usage'
It 'defaults to four-space indentation'
PROGRAM=prg
diff --git a/spec/usage_spec.sh b/spec/usage_spec.sh
@@ -121,6 +121,8 @@ Describe 'Command-Line Parsing'
}
do_reencrypt() {
mocklog do_reencrypt "$@"
+ %text:expand >&2
+ #|DECISION=${DECISION}
}
do_reencrypt_dir() {
mocklog do_reencrypt_dir "$@"
@@ -2052,6 +2054,89 @@ Describe 'Command-Line Parsing'
End
End
+ Describe 'cmd_reencrypt'
+ COMMAND=reencrypt
+ DECISION=default
+
+ It 're-encrypts multiple files and directories'
+ result() {
+ %text
+ #|$ check_sneaky_path file-1
+ #|$ check_sneaky_path dir/
+ #|$ check_sneaky_path sub/file-2
+ #|$ do_reencrypt file-1
+ #|DECISION=default
+ #|$ do_reencrypt dir/
+ #|DECISION=default
+ #|$ do_reencrypt sub/file-2
+ #|DECISION=default
+ }
+ When call cmd_reencrypt file-1 dir/ sub/file-2
+ The status should be success
+ The output should be blank
+ The error should equal "$(result)"
+ End
+
+ It 'interactively re-encrypts with a long option'
+ result() {
+ %text
+ #|$ check_sneaky_path arg
+ #|$ do_reencrypt arg
+ #|DECISION=interactive
+ }
+ When call cmd_reencrypt --interactive arg
+ The status should be success
+ The output should be blank
+ The error should equal "$(result)"
+ End
+
+ It 'interactively re-encrypts with a short option'
+ result() {
+ %text
+ #|$ check_sneaky_path arg
+ #|$ do_reencrypt arg
+ #|DECISION=interactive
+ }
+ When call cmd_reencrypt -i arg
+ The status should be success
+ The output should be blank
+ The error should equal "$(result)"
+ End
+
+ It 're-encrypts a file named like a flag'
+ result() {
+ %text
+ #|$ check_sneaky_path -s
+ #|$ do_reencrypt -s
+ #|DECISION=default
+ }
+ When call cmd_reencrypt -- -s
+ The status should be success
+ The output should be blank
+ The error should equal "$(result)"
+ End
+
+ usage_text() { %text
+ #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ...
+ }
+
+ It 'reports a bad option'
+ cat() { @cat; }
+ When run cmd_reencrypt -s arg
+ The status should equal 1
+ The output should be blank
+ The error should equal "$(usage_text)"
+ End
+
+ It 'reports a lack of argument'
+ cat() { @cat; }
+ When run cmd_reencrypt
+ The status should equal 1
+ The output should be blank
+ The error should equal "$(usage_text)"
+ End
+ End
+
Describe 'cmd_usage'
COMMAND=usage
CLIP_TIME='$CLIP_TIME'
@@ -2082,6 +2167,7 @@ Describe 'Command-Line Parsing'
The output should include 'prg [list]'
The output should include 'prg move'
The output should include 'prg random'
+ The output should include 'prg reencrypt'
The output should include 'prg [show]'
The output should include 'prg version'
End
diff --git a/src/pashage.sh b/src/pashage.sh
@@ -1495,6 +1495,42 @@ cmd_random() {
random_chars "${1:-${GENERATED_LENGTH}}" "${2:-${CHARACTER_SET}}"
}
+cmd_reencrypt() {
+ DECISION=default
+ OVERWRITE=yes
+ PARSE_ERROR=no
+
+ while [ $# -ge 1 ]; do
+ case "$1" in
+ -i|--interactive)
+ DECISION=interactive
+ shift ;;
+ --)
+ shift
+ break ;;
+ -*)
+ PARSE_ERROR=yes
+ break ;;
+ *)
+ break ;;
+ esac
+ done
+
+ if [ "${PARSE_ERROR}" = yes ] || [ $# -eq 0 ]; then
+ cmd_usage 'Usage: ' reencrypt >&2
+ exit 1
+ fi
+
+ unset PARSE_ERROR
+
+ check_sneaky_paths "$@"
+
+ for ARG in "$@"; do
+ do_reencrypt "${ARG}"
+ done
+ unset ARG
+}
+
# Outputs the whole usage text
# $1: indentation
# ... commands to document
@@ -1515,8 +1551,8 @@ cmd_usage(){
if [ $# -eq 0 ]; then
echo 'Usage:'
- set -- list show copy delete edit find generate \
- git gitconfig grep help init insert move random version
+ set -- list show copy delete edit find generate git gitconfig \
+ grep help init insert move random reencrypt version
VERBOSE=yes
else
VERBOSE=no
@@ -1674,6 +1710,15 @@ ${I} using the given character set (or ${CHARACTER_SET} if unspecified)
${I} without recording it in the password store.
EOF
;;
+ reencrypt)
+ cat <<EOF
+${F}${PROGRAM} reencrypt [--interactive,-i] pass-name|subfolder ...
+EOF
+ [ "${VERBOSE}" = yes ] && cat <<EOF
+${I} Re-encrypt in-place a secret or all the secrets in a subfolder,
+${I} optionally asking before each one.
+EOF
+ ;;
version)
cat <<EOF
${F}${PROGRAM} version
diff --git a/src/run.sh b/src/run.sh
@@ -119,6 +119,8 @@ case "${COMMAND}" in
ls) shift; cmd_list_or_show "$@" ;;
move|mv) shift; cmd_move "$@" ;;
random) shift; cmd_random "$@" ;;
+ re-encrypt) shift; cmd_reencrypt "$@" ;;
+ reencrypt) shift; cmd_reencrypt "$@" ;;
remove) shift; cmd_delete "$@" ;;
rm) shift; cmd_delete "$@" ;;
show) shift; cmd_list_or_show "$@" ;;