lockbox

password manager
Log | Files | Refs | README | LICENSE

commit eb753d2a89ba8b323904500b25c0c6a332772dcb
parent 899195ad02cd51dcf2cb190849947cb206d8b0e6
Author: Sean Enck <sean@ttypty.com>
Date:   Mon,  7 Oct 2024 18:51:42 -0400

cleaning up shell conditional handling

Diffstat:
Minternal/app/completions.go | 78++++++++++++++++++++++++++++++++++++++++++------------------------------------
Minternal/config/core.go | 38++++++++++++++------------------------
Minternal/config/core_test.go | 14--------------
Minternal/config/key.go | 18+++++++-----------
Minternal/config/key_test.go | 7-------
Minternal/config/vars.go | 9+++++----
6 files changed, 68 insertions(+), 96 deletions(-)

diff --git a/internal/app/completions.go b/internal/app/completions.go @@ -6,11 +6,16 @@ import ( "embed" "fmt" "slices" + "sort" "text/template" "github.com/seanenck/lockbox/internal/config" ) +const ( + shellIsNotText = `[ "%s" != "%s" ]` +) + type ( // Completions handles the inputs to completions for templating Completions struct { @@ -32,10 +37,12 @@ type ( TOTPSubCommands []CompletionOption Conditionals struct { Not struct { - ReadOnly string - NoClip string - NoTOTP string - AskMode string + ReadOnly string + NoClip string + NoTOTP string + AskMode string + Ever string + NoPasswordGen string } } } @@ -44,31 +51,28 @@ type ( Conditional string Key string } - shellPreparer interface { - ShellIsNotConditional(string) string - } - emptyShellPreparer struct{} ) //go:embed shell/* var shell embed.FS -func (e emptyShellPreparer) ShellIsNotConditional(s string) string { - return config.ShellIsNotConditional("1", s) +func newShellIsNotEqualConditional(keyed interface{ Key() string }, right string) string { + return fmt.Sprintf(shellIsNotText, fmt.Sprintf("$%s", keyed.Key()), right) } -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}) - } +func (c Completions) newGenOptions(defaults []string, kv map[string]string) []CompletionOption { opt := []CompletionOption{} - emptyPrepare := emptyShellPreparer{} for _, a := range defaults { - opt = genOption(opt, a, emptyPrepare, "0") + opt = append(opt, CompletionOption{c.Conditionals.Not.Ever, a}) + } + var keys []string + for k := range kv { + keys = append(keys, k) } - for key, env := range kv { - opt = genOption(opt, key, env, config.YesValue) + sort.Strings(keys) + for _, key := range keys { + check := kv[key] + opt = append(opt, CompletionOption{check, key}) } return opt } @@ -94,25 +98,27 @@ 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.Not.ReadOnly = config.EnvReadOnly.ShellIsNotConditional(config.YesValue) - c.Conditionals.Not.NoClip = config.EnvNoClip.ShellIsNotConditional(config.YesValue) - c.Conditionals.Not.NoTOTP = config.EnvNoTOTP.ShellIsNotConditional(config.YesValue) - c.Conditionals.Not.AskMode = config.KeyModeIsNotAskConditional() + c.Conditionals.Not.ReadOnly = newShellIsNotEqualConditional(config.EnvReadOnly, config.YesValue) + c.Conditionals.Not.NoClip = newShellIsNotEqualConditional(config.EnvNoClip, config.YesValue) + c.Conditionals.Not.NoTOTP = newShellIsNotEqualConditional(config.EnvNoTOTP, config.YesValue) + c.Conditionals.Not.AskMode = newShellIsNotEqualConditional(config.EnvKeyMode, string(config.AskKeyMode)) + c.Conditionals.Not.NoPasswordGen = newShellIsNotEqualConditional(config.EnvNoPasswordGen, config.YesValue) + c.Conditionals.Not.Ever = fmt.Sprintf(shellIsNotText, "1", "0") - c.Options = newGenOptions([]string{EnvCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, JSONCommand}, - 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.Options = c.newGenOptions([]string{EnvCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, JSONCommand}, + map[string]string{ + ClipCommand: c.Conditionals.Not.NoClip, + TOTPCommand: c.Conditionals.Not.NoTOTP, + MoveCommand: c.Conditionals.Not.ReadOnly, + RemoveCommand: c.Conditionals.Not.ReadOnly, + InsertCommand: c.Conditionals.Not.ReadOnly, + MultiLineCommand: c.Conditionals.Not.ReadOnly, + PasswordGenerateCommand: c.Conditionals.Not.NoPasswordGen, }) - c.TOTPSubCommands = newGenOptions([]string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand}, - map[string]shellPreparer{ - TOTPClipCommand: config.EnvNoClip, - TOTPInsertCommand: config.EnvReadOnly, + c.TOTPSubCommands = c.newGenOptions([]string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand}, + map[string]string{ + TOTPClipCommand: c.Conditionals.Not.NoClip, + TOTPInsertCommand: c.Conditionals.Not.ReadOnly, }) using, err := readShell(completionType) if err != nil { diff --git a/internal/config/core.go b/internal/config/core.go @@ -129,13 +129,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 +145,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 +179,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 +195,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 +346,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 +371,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,13 +507,3 @@ func newDefaultedEnvironment[T any](val T, base environmentBase) environmentDefa obj.defaultValue = val return obj } - -// ShellIsNotConditional will produce a shell-ready conditional statement for an environment variable -func (e environmentBase) ShellIsNotConditional(compareTo string) string { - return ShellIsNotConditional(fmt.Sprintf("$%s", e.key()), compareTo) -} - -// ShellIsNotConditional will produce a shell-ready conditional statement -func ShellIsNotConditional(key, compareTo string) string { - return fmt.Sprintf("[ \"%s\" != \"%s\" ]", key, compareTo) -} diff --git a/internal/config/core_test.go b/internal/config/core_test.go @@ -52,20 +52,6 @@ func TestKeyValue(t *testing.T) { } } -func TestShellIsNotConditionalEnv(t *testing.T) { - val := config.EnvStore.ShellIsNotConditional("x") - if val != `[ "$LOCKBOX_STORE" != "x" ]` { - t.Errorf("invalid conditiona: %s", val) - } -} - -func TestShellIsNotConditional(t *testing.T) { - val := config.ShellIsNotConditional("y", "x") - if val != `[ "y" != "x" ]` { - t.Errorf("invalid conditiona: %s", val) - } -} - func TestNewPlatform(t *testing.T) { for _, item := range config.Platforms.List() { os.Setenv("LOCKBOX_PLATFORM", item) diff --git a/internal/config/key.go b/internal/config/key.go @@ -23,8 +23,9 @@ type ( const ( plainKeyMode KeyModeType = "plaintext" - askKeyMode KeyModeType = "ask" - noKeyMode KeyModeType = "none" + // AskKeyMode is the mode in which the user is prompted for key input (each time) + AskKeyMode KeyModeType = "ask" + noKeyMode KeyModeType = "none" // IgnoreKeyMode will ignore the value set in the key (acts like no key) IgnoreKeyMode KeyModeType = "ignore" commandKeyMode KeyModeType = "command" @@ -35,7 +36,7 @@ const ( // NewKey will create a new key func NewKey(defaultKeyModeType KeyModeType) (Key, error) { useKey := envKey.Get() - keyMode := envKeyMode.Get() + keyMode := EnvKeyMode.Get() if keyMode == "" { keyMode = string(defaultKeyModeType) } @@ -46,7 +47,7 @@ func NewKey(defaultKeyModeType KeyModeType) (Key, error) { case string(noKeyMode): requireEmptyKey = true case string(commandKeyMode), string(plainKeyMode): - case string(askKeyMode): + case string(AskKeyMode): isInteractive, err := EnvInteractive.Get() if err != nil { return Key{}, err @@ -77,7 +78,7 @@ func (k Key) empty() bool { // Ask will indicate if prompting is required to get the key func (k Key) Ask() bool { - return k.valid && k.mode == askKeyMode + return k.valid && k.mode == AskKeyMode } // Read will read the key as configured by the mode @@ -93,7 +94,7 @@ func (k Key) Read(ask AskPassword) (string, error) { } useKey := k.inputKey switch k.mode { - case askKeyMode: + case AskKeyMode: read, err := ask() if err != nil { return "", err @@ -117,8 +118,3 @@ func (k Key) Read(ask AskPassword) (string, error) { } return key, nil } - -// KeyModeIsNotAskConditional will get the key mode for 'ask' as a shell conditional -func KeyModeIsNotAskConditional() string { - return envKeyMode.ShellIsNotConditional(string(askKeyMode)) -} diff --git a/internal/config/key_test.go b/internal/config/key_test.go @@ -202,10 +202,3 @@ func TestCommandKey(t *testing.T) { t.Errorf("invalid error: %v", err) } } - -func TestKeyModeAskConditional(t *testing.T) { - val := config.KeyModeIsNotAskConditional() - 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{ @@ -288,7 +288,8 @@ Note that this setting is not output as part of the environment.`, noEnvironment canDefault: true, allowed: []string{detectEnvironment, fileExample, noEnvironment}, }) - envKeyMode = environmentRegister( + // EnvKeyMode is the variable for indicating the keymode used to get the key + EnvKeyMode = environmentRegister( EnvironmentString{ environmentDefault: newDefaultedEnvironment(string(DefaultKeyMode), environmentBase{ @@ -297,7 +298,7 @@ Note that this setting is not output as part of the environment.`, noEnvironment desc: fmt.Sprintf(`How to retrieve the database store password. Set to '%s' when only using a key file. Set to '%s' to ignore the set key value`, noKeyMode, IgnoreKeyMode), }), - allowed: []string{string(askKeyMode), string(commandKeyMode), string(IgnoreKeyMode), string(noKeyMode), string(plainKeyMode)}, + allowed: []string{string(AskKeyMode), string(commandKeyMode), string(IgnoreKeyMode), string(noKeyMode), string(plainKeyMode)}, canDefault: true, }) envKey = environmentRegister( @@ -430,7 +431,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)