From 997e8300570c6a68809278ed9ce35c1a5ead6a82 Mon Sep 17 00:00:00 2001 From: Dominik Schulz Date: Thu, 15 Mar 2018 15:36:26 +0100 Subject: [PATCH] Implement secrets interface (#714) --- action/insert.go | 9 +++++++-- action/otp.go | 4 ++-- action/show.go | 3 +-- store/root/git.go | 4 ++-- store/root/read.go | 6 +++--- store/root/write.go | 6 +++--- store/secret.go | 16 ++++++++++++++++ store/secret/secret.go | 9 ++++++--- store/sub/git.go | 2 +- store/sub/read.go | 2 +- store/sub/write.go | 3 +-- utils/jsonapi/api_test.go | 3 ++- utils/jsonapi/responses.go | 3 ++- utils/jsonapi/responses_test.go | 3 ++- utils/tpl/template.go | 4 ++-- utils/tpl/template_test.go | 3 ++- 16 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 store/secret.go diff --git a/action/insert.go b/action/insert.go index d74c4f17f8..c6ed318375 100644 --- a/action/insert.go +++ b/action/insert.go @@ -7,6 +7,7 @@ import ( "io" "os" + "github.com/justwatchcom/gopass/store" "github.com/justwatchcom/gopass/store/secret" "github.com/justwatchcom/gopass/store/sub" "github.com/justwatchcom/gopass/utils/ctxutil" @@ -97,13 +98,15 @@ func (s *Action) insertStdin(ctx context.Context, name string, content []byte) e } func (s *Action) insertSingle(ctx context.Context, name, pw string) error { - sec := &secret.Secret{} + var sec store.Secret if s.Store.Exists(ctx, name) { var err error sec, err = s.Store.Get(ctx, name) if err != nil { return exitError(ctx, ExitDecrypt, err, "failed to decrypt existing secret: %s", err) } + } else { + sec = &secret.Secret{} } sec.SetPassword(pw) printAuditResult(ctx, sec.Password()) @@ -123,13 +126,15 @@ func (s *Action) insertYAML(ctx context.Context, name, key string, content []byt content = []byte(pw) } - sec := secret.New("", "") + var sec store.Secret if s.Store.Exists(ctx, name) { var err error sec, err = s.Store.Get(ctx, name) if err != nil { return exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err) } + } else { + sec = &secret.Secret{} } if err := sec.SetValue(key, string(content)); err != nil { return exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err) diff --git a/action/otp.go b/action/otp.go index b95a43928c..2f7594e428 100644 --- a/action/otp.go +++ b/action/otp.go @@ -9,7 +9,7 @@ import ( "time" "github.com/gokyle/twofactor" - "github.com/justwatchcom/gopass/store/secret" + "github.com/justwatchcom/gopass/store" "github.com/justwatchcom/gopass/utils/out" "github.com/urfave/cli" ) @@ -69,7 +69,7 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip bool) error { return nil } -func otpData(ctx context.Context, name string, sec *secret.Secret) (twofactor.OTP, string, error) { +func otpData(ctx context.Context, name string, sec store.Secret) (twofactor.OTP, string, error) { otpURL := "" // check body for _, line := range strings.Split(sec.Body(), "\n") { diff --git a/action/show.go b/action/show.go index 3a4a57a157..c20e11f5bf 100644 --- a/action/show.go +++ b/action/show.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/justwatchcom/gopass/store" - "github.com/justwatchcom/gopass/store/secret" "github.com/justwatchcom/gopass/utils/ctxutil" "github.com/justwatchcom/gopass/utils/out" "github.com/justwatchcom/gopass/utils/qrcon" @@ -76,7 +75,7 @@ func (s *Action) showHandleRevision(ctx context.Context, c *cli.Context, name, k return s.showHandleOutput(ctx, name, key, sec) } -func (s *Action) showHandleOutput(ctx context.Context, name, key string, sec *secret.Secret) error { +func (s *Action) showHandleOutput(ctx context.Context, name, key string, sec store.Secret) error { var content string switch { diff --git a/store/root/git.go b/store/root/git.go index 69dff16870..edb739ffa0 100644 --- a/store/root/git.go +++ b/store/root/git.go @@ -5,7 +5,7 @@ import ( "github.com/blang/semver" "github.com/justwatchcom/gopass/backend" - "github.com/justwatchcom/gopass/store/secret" + "github.com/justwatchcom/gopass/store" ) // Sync returns the sync backend @@ -59,7 +59,7 @@ func (r *Store) ListRevisions(ctx context.Context, name string) ([]backend.Revis } // GetRevision will try to retrieve the given revision from the sync backend -func (r *Store) GetRevision(ctx context.Context, name, revision string) (*secret.Secret, error) { +func (r *Store) GetRevision(ctx context.Context, name, revision string) (store.Secret, error) { ctx, store, name := r.getStore(ctx, name) return store.GetRevision(ctx, name, revision) } diff --git a/store/root/read.go b/store/root/read.go index 1e913497d8..0bd21df9a2 100644 --- a/store/root/read.go +++ b/store/root/read.go @@ -3,18 +3,18 @@ package root import ( "context" - "github.com/justwatchcom/gopass/store/secret" + "github.com/justwatchcom/gopass/store" ) // Get returns the plaintext of a single key -func (r *Store) Get(ctx context.Context, name string) (*secret.Secret, error) { +func (r *Store) Get(ctx context.Context, name string) (store.Secret, error) { // forward to substore ctx, store, name := r.getStore(ctx, name) return store.Get(ctx, name) } // GetContext returns the plaintext and the context of a single key -func (r *Store) GetContext(ctx context.Context, name string) (*secret.Secret, context.Context, error) { +func (r *Store) GetContext(ctx context.Context, name string) (store.Secret, context.Context, error) { // forward to substore ctx, store, name := r.getStore(ctx, name) sec, err := store.Get(ctx, name) diff --git a/store/root/write.go b/store/root/write.go index be75a90317..a955c1efaf 100644 --- a/store/root/write.go +++ b/store/root/write.go @@ -3,17 +3,17 @@ package root import ( "context" - "github.com/justwatchcom/gopass/store/secret" + "github.com/justwatchcom/gopass/store" ) // Set encodes and write the ciphertext of one entry to disk -func (r *Store) Set(ctx context.Context, name string, sec *secret.Secret) error { +func (r *Store) Set(ctx context.Context, name string, sec store.Secret) error { ctx, store, name := r.getStore(ctx, name) return store.Set(ctx, name, sec) } // SetContext encodes and write the ciphertext of one entry to disk and propagate the context -func (r *Store) SetContext(ctx context.Context, name string, sec *secret.Secret) (context.Context, error) { +func (r *Store) SetContext(ctx context.Context, name string, sec store.Secret) (context.Context, error) { ctx, store, name := r.getStore(ctx, name) return ctx, store.Set(ctx, name, sec) } diff --git a/store/secret.go b/store/secret.go new file mode 100644 index 0000000000..08df6f29f6 --- /dev/null +++ b/store/secret.go @@ -0,0 +1,16 @@ +package store + +// Secret is an in-memory secret with a key/value part +type Secret interface { + Body() string + Bytes() ([]byte, error) + Data() map[string]interface{} + DeleteKey(string) error + Equal(Secret) bool + Password() string + SetBody(string) error + SetPassword(string) + SetValue(string, string) error + String() string + Value(string) (string, error) +} diff --git a/store/secret/secret.go b/store/secret/secret.go index eb63bb049f..d9b13482a1 100644 --- a/store/secret/secret.go +++ b/store/secret/secret.go @@ -3,8 +3,11 @@ package secret import ( "bytes" "os" + "reflect" "strings" "sync" + + "github.com/justwatchcom/gopass/store" ) var debug bool @@ -115,11 +118,11 @@ func (s *Secret) SetBody(b string) error { } // Equal returns true if two secrets are equal -func (s *Secret) Equal(other *Secret) bool { - if s == nil && other == nil { +func (s *Secret) Equal(other store.Secret) bool { + if s == nil && (other == nil || reflect.ValueOf(other).IsNil()) { return true } - if s == nil || other == nil { + if s == nil || other == nil || reflect.ValueOf(other).IsNil() { return false } diff --git a/store/sub/git.go b/store/sub/git.go index 0af0cd400f..f1549a6726 100644 --- a/store/sub/git.go +++ b/store/sub/git.go @@ -77,7 +77,7 @@ func (s *Store) ListRevisions(ctx context.Context, name string) ([]backend.Revis } // GetRevision will retrieve a single revision from the backend -func (s *Store) GetRevision(ctx context.Context, name, revision string) (*secret.Secret, error) { +func (s *Store) GetRevision(ctx context.Context, name, revision string) (store.Secret, error) { p := s.passfile(name) ciphertext, err := s.rcs.GetRevision(ctx, p, revision) if err != nil { diff --git a/store/sub/read.go b/store/sub/read.go index e777c5b3ac..71701211a0 100644 --- a/store/sub/read.go +++ b/store/sub/read.go @@ -9,7 +9,7 @@ import ( ) // Get returns the plaintext of a single key -func (s *Store) Get(ctx context.Context, name string) (*secret.Secret, error) { +func (s *Store) Get(ctx context.Context, name string) (store.Secret, error) { p := s.passfile(name) ciphertext, err := s.storage.Get(ctx, p) diff --git a/store/sub/write.go b/store/sub/write.go index de3d54b66b..d36e577fa2 100644 --- a/store/sub/write.go +++ b/store/sub/write.go @@ -6,14 +6,13 @@ import ( "strings" "github.com/justwatchcom/gopass/store" - "github.com/justwatchcom/gopass/store/secret" "github.com/justwatchcom/gopass/utils/ctxutil" "github.com/justwatchcom/gopass/utils/out" "github.com/pkg/errors" ) // Set encodes and writes the cipertext of one entry to disk -func (s *Store) Set(ctx context.Context, name string, sec *secret.Secret) error { +func (s *Store) Set(ctx context.Context, name string, sec store.Secret) error { if strings.Contains(name, "//") { return errors.Errorf("invalid secret name: %s", name) } diff --git a/utils/jsonapi/api_test.go b/utils/jsonapi/api_test.go index bcd4e586a5..7cbc4e0103 100644 --- a/utils/jsonapi/api_test.go +++ b/utils/jsonapi/api_test.go @@ -14,6 +14,7 @@ import ( "github.com/justwatchcom/gopass/backend" "github.com/justwatchcom/gopass/config" + "github.com/justwatchcom/gopass/store" "github.com/justwatchcom/gopass/store/root" "github.com/justwatchcom/gopass/store/secret" "github.com/stretchr/testify/assert" @@ -21,7 +22,7 @@ import ( type storedSecret struct { Name []string - Secret *secret.Secret + Secret store.Secret } func TestRespondMessageBrokenInput(t *testing.T) { diff --git a/utils/jsonapi/responses.go b/utils/jsonapi/responses.go index 47c6d13bec..2978866a63 100644 --- a/utils/jsonapi/responses.go +++ b/utils/jsonapi/responses.go @@ -10,6 +10,7 @@ import ( "path" + "github.com/justwatchcom/gopass/store" "github.com/justwatchcom/gopass/store/secret" "github.com/justwatchcom/gopass/utils/pwgen" "github.com/pkg/errors" @@ -118,7 +119,7 @@ func (api *API) respondGetLogin(ctx context.Context, msgBytes []byte) error { }, api.Writer) } -func (api *API) getUsername(name string, sec *secret.Secret) string { +func (api *API) getUsername(name string, sec store.Secret) string { // look for a meta-data entry containing the username first for _, key := range []string{"login", "username", "user"} { value, err := sec.Value(key) diff --git a/utils/jsonapi/responses_test.go b/utils/jsonapi/responses_test.go index 04115cdd50..adcef88bb1 100644 --- a/utils/jsonapi/responses_test.go +++ b/utils/jsonapi/responses_test.go @@ -3,6 +3,7 @@ package jsonapi import ( "testing" + "github.com/justwatchcom/gopass/store" "github.com/justwatchcom/gopass/store/secret" ) @@ -10,7 +11,7 @@ func TestGetUsername(t *testing.T) { a := &API{} for _, tc := range []struct { Name string - Sec *secret.Secret + Sec store.Secret Out string }{ { diff --git a/utils/tpl/template.go b/utils/tpl/template.go index d94c492323..9954c41291 100644 --- a/utils/tpl/template.go +++ b/utils/tpl/template.go @@ -6,11 +6,11 @@ import ( "path/filepath" "text/template" - "github.com/justwatchcom/gopass/store/secret" + "github.com/justwatchcom/gopass/store" ) type kvstore interface { - Get(context.Context, string) (*secret.Secret, error) + Get(context.Context, string) (store.Secret, error) } type payload struct { diff --git a/utils/tpl/template_test.go b/utils/tpl/template_test.go index f11604682b..c9c0760e00 100644 --- a/utils/tpl/template_test.go +++ b/utils/tpl/template_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/justwatchcom/gopass/store" "github.com/justwatchcom/gopass/store/secret" ) type kvMock struct{} -func (k kvMock) Get(ctx context.Context, key string) (*secret.Secret, error) { +func (k kvMock) Get(ctx context.Context, key string) (store.Secret, error) { return secret.New("barfoo", "---\nbarkey: barvalue\n"), nil }