forked from gopasspw/gopass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
edit.go
121 lines (102 loc) · 3.33 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
package action
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"github.com/fatih/color"
"github.com/justwatchcom/gopass/store/secret"
"github.com/justwatchcom/gopass/store/sub"
"github.com/justwatchcom/gopass/utils/fsutil"
"github.com/justwatchcom/gopass/utils/out"
"github.com/justwatchcom/gopass/utils/pwgen"
"github.com/justwatchcom/gopass/utils/tpl"
shellquote "github.com/kballard/go-shellquote"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
// Edit the content of a password file
func (s *Action) Edit(ctx context.Context, c *cli.Context) error {
name := c.Args().First()
if name == "" {
return errors.Errorf("provide a secret name")
}
var content []byte
var changed bool
if s.Store.Exists(ctx, name) {
sec, err := s.Store.Get(ctx, name)
if err != nil {
return errors.Errorf("failed to decrypt %s: %v", name, err)
}
content, err = sec.Bytes()
if err != nil {
return errors.Errorf("failed to decode %s: %v", name, err)
}
} else if tmpl, found := s.Store.LookupTemplate(ctx, name); found {
changed = true
// load template if it exists
content = []byte(pwgen.GeneratePassword(defaultLength, false))
if nc, err := tpl.Execute(ctx, string(tmpl), name, content, s.Store); err == nil {
content = nc
} else {
fmt.Printf("failed to execute template: %s\n", err)
}
}
nContent, err := s.editor(ctx, content)
if err != nil {
return s.exitError(ctx, ExitUnknown, err, "failed to invoke editor: %s", err)
}
// If content is equal, nothing changed, exiting
if bytes.Equal(content, nContent) && !changed {
return nil
}
nSec, err := secret.Parse(nContent)
if err != nil {
out.Red(ctx, "WARNING: Invalid YAML: %s", err)
}
if pw := nSec.Password(); pw != "" {
printAuditResult(ctx, pw)
}
return s.Store.Set(sub.WithReason(ctx, fmt.Sprintf("Edited with %s", getEditor())), name, nSec)
}
func (s *Action) editor(ctx context.Context, content []byte) ([]byte, error) {
editor := getEditor()
tmpfile, err := fsutil.TempFile(ctx, "gopass-edit")
if err != nil {
return []byte{}, errors.Errorf("failed to create tmpfile %s: %s", editor, err)
}
defer func() {
if err := tmpfile.Remove(ctx); err != nil {
color.Red("Failed to remove tempfile at %s: %s", tmpfile.Name(), err)
}
}()
if _, err := tmpfile.Write(content); err != nil {
return []byte{}, errors.Errorf("failed to write tmpfile to start with %s %v: %s", editor, tmpfile.Name(), err)
}
if err := tmpfile.Close(); err != nil {
return []byte{}, errors.Errorf("failed to close tmpfile to start with %s %v: %s", editor, tmpfile.Name(), err)
}
cmdArgs, err := shellquote.Split(editor)
if err != nil {
return []byte{}, errors.Errorf("failed to parse EDITOR command `%s`", editor)
}
editor = cmdArgs[0]
args := append(cmdArgs[1:], tmpfile.Name())
cmd := exec.Command(editor, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return []byte{}, errors.Errorf("failed to run %s with %s file", editor, tmpfile.Name())
}
nContent, err := ioutil.ReadFile(tmpfile.Name())
if err != nil {
return []byte{}, errors.Errorf("failed to read from tmpfile: %v", err)
}
// enforce unix line endings in the password store
nContent = bytes.Replace(nContent, []byte("\r\n"), []byte("\n"), -1)
nContent = bytes.Replace(nContent, []byte("\r"), []byte("\n"), -1)
return nContent, nil
}