lockbox

password manager
Log | Files | Refs | README | LICENSE

commit fee08de124dc0715238be62cb426f8cffc226de4
parent f380c603ce6e09f85bcbcc663b5bbe71e0fb2ade
Author: Sean Enck <sean@ttypty.com>
Date:   Sun, 15 Jun 2025 08:23:06 -0400

help is always full, though it is now going to add more information

Diffstat:
Minternal/app/commands/core.go | 3++-
Minternal/app/commands/core_test.go | 11+++++++++++
Minternal/app/help/core.go | 66++++++++++++++++++++++--------------------------------------------
Minternal/app/help/core_test.go | 47+----------------------------------------------
Minternal/app/help/doc/completions.txt | 2+-
Ainternal/app/help/doc/readonly.txt | 5+++++
6 files changed, 42 insertions(+), 92 deletions(-)

diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go @@ -76,6 +76,7 @@ var ( KeyFile string NoKey string }{"keyfile", "nokey"} + ReadOnly = []string{Insert, Move, ReKey, Remove, Unset} ) // AllowedInReadOnly indicates any commands that are allowed in readonly mode @@ -83,7 +84,7 @@ func AllowedInReadOnly(cmds ...string) []string { if config.EnvReadOnly.Get() { var allowed []string for _, item := range cmds { - if slices.Contains([]string{Move, Insert, Unset, Remove, ReKey}, item) { + if slices.Contains(ReadOnly, item) { continue } allowed = append(allowed, item) diff --git a/internal/app/commands/core_test.go b/internal/app/commands/core_test.go @@ -1,6 +1,8 @@ package commands_test import ( + "fmt" + "sort" "testing" "git.sr.ht/~enckse/lockbox/internal/app/commands" @@ -23,3 +25,12 @@ func TestIsReadOnly(t *testing.T) { t.Error("invalid, is not readonly") } } + +func TestReadOnlyOrder(t *testing.T) { + had := fmt.Sprintf("%v", commands.ReadOnly) + v := commands.ReadOnly + sort.Strings(v) + if had != fmt.Sprintf("%v", v) { + t.Error("invalid readonly sort") + } +} diff --git a/internal/app/help/core.go b/internal/app/help/core.go @@ -36,6 +36,7 @@ type ( HelpCommand string HelpConfigCommand string NoColor string + ReadOnlyCommands string Config struct { Env string Home string @@ -76,11 +77,7 @@ func Usage(verbose bool, exe string) ([]string, error) { isGroup = "group" ) var results []string - isReadOnly := config.EnvReadOnly.Get() - canClip := config.EnvFeatureClip.Get() - if canClip { - results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard")) - } + results = append(results, command(commands.Clip, isEntry, "copy the entry's value into the clipboard")) results = append(results, command(commands.Completions, "<shell>", "generate completions via auto-detection")) for _, c := range commands.CompletionTypes { results = append(results, subCommand(commands.Completions, c, "", fmt.Sprintf("generate %s completions", c))) @@ -89,35 +86,27 @@ func Usage(verbose bool, exe string) ([]string, error) { results = append(results, command(commands.Help, "", "show this usage information")) results = append(results, subCommand(commands.Help, commands.HelpAdvanced, "", "display verbose help information")) results = append(results, subCommand(commands.Help, commands.HelpConfig, "", "display verbose configuration information")) - if !isReadOnly { - results = append(results, command(commands.Insert, isEntry, "insert a new entry into the store")) - results = append(results, command(commands.Unset, isEntry, "clear an entry value")) - results = append(results, command(commands.Move, fmt.Sprintf("%s %s", isGroup, isGroup), "move a group from source to destination")) - results = append(results, command(commands.ReKey, "", "rekey/reinitialize the database credentials")) - results = append(results, command(commands.Remove, isGroup, "remove an entry from the store")) - } + results = append(results, command(commands.Insert, isEntry, "insert a new entry into the store")) + results = append(results, command(commands.Unset, isEntry, "clear an entry value")) + results = append(results, command(commands.Move, fmt.Sprintf("%s %s", isGroup, isGroup), "move a group from source to destination")) + results = append(results, command(commands.ReKey, "", "rekey/reinitialize the database credentials")) + results = append(results, command(commands.Remove, isGroup, "remove an entry from the store")) results = append(results, command(commands.JSON, isFilter, "display detailed information")) results = append(results, command(commands.List, isFilter, "list entries")) results = append(results, command(commands.Groups, isFilter, "list groups")) results = append(results, command(commands.Show, isEntry, "show the entry's value")) - canTOTP := config.EnvFeatureTOTP.Get() - if canTOTP { - results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code")) - if canClip { - results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard")) - } - results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings")) - results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code")) - results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)")) - results = append(results, subCommand(commands.TOTP, commands.TOTPURL, isEntry, "display TOTP url information")) - results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)")) - results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry")) - } + results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code")) + results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard")) + results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings")) + results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code")) + results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)")) + results = append(results, subCommand(commands.TOTP, commands.TOTPURL, isEntry, "display TOTP url information")) + results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)")) + results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry")) results = append(results, command(commands.Version, "", "display version information")) sort.Strings(results) usage := []string{fmt.Sprintf("%s usage:", exe)} if verbose { - canColor := config.EnvFeatureColor.Get() results = append(results, "") document := Documentation{ Executable: filepath.Base(exe), @@ -128,6 +117,7 @@ func Usage(verbose bool, exe string) ([]string, error) { HelpCommand: commands.Help, HelpConfigCommand: commands.HelpConfig, NoColor: config.NoColorFlag, + ReadOnlyCommands: strings.Join(commands.ReadOnly, ", "), } document.Config.Env = config.ConfigEnv document.Config.Home = config.ConfigHome @@ -156,30 +146,18 @@ func Usage(verbose bool, exe string) ([]string, error) { if !strings.HasSuffix(n, textFile) { continue } - section := strings.TrimSuffix(filepath.Base(n), textFile) - skip := false adding := "" - for k, v := range map[string]bool{ - "totp": canTOTP, - "color": canColor, - "clipboard": canClip, + section := strings.TrimSuffix(filepath.Base(n), textFile) + for _, key := range []string{ + "totp", + "color", + "clipboard", } { - if section == k { - if !v { - skip = true - } + if section == key { adding = "This functionality can be controlled by a configuration feature flag." break } } - if !skip { - if section == "rekey" || section == "globs" { - skip = isReadOnly - } - } - if skip { - continue - } header := fmt.Sprintf("[%s]", section) s, err := processDoc(header, n, document) if err != nil { diff --git a/internal/app/help/core_test.go b/internal/app/help/core_test.go @@ -1,12 +1,10 @@ package help_test import ( - "fmt" "strings" "testing" "git.sr.ht/~enckse/lockbox/internal/app/help" - "git.sr.ht/~enckse/lockbox/internal/config/store" ) func TestUsage(t *testing.T) { @@ -15,7 +13,7 @@ func TestUsage(t *testing.T) { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = help.Usage(true, "lb") - if len(u) != 121 { + if len(u) != 128 { t.Errorf("invalid verbose usage, out of date? %d", len(u)) } for _, usage := range u { @@ -26,46 +24,3 @@ func TestUsage(t *testing.T) { } } } - -func TestFlags(t *testing.T) { - defer store.Clear() - for _, feature := range []string{"clip", "totp", "color"} { - store.Clear() - key := fmt.Sprintf("LOCKBOX_FEATURE_%s", strings.ToUpper(feature)) - store.SetBool(key, true) - u, _ := help.Usage(true, "lb") - if !strings.Contains(strings.Join(u, "\n"), feature) { - t.Errorf("verbose help lacks: %s", feature) - } - store.SetBool(key, false) - u, _ = help.Usage(true, "lb") - if strings.Contains(strings.Join(u, "\n"), feature) { - t.Errorf("verbose help has: %s", feature) - } - } -} - -func TestReadOnly(t *testing.T) { - defer store.Clear() - check := func(require bool) { - u, _ := help.Usage(true, "lb") - text := strings.Join(u, "\n") - for _, need := range []string{" mv ", " rm ", "[rekey]", " insert ", " unset ", "[globs]"} { - has := strings.Contains(text, need) - if has { - if require { - continue - } - t.Errorf("has unwanted text: %s", need) - } else { - if require { - t.Errorf("missing required text: %s", need) - } - } - } - } - - check(true) - store.SetBool("LOCKBOX_READONLY", true) - check(false) -} diff --git a/internal/app/help/doc/completions.txt b/internal/app/help/doc/completions.txt @@ -2,5 +2,5 @@ Completions are available for certain shells. Generation of completions is handled by `{{ $.Executable }} {{ $.CompletionsCommand }}` (detecting the shell by default), provide an additional argument to the command of a the specific shell to generate completions for. Generated completions use -various `{{ $.Executable }}` settings to disable components depending on +various `{{ $.Executable }}` feature flags and settings to disable components depending on user settings. diff --git a/internal/app/help/doc/readonly.txt b/internal/app/help/doc/readonly.txt @@ -0,0 +1,5 @@ +In readonly mode the underlying database will block any commands that +write to the underlying store when `{{ $.Executable }}` is called. The +following commands are not allowed in readonly mode: {{ $.ReadOnlyCommands }} + +This functionality can be controlled via configuration.