pashage

Yet Another Opinionated Re-engineering of the Unix Password Store
git clone https://git.instinctive.eu/pashage.git
Log | Files | Refs | README | LICENSE

commit b9ac582d07c6c85dca72b83693338b4b6313391d
parent 8e18b025e10d540468bcd3c06d2ca75169f6cb95
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Mon,  4 Nov 2024 23:41:00 +0000

Re-encryption decision in copy and move commands is controlled by flags
Diffstat:
MREADME.md | 3+++
Mspec/pashage_extra_spec.sh | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mspec/usage_spec.sh | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/pashage.sh | 30++++++++++++++++++++++++++----
4 files changed, 426 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md @@ -70,6 +70,9 @@ passwords. ### New Features and Extensions +- The commands `copy` and `move` have new flags to control re-encryption +(always, never, ask for each file). + - The new `gitconfig` command configures an existing store repository to decrypt before `diff`. diff --git a/spec/pashage_extra_spec.sh b/spec/pashage_extra_spec.sh @@ -247,23 +247,142 @@ Describe 'Integrated Command Functions' The result of function check_git_log should be successful End - It 'display copy usage with `c*` commands' + It 'does not re-encrypt by default when recipients do not change' + When call cmd_move stale renamed + The status should be success + The error should be blank + The output should be blank + expected_log() { %text + #|Move stale.age to renamed.age + #| + #| stale.age => renamed.age | 0 + #| 1 file changed, 0 insertions(+), 0 deletions(-) + setup_log + } + The result of function check_git_log should be successful + End + + It 're-encrypts by default when recipients change' + When call cmd_move stale shared + The status should be success + The error should be blank + The output should be blank + expected_file() { %text + #|ageRecipient:myself + #|ageRecipient:friend + #|age:0-password + } + The contents of file "${PREFIX}/shared/stale.age" should \ + equal "$(expected_file)" + expected_log() { %text + #|Move stale.age to shared/stale.age + #| + #| stale.age => shared/stale.age | 2 +- + #| 1 file changed, 1 insertion(+), 1 deletion(-) + setup_log + } + The result of function check_git_log should be successful + End + + It 'always re-encrypts when forced' + When call cmd_move --reencrypt stale renamed + The status should be success + The error should be blank + The output should be blank + expected_log() { %text + #|Move stale.age to renamed.age + #| + #| stale.age => renamed.age | 1 - + #| 1 file changed, 1 deletion(-) + setup_log + } + The result of function check_git_log should be successful + End + + It 'never re-encrypts when forced' + When call cmd_move --keep stale shared + The status should be success + The error should be blank + The output should be blank + expected_log() { %text + #|Move stale.age to shared/stale.age + #| + #| stale.age => shared/stale.age | 0 + #| 1 file changed, 0 insertions(+), 0 deletions(-) + setup_log + } + The result of function check_git_log should be successful + End + + It 'interactively re-encrypts when asked' + Data + #|n + #|y + End + When call cmd_move --interactive stale extra/subdir/file shared + The status should be success + The error should be blank + The output should equal 'Reencrypt stale into shared/stale? [y/n]Reencrypt extra/subdir/file into shared/file? [y/n]' + expected_file() { %text + #|ageRecipient:myself + #|ageRecipient:friend + #|age:Pa55worD + } + The contents of file "${PREFIX}/shared/file.age" should \ + equal "$(expected_file)" + expected_log() { %text + #|Move extra/subdir/file.age to shared/file.age + #| + #| {extra/subdir => shared}/file.age | 1 + + #| 1 file changed, 1 insertion(+) + #|Move stale.age to shared/stale.age + #| + #| stale.age => shared/stale.age | 0 + #| 1 file changed, 0 insertions(+), 0 deletions(-) + setup_log + } + The result of function check_git_log should be successful + End + + It 'displays usage when called with incompatible reencryption arguments' + PROGRAM=prg + COMMAND=copy + When run cmd_copy_move -eik stale shared/ + The status should equal 1 + The output should be blank + expected_err() { %text + #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + } + The error should equal "$(expected_err)" + The result of function check_git_log should be successful + End + + It 'displays copy usage with `c*` commands' PROGRAM=prg COMMAND=curious When run cmd_copy_move single The status should equal 1 The output should be blank - The error should equal 'Usage: prg copy [--force,-f] old-path new-path' + expected_err() { %text + #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + } + The error should equal "$(expected_err)" The result of function check_git_log should be successful End - It 'display move usage with `m*` commands' + It 'displays move usage with `m*` commands' PROGRAM=prg COMMAND=memory When run cmd_copy_move single The status should equal 1 The output should be blank - The error should equal 'Usage: prg move [--force,-f] old-path new-path' + expected_err() { %text + #|Usage: prg move [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + } + The error should equal "$(expected_err)" The result of function check_git_log should be successful End @@ -274,8 +393,10 @@ Describe 'Integrated Command Functions' The status should equal 1 The output should be blank expected_err() { %text - #|Usage: prg copy [--force,-f] old-path new-path - #| prg move [--force,-f] old-path new-path + #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + #| prg move [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path } The error should equal "$(expected_err)" The result of function check_git_log should be successful diff --git a/spec/usage_spec.sh b/spec/usage_spec.sh @@ -208,6 +208,108 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + It 'always reencrypts with a long option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Copy + #|DECISION=force + #|OVERWRITE=no + #|SCM_ACTION=scm_cp + } + When call cmd_copy --reencrypt src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'always reencrypts with a short option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Copy + #|DECISION=force + #|OVERWRITE=no + #|SCM_ACTION=scm_cp + } + When call cmd_copy -e src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'interactively reencrypts with a long option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Copy + #|DECISION=interactive + #|OVERWRITE=no + #|SCM_ACTION=scm_cp + } + When call cmd_copy --interactive src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'interactively reencrypts with a short option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Copy + #|DECISION=interactive + #|OVERWRITE=no + #|SCM_ACTION=scm_cp + } + When call cmd_copy -i src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'never reencrypts with a long option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Copy + #|DECISION=keep + #|OVERWRITE=no + #|SCM_ACTION=scm_cp + } + When call cmd_copy --keep src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'never reencrypts with a short option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Copy + #|DECISION=keep + #|OVERWRITE=no + #|SCM_ACTION=scm_cp + } + When call cmd_copy -k src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + It 'copies a file named like a flag' result() { %text @@ -225,11 +327,40 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + usage_text() { %text + #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + } + It 'reports a bad option' cat() { @cat; } When run cmd_copy -s arg The output should be blank - The error should equal 'Usage: prg copy [--force,-f] old-path new-path' + The error should equal "$(usage_text)" + The status should equal 1 + End + + It 'reports incompatible re-encryption options (-e and -i)' + cat() { @cat; } + When run cmd_copy -ei src dest + The output should be blank + The error should equal "$(usage_text)" + The status should equal 1 + End + + It 'reports incompatible re-encryption options (-i and -k)' + cat() { @cat; } + When run cmd_copy -ik src dest + The output should be blank + The error should equal "$(usage_text)" + The status should equal 1 + End + + It 'reports incompatible re-encryption options (-k and -e)' + cat() { @cat; } + When run cmd_copy -ke src dest + The output should be blank + The error should equal "$(usage_text)" The status should equal 1 End @@ -237,7 +368,7 @@ Describe 'Command-Line Parsing' cat() { @cat; } When run cmd_copy src The output should be blank - The error should equal 'Usage: prg copy [--force,-f] old-path new-path' + The error should equal "$(usage_text)" The status should equal 1 End End @@ -248,8 +379,10 @@ Describe 'Command-Line Parsing' It 'reports both commands when confused' cat() { @cat; } result() { %text - #|Usage: prg copy [--force,-f] old-path new-path - #| prg move [--force,-f] old-path new-path + #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + #| prg move [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path } When run cmd_copy src The output should be blank @@ -1619,6 +1752,108 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + It 'always reencrypts with a long option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Move + #|DECISION=force + #|OVERWRITE=no + #|SCM_ACTION=scm_mv + } + When call cmd_move --reencrypt src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'always reencrypts with a short option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Move + #|DECISION=force + #|OVERWRITE=no + #|SCM_ACTION=scm_mv + } + When call cmd_move -e src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'interactively reencrypts with a long option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Move + #|DECISION=interactive + #|OVERWRITE=no + #|SCM_ACTION=scm_mv + } + When call cmd_move --interactive src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'interactively reencrypts with a short option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Move + #|DECISION=interactive + #|OVERWRITE=no + #|SCM_ACTION=scm_mv + } + When call cmd_move -i src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'never reencrypts with a long option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Move + #|DECISION=keep + #|OVERWRITE=no + #|SCM_ACTION=scm_mv + } + When call cmd_move --keep src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'never reencrypts with a short option' + result() { + %text + #|$ check_sneaky_path src + #|$ check_sneaky_path dest + #|$ do_copy_move src dest + #|ACTION=Move + #|DECISION=keep + #|OVERWRITE=no + #|SCM_ACTION=scm_mv + } + When call cmd_move -k src dest + The status should be success + The output should be blank + The error should equal "$(result)" + End + It 'moves a file named like a flag' result() { %text @@ -1636,11 +1871,40 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + usage_text() { %text + #|Usage: prg move [--reencrypt,-e | --interactive,-i | --keep,-k ] + #| [--force,-f] old-path new-path + } + It 'reports a bad option' cat() { @cat; } When run cmd_move -s arg The output should be blank - The error should equal 'Usage: prg move [--force,-f] old-path new-path' + The error should equal "$(usage_text)" + The status should equal 1 + End + + It 'reports incompatible re-encryption options (-e and -i)' + cat() { @cat; } + When run cmd_move -ei src dest + The output should be blank + The error should equal "$(usage_text)" + The status should equal 1 + End + + It 'reports incompatible re-encryption options (-i and -k)' + cat() { @cat; } + When run cmd_move -ik src dest + The output should be blank + The error should equal "$(usage_text)" + The status should equal 1 + End + + It 'reports incompatible re-encryption options (-k and -e)' + cat() { @cat; } + When run cmd_move -ke src dest + The output should be blank + The error should equal "$(usage_text)" The status should equal 1 End @@ -1648,7 +1912,7 @@ Describe 'Command-Line Parsing' cat() { @cat; } When run cmd_move src The output should be blank - The error should equal 'Usage: prg move [--force,-f] old-path new-path' + The error should equal "$(usage_text)" The status should equal 1 End End diff --git a/src/pashage.sh b/src/pashage.sh @@ -1005,6 +1005,26 @@ cmd_copy_move() { -f|--force) OVERWRITE=yes shift ;; + -e|--reencrypt) + [ "${DECISION}" = default ] || PARSE_ERROR=yes + DECISION=force + shift ;; + -i|--interactive) + [ "${DECISION}" = default ] || PARSE_ERROR=yes + DECISION=interactive + shift ;; + -k|--keep) + [ "${DECISION}" = default ] || PARSE_ERROR=yes + DECISION=keep + shift ;; + -[efik]?*) + REST="${1#??}" + FIRST="${1%"${REST}"}" + shift + set -- "${FIRST}" "-${REST}" "$@" + unset FIRST + unset REST + ;; --) shift break ;; @@ -1505,11 +1525,12 @@ EOF ;; copy) cat <<EOF -${F}${PROGRAM} copy [--force,-f] old-path new-path +${F}${PROGRAM} copy [--reencrypt,-e | --interactive,-i | --keep,-k ] +${I}${BLANKPG} [--force,-f] old-path new-path EOF [ "${VERBOSE}" = yes ] && cat <<EOF ${I} Copies old-path to new-path, optionally forcefully, -${I} selectively reencrypting. +${I} reencrypting if needed or forced. EOF ;; delete) @@ -1606,11 +1627,12 @@ EOF ;; move) cat <<EOF -${F}${PROGRAM} move [--force,-f] old-path new-path +${F}${PROGRAM} move [--reencrypt,-e | --interactive,-i | --keep,-k ] +${I}${BLANKPG} [--force,-f] old-path new-path EOF [ "${VERBOSE}" = yes ] && cat <<EOF ${I} Renames or moves old-path to new-path, optionally forcefully, -${I} selectively reencrypting. +${I} reencrypting if needed or forced. EOF ;; random)