-
-
Notifications
You must be signed in to change notification settings - Fork 485
/
edit.go
110 lines (93 loc) · 3.23 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
package action
import (
"bytes"
"context"
"fmt"
"github.com/gopasspw/gopass/internal/audit"
"github.com/gopasspw/gopass/internal/editor"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/ctxutil"
"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 ExitError(ExitUsage, nil, "Usage: %s edit secret", s.Name)
}
return s.edit(ctx, c, name)
}
func (s *Action) edit(ctx context.Context, c *cli.Context, name string) error {
ed := editor.Path(c)
if err := editor.Check(ctx, ed); err != nil {
out.Warningf(ctx, "Failed to check editor config: %s", err)
}
// 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 ExitError(ExitUnknown, 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.ParsePlain(nContent)
// if the secret has a password, we check it's 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 {
return ExitError(ExitEncrypt, err, "failed to encrypt secret %s: %s", name, err)
}
return nil
}
func (s *Action) editGetContent(ctx context.Context, name string, create bool) (string, []byte, bool, error) {
if !s.Store.Exists(ctx, name) && !create {
newName := ""
// capture only the name of the selected secret
cb := func(ctx context.Context, c *cli.Context, name string, recurse bool) error {
newName = name
return nil
}
if err := s.find(ctx, nil, name, cb, false); err == nil {
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 "", nil, false, err
}
if cont {
name = newName
}
}
}
// 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, ExitError(ExitDecrypt, 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
if content, found := s.renderTemplate(ctx, name, []byte(pwgen.GeneratePassword(defaultLength, false))); found {
return name, content, true, nil
}
// new entry, no template
return name, nil, false, nil
}