lockbox

password manager
Log | Files | Refs | README | LICENSE

commit f19188da18af16b76d851e6c2b7b68bf4e7f1100
parent c5c20832eff040d7e905e19e6267063977af760d
Author: Sean Enck <sean@ttypty.com>
Date:   Fri, 31 Mar 2023 19:17:00 -0400

allow json to do plaintext outputs

Diffstat:
Minternal/app/rekey.go | 19++++++-------------
Minternal/app/rekey_test.go | 12------------
Minternal/backend/query.go | 12+++++++++++-
Minternal/backend/query_test.go | 17+++++++++++++++--
Minternal/backend/types.go | 2+-
Minternal/cli/core_test.go | 2+-
Minternal/inputs/env.go | 8++++++++
Minternal/inputs/env_test.go | 6+++++-
Mtests/expected.log | 22++++++++++++++--------
Mtests/run.sh | 2++
10 files changed, 63 insertions(+), 39 deletions(-)

diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -11,13 +11,13 @@ import ( "github.com/enckse/lockbox/internal/backend" "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" + "github.com/enckse/pgl/os/env" ) type ( // Keyer defines how rekeying happens Keyer interface { JSON() (map[string]backend.JSON, error) - Show(string) ([]byte, error) Insert(ReKeyEntry) error } // ReKeyEntry is an entry that is being rekeyed @@ -41,11 +41,6 @@ func NewDefaultKeyer() (DefaultKeyer, error) { return DefaultKeyer{exe: exe}, nil } -// Show will get entry payload -func (r DefaultKeyer) Show(entry string) ([]byte, error) { - return exec.Command(r.exe, cli.ShowCommand, entry).Output() -} - // JSON will get the JSON backing entries func (r DefaultKeyer) JSON() (map[string]backend.JSON, error) { out, err := exec.Command(r.exe, cli.JSONCommand).Output() @@ -79,13 +74,15 @@ func (r DefaultKeyer) Insert(entry ReKeyEntry) error { // ReKey handles entry rekeying func ReKey(cmd CommandOptions, r Keyer) error { args := cmd.Args() - env, err := inputs.GetReKey(args) + vars, err := inputs.GetReKey(args) if err != nil { return err } if !cmd.Confirm("proceed with rekey") { return nil } + + os.Setenv(inputs.JSONPlainTextEnv, env.Yes) entries, err := r.JSON() if err != nil { return err @@ -99,14 +96,10 @@ func ReKey(cmd CommandOptions, r Keyer) error { if modTime == "" { return errors.New("did not read modtime") } - data, err := r.Show(path) - if err != nil { - return err - } var insertEnv []string - insertEnv = append(insertEnv, env...) + insertEnv = append(insertEnv, vars...) insertEnv = append(insertEnv, fmt.Sprintf("%s=%s", inputs.ModTimeEnv, modTime)) - if err := r.Insert(ReKeyEntry{Path: path, Env: insertEnv, Data: data}); err != nil { + 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 @@ -25,14 +25,6 @@ func (m *mockKeyer) JSON() (map[string]backend.JSON, error) { return m.items, nil } -func (m *mockKeyer) Show(entry string) ([]byte, error) { - val, ok := m.data[entry] - if !ok { - return nil, errors.New("no data") - } - return val, nil -} - func (m *mockKeyer) Insert(entry app.ReKeyEntry) error { m.rekeys++ if entry.Path == "error" { @@ -60,10 +52,6 @@ func TestErrors(t *testing.T) { if err := app.ReKey(cmd, m); err == nil || err.Error() != "did not read modtime" { t.Errorf("invalid error: %v", err) } - m.items = map[string]backend.JSON{"test1": {ModTime: "2"}} - if err := app.ReKey(cmd, m); err == nil || err.Error() != "no data" { - t.Errorf("invalid error: %v", err) - } m.data = make(map[string][]byte) m.data["test1"] = []byte{1} m.data["error"] = []byte{2} diff --git a/internal/backend/query.go b/internal/backend/query.go @@ -9,6 +9,7 @@ import ( "sort" "strings" + "github.com/enckse/lockbox/internal/inputs" "github.com/enckse/pgl/types/collections" "github.com/tobischo/gokeepasslib/v3" ) @@ -117,6 +118,11 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { if isSort { sort.Strings(keys) } + plain, err := inputs.IsJSONPlainText() + if err != nil { + return nil, err + } + jsonHash := !plain var results []QueryEntity for _, k := range keys { entity := QueryEntity{Path: k} @@ -132,7 +138,11 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { switch args.Values { case JSONValue: t := getValue(e.backing, modTimeKey) - s := JSON{ModTime: t, Hash: fmt.Sprintf("%x", sha512.Sum512([]byte(val)))} + data := val + if jsonHash { + data = fmt.Sprintf("%x", sha512.Sum512([]byte(val))) + } + s := JSON{ModTime: t, Data: data} m, err := json.Marshal(s) if err != nil { return nil, err diff --git a/internal/backend/query_test.go b/internal/backend/query_test.go @@ -90,7 +90,7 @@ func TestValueModes(t *testing.T) { if err := json.Unmarshal([]byte(q.Value), &m); err != nil { t.Errorf("no error: %v", err) } - if m.Hash != "44276ba24db13df5568aa6db81e0190ab9d35d2168dce43dca61e628f5c666b1d8b091f1dda59c2359c86e7d393d59723a421d58496d279031e7f858c11d893e" { + if m.Data != "44276ba24db13df5568aa6db81e0190ab9d35d2168dce43dca61e628f5c666b1d8b091f1dda59c2359c86e7d393d59723a421d58496d279031e7f858c11d893e" { t.Errorf("invalid result value: %s", q.Value) } if len(m.ModTime) < 20 { @@ -111,7 +111,20 @@ func TestValueModes(t *testing.T) { if err := json.Unmarshal([]byte(q.Value), &m); err != nil { t.Errorf("no error: %v", err) } - if len(m.ModTime) < 20 || m.Hash == "" { + if len(m.ModTime) < 20 || m.Data == "" { + t.Errorf("invalid json: %v", m) + } + os.Setenv("LOCKBOX_JSON_PLAINTEXT", "yes") + defer os.Clearenv() + q, err = fullSetup(t, true).Get("test/test/abc", backend.JSONValue) + if err != nil { + t.Errorf("no error: %v", err) + } + m = backend.JSON{} + if err := json.Unmarshal([]byte(q.Value), &m); err != nil { + t.Errorf("no error: %v", err) + } + if len(m.ModTime) < 20 || m.Data != "tedst" { t.Errorf("invalid json: %v", m) } } diff --git a/internal/backend/types.go b/internal/backend/types.go @@ -56,7 +56,7 @@ type ( // JSON is an entry as a JSON string JSON struct { ModTime string `json:"modtime"` - Hash string `json:"hash"` + Data string `json:"data"` } ) diff --git a/internal/cli/core_test.go b/internal/cli/core_test.go @@ -14,7 +14,7 @@ func TestUsage(t *testing.T) { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = cli.Usage(true) - if len(u) != 82 { + if len(u) != 83 { t.Errorf("invalid verbose usage, out of date? %d", len(u)) } for _, usage := range u { diff --git a/internal/inputs/env.go b/internal/inputs/env.go @@ -66,6 +66,8 @@ const ( ModTimeFormat = time.RFC3339 // MaxTOTPTimeDefault is the max TOTP time to run (default) MaxTOTPTimeDefault = "120" + // JSONPlainTextEnv toggles plain text on for JSON outputs + JSONPlainTextEnv = prefixKey + "JSON_PLAINTEXT" ) var isYesNoArgs = []string{env.Yes, env.No} @@ -226,6 +228,11 @@ func IsInteractive() (bool, error) { return isYesNoEnv(true, interactiveEnv) } +// IsJSONPlainText indicates if JSON should plaintext values (not hashed) +func IsJSONPlainText() (bool, error) { + return isYesNoEnv(false, JSONPlainTextEnv) +} + // TOTPToken gets the name of the totp special case tokens func TOTPToken() string { return env.GetOrDefault(fieldTOTPEnv, defaultTOTPField) @@ -277,5 +284,6 @@ func ListEnvironmentVariables(showValues bool) []string { results = append(results, e.formatEnvironmentVariable(false, clipOSC52Env, env.No, "enable OSC52 clipboard mode", isYesNoArgs)) results = append(results, e.formatEnvironmentVariable(false, KeyFileEnv, "", "additional keyfile to access/protect the database", []string{"keyfile"})) results = append(results, e.formatEnvironmentVariable(false, ModTimeEnv, ModTimeFormat, fmt.Sprintf("input modification time to set for the entry\n(expected format: %s)", ModTimeFormat), []string{"modtime"})) + results = append(results, e.formatEnvironmentVariable(false, JSONPlainTextEnv, env.No, "JSON output will show values as plaintext (not hashed)", isYesNoArgs)) return results } diff --git a/internal/inputs/env_test.go b/internal/inputs/env_test.go @@ -53,6 +53,10 @@ func TestIsReadOnly(t *testing.T) { checkYesNo("LOCKBOX_READONLY", t, inputs.IsReadOnly, false) } +func TestIsJSONPlaintext(t *testing.T) { + checkYesNo("LOCKBOX_JSON_PLAINTEXT", t, inputs.IsJSONPlainText, false) +} + func TestIsOSC52(t *testing.T) { checkYesNo("LOCKBOX_CLIP_OSC52", t, inputs.IsClipOSC52, false) } @@ -112,7 +116,7 @@ func TestListVariables(t *testing.T) { known[trim] = struct{}{} } l := len(known) - if l != 20 { + if l != 21 { t.Errorf("invalid env count, outdated? %d", l) } } diff --git a/tests/expected.log b/tests/expected.log @@ -15,15 +15,15 @@ keys2/k/three { "key/a/one": { "modtime": "XXXX-XX-XX", - "hash": "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff" + "data": "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff" }, "keys/k/one2": { "modtime": "XXXX-XX-XX", - "hash": "6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9" + "data": "6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9" }, "keys2/k/three": { "modtime": "XXXX-XX-XX", - "hash": "132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f" + "data": "132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f" } } @@ -33,7 +33,7 @@ test4 { "keys2/k/three": { "modtime": "XXXX-XX-XX", - "hash": "132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f" + "data": "132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f" } } @@ -44,19 +44,19 @@ XXXXXX { "key/a/one": { "modtime": "XXXX-XX-XX", - "hash": "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff" + "data": "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff" }, "keys/k/one2": { "modtime": "XXXX-XX-XX", - "hash": "6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9" + "data": "6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9" }, "keys2/k/three": { "modtime": "XXXX-XX-XX", - "hash": "132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f" + "data": "132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f" }, "test/k/totp": { "modtime": "XXXX-XX-XX", - "hash": "7ef183065ba70aaa417b87ea0a96b7e550a938a52440c640a07537f7794d8a89e50078eca6a7cbcfacabd97a2db06d11e82ddf7556ca909c4df9fc0d006013b1" + "data": "7ef183065ba70aaa417b87ea0a96b7e550a938a52440c640a07537f7794d8a89e50078eca6a7cbcfacabd97a2db06d11e82ddf7556ca909c4df9fc0d006013b1" } } delete entry? (y/N) @@ -127,5 +127,11 @@ proceed with rekey? (y/N) rekeying: keys/k/one2 keys/k/one2 test2 +{ + "keys/k/one2": { + "modtime": "XXXX-XX-XX", + "data": "test2" + } +} clipboard will clear in 5 seconds Wrong password? HMAC-SHA256 of header mismatching diff --git a/tests/run.sh b/tests/run.sh @@ -99,6 +99,8 @@ _rekey() { echo ${LB_BINARY} ls ${LB_BINARY} show keys/k/one2 + export LOCKBOX_JSON_PLAINTEXT=yes + ${LB_BINARY} json k } _clipboard() {