lockbox

password manager
Log | Files | Refs | README | LICENSE

commit e169c2834d76b50eddc556db0e111faac8ccce4c
parent b1c2db039ea5d3fef59996151bbf22405cfe9389
Author: Sean Enck <sean@ttypty.com>
Date:   Sun,  6 Oct 2024 19:35:34 -0400

disable state in ask pass mode

Diffstat:
Minternal/app/completions.go | 51+++++++++++++++++++++++++++++----------------------
Minternal/app/shell/bash.sh | 16++++++++++++----
Minternal/app/shell/fish.sh | 18+++++++++++++-----
Minternal/app/shell/zsh.sh | 16++++++++++++----
Minternal/config/core.go | 35+++++++++++++++++++++--------------
Minternal/config/core_test.go | 8++++----
Minternal/config/key.go | 5+++++
Minternal/config/key_test.go | 7+++++++
Minternal/config/vars.go | 4++--
9 files changed, 105 insertions(+), 55 deletions(-)

diff --git a/internal/app/completions.go b/internal/app/completions.go @@ -34,6 +34,7 @@ type ( ReadOnly string NoClip string NoTOTP string + AskMode string } } // CompletionOption are conditional wrapped logic for options that may be disabled @@ -41,26 +42,31 @@ type ( Conditional string Key string } + shellPreparer interface { + ShellIsNotConditional(string) string + } + emptyShellPreparer struct{} ) //go:embed shell/* var shell embed.FS -func newConditional(left, right string) string { - return fmt.Sprintf("[ \"%s\" != \"%s\" ]", left, right) +func (e emptyShellPreparer) ShellIsNotConditional(s string) string { + return fmt.Sprintf(config.ShellIsNotConditional, "1", s) } -func newGenOptions(defaults []string, kv map[string]string) []CompletionOption { - genOption := func(to []CompletionOption, command, left, right string) []CompletionOption { - conditional := newConditional(left, right) - return append(to, CompletionOption{conditional, command}) +func newGenOptions(defaults []string, kv map[string]shellPreparer) []CompletionOption { + genOption := func(to []CompletionOption, command string, prep shellPreparer, compareTo string) []CompletionOption { + val := prep.ShellIsNotConditional(compareTo) + return append(to, CompletionOption{val, command}) } opt := []CompletionOption{} + emptyPrepare := emptyShellPreparer{} for _, a := range defaults { - opt = genOption(opt, a, "1", "0") + opt = genOption(opt, a, emptyPrepare, "0") } for key, env := range kv { - opt = genOption(opt, key, fmt.Sprintf("$%s", env), config.YesValue) + opt = genOption(opt, key, env, config.YesValue) } return opt } @@ -86,24 +92,25 @@ func GenerateCompletions(completionType, exe string) ([]string, error) { DoList: fmt.Sprintf("%s %s", exe, ListCommand), DoTOTPList: fmt.Sprintf("%s %s %s", exe, TOTPCommand, TOTPListCommand), } - c.Conditionals.ReadOnly = newConditional(config.EnvReadOnly.Key(), config.YesValue) - c.Conditionals.NoClip = newConditional(config.EnvNoClip.Key(), config.YesValue) - c.Conditionals.NoTOTP = newConditional(config.EnvNoTOTP.Key(), config.YesValue) + c.Conditionals.ReadOnly = config.EnvReadOnly.ShellIsNotConditional(config.YesValue) + c.Conditionals.NoClip = config.EnvNoClip.ShellIsNotConditional(config.YesValue) + c.Conditionals.NoTOTP = config.EnvNoTOTP.ShellIsNotConditional(config.YesValue) + c.Conditionals.AskMode = config.KeyModeAskConditional() c.Options = newGenOptions([]string{EnvCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, JSONCommand}, - map[string]string{ - ClipCommand: config.EnvNoClip.Key(), - TOTPCommand: config.EnvNoTOTP.Key(), - MoveCommand: config.EnvReadOnly.Key(), - RemoveCommand: config.EnvReadOnly.Key(), - InsertCommand: config.EnvReadOnly.Key(), - MultiLineCommand: config.EnvReadOnly.Key(), - PasswordGenerateCommand: config.EnvNoPasswordGen.Key(), + map[string]shellPreparer{ + ClipCommand: config.EnvNoClip, + TOTPCommand: config.EnvNoTOTP, + MoveCommand: config.EnvReadOnly, + RemoveCommand: config.EnvReadOnly, + InsertCommand: config.EnvReadOnly, + MultiLineCommand: config.EnvReadOnly, + PasswordGenerateCommand: config.EnvNoPasswordGen, }) c.TOTPSubCommands = newGenOptions([]string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand}, - map[string]string{ - TOTPClipCommand: config.EnvNoClip.Key(), - TOTPInsertCommand: config.EnvReadOnly.Key(), + map[string]shellPreparer{ + TOTPClipCommand: config.EnvNoClip, + TOTPInsertCommand: config.EnvReadOnly, }) using, err := readShell(completionType) if err != nil { diff --git a/internal/app/shell/bash.sh b/internal/app/shell/bash.sh @@ -33,7 +33,9 @@ _{{ $.Executable }}() { opts="{{ $.HelpAdvancedCommand }}" ;; "{{ $.InsertCommand }}" | "{{ $.MultiLineCommand }}" | "{{ $.MoveCommand }}" | "{{ $.RemoveCommand }}") - opts="$opts $({{ $.DoList }})" + if {{ $.Conditionals.AskMode }}; then + opts="$opts $({{ $.DoList }})" + fi ;; "{{ $.TOTPCommand }}") opts="{{ $.TOTPListCommand }} " @@ -44,21 +46,27 @@ _{{ $.Executable }}() { {{- end}} ;; "{{ $.ShowCommand }}" | "{{ $.JSONCommand }}" | "{{ $.ClipCommand }}") - opts=$({{ $.DoList }}) + if {{ $.Conditionals.AskMode }}; then + opts=$({{ $.DoList }}) + fi ;; esac else if [ "$COMP_CWORD" -eq 3 ]; then case "$chosen" in "{{ $.MoveCommand }}") - opts=$({{ $.DoList }}) + if {{ $.Conditionals.AskMode }}; then + opts=$({{ $.DoList }}) + fi ;; "{{ $.TOTPCommand }}") case "${COMP_WORDS[2]}" in {{- range $key, $value := $.TOTPSubCommands }} "{{ $value.Key }}") if {{ $value.Conditional }}; then - opts=$({{ $.DoTOTPList }}) + if {{ $.Conditionals.AskMode }}; then + opts=$({{ $.DoTOTPList }}) + fi fi ;; {{- end}} diff --git a/internal/app/shell/fish.sh b/internal/app/shell/fish.sh @@ -14,8 +14,10 @@ function {{ $.Executable }}-completion complete -c {{ $.Executable }} -n "not __fish_seen_subcommand_from $commands" -a "$commands" complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.HelpCommand }}; and test (count (commandline -opc)) -lt 3" -a "{{ $.HelpAdvancedCommand }}" if {{ $.Conditionals.ReadOnly }} - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.InsertCommand }} {{ $.MultiLineCommand }} {{ $.RemoveCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList }})" - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.MoveCommand }}; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoList }})" + if {{ $.Conditionals.AskMode }} + complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.InsertCommand }} {{ $.MultiLineCommand }} {{ $.RemoveCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList }})" + complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.MoveCommand }}; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoList }})" + end end if {{ $.Conditionals.NoTOTP }} set -f totps "" @@ -28,12 +30,18 @@ function {{ $.Executable }}-completion end {{- end }} complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and not __fish_seen_subcommand_from $totps" -a "$totps" - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and __fish_seen_subcommand_from $totps; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoTOTPList }})" + if {{ $.Conditionals.AskMode }} + complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and __fish_seen_subcommand_from $totps; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoTOTPList }})" + end end if {{ $.Conditionals.NoClip }} - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.ClipCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList}})" + if {{ $.Conditionals.AskMode }} + complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.ClipCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList}})" + end + end + if {{ $.Conditionals.AskMode }} + complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.ShowCommand }} {{ $.JSONCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList}})" end - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.ShowCommand }} {{ $.JSONCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList}})" end {{ $.Executable }}-completion diff --git a/internal/app/shell/zsh.sh b/internal/app/shell/zsh.sh @@ -46,13 +46,17 @@ _{{ $.Executable }}() { ;; "{{ $.InsertCommand }}" | "{{ $.MultiLineCommand }}" | "{{ $.RemoveCommand }}") if [ "$len" -eq 3 ]; then - compadd "$@" $({{ $.DoList }}) + if {{ $.Conditionals.AskMode }}; then + compadd "$@" $({{ $.DoList }}) + fi fi ;; "{{ $.MoveCommand }}") case "$len" in 3 | 4) - compadd "$@" $({{ $.DoList }}) + if {{ $.Conditionals.AskMode }}; then + compadd "$@" $({{ $.DoList }}) + fi ;; esac ;; @@ -71,7 +75,9 @@ _{{ $.Executable }}() { {{- range $key, $value := .TOTPSubCommands }} "{{ $value.Key }}") if {{ $value.Conditional }}; then - compadd "$@" $({{ $.DoTOTPList }}) + if {{ $.Conditionals.AskMode }}; then + compadd "$@" $({{ $.DoTOTPList }}) + fi fi ;; {{- end}} @@ -80,7 +86,9 @@ _{{ $.Executable }}() { ;; "{{ $.ShowCommand }}" | "{{ $.JSONCommand }}" | "{{ $.ClipCommand }}") if [ "$len" -eq 3 ]; then - compadd "$@" $({{ $.DoList }}) + if {{ $.Conditionals.AskMode }}; then + compadd "$@" $({{ $.DoList }}) + fi fi ;; esac diff --git a/internal/config/core.go b/internal/config/core.go @@ -34,6 +34,8 @@ const ( YesValue = yes // TemplateVariable is used to handle '$' in shell vars (due to expansion) TemplateVariable = "[%]" + // ShellIsNotConditional is the simple shell conditional statement used for env compares in any shell + ShellIsNotConditional = "[ \"%s\" != \"%s\" ]" ) var ( @@ -129,13 +131,13 @@ func environOrDefault(envKey, defaultValue string) string { return val } -func (e environmentBase) Key() string { +func (e environmentBase) key() string { return fmt.Sprintf("LOCKBOX_%s%s", string(e.cat), e.subKey) } // Get will get the boolean value for the setting func (e EnvironmentBool) Get() (bool, error) { - read := strings.ToLower(strings.TrimSpace(getExpand(e.Key()))) + read := strings.ToLower(strings.TrimSpace(getExpand(e.key()))) switch read { case no: return false, nil @@ -145,13 +147,13 @@ func (e EnvironmentBool) Get() (bool, error) { return e.defaultValue, nil } - return false, fmt.Errorf("invalid yes/no env value for %s", e.Key()) + return false, fmt.Errorf("invalid yes/no env value for %s", e.key()) } // Get will get the integer value for the setting func (e EnvironmentInt) Get() (int, error) { val := e.defaultValue - use := getExpand(e.Key()) + use := getExpand(e.key()) if use != "" { i, err := strconv.Atoi(use) if err != nil { @@ -179,14 +181,14 @@ func (e EnvironmentInt) Get() (int, error) { // Get will read the string from the environment func (e EnvironmentString) Get() string { if !e.canDefault { - return getExpand(e.Key()) + return getExpand(e.key()) } - return environOrDefault(e.Key(), e.defaultValue) + return environOrDefault(e.key(), e.defaultValue) } // Get will read (and shlex) the value if set func (e EnvironmentCommand) Get() ([]string, error) { - value := environOrDefault(e.Key(), "") + value := environOrDefault(e.key(), "") if strings.TrimSpace(value) == "" { return nil, nil } @@ -195,24 +197,24 @@ func (e EnvironmentCommand) Get() ([]string, error) { // KeyValue will get the string representation of the key+value func (e environmentBase) KeyValue(value string) string { - return fmt.Sprintf("%s=%s", e.Key(), value) + return fmt.Sprintf("%s=%s", e.key(), value) } // Setenv will do an environment set for the value to key func (e environmentBase) Set(value string) error { - unset, err := IsUnset(e.Key(), value) + unset, err := IsUnset(e.key(), value) if err != nil { return err } if unset { return nil } - return os.Setenv(e.Key(), value) + return os.Setenv(e.key(), value) } // Get will retrieve the value with the formatted input included func (e EnvironmentFormatter) Get(value string) string { - return e.fxn(e.Key(), value) + return e.fxn(e.key(), value) } func (e EnvironmentString) values() (string, []string) { @@ -346,8 +348,8 @@ func Environ() []string { var results []string for _, k := range os.Environ() { for _, r := range registeredEnv { - key := r.self().Key() - if key == EnvConfig.Key() { + key := r.self().key() + if key == EnvConfig.key() { continue } key = fmt.Sprintf("%s=", key) @@ -371,7 +373,7 @@ func ExpandParsed(inputs map[string]string) (map[string]string, error) { } var err error var cycles int - possibleCycles, ok := inputs[envConfigExpands.Key()] + possibleCycles, ok := inputs[envConfigExpands.key()] if ok { cycles, err = strconv.Atoi(possibleCycles) } else { @@ -507,3 +509,8 @@ func newDefaultedEnvironment[T any](val T, base environmentBase) environmentDefa obj.defaultValue = val return obj } + +// ShellIsNotConditional will produces a shell-ready conditional statement +func (e environmentBase) ShellIsNotConditional(compareTo string) string { + return fmt.Sprintf(ShellIsNotConditional, fmt.Sprintf("$%s", e.key()), compareTo) +} diff --git a/internal/config/core_test.go b/internal/config/core_test.go @@ -52,10 +52,10 @@ func TestKeyValue(t *testing.T) { } } -func TestKey(t *testing.T) { - val := config.EnvStore.Key() - if val != "LOCKBOX_STORE" { - t.Errorf("invalid key") +func TestShellConditional(t *testing.T) { + val := config.EnvStore.ShellIsNotConditional("x") + if val != `[ "$LOCKBOX_STORE" != "x" ]` { + t.Errorf("invalid conditiona: %s", val) } } diff --git a/internal/config/key.go b/internal/config/key.go @@ -117,3 +117,8 @@ func (k Key) Read(ask AskPassword) (string, error) { } return key, nil } + +// KeyModeAskConditional will get the key mode for 'ask' as a shell conditional +func KeyModeAskConditional() string { + return envKeyMode.ShellIsNotConditional(string(askKeyMode)) +} diff --git a/internal/config/key_test.go b/internal/config/key_test.go @@ -202,3 +202,10 @@ func TestCommandKey(t *testing.T) { t.Errorf("invalid error: %v", err) } } + +func TestKeyModeAskConditional(t *testing.T) { + val := config.KeyModeAskConditional() + if val != `[ "$LOCKBOX_KEYMODE" != "ask" ]` { + t.Errorf("invalid value: %s", val) + } +} diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -120,7 +120,7 @@ var ( }), }) // EnvDefaultCompletionKey is the key for default completion handling - EnvDefaultCompletionKey = EnvDefaultCompletion.Key() + EnvDefaultCompletionKey = EnvDefaultCompletion.key() // EnvNoColor indicates if color outputs are disabled EnvNoColor = environmentRegister( EnvironmentBool{ @@ -430,7 +430,7 @@ func ListEnvironmentVariables() []string { if r != "" { requirement = r } - text := fmt.Sprintf("\n%s\n%s requirement: %s\n default: %s\n options: %s\n", env.Key(), description, requirement, value, strings.Join(allow, "|")) + text := fmt.Sprintf("\n%s\n%s requirement: %s\n default: %s\n options: %s\n", env.key(), description, requirement, value, strings.Join(allow, "|")) results = append(results, text) } sort.Strings(results)