-
-
Notifications
You must be signed in to change notification settings - Fork 481
/
edit.go
147 lines (119 loc) · 4.26 KB
/
edit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package action
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/gopasspw/gopass/internal/action/exit"
"github.com/gopasspw/gopass/internal/audit"
"github.com/gopasspw/gopass/internal/config"
"github.com/gopasspw/gopass/internal/editor"
"github.com/gopasspw/gopass/internal/hook"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/store"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/gopass/secrets"
"github.com/gopasspw/gopass/pkg/pwgen"
"github.com/gopasspw/gopass/pkg/termio"
"github.com/urfave/cli/v2"
)
// Edit the content of a password file.
func (s *Action) Edit(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
name := c.Args().First()
if name == "" {
return exit.Error(exit.Usage, nil, "Usage: %s edit secret", s.Name)
}
if err := hook.Invoke(ctx, "edit.pre-hook", name); err != nil {
return exit.Error(exit.Hook, err, "edit.pre-hook failed: %s", err)
}
if err := s.Store.CheckRecipients(ctx, name); err != nil {
return exit.Error(exit.Recipients, err, "Invalid recipients detected: %s", err)
}
if err := s.edit(ctx, c, name); err != nil {
return err
}
return hook.InvokeRoot(ctx, "edit.post-hook", name, s.Store)
}
func (s *Action) edit(ctx context.Context, c *cli.Context, name string) error {
ed := editor.Path(c)
// get existing content or generate new one from a template.
name, content, changed, err := s.editGetContent(ctx, name, c.Bool("create"))
if err != nil {
return err
}
// invoke the editor to let the user edit the content.
newContent, err := editor.Invoke(ctx, ed, content)
if err != nil {
return exit.Error(exit.Unknown, err, "failed to invoke editor: %s", err)
}
return s.editUpdate(ctx, name, content, newContent, changed, ed)
}
func (s *Action) editUpdate(ctx context.Context, name string, content, nContent []byte, changed bool, ed string) error {
// If content is equal, nothing changed, exiting.
if bytes.Equal(content, nContent) && !changed {
return nil
}
nSec := secrets.ParseAKV(nContent)
// if the secret has a password, we check its strength.
if pw := nSec.Password(); pw != "" {
audit.Single(ctx, pw)
}
// write result (back) to store.
if err := s.Store.Set(ctxutil.WithCommitMessage(ctx, fmt.Sprintf("Edited with %s", ed)), name, nSec); err != nil {
if !errors.Is(err, store.ErrMeaninglessWrite) {
return exit.Error(exit.Encrypt, err, "failed to encrypt secret %s: %s", name, err)
}
out.Warningf(ctx, "The new value of the password is equal to its current value. Not writing it again.")
}
return nil
}
func (s *Action) editGetContent(ctx context.Context, name string, create bool) (string, []byte, bool, error) {
if !s.Store.Exists(ctx, name) && !create && !config.Bool(ctx, "edit.auto-create") {
var err error
name, err = s.editFindName(ctx, name)
if err != nil {
return "", nil, false, err
}
}
// edit existing entry.
if s.Store.Exists(ctx, name) {
// we make sure we are not parsing the content of the file when editing.
sec, err := s.Store.Get(ctxutil.WithShowParsing(ctx, false), name)
if err != nil {
return name, nil, false, exit.Error(exit.Decrypt, err, "failed to decrypt %s: %s", name, err)
}
return name, sec.Bytes(), false, nil
}
if !create {
out.Warningf(ctx, "Entry %s not found. Creating new secret ...", name)
}
// load template if it exists.
pwLength, _ := config.DefaultPasswordLengthFromEnv(ctx)
if content, found := s.renderTemplate(ctx, name, []byte(pwgen.GeneratePassword(pwLength, false))); found {
return name, content, true, nil
}
// new entry, no template.
return name, nil, false, nil
}
func (s *Action) editFindName(ctx context.Context, name string) (string, error) {
newName := ""
// capture only the name of the selected secret.
cb := func(ctx context.Context, c *cli.Context, selectedName string, recurse bool) error {
newName = selectedName
return nil
}
if err := s.find(ctx, nil, name, cb, false); err != nil {
debug.Log("failed to find secret %s: %s", name, err)
return name, err
}
cont, err := termio.AskForBool(ctx, fmt.Sprintf("Secret does not exist %q. Found possible match in %q. Edit existing entry?", name, newName), true)
if err != nil {
return name, err
}
if cont {
return newName, nil
}
return name, nil
}