lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 8d33a511c1e08acddf0350bbe807818367b4b172
parent 84574dd5ca45921e63df738561bfe10f92ca2a76
Author: Sean Enck <sean@ttypty.com>
Date:   Sat, 10 Aug 2024 17:09:48 -0400

allow controlling modtime import

Diffstat:
Minternal/app/core.go | 18++++++++++++++----
Minternal/app/core_test.go | 2+-
Minternal/app/doc/rekey.txt | 6+++++-
Minternal/app/rekey.go | 22+++++++++++++++++-----
Minternal/app/rekey_test.go | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Minternal/config/core.go | 13+++++++++++++
Minternal/config/vars.go | 14++++++++++----
Minternal/config/vars_test.go | 16+++++++++++++---
8 files changed, 137 insertions(+), 21 deletions(-)

diff --git a/internal/app/core.go b/internal/app/core.go @@ -106,10 +106,16 @@ type ( CompletionsCommand string CompletionsEnv string ReKey struct { - Store string - KeyFile string - Key string - KeyMode string + Store string + KeyFile string + Key string + KeyMode string + ModMode string + ModModes struct { + Skip string + Error string + None string + } } } ) @@ -227,6 +233,10 @@ func Usage(verbose bool, exe string) ([]string, error) { document.ReKey.Key = setDocFlag(config.ReKeyKeyFlag) document.ReKey.KeyMode = setDocFlag(config.ReKeyKeyModeFlag) document.ReKey.KeyFile = setDocFlag(config.ReKeyKeyModeFlag) + document.ReKey.ModMode = setDocFlag(config.ReKeyModModeFlag) + document.ReKey.ModModes.None = config.ReKeyModModeNone + document.ReKey.ModModes.Skip = config.ReKeyModModeSkip + document.ReKey.ModModes.Error = config.ReKeyModModeError files, err := docs.ReadDir(docDir) if err != nil { return nil, err diff --git a/internal/app/core_test.go b/internal/app/core_test.go @@ -13,7 +13,7 @@ func TestUsage(t *testing.T) { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = app.Usage(true, "lb") - if len(u) != 107 { + if len(u) != 109 { t.Errorf("invalid verbose usage, out of date? %d", len(u)) } for _, usage := range u { diff --git a/internal/app/doc/rekey.txt b/internal/app/doc/rekey.txt @@ -2,6 +2,10 @@ The password store can have the key (and file) changed via the '{{ $.ReKeyComman subcommand. This command requires that '{{ $.ReKey.Store }}' is set and a combination of new key settings are configured via '{{ $.ReKey.Key }}', '{{ $.ReKey.KeyMode }}', and '{{ $.ReKey.KeyFile }}' depending on the new database credential preferences. The settings correspond to the 'LOCKBOX_' -settings normally used when running `{{ $.Executable }}`. +settings normally used when running `{{ $.Executable }}`. Additionally +'{{ $.ReKey.ModMode }}' can be set to `{{ $.ReKey.ModModes.Skip }}` to skip +modtime issues, `{{ $.ReKey.ModModes.None }}` to disable modtime imports, or +`{{ $.ReKey.ModModes.Error }}` to ignore missing modification times (the +default). Note that is an advanced feature and should be used with caution/backups/etc. diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -91,13 +91,25 @@ func ReKey(cmd CommandOptions, r Keyer) error { if _, err := fmt.Fprintf(writer, "rekeying: %s\n", path); err != nil { return err } - modTime := strings.TrimSpace(entry.ModTime) - if modTime == "" { - return errors.New("did not read modtime") + var modTime string + if vars.ModMode != config.ReKeyModModeNone { + modTime = strings.TrimSpace(entry.ModTime) + if modTime == "" { + switch vars.ModMode { + case config.ReKeyModModeSkip: + case config.ReKeyModModeError: + return errors.New("did not read modtime") + default: + return errors.New("unknown modtime control") + } + } } + var insertEnv []string - insertEnv = append(insertEnv, vars...) - insertEnv = append(insertEnv, config.EnvModTime.KeyValue(modTime)) + insertEnv = append(insertEnv, vars.Env...) + if modTime != "" { + insertEnv = append(insertEnv, config.EnvModTime.KeyValue(modTime)) + } if err := r.Insert(ReKeyEntry{Path: path, Env: insertEnv, Data: []byte(entry.Data)}); err != nil { return err } diff --git a/internal/app/rekey_test.go b/internal/app/rekey_test.go @@ -3,6 +3,8 @@ package app_test import ( "bytes" "errors" + "fmt" + "strings" "testing" "github.com/seanenck/lockbox/internal/app" @@ -13,8 +15,8 @@ type ( mockKeyer struct { data map[string][]byte err error - rekeys int items map[string]backend.JSON + rekeys [][]string } ) @@ -26,7 +28,7 @@ func (m *mockKeyer) JSON() (map[string]backend.JSON, error) { } func (m *mockKeyer) Insert(entry app.ReKeyEntry) error { - m.rekeys++ + m.rekeys = append(m.rekeys, entry.Env) if entry.Path == "error" { return errors.New("bad insert") } @@ -87,7 +89,66 @@ func TestReKey(t *testing.T) { if cmd.buf.String() == "" { t.Error("invalid data") } - if m.rekeys != 2 { + if len(m.rekeys) != 2 { t.Errorf("invalid results") } + obj := fmt.Sprintf("%v", m.rekeys) + for _, idx := range []int{1, 2} { + if !strings.Contains(obj, fmt.Sprintf("LOCKBOX_SET_MODTIME=%d", idx)) { + t.Errorf("missing converted modtime: %s", obj) + } + } +} + +func modTimeKey(t *testing.T, mode string, modSet int) { + cmd := &mockCommand{} + cmd.confirm = true + cmd.buf = bytes.Buffer{} + cmd.args = []string{"-store", "store", "-key", "abc"} + m := &mockKeyer{} + m.items = map[string]backend.JSON{ + "test1": {ModTime: "1"}, + "test2": {ModTime: ""}, + } + m.data = make(map[string][]byte) + m.data["test1"] = []byte{1} + m.data["test2"] = []byte{2} + cmd.buf = bytes.Buffer{} + cmd.args = []string{"-store", "store", "-key", "abc", "-modtime", mode} + if err := app.ReKey(cmd, m); err != nil { + t.Errorf("invalid error: %v", err) + } + if len(m.rekeys) != 2 { + t.Errorf("invalid results") + } + if strings.Count(fmt.Sprintf("%v", m.rekeys), "LOCKBOX_SET_MODTIME=") != modSet { + t.Errorf("invalid object: %v", m.rekeys) + } +} + +func TestReKeyModTime(t *testing.T) { + cmd := &mockCommand{} + cmd.confirm = true + cmd.buf = bytes.Buffer{} + cmd.args = []string{"-store", "store", "-key", "abc"} + m := &mockKeyer{} + m.items = map[string]backend.JSON{ + "test1": {ModTime: "1"}, + "test3": {ModTime: "a"}, + "test2": {ModTime: ""}, + } + m.data = make(map[string][]byte) + m.data["test1"] = []byte{1} + m.data["test2"] = []byte{2} + m.data["test3"] = []byte{4} + cmd.buf = bytes.Buffer{} + if err := app.ReKey(cmd, m); err == nil || err.Error() != "did not read modtime" { + t.Errorf("invalid error: %v", err) + } + cmd.args = []string{"-store", "store", "-key", "abc", "-modtime", "xyz"} + if err := app.ReKey(cmd, m); err == nil || err.Error() != "unknown modtime setting for import: xyz" { + t.Errorf("invalid error: %v", err) + } + modTimeKey(t, "none", 0) + modTimeKey(t, "skip", 1) } diff --git a/internal/config/core.go b/internal/config/core.go @@ -41,11 +41,19 @@ const ( ReKeyKeyFlag = "key" // ReKeyKeyModeFlag is the flag used for rekey to set the key mode ReKeyKeyModeFlag = "keymode" + // ReKeyModModeFlag indicates how to control modtime inserts + ReKeyModModeFlag = "modtime" // sub categories clipCategory keyCategory = "CLIP_" totpCategory keyCategory = "TOTP_" // YesValue are yes (on) values YesValue = yes + // ReKeyModModeSkip will skip modtime import issues + ReKeyModModeSkip = "skip" + // ReKeyModModeNone will not attempt to import modtimes at all + ReKeyModModeNone = "none" + // ReKeyModModeError will error on modtime import issues + ReKeyModModeError = "error" ) var ( @@ -114,6 +122,11 @@ type ( Env []string Default bool } + // ReKeyArgs are the arguments for rekeying + ReKeyArgs struct { + Env []string + ModMode string + } ) func shlex(in string) ([]string, error) { diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -6,6 +6,7 @@ import ( "fmt" "net/url" "os" + "slices" "sort" "strings" "time" @@ -207,14 +208,19 @@ This value can NOT be an expansion itself.`, ) // GetReKey will get the rekey environment settings -func GetReKey(args []string) ([]string, error) { +func GetReKey(args []string) (ReKeyArgs, error) { set := flag.NewFlagSet("rekey", flag.ExitOnError) store := set.String(ReKeyStoreFlag, "", "new store") key := set.String(ReKeyKeyFlag, "", "new key") keyFile := set.String(ReKeyKeyFileFlag, "", "new keyfile") keyMode := set.String(ReKeyKeyModeFlag, "", "new keymode") + modSetting := set.String(ReKeyModModeFlag, ReKeyModModeError, "import setting for modtime") if err := set.Parse(args); err != nil { - return nil, err + return ReKeyArgs{}, err + } + mod := *modSetting + if !slices.Contains([]string{ReKeyModModeError, ReKeyModModeSkip, ReKeyModModeNone}, mod) { + return ReKeyArgs{}, fmt.Errorf("unknown modtime setting for import: %s", mod) } type keyer struct { env EnvironmentString @@ -234,10 +240,10 @@ func GetReKey(args []string) ([]string, error) { out = append(out, k.env.KeyValue(k.in)) } if !inStore.has || (!inKey.has && !inKeyFile.has) { - return nil, fmt.Errorf("missing required arguments for rekey:\n -help for information on the flags or the lockbox help documentation for detailed usage") + return ReKeyArgs{}, fmt.Errorf("missing required arguments for rekey:\n -help for information on the flags or the lockbox help documentation for detailed usage") } sort.Strings(out) - return out, nil + return ReKeyArgs{Env: out, ModMode: mod}, nil } // ListEnvironmentVariables will print information about env variables diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go @@ -107,20 +107,30 @@ func TestReKey(t *testing.T) { if err == nil || !strings.HasPrefix(err.Error(), "missing required arguments for rekey") { t.Errorf("failed: %v", err) } + _, err = config.GetReKey([]string{"-store", "abc", "-key", "aaa", "-modtime", "xyz"}) + if err == nil || !strings.HasPrefix(err.Error(), "unknown modtime setting for import: xyz") { + t.Errorf("failed: %v", err) + } out, err := config.GetReKey([]string{"-store", "abc", "-key", "aaa"}) if err != nil { t.Errorf("failed: %v", err) } - if fmt.Sprintf("%v", out) != "[LOCKBOX_KEY=aaa LOCKBOX_KEYFILE= LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" { + if fmt.Sprintf("%v", out.Env) != "[LOCKBOX_KEY=aaa LOCKBOX_KEYFILE= LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" { t.Errorf("invalid env: %v", out) } - out, err = config.GetReKey([]string{"-store", "abc", "-keyfile", "aaa"}) + if out.ModMode != "error" { + t.Errorf("invalid modtime setting: %v", out) + } + out, err = config.GetReKey([]string{"-store", "abc", "-keyfile", "aaa", "-modtime", "none"}) if err != nil { t.Errorf("failed: %v", err) } - if fmt.Sprintf("%v", out) != "[LOCKBOX_KEY= LOCKBOX_KEYFILE=aaa LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" { + if fmt.Sprintf("%v", out.Env) != "[LOCKBOX_KEY= LOCKBOX_KEYFILE=aaa LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" { t.Errorf("invalid env: %v", out) } + if out.ModMode != "none" { + t.Errorf("invalid modtime setting: %v", out) + } os.Setenv("LOCKBOX_KEY_NEW", "") os.Setenv("LOCKBOX_STORE_NEW", "") os.Setenv("LOCKBOX_KEY_NEW", "")