mirror of
https://github.com/gopasspw/gopass.git
synced 2025-12-08 19:24:54 +00:00
Refactor and cleanup codebase (#715)
This commit is contained in:
parent
997e830057
commit
da436e6a79
3
Makefile
3
Makefile
@ -180,6 +180,9 @@ codequality:
|
||||
@unconvert -v $(PKGS) || exit 1
|
||||
@printf '%s\n' '$(OK)'
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w $(GOFILES_NOVENDOR)
|
||||
|
||||
fuzz-gpg:
|
||||
mkdir -p workdir/gpg-cli/corpus
|
||||
go-fuzz-build github.com/justwatchcom/gopass/backend/gpg/cli
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
clipboardNotSupported = "WARNING: No clipboard available. Install xsel or xclip or use -p to print to console"
|
||||
)
|
||||
|
||||
func copyToClipboard(ctx context.Context, name string, content []byte) error {
|
||||
if clipboard.Unsupported {
|
||||
out.Yellow(ctx, clipboardNotSupported)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := clipboard.WriteAll(string(content)); err != nil {
|
||||
return errors.Wrapf(err, "failed to write to clipboard")
|
||||
}
|
||||
|
||||
if err := clearClipboard(ctx, content, ctxutil.GetClipTimeout(ctx)); err != nil {
|
||||
return errors.Wrapf(err, "failed to clear clipboard")
|
||||
}
|
||||
|
||||
out.Print(ctx, "Copied %s to clipboard. Will clear in %d seconds.", color.YellowString(name), ctxutil.GetClipTimeout(ctx))
|
||||
return nil
|
||||
}
|
||||
129
action/edit.go
129
action/edit.go
@ -1,129 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/pwgen"
|
||||
"github.com/justwatchcom/gopass/utils/tempfile"
|
||||
"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 exitError(ctx, ExitUsage, nil, "Usage: %s edit secret", s.Name)
|
||||
}
|
||||
|
||||
editor := getEditor(c)
|
||||
|
||||
var content []byte
|
||||
var changed bool
|
||||
if s.Store.Exists(ctx, name) {
|
||||
sec, err := s.Store.Get(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitDecrypt, err, "failed to decrypt %s: %s", name, err)
|
||||
}
|
||||
content, err = sec.Bytes()
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitDecrypt, err, "failed to decode %s: %s", 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.Fprintf(stdout, "failed to execute template: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
nContent, err := s.editor(ctx, editor, content)
|
||||
if err != nil {
|
||||
return 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)
|
||||
}
|
||||
|
||||
if err := s.Store.Set(sub.WithReason(ctx, fmt.Sprintf("Edited with %s", editor)), name, nSec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to encrypt secret %s: %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Action) editor(ctx context.Context, editor string, content []byte) ([]byte, error) {
|
||||
if !ctxutil.IsTerminal(ctx) {
|
||||
return nil, errors.New("need terminal")
|
||||
}
|
||||
|
||||
tmpfile, err := tempfile.New(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 = stdin
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
out.Debug(ctx, "editor - cmd: %s %+v - error: %+v", cmd.Path, cmd.Args, err)
|
||||
return []byte{}, errors.Errorf("failed to run %s with %s file: %s", editor, tmpfile.Name(), err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
121
action/otp.go
121
action/otp.go
@ -1,121 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gokyle/twofactor"
|
||||
"github.com/justwatchcom/gopass/store"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
// we might want to replace this with the currently un-exported step value
|
||||
// from twofactor.FromURL if it gets ever exported
|
||||
otpPeriod = 30
|
||||
)
|
||||
|
||||
// OTP implements OTP token handling for TOTP and HOTP
|
||||
func (s *Action) OTP(ctx context.Context, c *cli.Context) error {
|
||||
name := c.Args().First()
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "usage: %s otp [name]", s.Name)
|
||||
}
|
||||
qrf := c.String("qr")
|
||||
clip := c.Bool("clip")
|
||||
|
||||
return s.otp(ctx, name, qrf, clip)
|
||||
}
|
||||
|
||||
func (s *Action) otp(ctx context.Context, name, qrf string, clip bool) error {
|
||||
sec, err := s.Store.Get(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitDecrypt, err, "failed to get entry '%s': %s", name, err)
|
||||
}
|
||||
|
||||
otp, label, err := otpData(ctx, name, sec)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "No OTP entry found for %s: %s", name, err)
|
||||
}
|
||||
token := otp.OTP()
|
||||
|
||||
now := time.Now()
|
||||
t := now.Add(otpPeriod * time.Second)
|
||||
|
||||
expiresAt := time.Unix(t.Unix()+otpPeriod-(t.Unix()%otpPeriod), 0)
|
||||
secondsLeft := int(time.Until(expiresAt).Seconds())
|
||||
|
||||
if secondsLeft >= otpPeriod {
|
||||
secondsLeft = secondsLeft - otpPeriod
|
||||
}
|
||||
|
||||
out.Yellow(ctx, "%s lasts %ds \t|%s%s|", token, secondsLeft, strings.Repeat("-", otpPeriod-secondsLeft), strings.Repeat("=", secondsLeft))
|
||||
|
||||
if clip {
|
||||
if err := copyToClipboard(ctx, fmt.Sprintf("token for %s", name), []byte(token)); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to copy to clipboard: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if qrf != "" {
|
||||
return s.otpWriteQRFile(ctx, otp, label, qrf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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") {
|
||||
if strings.HasPrefix(line, "otpauth://") {
|
||||
otpURL = line
|
||||
break
|
||||
}
|
||||
}
|
||||
if otpURL != "" {
|
||||
return twofactor.FromURL(otpURL)
|
||||
}
|
||||
|
||||
// check yaml entry and fall back to password if we don't have one
|
||||
label := name
|
||||
secKey, err := sec.Value("totp")
|
||||
if err != nil {
|
||||
secKey = sec.Password()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(secKey, "otpauth://") {
|
||||
return twofactor.FromURL(secKey)
|
||||
}
|
||||
|
||||
otp, err := twofactor.NewGoogleTOTP(secKey)
|
||||
return otp, label, err
|
||||
}
|
||||
|
||||
func (s *Action) otpWriteQRFile(ctx context.Context, otp twofactor.OTP, label, file string) error {
|
||||
var qr []byte
|
||||
var err error
|
||||
switch otp.Type() {
|
||||
case twofactor.OATH_HOTP:
|
||||
hotp := otp.(*twofactor.HOTP)
|
||||
qr, err = hotp.QR(label)
|
||||
case twofactor.OATH_TOTP:
|
||||
totp := otp.(*twofactor.TOTP)
|
||||
qr, err = totp.QR(label)
|
||||
default:
|
||||
err = errors.New("QR codes can only be generated for OATH OTPs")
|
||||
}
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "%s", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(file, qr, 0600); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to write QR code: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/justwatchcom/gopass/utils/notify"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Unclip tries to erase the content of the clipboard
|
||||
func (s *Action) Unclip(ctx context.Context, c *cli.Context) error {
|
||||
if clipboard.Unsupported {
|
||||
return exitError(ctx, ExitIO, nil, clipboardNotSupported)
|
||||
}
|
||||
|
||||
force := c.Bool("force")
|
||||
timeout := c.Int("timeout")
|
||||
checksum := os.Getenv("GOPASS_UNCLIP_CHECKSUM")
|
||||
|
||||
time.Sleep(time.Second * time.Duration(timeout))
|
||||
|
||||
cur, err := clipboard.ReadAll()
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to read clipboard: %s", err)
|
||||
}
|
||||
|
||||
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(cur)))
|
||||
|
||||
if hash != checksum && !force {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := clipboard.WriteAll(""); err != nil {
|
||||
_ = notify.Notify(ctx, "gopass - clipboard", "Failed to clear clipboard")
|
||||
return exitError(ctx, ExitIO, err, "failed to write clipboard: %s", err)
|
||||
}
|
||||
|
||||
if err := s.clearClipboardHistory(ctx); err != nil {
|
||||
_ = notify.Notify(ctx, "gopass - clipboard", "Failed to clear clipboard history")
|
||||
return exitError(ctx, ExitIO, err, "failed to clear clipboard history: %s", err)
|
||||
}
|
||||
|
||||
if err := notify.Notify(ctx, "gopass -clipboard", "Clipboard has been cleared"); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to send unclip notification: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
package action
|
||||
|
||||
import "context"
|
||||
|
||||
func (s *Action) clearClipboardHistory(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestUnclip(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
c := cli.NewContext(app, fs, nil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.EqualError(t, act.Unclip(ctx, c), clipboardNotSupported)
|
||||
}
|
||||
226
action/xc.go
226
action/xc.go
@ -1,226 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/justwatchcom/gopass/backend/crypto/xc"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/utils/agent/client"
|
||||
"github.com/justwatchcom/gopass/utils/fsutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// XCListPrivateKeys list the XC private keys
|
||||
func (s *Action) XCListPrivateKeys(ctx context.Context, c *cli.Context) error {
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
kl, err := crypto.ListPrivateKeyIDs(ctx)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to list private keys")
|
||||
}
|
||||
|
||||
out.Print(ctx, "XC Private Keys:")
|
||||
for _, key := range kl {
|
||||
out.Print(ctx, "%s - %s", key, crypto.FormatKey(ctx, key))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// XCListPublicKeys lists the XC public keys
|
||||
func (s *Action) XCListPublicKeys(ctx context.Context, c *cli.Context) error {
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
kl, err := crypto.ListPublicKeyIDs(ctx)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to list public keys")
|
||||
}
|
||||
|
||||
out.Print(ctx, "XC Public Keys:")
|
||||
for _, key := range kl {
|
||||
out.Print(ctx, "%s - %s", key, crypto.FormatKey(ctx, key))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// XCGenerateKeypair generates a new XC keypair
|
||||
func (s *Action) XCGenerateKeypair(ctx context.Context, c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
email := c.String("email")
|
||||
pw := c.String("passphrase")
|
||||
|
||||
if name == "" {
|
||||
var err error
|
||||
name, err = termio.AskForString(ctx, "What is your full name?", "")
|
||||
if err != nil || name == "" {
|
||||
return exitError(ctx, ExitNoName, err, "please provide a name")
|
||||
}
|
||||
}
|
||||
if email == "" {
|
||||
var err error
|
||||
email, err = termio.AskForString(ctx, "What is your email?", "")
|
||||
if err != nil || name == "" {
|
||||
return exitError(ctx, ExitNoName, err, "please provide a email")
|
||||
}
|
||||
}
|
||||
if pw == "" {
|
||||
var err error
|
||||
pw, err = termio.AskForPassword(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to ask for password: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
return crypto.CreatePrivateKeyBatch(ctx, name, email, pw)
|
||||
}
|
||||
|
||||
// XCExportPublicKey exports an XC key
|
||||
func (s *Action) XCExportPublicKey(ctx context.Context, c *cli.Context) error {
|
||||
id := c.String("id")
|
||||
file := c.String("file")
|
||||
|
||||
if id == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need id")
|
||||
}
|
||||
if file == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if fsutil.IsFile(file) {
|
||||
return exitError(ctx, ExitUnknown, nil, "output file already exists")
|
||||
}
|
||||
|
||||
pk, err := crypto.ExportPublicKey(ctx, id)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to export key: %s", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(file, pk, 0600); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to write file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XCImportPublicKey imports an XC key
|
||||
func (s *Action) XCImportPublicKey(ctx context.Context, c *cli.Context) error {
|
||||
file := c.String("file")
|
||||
|
||||
if file == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if !fsutil.IsFile(file) {
|
||||
return exitError(ctx, ExitNotFound, nil, "input file not found")
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to read file")
|
||||
}
|
||||
return crypto.ImportPublicKey(ctx, buf)
|
||||
}
|
||||
|
||||
// XCRemoveKey removes a key from the keyring
|
||||
func (s *Action) XCRemoveKey(ctx context.Context, c *cli.Context) error {
|
||||
id := c.String("id")
|
||||
|
||||
if id == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need id")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
return crypto.RemoveKey(id)
|
||||
}
|
||||
|
||||
// XCExportPrivateKey exports an XC key
|
||||
func (s *Action) XCExportPrivateKey(ctx context.Context, c *cli.Context) error {
|
||||
id := c.String("id")
|
||||
file := c.String("file")
|
||||
|
||||
if id == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need id")
|
||||
}
|
||||
if file == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if fsutil.IsFile(file) {
|
||||
return exitError(ctx, ExitUnknown, nil, "output file already exists")
|
||||
}
|
||||
|
||||
pk, err := crypto.ExportPrivateKey(ctx, id)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to export key: %s", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(file, pk, 0600); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to write file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XCImportPrivateKey imports an XC key
|
||||
func (s *Action) XCImportPrivateKey(ctx context.Context, c *cli.Context) error {
|
||||
file := c.String("file")
|
||||
|
||||
if file == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if !fsutil.IsFile(file) {
|
||||
return exitError(ctx, ExitNotFound, nil, "input file not found")
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to read file")
|
||||
}
|
||||
return crypto.ImportPrivateKey(ctx, buf)
|
||||
}
|
||||
10
app.go
10
app.go
@ -5,11 +5,11 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/blang/semver"
|
||||
ap "github.com/justwatchcom/gopass/action"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
ap "github.com/justwatchcom/gopass/pkg/action"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
27
commands.go
27
commands.go
@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
ap "github.com/justwatchcom/gopass/action"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/utils/agent"
|
||||
"github.com/justwatchcom/gopass/utils/agent/client"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
ap "github.com/justwatchcom/gopass/pkg/action"
|
||||
"github.com/justwatchcom/gopass/pkg/action/xc"
|
||||
"github.com/justwatchcom/gopass/pkg/agent"
|
||||
"github.com/justwatchcom/gopass/pkg/agent/client"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -1012,25 +1013,25 @@ func getCommands(ctx context.Context, action *ap.Action, app *cli.App) []cli.Com
|
||||
{
|
||||
Name: "list-private-keys",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCListPrivateKeys(withGlobalFlags(ctx, c), c)
|
||||
return xc.ListPrivateKeys(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "list-public-keys",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCListPublicKeys(withGlobalFlags(ctx, c), c)
|
||||
return xc.ListPublicKeys(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "generate",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCGenerateKeypair(withGlobalFlags(ctx, c), c)
|
||||
return xc.GenerateKeypair(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "export",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCExportPublicKey(withGlobalFlags(ctx, c), c)
|
||||
return xc.ExportPublicKey(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
@ -1044,7 +1045,7 @@ func getCommands(ctx context.Context, action *ap.Action, app *cli.App) []cli.Com
|
||||
{
|
||||
Name: "import",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCImportPublicKey(withGlobalFlags(ctx, c), c)
|
||||
return xc.ImportPublicKey(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
@ -1058,7 +1059,7 @@ func getCommands(ctx context.Context, action *ap.Action, app *cli.App) []cli.Com
|
||||
{
|
||||
Name: "export-private-key",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCExportPrivateKey(withGlobalFlags(ctx, c), c)
|
||||
return xc.ExportPrivateKey(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
@ -1072,7 +1073,7 @@ func getCommands(ctx context.Context, action *ap.Action, app *cli.App) []cli.Com
|
||||
{
|
||||
Name: "import-private-key",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCImportPrivateKey(withGlobalFlags(ctx, c), c)
|
||||
return xc.ImportPrivateKey(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
@ -1086,7 +1087,7 @@ func getCommands(ctx context.Context, action *ap.Action, app *cli.App) []cli.Com
|
||||
{
|
||||
Name: "remove",
|
||||
Action: func(c *cli.Context) error {
|
||||
return action.XCRemoveKey(withGlobalFlags(ctx, c), c)
|
||||
return xc.RemoveKey(withGlobalFlags(ctx, c), c)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
|
||||
@ -10,12 +10,12 @@ import (
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/blang/semver"
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/action"
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/pkg/action"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -6,10 +6,10 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/backend/crypto/gpg"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/backend/crypto/gpg"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
|
||||
@ -5,9 +5,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/backend/crypto/gpg"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/backend/crypto/gpg"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
4
main.go
4
main.go
@ -12,8 +12,8 @@ import (
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/protect"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/protect"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -7,15 +7,14 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/store/root"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/root"
|
||||
)
|
||||
|
||||
var (
|
||||
stdin io.Reader = os.Stdin
|
||||
stdout io.Writer = os.Stdout
|
||||
stderr io.Writer = os.Stderr
|
||||
)
|
||||
|
||||
// Action knows everything to run gopass CLI actions
|
||||
@ -47,7 +46,7 @@ func newAction(ctx context.Context, cfg *config.Config, sv semver.Version) (*Act
|
||||
|
||||
store, err := root.New(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, exitError(ctx, ExitUnknown, err, "failed to init root store: %s", err)
|
||||
return nil, ExitError(ctx, ExitUnknown, err, "failed to init root store: %s", err)
|
||||
}
|
||||
act.Store = store
|
||||
|
||||
@ -8,8 +8,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
34
pkg/action/audit.go
Normal file
34
pkg/action/audit.go
Normal file
@ -0,0 +1,34 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/audit"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Audit validates passwords against common flaws
|
||||
func (s *Action) Audit(ctx context.Context, c *cli.Context) error {
|
||||
filter := c.Args().First()
|
||||
|
||||
t, err := s.Store.Tree(ctx)
|
||||
if err != nil {
|
||||
return ExitError(ctx, ExitList, err, "failed to get store tree: %s", err)
|
||||
}
|
||||
if filter != "" {
|
||||
subtree, err := t.FindFolder(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t = subtree
|
||||
}
|
||||
list := t.List(0)
|
||||
|
||||
if len(list) < 1 {
|
||||
out.Yellow(ctx, "No secrets found")
|
||||
return nil
|
||||
}
|
||||
|
||||
return audit.Batch(ctx, list, s.Store)
|
||||
}
|
||||
@ -7,10 +7,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -11,10 +11,10 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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/pkg/fsutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -28,7 +28,7 @@ const (
|
||||
func (s *Action) BinaryCat(ctx context.Context, c *cli.Context) error {
|
||||
name := c.Args().First()
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitNoName, nil, "need a name")
|
||||
return ExitError(ctx, ExitNoName, nil, "need a name")
|
||||
}
|
||||
if !strings.HasSuffix(name, BinarySuffix) {
|
||||
name += BinarySuffix
|
||||
@ -37,7 +37,7 @@ func (s *Action) BinaryCat(ctx context.Context, c *cli.Context) error {
|
||||
// handle pipe to stdin
|
||||
info, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to stat stdin: %s", err)
|
||||
return ExitError(ctx, ExitIO, err, "failed to stat stdin: %s", err)
|
||||
}
|
||||
|
||||
// if content is piped to stdin, read and save it
|
||||
@ -45,7 +45,7 @@ func (s *Action) BinaryCat(ctx context.Context, c *cli.Context) error {
|
||||
content := &bytes.Buffer{}
|
||||
|
||||
if written, err := io.Copy(content, os.Stdin); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "Failed to copy after %d bytes: %s", written, err)
|
||||
return ExitError(ctx, ExitIO, err, "Failed to copy after %d bytes: %s", written, err)
|
||||
}
|
||||
|
||||
return s.Store.Set(
|
||||
@ -57,7 +57,7 @@ func (s *Action) BinaryCat(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
buf, err := s.binaryGet(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitDecrypt, err, "failed to read secret: %s", err)
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to read secret: %s", err)
|
||||
}
|
||||
|
||||
out.Yellow(ctx, string(buf))
|
||||
@ -68,7 +68,7 @@ func (s *Action) BinaryCat(ctx context.Context, c *cli.Context) error {
|
||||
func (s *Action) BinarySum(ctx context.Context, c *cli.Context) error {
|
||||
name := c.Args().First()
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s binary sha256 name", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s binary sha256 name", s.Name)
|
||||
}
|
||||
if !strings.HasSuffix(name, BinarySuffix) {
|
||||
name += BinarySuffix
|
||||
@ -76,7 +76,7 @@ func (s *Action) BinarySum(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
buf, err := s.binaryGet(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitDecrypt, err, "failed to read secret: %s", err)
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to read secret: %s", err)
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
@ -93,7 +93,7 @@ func (s *Action) BinaryCopy(ctx context.Context, c *cli.Context) error {
|
||||
to := c.Args().Get(1)
|
||||
|
||||
if err := s.binaryCopy(ctx, from, to, false); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "%s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "%s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -106,7 +106,7 @@ func (s *Action) BinaryMove(ctx context.Context, c *cli.Context) error {
|
||||
to := c.Args().Get(1)
|
||||
|
||||
if err := s.binaryCopy(ctx, from, to, true); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "%s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "%s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -9,9 +9,9 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
12
pkg/action/clihelper.go
Normal file
12
pkg/action/clihelper.go
Normal file
@ -0,0 +1,12 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
)
|
||||
|
||||
// ConfirmRecipients asks the user to confirm a given set of recipients
|
||||
func (s *Action) ConfirmRecipients(ctx context.Context, name string, recipients []string) ([]string, error) {
|
||||
return cui.ConfirmRecipients(ctx, s.Store.Crypto(ctx, name), name, recipients)
|
||||
}
|
||||
@ -5,14 +5,15 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
"github.com/justwatchcom/gopass/backend/crypto/xc"
|
||||
gitcli "github.com/justwatchcom/gopass/backend/rcs/git/cli"
|
||||
"github.com/justwatchcom/gopass/backend/rcs/git/gogit"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/utils/fsutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
"github.com/justwatchcom/gopass/pkg/backend/crypto/xc"
|
||||
gitcli "github.com/justwatchcom/gopass/pkg/backend/rcs/git/cli"
|
||||
"github.com/justwatchcom/gopass/pkg/backend/rcs/git/gogit"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
"github.com/justwatchcom/gopass/pkg/fsutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -22,7 +23,7 @@ func (s *Action) Clone(ctx context.Context, c *cli.Context) error {
|
||||
ctx = backend.WithRCSBackendString(ctx, c.String("sync"))
|
||||
|
||||
if len(c.Args()) < 1 {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s clone repo [mount]", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s clone repo [mount]", s.Name)
|
||||
}
|
||||
|
||||
repo := c.Args()[0]
|
||||
@ -41,20 +42,20 @@ func (s *Action) clone(ctx context.Context, repo, mount, path string) error {
|
||||
path = config.PwStoreDir(mount)
|
||||
}
|
||||
if mount == "" && s.Store.Initialized(ctx) {
|
||||
return exitError(ctx, ExitAlreadyInitialized, nil, "Can not clone %s to the root store, as this store is already initialized. Please try cloning to a submount: `%s clone %s sub`", repo, s.Name, repo)
|
||||
return ExitError(ctx, ExitAlreadyInitialized, nil, "Can not clone %s to the root store, as this store is already initialized. Please try cloning to a submount: `%s clone %s sub`", repo, s.Name, repo)
|
||||
}
|
||||
|
||||
// clone repo
|
||||
switch backend.GetRCSBackend(ctx) {
|
||||
case backend.GoGit:
|
||||
if _, err := gogit.Clone(ctx, repo, path); err != nil {
|
||||
return exitError(ctx, ExitGit, err, "failed to clone repo '%s' to '%s'", repo, path)
|
||||
return ExitError(ctx, ExitGit, err, "failed to clone repo '%s' to '%s'", repo, path)
|
||||
}
|
||||
case backend.GitCLI:
|
||||
fallthrough
|
||||
default:
|
||||
if _, err := gitcli.Clone(ctx, repo, path); err != nil {
|
||||
return exitError(ctx, ExitGit, err, "failed to clone repo '%s' to '%s'", repo, path)
|
||||
return ExitError(ctx, ExitGit, err, "failed to clone repo '%s' to '%s'", repo, path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,10 +65,10 @@ func (s *Action) clone(ctx context.Context, repo, mount, path string) error {
|
||||
// add mount
|
||||
if mount != "" {
|
||||
if !s.Store.Initialized(ctx) {
|
||||
return exitError(ctx, ExitNotInitialized, nil, "Root-Store is not initialized. Clone or init root store first")
|
||||
return ExitError(ctx, ExitNotInitialized, nil, "Root-Store is not initialized. Clone or init root store first")
|
||||
}
|
||||
if err := s.Store.AddMount(ctx, mount, path); err != nil {
|
||||
return exitError(ctx, ExitMount, err, "Failed to add mount: %s", err)
|
||||
return ExitError(ctx, ExitMount, err, "Failed to add mount: %s", err)
|
||||
}
|
||||
out.Green(ctx, "Mounted password store %s at mount point `%s` ...", path, mount)
|
||||
s.cfg.Mounts[mount].Path.Crypto = backend.GetCryptoBackend(ctx)
|
||||
@ -81,7 +82,7 @@ func (s *Action) clone(ctx context.Context, repo, mount, path string) error {
|
||||
|
||||
// save new mount in config file
|
||||
if err := s.cfg.Save(); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "Failed to update config: %s", err)
|
||||
return ExitError(ctx, ExitIO, err, "Failed to update config: %s", err)
|
||||
}
|
||||
|
||||
// try to init git config
|
||||
@ -110,19 +111,19 @@ func (s *Action) clone(ctx context.Context, repo, mount, path string) error {
|
||||
func (s *Action) cloneGetGitConfig(ctx context.Context, name string) (string, string, error) {
|
||||
// for convenience, set defaults to user-selected values from available private keys
|
||||
// NB: discarding returned error since this is merely a best-effort look-up for convenience
|
||||
username, email, _ := s.askForGitConfigUser(ctx, name)
|
||||
username, email, _ := cui.AskForGitConfigUser(ctx, s.Store.Crypto(ctx, name), name)
|
||||
if username == "" {
|
||||
var err error
|
||||
username, err = termio.AskForString(ctx, color.CyanString("Please enter a user name for password store git config"), username)
|
||||
if err != nil {
|
||||
return "", "", exitError(ctx, ExitIO, err, "Failed to read user input: %s", err)
|
||||
return "", "", ExitError(ctx, ExitIO, err, "Failed to read user input: %s", err)
|
||||
}
|
||||
}
|
||||
if email == "" {
|
||||
var err error
|
||||
email, err = termio.AskForString(ctx, color.CyanString("Please enter an email address for password store git config"), email)
|
||||
if err != nil {
|
||||
return "", "", exitError(ctx, ExitIO, err, "Failed to read user input: %s", err)
|
||||
return "", "", ExitError(ctx, ExitIO, err, "Failed to read user input: %s", err)
|
||||
}
|
||||
}
|
||||
return username, email, nil
|
||||
@ -9,11 +9,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
git "github.com/justwatchcom/gopass/backend/rcs/git/cli"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
git "github.com/justwatchcom/gopass/pkg/backend/rcs/git/cli"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -6,8 +6,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
fishcomp "github.com/justwatchcom/gopass/utils/completion/fish"
|
||||
zshcomp "github.com/justwatchcom/gopass/utils/completion/zsh"
|
||||
fishcomp "github.com/justwatchcom/gopass/pkg/completion/fish"
|
||||
zshcomp "github.com/justwatchcom/gopass/pkg/completion/zsh"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -6,8 +6,8 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -23,11 +23,11 @@ func (s *Action) Config(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
if len(c.Args()) > 2 {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s config key value", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s config key value", s.Name)
|
||||
}
|
||||
|
||||
if err := s.setConfigValue(ctx, c.String("store"), c.Args()[0], c.Args()[1]); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "Error setting config value")
|
||||
return ExitError(ctx, ExitUnknown, err, "Error setting config value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -8,10 +8,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -13,7 +13,7 @@ func (s *Action) Copy(ctx context.Context, c *cli.Context) error {
|
||||
force := c.Bool("force")
|
||||
|
||||
if len(c.Args()) != 2 {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s cp old-path new-path", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s cp old-path new-path", s.Name)
|
||||
}
|
||||
|
||||
from := c.Args()[0]
|
||||
@ -24,17 +24,17 @@ func (s *Action) Copy(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
func (s *Action) copy(ctx context.Context, from, to string, force bool) error {
|
||||
if !s.Store.Exists(ctx, from) && !s.Store.IsDir(ctx, from) {
|
||||
return exitError(ctx, ExitNotFound, nil, "%s does not exist", from)
|
||||
return ExitError(ctx, ExitNotFound, nil, "%s does not exist", from)
|
||||
}
|
||||
|
||||
if !force {
|
||||
if s.Store.Exists(ctx, to) && !termio.AskForConfirmation(ctx, fmt.Sprintf("%s already exists. Overwrite it?", to)) {
|
||||
return exitError(ctx, ExitAborted, nil, "not overwriting your current secret")
|
||||
return ExitError(ctx, ExitAborted, nil, "not overwriting your current secret")
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Store.Copy(ctx, from, to); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to copy from '%s' to '%s'", from, to)
|
||||
return ExitError(ctx, ExitIO, err, "failed to copy from '%s' to '%s'", from, to)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -6,89 +6,29 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/cui"
|
||||
"github.com/justwatchcom/gopass/utils/fsutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/pwgen"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/clipboard"
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
"github.com/justwatchcom/gopass/pkg/fsutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/pwgen"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/martinhoefling/goxkcdpwgen/xkcdpwgen"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type createAction struct {
|
||||
order int
|
||||
name string
|
||||
fn func(context.Context, *cli.Context) error
|
||||
}
|
||||
|
||||
type createActions []createAction
|
||||
|
||||
func (ca createActions) Len() int {
|
||||
return len(ca)
|
||||
}
|
||||
|
||||
func (ca createActions) Less(i, j int) bool {
|
||||
return ca[i].order < ca[j].order
|
||||
}
|
||||
|
||||
func (ca createActions) Swap(i, j int) {
|
||||
ca[i], ca[j] = ca[j], ca[i]
|
||||
}
|
||||
|
||||
func (ca createActions) Selection() []string {
|
||||
sort.Sort(ca)
|
||||
keys := make([]string, 0, len(ca))
|
||||
for _, a := range ca {
|
||||
keys = append(keys, a.name)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (ca createActions) Run(ctx context.Context, c *cli.Context, i int) error {
|
||||
if len(ca) < i || i >= len(ca) {
|
||||
return exitError(ctx, ExitUnknown, nil, "action not found")
|
||||
}
|
||||
if ca[i].fn == nil {
|
||||
return exitError(ctx, ExitUnknown, nil, "action invalid")
|
||||
}
|
||||
return ca[i].fn(ctx, c)
|
||||
}
|
||||
|
||||
// Create displays the password creation wizard
|
||||
func (s *Action) Create(ctx context.Context, c *cli.Context) error {
|
||||
acts := createActions{
|
||||
{
|
||||
order: 1,
|
||||
name: "Website Login",
|
||||
fn: s.createWebsite,
|
||||
},
|
||||
{
|
||||
order: 2,
|
||||
name: "PIN Code (numerical)",
|
||||
fn: s.createPIN,
|
||||
},
|
||||
{
|
||||
order: 3,
|
||||
name: "Generic",
|
||||
fn: s.createGeneric,
|
||||
},
|
||||
{
|
||||
order: 4,
|
||||
name: "AWS Secret Key",
|
||||
fn: s.createAWS,
|
||||
},
|
||||
{
|
||||
order: 5,
|
||||
name: "GCP Service Account",
|
||||
fn: s.createGCP,
|
||||
},
|
||||
}
|
||||
acts := make(cui.Actions, 0, 5)
|
||||
acts = append(acts, cui.Action{Name: "Website Login", Fn: s.createWebsite})
|
||||
acts = append(acts, cui.Action{Name: "PIN Code (numerical)", Fn: s.createPIN})
|
||||
acts = append(acts, cui.Action{Name: "Generic", Fn: s.createGeneric})
|
||||
acts = append(acts, cui.Action{Name: "AWS Secret Key", Fn: s.createAWS})
|
||||
acts = append(acts, cui.Action{Name: "GCP Service Account", Fn: s.createGCP})
|
||||
act, sel := cui.GetSelection(ctx, "Please select the type of secret you would like to create", "<↑/↓> to change the selection, <→> to select, <ESC> to quit", acts.Selection())
|
||||
switch act {
|
||||
case "default":
|
||||
@ -96,7 +36,7 @@ func (s *Action) Create(ctx context.Context, c *cli.Context) error {
|
||||
case "show":
|
||||
return acts.Run(ctx, c, sel)
|
||||
default:
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +74,7 @@ func (s *Action) createWebsite(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
hostname := extractHostname(urlStr)
|
||||
if hostname == "" {
|
||||
return exitError(ctx, ExitUnknown, err, "Can not parse URL '%s'. Please use 'gopass edit' to manually create the secret", urlStr)
|
||||
return ExitError(ctx, ExitUnknown, err, "Can not parse URL '%s'. Please use 'gopass edit' to manually create the secret", urlStr)
|
||||
}
|
||||
|
||||
username, err = termio.AskForString(ctx, "Please enter the Username/Login", "")
|
||||
@ -161,7 +101,7 @@ func (s *Action) createWebsite(ctx context.Context, c *cli.Context) error {
|
||||
comment, _ = termio.AskForString(ctx, "Comments (optional)", "")
|
||||
|
||||
// select store
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
|
||||
// generate name, ask for override if already taken
|
||||
if store != "" {
|
||||
@ -183,7 +123,7 @@ func (s *Action) createWebsite(ctx context.Context, c *cli.Context) error {
|
||||
_ = sec.SetValue("username", username)
|
||||
_ = sec.SetValue("comment", comment)
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Created new entry"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
}
|
||||
|
||||
return s.createPrintOrCopy(ctx, c, name, password, genPw)
|
||||
@ -203,8 +143,8 @@ func (s *Action) createPrintOrCopy(ctx context.Context, c *cli.Context, name, pa
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := copyToClipboard(ctx, name, []byte(password)); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to copy to clipboard: %s", err)
|
||||
if err := clipboard.CopyTo(ctx, name, []byte(password)); err != nil {
|
||||
return ExitError(ctx, ExitIO, err, "failed to copy to clipboard: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -225,14 +165,14 @@ func (s *Action) createPIN(ctx context.Context, c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
if authority == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Authority must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Authority must not be empty")
|
||||
}
|
||||
application, err = termio.AskForString(ctx, "Please enter the entity (e.g. Credit Card) this PIN is for", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if application == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Application must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Application must not be empty")
|
||||
}
|
||||
genPw, err = termio.AskForBool(ctx, "Do you want to generate a new PIN?", true)
|
||||
if err != nil {
|
||||
@ -252,7 +192,7 @@ func (s *Action) createPIN(ctx context.Context, c *cli.Context) error {
|
||||
comment, _ = termio.AskForString(ctx, "Comments (optional)", "")
|
||||
|
||||
// select store
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
|
||||
// generate name, ask for override if already taken
|
||||
if store != "" {
|
||||
@ -269,7 +209,7 @@ func (s *Action) createPIN(ctx context.Context, c *cli.Context) error {
|
||||
_ = sec.SetValue("application", application)
|
||||
_ = sec.SetValue("comment", comment)
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Created new entry"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
}
|
||||
|
||||
return s.createPrintOrCopy(ctx, c, name, password, genPw)
|
||||
@ -291,14 +231,14 @@ func (s *Action) createAWS(ctx context.Context, c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
if account == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Account must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Account must not be empty")
|
||||
}
|
||||
username, err = termio.AskForString(ctx, "Please enter the name of the AWS IAM User this key belongs to", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if username == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Username must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Username must not be empty")
|
||||
}
|
||||
accesskey, err = termio.AskForString(ctx, "Please enter the Access Key ID (AWS_ACCESS_KEY_ID)", "")
|
||||
if err != nil {
|
||||
@ -311,7 +251,7 @@ func (s *Action) createAWS(ctx context.Context, c *cli.Context) error {
|
||||
region, _ = termio.AskForString(ctx, "Please enter the default Region (AWS_DEFAULT_REGION) (optional)", "")
|
||||
|
||||
// select store
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
|
||||
// generate name, ask for override if already taken
|
||||
if store != "" {
|
||||
@ -330,7 +270,7 @@ func (s *Action) createAWS(ctx context.Context, c *cli.Context) error {
|
||||
_ = sec.SetValue("accesskey", accesskey)
|
||||
_ = sec.SetValue("region", region)
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Created new entry"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -363,7 +303,7 @@ func (s *Action) createGCP(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
}
|
||||
if username == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Username must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Username must not be empty")
|
||||
}
|
||||
if project == "" {
|
||||
project, err = termio.AskForString(ctx, "Please enter the name of this GCP project", "")
|
||||
@ -372,11 +312,11 @@ func (s *Action) createGCP(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
}
|
||||
if project == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Project must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Project must not be empty")
|
||||
}
|
||||
|
||||
// select store
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
|
||||
// generate name, ask for override if already taken
|
||||
if store != "" {
|
||||
@ -391,7 +331,7 @@ func (s *Action) createGCP(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
sec := secret.New("", string(buf))
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Created new entry"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -427,7 +367,7 @@ func (s *Action) createGeneric(ctx context.Context, c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
if shortname == "" {
|
||||
return exitError(ctx, ExitUnknown, nil, "Name must not be empty")
|
||||
return ExitError(ctx, ExitUnknown, nil, "Name must not be empty")
|
||||
}
|
||||
genPw, err = termio.AskForBool(ctx, "Do you want to generate a new password?", true)
|
||||
if err != nil {
|
||||
@ -446,7 +386,7 @@ func (s *Action) createGeneric(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
// select store
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
|
||||
// generate name, ask for override if already taken
|
||||
if store != "" {
|
||||
@ -476,7 +416,7 @@ func (s *Action) createGeneric(ctx context.Context, c *cli.Context) error {
|
||||
_ = sec.SetValue(key, val)
|
||||
}
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Created new entry"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
}
|
||||
|
||||
return s.createPrintOrCopy(ctx, c, name, password, genPw)
|
||||
@ -10,10 +10,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -31,28 +31,6 @@ func TestExtractHostname(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateActions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cas := createActions{
|
||||
{
|
||||
order: 66,
|
||||
name: "bar",
|
||||
fn: func(context.Context, *cli.Context) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
order: 1,
|
||||
name: "foo",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, []string{"foo", "bar"}, cas.Selection())
|
||||
assert.Error(t, cas.Run(ctx, nil, 0))
|
||||
assert.NoError(t, cas.Run(ctx, nil, 1))
|
||||
assert.Error(t, cas.Run(ctx, nil, 2))
|
||||
assert.Error(t, cas.Run(ctx, nil, 66))
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -17,7 +17,7 @@ func (s *Action) Delete(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
name := c.Args().First()
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s rm name", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s rm name", s.Name)
|
||||
}
|
||||
|
||||
key := c.Args().Get(1)
|
||||
@ -34,7 +34,7 @@ func (s *Action) Delete(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
if recursive {
|
||||
if err := s.Store.Prune(ctx, name); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to prune '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to prune '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -49,7 +49,7 @@ func (s *Action) Delete(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
if err := s.Store.Delete(ctx, name); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "Can not delete '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitIO, err, "Can not delete '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -57,13 +57,13 @@ func (s *Action) Delete(ctx context.Context, c *cli.Context) error {
|
||||
func (s *Action) deleteKeyFromYAML(ctx context.Context, name, key string) error {
|
||||
sec, err := s.Store.Get(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "Can not delete key '%s' from '%s': %s", key, name, err)
|
||||
return ExitError(ctx, ExitIO, err, "Can not delete key '%s' from '%s': %s", key, name, err)
|
||||
}
|
||||
if err := sec.DeleteKey(key); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "Can not delete key '%s' from '%s': %s", key, name, err)
|
||||
return ExitError(ctx, ExitIO, err, "Can not delete key '%s' from '%s': %s", key, name, err)
|
||||
}
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Updated Key in YAML"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "Can not delete key '%s' from '%s': %s", key, name, err)
|
||||
return ExitError(ctx, ExitIO, err, "Can not delete key '%s' from '%s': %s", key, name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -7,10 +7,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
72
pkg/action/edit.go
Normal file
72
pkg/action/edit.go
Normal file
@ -0,0 +1,72 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/audit"
|
||||
"github.com/justwatchcom/gopass/pkg/editor"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/pwgen"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/tpl"
|
||||
"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 ExitError(ctx, ExitUsage, nil, "Usage: %s edit secret", s.Name)
|
||||
}
|
||||
|
||||
ed := editor.Path(c)
|
||||
|
||||
var content []byte
|
||||
var changed bool
|
||||
if s.Store.Exists(ctx, name) {
|
||||
sec, err := s.Store.Get(ctx, name)
|
||||
if err != nil {
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to decrypt %s: %s", name, err)
|
||||
}
|
||||
content, err = sec.Bytes()
|
||||
if err != nil {
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to decode %s: %s", 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.Fprintf(stdout, "failed to execute template: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
nContent, err := editor.Invoke(ctx, ed, content)
|
||||
if err != nil {
|
||||
return 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 != "" {
|
||||
audit.Single(ctx, pw)
|
||||
}
|
||||
|
||||
if err := s.Store.Set(sub.WithReason(ctx, fmt.Sprintf("Edited with %s", ed)), name, nSec); err != nil {
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to encrypt secret %s: %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -53,7 +53,8 @@ const (
|
||||
ExitGPG
|
||||
)
|
||||
|
||||
func exitError(ctx context.Context, exitCode int, err error, format string, args ...interface{}) error {
|
||||
// ExitError returns a user friendly CLI error
|
||||
func ExitError(ctx context.Context, exitCode int, err error, format string, args ...interface{}) error {
|
||||
if err != nil {
|
||||
out.Debug(ctx, "Stacktrace: %+v", err)
|
||||
}
|
||||
@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -19,7 +19,7 @@ func TestExitError(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
|
||||
assert.Error(t, exitError(ctx, ExitUnknown, fmt.Errorf("test"), "test"))
|
||||
assert.Error(t, ExitError(ctx, ExitUnknown, fmt.Errorf("test"), "test"))
|
||||
sv := buf.String()
|
||||
if !strings.Contains(sv, "Stacktrace") {
|
||||
t.Errorf("Should contain an stacktrace")
|
||||
@ -6,9 +6,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/cui"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/schollz/closestmatch"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -18,12 +18,12 @@ func (s *Action) Find(ctx context.Context, c *cli.Context) error {
|
||||
ctx = WithClip(ctx, c.Bool("clip"))
|
||||
|
||||
if !c.Args().Present() {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s find arg", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s find arg", s.Name)
|
||||
}
|
||||
|
||||
l, err := s.Store.List(ctx, 0)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
return ExitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
}
|
||||
|
||||
needle := strings.ToLower(c.Args().First())
|
||||
@ -44,7 +44,7 @@ func (s *Action) Find(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
// if there are still no results we abort
|
||||
if len(choices) < 1 {
|
||||
return exitError(ctx, ExitNotFound, nil, "no results found")
|
||||
return ExitError(ctx, ExitNotFound, nil, "no results found")
|
||||
}
|
||||
|
||||
// do not invoke wizard if not printing to terminal or if
|
||||
@ -83,7 +83,7 @@ func (s *Action) findSelection(ctx context.Context, c *cli.Context, choices []st
|
||||
}
|
||||
return s.show(ctx, c, needle, "", true)
|
||||
default:
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,10 +9,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -7,13 +7,14 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/pwgen"
|
||||
"github.com/justwatchcom/gopass/utils/pwgen/xkcdgen"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/clipboard"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/pwgen"
|
||||
"github.com/justwatchcom/gopass/pkg/pwgen/xkcdgen"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -39,14 +40,14 @@ func (s *Action) Generate(ctx context.Context, c *cli.Context) error {
|
||||
var err error
|
||||
name, err = termio.AskForString(ctx, "Which name do you want to use?", "")
|
||||
if err != nil || name == "" {
|
||||
return exitError(ctx, ExitNoName, err, "please provide a password name")
|
||||
return ExitError(ctx, ExitNoName, err, "please provide a password name")
|
||||
}
|
||||
}
|
||||
|
||||
// ask for confirmation before overwriting existing entry
|
||||
if !force { // don't check if it's force anyway
|
||||
if s.Store.Exists(ctx, name) && key == "" && !termio.AskForConfirmation(ctx, fmt.Sprintf("An entry already exists for %s. Overwrite the current password?", name)) {
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted. not overwriting your current password")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted. not overwriting your current password")
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +66,7 @@ func (s *Action) Generate(ctx context.Context, c *cli.Context) error {
|
||||
// if requested launch editor to add more data to the generated secret
|
||||
if (edit || ctxutil.IsAskForMore(ctx)) && termio.AskForConfirmation(ctx, fmt.Sprintf("Do you want to add more data for %s?", name)) {
|
||||
if err := s.Edit(ctx, c); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to edit '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to edit '%s': %s", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,8 +103,8 @@ func (s *Action) generateCopyOrPrint(ctx context.Context, c *cli.Context, name,
|
||||
return nil
|
||||
}
|
||||
if ctxutil.IsAutoClip(ctx) || c.Bool("clip") {
|
||||
if err := copyToClipboard(ctx, name, []byte(password)); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to copy to clipboard: %s", err)
|
||||
if err := clipboard.CopyTo(ctx, name, []byte(password)); err != nil {
|
||||
return ExitError(ctx, ExitIO, err, "failed to copy to clipboard: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -125,19 +126,19 @@ func (s *Action) generatePassword(ctx context.Context, c *cli.Context, length st
|
||||
question := "How long should the password be?"
|
||||
iv, err := termio.AskForInt(ctx, question, candidateLength)
|
||||
if err != nil {
|
||||
return "", exitError(ctx, ExitUsage, err, "password length must be a number")
|
||||
return "", ExitError(ctx, ExitUsage, err, "password length must be a number")
|
||||
}
|
||||
pwlen = iv
|
||||
} else {
|
||||
iv, err := strconv.Atoi(length)
|
||||
if err != nil {
|
||||
return "", exitError(ctx, ExitUsage, err, "password length must be a number")
|
||||
return "", ExitError(ctx, ExitUsage, err, "password length must be a number")
|
||||
}
|
||||
pwlen = iv
|
||||
}
|
||||
|
||||
if pwlen < 1 {
|
||||
return "", exitError(ctx, ExitUsage, nil, "password length must not be zero")
|
||||
return "", ExitError(ctx, ExitUsage, nil, "password length must not be zero")
|
||||
}
|
||||
|
||||
return pwgen.GeneratePassword(pwlen, symbols), nil
|
||||
@ -155,19 +156,19 @@ func (s *Action) generatePasswordXKCD(ctx context.Context, c *cli.Context, lengt
|
||||
question := "How many words should be combined to a password?"
|
||||
iv, err := termio.AskForInt(ctx, question, candidateLength)
|
||||
if err != nil {
|
||||
return "", exitError(ctx, ExitUsage, err, "password length must be a number")
|
||||
return "", ExitError(ctx, ExitUsage, err, "password length must be a number")
|
||||
}
|
||||
pwlen = iv
|
||||
} else {
|
||||
iv, err := strconv.Atoi(length)
|
||||
if err != nil {
|
||||
return "", exitError(ctx, ExitUsage, err, "password length must be a number: %s", err)
|
||||
return "", ExitError(ctx, ExitUsage, err, "password length must be a number: %s", err)
|
||||
}
|
||||
pwlen = iv
|
||||
}
|
||||
|
||||
if pwlen < 1 {
|
||||
return "", exitError(ctx, ExitUsage, nil, "password length must not be zero")
|
||||
return "", ExitError(ctx, ExitUsage, nil, "password length must not be zero")
|
||||
}
|
||||
|
||||
return xkcdgen.RandomLengthDelim(pwlen, xkcdSeparator, c.String("xkcdlang"))
|
||||
@ -178,13 +179,13 @@ func (s *Action) generateSetPassword(ctx context.Context, name, key, password st
|
||||
if key != "" {
|
||||
sec, ctx, err := s.Store.GetContext(ctx, name)
|
||||
if err != nil {
|
||||
return ctx, exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
return ctx, ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
if err := sec.SetValue(key, password); err != nil {
|
||||
return ctx, exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
return ctx, ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Generated password for YAML key"), name, sec); err != nil {
|
||||
return ctx, exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
return ctx, ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
@ -193,11 +194,11 @@ func (s *Action) generateSetPassword(ctx context.Context, name, key, password st
|
||||
if s.Store.Exists(ctx, name) {
|
||||
sec, ctx, err := s.Store.GetContext(ctx, name)
|
||||
if err != nil {
|
||||
return ctx, exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
return ctx, ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
sec.SetPassword(password)
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Generated password for YAML key"), name, sec); err != nil {
|
||||
return ctx, exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
return ctx, ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
@ -206,7 +207,7 @@ func (s *Action) generateSetPassword(ctx context.Context, name, key, password st
|
||||
var err error
|
||||
ctx, err = s.Store.SetContext(sub.WithReason(ctx, "Generated Password"), name, secret.New(password, ""))
|
||||
if err != nil {
|
||||
return ctx, exitError(ctx, ExitEncrypt, err, "failed to create '%s': %s", name, err)
|
||||
return ctx, ExitError(ctx, ExitEncrypt, err, "failed to create '%s': %s", name, err)
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -5,8 +5,9 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -18,7 +19,7 @@ func (s *Action) GitInit(ctx context.Context, c *cli.Context) error {
|
||||
ue := c.String("useremail")
|
||||
|
||||
if err := s.gitInit(ctx, store, un, ue); err != nil {
|
||||
return exitError(ctx, ExitGit, err, "failed to initialize git: %s", err)
|
||||
return ExitError(ctx, ExitGit, err, "failed to initialize git: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -45,7 +46,7 @@ func (s *Action) getUserData(ctx context.Context, store, un, ue string) (string,
|
||||
|
||||
// for convenience, set defaults to user-selected values from available private keys
|
||||
// NB: discarding returned error since this is merely a best-effort look-up for convenience
|
||||
userName, userEmail, _ := s.askForGitConfigUser(ctx, store)
|
||||
userName, userEmail, _ := cui.AskForGitConfigUser(ctx, s.Store.Crypto(ctx, store), store)
|
||||
|
||||
if userName == "" {
|
||||
var err error
|
||||
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -5,21 +5,21 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Grep searches a string inside the content of all files
|
||||
func (s *Action) Grep(ctx context.Context, c *cli.Context) error {
|
||||
if !c.Args().Present() {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s grep arg", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s grep arg", s.Name)
|
||||
}
|
||||
|
||||
search := c.Args().First()
|
||||
|
||||
l, err := s.Store.List(ctx, 0)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
return ExitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
}
|
||||
|
||||
for _, v := range l {
|
||||
@ -7,10 +7,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -2,17 +2,17 @@ package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
|
||||
"github.com/fatih/color"
|
||||
hibpapi "github.com/justwatchcom/gopass/utils/hibp/api"
|
||||
hibpdump "github.com/justwatchcom/gopass/utils/hibp/dump"
|
||||
"github.com/justwatchcom/gopass/utils/notify"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/hashsum"
|
||||
hibpapi "github.com/justwatchcom/gopass/pkg/hibp/api"
|
||||
hibpdump "github.com/justwatchcom/gopass/pkg/hibp/dump"
|
||||
"github.com/justwatchcom/gopass/pkg/notify"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/muesli/goprogressbar"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -34,7 +34,7 @@ func (s *Action) HIBP(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
func (s *Action) hibpAPI(ctx context.Context, force bool) error {
|
||||
if !force && !termio.AskForConfirmation(ctx, fmt.Sprintf("This command is checking all your secrets against the haveibeenpwned.com API.\n\nThis will send five bytes of each passwords SHA1 hash to an untrusted server!\n\nYou will be asked to unlock all your secrets!\nDo you want to continue?")) {
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
|
||||
shaSums, sortedShaSums, err := s.hibpPrecomputeHashes(ctx)
|
||||
@ -65,7 +65,7 @@ func (s *Action) hibpAPI(ctx context.Context, force bool) error {
|
||||
|
||||
func (s *Action) hibpDump(ctx context.Context, force bool, dumps []string) error {
|
||||
if !force && !termio.AskForConfirmation(ctx, fmt.Sprintf("This command is checking all your secrets against the haveibeenpwned.com hashes in %+v.\nYou will be asked to unlock all your secrets!\nDo you want to continue?", dumps)) {
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
|
||||
shaSums, sortedShaSums, err := s.hibpPrecomputeHashes(ctx)
|
||||
@ -75,7 +75,7 @@ func (s *Action) hibpDump(ctx context.Context, force bool, dumps []string) error
|
||||
|
||||
scanner, err := hibpdump.New(dumps...)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUsage, err, "Failed to create new HIBP Dump scanner: %s", err)
|
||||
return ExitError(ctx, ExitUsage, err, "Failed to create new HIBP Dump scanner: %s", err)
|
||||
}
|
||||
|
||||
matchedSums := scanner.LookupBatch(ctx, sortedShaSums)
|
||||
@ -96,7 +96,7 @@ func (s *Action) hibpPrecomputeHashes(ctx context.Context) (map[string]string, [
|
||||
// a very efficient stream compare in O(n)
|
||||
t, err := s.Store.Tree(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, exitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
return nil, nil, ExitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
}
|
||||
|
||||
pwList := t.List(0)
|
||||
@ -122,7 +122,7 @@ func (s *Action) hibpPrecomputeHashes(ctx context.Context) (map[string]string, [
|
||||
// check for context cancelation
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil, exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return nil, nil, ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
default:
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ func (s *Action) hibpPrecomputeHashes(ctx context.Context) (map[string]string, [
|
||||
if len(sec.Password()) < 1 {
|
||||
continue
|
||||
}
|
||||
sum := sha1sum(sec.Password())
|
||||
sum := hashsum.SHA1(sec.Password())
|
||||
shaSums[sum] = secret
|
||||
sortedShaSums = append(sortedShaSums, sum)
|
||||
}
|
||||
@ -169,11 +169,5 @@ func (s *Action) printHIBPMatches(ctx context.Context, matchList []string) error
|
||||
out.Red(ctx, "\t- %s", m)
|
||||
}
|
||||
out.Cyan(ctx, "The passwords in the listed secrets were included in public leaks in the past. This means they are likely included in many word-list attacks and provide only very little security. Strongly consider changing those passwords!")
|
||||
return exitError(ctx, ExitAudit, nil, "weak passwords found")
|
||||
}
|
||||
|
||||
func sha1sum(data string) string {
|
||||
h := sha1.New()
|
||||
_, _ = h.Write([]byte(data))
|
||||
return fmt.Sprintf("%X", h.Sum(nil))
|
||||
return ExitError(ctx, ExitAudit, nil, "weak passwords found")
|
||||
}
|
||||
@ -14,11 +14,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
hibpapi "github.com/justwatchcom/gopass/utils/hibp/api"
|
||||
hibpapi "github.com/justwatchcom/gopass/pkg/hibp/api"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -15,16 +15,16 @@ func (s *Action) History(ctx context.Context, c *cli.Context) error {
|
||||
showPassword := c.Bool("password")
|
||||
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s history [name]", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s history [name]", s.Name)
|
||||
}
|
||||
|
||||
if !s.Store.Exists(ctx, name) {
|
||||
return exitError(ctx, ExitNotFound, nil, "Secret not found")
|
||||
return ExitError(ctx, ExitNotFound, nil, "Secret not found")
|
||||
}
|
||||
|
||||
revs, err := s.Store.ListRevisions(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "Failed to get revisions: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "Failed to get revisions: %s", err)
|
||||
}
|
||||
|
||||
for _, rev := range revs {
|
||||
@ -8,11 +8,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -6,15 +6,15 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/backend"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/store/sub"
|
||||
"github.com/justwatchcom/gopass/utils/agent/client"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/cui"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/pwgen/xkcdgen"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/agent/client"
|
||||
"github.com/justwatchcom/gopass/pkg/backend"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/pwgen/xkcdgen"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -25,11 +25,11 @@ func (s *Action) Initialized(ctx context.Context, c *cli.Context) error {
|
||||
if !s.Store.Initialized(ctx) {
|
||||
out.Debug(ctx, "Store needs to be initialized")
|
||||
if !ctxutil.IsInteractive(ctx) {
|
||||
return exitError(ctx, ExitNotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
|
||||
return ExitError(ctx, ExitNotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
|
||||
}
|
||||
if ok, err := termio.AskForBool(ctx, "It seems you are new to gopass. Do you want to run the onboarding wizard?", true); err == nil && ok {
|
||||
if err := s.InitOnboarding(ctx, c); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to run onboarding wizard: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to run onboarding wizard: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -50,7 +50,7 @@ func (s *Action) Init(ctx context.Context, c *cli.Context) error {
|
||||
out.Cyan(ctx, "Initializing a new password store ...")
|
||||
|
||||
if err := s.init(ctx, alias, path, nogit, c.Args()...); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to initialized store: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to initialized store: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -67,7 +67,7 @@ func (s *Action) init(ctx context.Context, alias, path string, nogit bool, keys
|
||||
|
||||
out.Debug(ctx, "Checking private keys ...")
|
||||
if len(keys) < 1 {
|
||||
nk, err := s.askForPrivateKey(ctx, alias, color.CyanString("Please select a private key for encrypting secrets:"))
|
||||
nk, err := cui.AskForPrivateKey(ctx, s.Store.Crypto(ctx, alias), alias, color.CyanString("Please select a private key for encrypting secrets:"))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read user input")
|
||||
}
|
||||
@ -99,7 +99,7 @@ func (s *Action) init(ctx context.Context, alias, path string, nogit bool, keys
|
||||
|
||||
// write config
|
||||
if err := s.cfg.Save(); err != nil {
|
||||
return exitError(ctx, ExitConfig, err, "failed to write config: %s", err)
|
||||
return ExitError(ctx, ExitConfig, err, "failed to write config: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -7,12 +7,14 @@ 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"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/audit"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/editor"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/store/sub"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -25,7 +27,7 @@ func (s *Action) Insert(ctx context.Context, c *cli.Context) error {
|
||||
key := c.Args().Get(1)
|
||||
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitNoName, nil, "Usage: %s insert name", s.Name)
|
||||
return ExitError(ctx, ExitNoName, nil, "Usage: %s insert name", s.Name)
|
||||
}
|
||||
|
||||
return s.insert(ctx, c, name, key, echo, multiline, force)
|
||||
@ -46,7 +48,7 @@ func (s *Action) insert(ctx context.Context, c *cli.Context, name, key string, e
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if written, err := io.Copy(buf, os.Stdin); err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to copy after %d bytes: %s", written, err)
|
||||
return ExitError(ctx, ExitIO, err, "failed to copy after %d bytes: %s", written, err)
|
||||
}
|
||||
|
||||
content = buf.Bytes()
|
||||
@ -63,7 +65,7 @@ func (s *Action) insert(ctx context.Context, c *cli.Context, name, key string, e
|
||||
|
||||
// don't check if it's force anyway
|
||||
if !force && s.Store.Exists(ctx, name) && !termio.AskForConfirmation(ctx, fmt.Sprintf("An entry already exists for %s. Overwrite it?", name)) {
|
||||
return exitError(ctx, ExitAborted, nil, "not overwriting your current secret")
|
||||
return ExitError(ctx, ExitAborted, nil, "not overwriting your current secret")
|
||||
}
|
||||
|
||||
// if multi-line input is requested start an editor
|
||||
@ -80,7 +82,7 @@ func (s *Action) insert(ctx context.Context, c *cli.Context, name, key string, e
|
||||
|
||||
pw, err := termio.AskForPassword(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to ask for password: %s", err)
|
||||
return ExitError(ctx, ExitIO, err, "failed to ask for password: %s", err)
|
||||
}
|
||||
|
||||
return s.insertSingle(ctx, name, pw)
|
||||
@ -92,7 +94,7 @@ func (s *Action) insertStdin(ctx context.Context, name string, content []byte) e
|
||||
out.Red(ctx, "WARNING: Invalid YAML: %s", err)
|
||||
}
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Read secret from STDIN"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -103,16 +105,16 @@ func (s *Action) insertSingle(ctx context.Context, name, pw string) error {
|
||||
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)
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to decrypt existing secret: %s", err)
|
||||
}
|
||||
} else {
|
||||
sec = &secret.Secret{}
|
||||
}
|
||||
sec.SetPassword(pw)
|
||||
printAuditResult(ctx, sec.Password())
|
||||
audit.Single(ctx, sec.Password())
|
||||
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Inserted user supplied password"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to write secret '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to write secret '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -121,7 +123,7 @@ func (s *Action) insertYAML(ctx context.Context, name, key string, content []byt
|
||||
if ctxutil.IsInteractive(ctx) {
|
||||
pw, err := termio.AskForString(ctx, name+":"+key, "")
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to ask for user input: %s", err)
|
||||
return ExitError(ctx, ExitIO, err, "failed to ask for user input: %s", err)
|
||||
}
|
||||
content = []byte(pw)
|
||||
}
|
||||
@ -131,16 +133,16 @@ func (s *Action) insertYAML(ctx context.Context, name, key string, content []byt
|
||||
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)
|
||||
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)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
if err := s.Store.Set(sub.WithReason(ctx, "Inserted YAML value from STDIN"), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to set key '%s' of '%s': %s", key, name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -151,24 +153,24 @@ func (s *Action) insertMultiline(ctx context.Context, c *cli.Context, name strin
|
||||
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)
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to decrypt existing secret: %s", err)
|
||||
}
|
||||
buf, err = sec.Bytes()
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to encode secret: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to encode secret: %s", err)
|
||||
}
|
||||
}
|
||||
editor := getEditor(c)
|
||||
content, err := s.editor(ctx, editor, buf)
|
||||
ed := editor.Path(c)
|
||||
content, err := editor.Invoke(ctx, ed, buf)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to start editor: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to start editor: %s", err)
|
||||
}
|
||||
sec, err := secret.Parse(content)
|
||||
if err != nil {
|
||||
out.Red(ctx, "WARNING: Invalid YAML: %s", err)
|
||||
}
|
||||
if err := s.Store.Set(sub.WithReason(ctx, fmt.Sprintf("Inserted user supplied password with %s", editor)), name, sec); err != nil {
|
||||
return exitError(ctx, ExitEncrypt, err, "failed to store secret '%s': %s", name, err)
|
||||
if err := s.Store.Set(sub.WithReason(ctx, fmt.Sprintf("Inserted user supplied password with %s", ed)), name, sec); err != nil {
|
||||
return ExitError(ctx, ExitEncrypt, err, "failed to store secret '%s': %s", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -7,10 +7,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/utils/jsonapi"
|
||||
"github.com/justwatchcom/gopass/utils/jsonapi/manifest"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/jsonapi"
|
||||
"github.com/justwatchcom/gopass/pkg/jsonapi/manifest"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -10,10 +10,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termutil"
|
||||
"github.com/justwatchcom/gopass/utils/tree"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termutil"
|
||||
"github.com/justwatchcom/gopass/pkg/tree"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
@ -28,7 +28,7 @@ func (s *Action) List(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
l, err := s.Store.Tree(ctx)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
return ExitError(ctx, ExitList, err, "failed to list store: %s", err)
|
||||
}
|
||||
|
||||
if filter == "" {
|
||||
@ -67,7 +67,7 @@ func (s *Action) listFiltered(ctx context.Context, l tree.Tree, limit int, flat,
|
||||
fmt.Fprintln(so, subtree.Format(limit))
|
||||
if buf != nil {
|
||||
if err := s.pager(ctx, buf); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -103,7 +103,7 @@ func (s *Action) listAll(ctx context.Context, l tree.Tree, limit int, flat bool)
|
||||
fmt.Fprintln(so, l.Format(limit))
|
||||
if buf != nil {
|
||||
if err := s.pager(ctx, buf); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -7,11 +7,11 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/tree"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/tree"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -6,17 +6,17 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/config"
|
||||
"github.com/justwatchcom/gopass/store"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/tree/simple"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store"
|
||||
"github.com/justwatchcom/gopass/pkg/tree/simple"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// MountRemove removes an existing mount
|
||||
func (s *Action) MountRemove(ctx context.Context, c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s mount remove [alias]", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s mount remove [alias]", s.Name)
|
||||
}
|
||||
|
||||
if err := s.Store.RemoveMount(ctx, c.Args()[0]); err != nil {
|
||||
@ -24,7 +24,7 @@ func (s *Action) MountRemove(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
if err := s.cfg.Save(); err != nil {
|
||||
return exitError(ctx, ExitConfig, err, "failed to write config: %s", err)
|
||||
return ExitError(ctx, ExitConfig, err, "failed to write config: %s", err)
|
||||
}
|
||||
|
||||
out.Green(ctx, "Password Store %s umounted", c.Args()[0])
|
||||
@ -66,7 +66,7 @@ func (s *Action) MountAdd(ctx context.Context, c *cli.Context) error {
|
||||
alias := c.Args().Get(0)
|
||||
localPath := c.Args().Get(1)
|
||||
if alias == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "usage: %s mount add <alias> [local path]", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "usage: %s mount add <alias> [local path]", s.Name)
|
||||
}
|
||||
|
||||
if localPath == "" {
|
||||
@ -83,11 +83,11 @@ func (s *Action) MountAdd(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
if err := s.Store.AddMount(ctx, alias, localPath, keys...); err != nil {
|
||||
return exitError(ctx, ExitMount, err, "failed to add mount '%s' to '%s': %s", alias, localPath, err)
|
||||
return ExitError(ctx, ExitMount, err, "failed to add mount '%s' to '%s': %s", alias, localPath, err)
|
||||
}
|
||||
|
||||
if err := s.cfg.Save(); err != nil {
|
||||
return exitError(ctx, ExitConfig, err, "failed to save config: %s", err)
|
||||
return ExitError(ctx, ExitConfig, err, "failed to save config: %s", err)
|
||||
}
|
||||
|
||||
out.Green(ctx, "Mounted %s as %s", alias, localPath)
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -13,7 +13,7 @@ func (s *Action) Move(ctx context.Context, c *cli.Context) error {
|
||||
force := c.Bool("force")
|
||||
|
||||
if len(c.Args()) != 2 {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s mv old-path new-path", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s mv old-path new-path", s.Name)
|
||||
}
|
||||
|
||||
from := c.Args()[0]
|
||||
@ -21,12 +21,12 @@ func (s *Action) Move(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
if !force {
|
||||
if s.Store.Exists(ctx, to) && !termio.AskForConfirmation(ctx, fmt.Sprintf("%s already exists. Overwrite it?", to)) {
|
||||
return exitError(ctx, ExitAborted, nil, "not overwriting your current secret")
|
||||
return ExitError(ctx, ExitAborted, nil, "not overwriting your current secret")
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Store.Move(ctx, from, to); err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "%s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "%s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
68
pkg/action/otp.go
Normal file
68
pkg/action/otp.go
Normal file
@ -0,0 +1,68 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/clipboard"
|
||||
"github.com/justwatchcom/gopass/pkg/otp"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
// we might want to replace this with the currently un-exported step value
|
||||
// from twofactor.FromURL if it gets ever exported
|
||||
otpPeriod = 30
|
||||
)
|
||||
|
||||
// OTP implements OTP token handling for TOTP and HOTP
|
||||
func (s *Action) OTP(ctx context.Context, c *cli.Context) error {
|
||||
name := c.Args().First()
|
||||
if name == "" {
|
||||
return ExitError(ctx, ExitUsage, nil, "usage: %s otp [name]", s.Name)
|
||||
}
|
||||
qrf := c.String("qr")
|
||||
clip := c.Bool("clip")
|
||||
|
||||
return s.otp(ctx, name, qrf, clip)
|
||||
}
|
||||
|
||||
func (s *Action) otp(ctx context.Context, name, qrf string, clip bool) error {
|
||||
sec, err := s.Store.Get(ctx, name)
|
||||
if err != nil {
|
||||
return ExitError(ctx, ExitDecrypt, err, "failed to get entry '%s': %s", name, err)
|
||||
}
|
||||
|
||||
two, label, err := otp.Calculate(ctx, name, sec)
|
||||
if err != nil {
|
||||
return ExitError(ctx, ExitUnknown, err, "No OTP entry found for %s: %s", name, err)
|
||||
}
|
||||
token := two.OTP()
|
||||
|
||||
now := time.Now()
|
||||
t := now.Add(otpPeriod * time.Second)
|
||||
|
||||
expiresAt := time.Unix(t.Unix()+otpPeriod-(t.Unix()%otpPeriod), 0)
|
||||
secondsLeft := int(time.Until(expiresAt).Seconds())
|
||||
|
||||
if secondsLeft >= otpPeriod {
|
||||
secondsLeft = secondsLeft - otpPeriod
|
||||
}
|
||||
|
||||
out.Yellow(ctx, "%s lasts %ds \t|%s%s|", token, secondsLeft, strings.Repeat("-", otpPeriod-secondsLeft), strings.Repeat("=", secondsLeft))
|
||||
|
||||
if clip {
|
||||
if err := clipboard.CopyTo(ctx, fmt.Sprintf("token for %s", name), []byte(token)); err != nil {
|
||||
return ExitError(ctx, ExitIO, err, "failed to copy to clipboard: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if qrf != "" {
|
||||
return otp.WriteQRFile(ctx, two, label, qrf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -9,10 +9,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gokyle/twofactor"
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/cui"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/termio"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/cui"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -37,7 +37,7 @@ func (s *Action) RecipientsPrint(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
tree, err := s.Store.RecipientsTree(ctx, true)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitList, err, "failed to list recipients: %s", err)
|
||||
return ExitError(ctx, ExitList, err, "failed to list recipients: %s", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(stdout, tree.Format(0))
|
||||
@ -65,7 +65,7 @@ func (s *Action) RecipientsAdd(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
// select store
|
||||
if store == "" {
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
}
|
||||
|
||||
crypto := s.Store.Crypto(ctx, store)
|
||||
@ -86,7 +86,7 @@ func (s *Action) RecipientsAdd(ctx context.Context, c *cli.Context) error {
|
||||
case "show":
|
||||
recipients = []string{kl[sel]}
|
||||
default:
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,12 +110,12 @@ func (s *Action) RecipientsAdd(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
if err := s.Store.AddRecipient(ctxutil.WithNoConfirm(ctx, true), store, keys[0]); err != nil {
|
||||
return exitError(ctx, ExitRecipients, err, "failed to add recipient '%s': %s", r, err)
|
||||
return ExitError(ctx, ExitRecipients, err, "failed to add recipient '%s': %s", r, err)
|
||||
}
|
||||
added++
|
||||
}
|
||||
if added < 1 {
|
||||
return exitError(ctx, ExitUnknown, nil, "no key added")
|
||||
return ExitError(ctx, ExitUnknown, nil, "no key added")
|
||||
}
|
||||
|
||||
out.Green(ctx, "\nAdded %d recipients", added)
|
||||
@ -129,7 +129,7 @@ func (s *Action) RecipientsRemove(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
// select store
|
||||
if store == "" {
|
||||
store = s.askForStore(ctx)
|
||||
store = cui.AskForStore(ctx, s.Store)
|
||||
}
|
||||
|
||||
crypto := s.Store.Crypto(ctx, store)
|
||||
@ -155,7 +155,7 @@ func (s *Action) RecipientsRemove(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
}
|
||||
if err := s.Store.RemoveRecipient(ctxutil.WithNoConfirm(ctx, true), store, strings.TrimPrefix(r, "0x")); err != nil {
|
||||
return exitError(ctx, ExitRecipients, err, "failed to remove recipient '%s': %s", r, err)
|
||||
return ExitError(ctx, ExitRecipients, err, "failed to remove recipient '%s': %s", r, err)
|
||||
}
|
||||
fmt.Fprintf(stdout, removalWarning, r)
|
||||
removed++
|
||||
@ -184,6 +184,6 @@ func (s *Action) recipientsSelectForRemoval(ctx context.Context, store string) (
|
||||
case "show":
|
||||
return []string{ids[sel]}, nil
|
||||
default:
|
||||
return nil, exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return nil, ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
}
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/muesli/goprogressbar"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
@ -6,10 +6,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/justwatchcom/gopass/store"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/qrcon"
|
||||
"github.com/justwatchcom/gopass/pkg/clipboard"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/qrcon"
|
||||
"github.com/justwatchcom/gopass/pkg/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -32,14 +33,14 @@ func (s *Action) Show(ctx context.Context, c *cli.Context) error {
|
||||
}
|
||||
|
||||
if err := s.show(ctx, c, name, key, true); err != nil {
|
||||
return exitError(ctx, ExitDecrypt, err, "%s", err)
|
||||
return ExitError(ctx, ExitDecrypt, err, "%s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Action) show(ctx context.Context, c *cli.Context, name, key string, recurse bool) error {
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "Usage: %s show [name]", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "Usage: %s show [name]", s.Name)
|
||||
}
|
||||
|
||||
if s.Store.IsDir(ctx, name) && !s.Store.Exists(ctx, name) {
|
||||
@ -85,13 +86,13 @@ func (s *Action) showHandleOutput(ctx context.Context, name, key string, sec sto
|
||||
return s.showHandleYAMLError(ctx, name, key, err)
|
||||
}
|
||||
if IsClip(ctx) {
|
||||
return copyToClipboard(ctx, name, []byte(val))
|
||||
return clipboard.CopyTo(ctx, name, []byte(val))
|
||||
}
|
||||
content = val
|
||||
case IsPrintQR(ctx):
|
||||
return s.showPrintQR(ctx, name, sec.Password())
|
||||
case IsClip(ctx):
|
||||
return copyToClipboard(ctx, name, []byte(sec.Password()))
|
||||
return clipboard.CopyTo(ctx, name, []byte(sec.Password()))
|
||||
default:
|
||||
switch {
|
||||
case IsPasswordOnly(ctx):
|
||||
@ -101,14 +102,14 @@ func (s *Action) showHandleOutput(ctx context.Context, name, key string, sec sto
|
||||
if content == "" {
|
||||
if ctxutil.IsAutoClip(ctx) {
|
||||
out.Yellow(ctx, "No safe content to display, you can force display with show -f.\nCopying password instead.")
|
||||
return copyToClipboard(ctx, name, []byte(sec.Password()))
|
||||
return clipboard.CopyTo(ctx, name, []byte(sec.Password()))
|
||||
}
|
||||
return exitError(ctx, ExitNotFound, store.ErrNoBody, store.ErrNoBody.Error())
|
||||
return ExitError(ctx, ExitNotFound, store.ErrNoBody, store.ErrNoBody.Error())
|
||||
}
|
||||
default:
|
||||
buf, err := sec.Bytes()
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to encode secret: %s", err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to encode secret: %s", err)
|
||||
}
|
||||
content = string(buf)
|
||||
}
|
||||
@ -121,11 +122,11 @@ func (s *Action) showHandleOutput(ctx context.Context, name, key string, sec sto
|
||||
|
||||
func (s *Action) showHandleError(ctx context.Context, c *cli.Context, name string, recurse bool, err error) error {
|
||||
if err != store.ErrNotFound || !recurse || !ctxutil.IsTerminal(ctx) {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to retrieve secret '%s': %s", name, err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to retrieve secret '%s': %s", name, err)
|
||||
}
|
||||
out.Yellow(ctx, "Entry '%s' not found. Starting search...", name)
|
||||
if err := s.Find(ctx, c); err != nil {
|
||||
return exitError(ctx, ExitNotFound, err, "%s", err)
|
||||
return ExitError(ctx, ExitNotFound, err, "%s", err)
|
||||
}
|
||||
os.Exit(ExitNotFound)
|
||||
return nil
|
||||
@ -133,18 +134,18 @@ func (s *Action) showHandleError(ctx context.Context, c *cli.Context, name strin
|
||||
|
||||
func (s *Action) showHandleYAMLError(ctx context.Context, name, key string, err error) error {
|
||||
if errors.Cause(err) == store.ErrYAMLValueUnsupported {
|
||||
return exitError(ctx, ExitUnsupported, err, "Can not show nested key directly. Use 'gopass show %s'", name)
|
||||
return ExitError(ctx, ExitUnsupported, err, "Can not show nested key directly. Use 'gopass show %s'", name)
|
||||
}
|
||||
if errors.Cause(err) == store.ErrNotFound {
|
||||
return exitError(ctx, ExitNotFound, err, "Secret '%s' not found", name)
|
||||
return ExitError(ctx, ExitNotFound, err, "Secret '%s' not found", name)
|
||||
}
|
||||
return exitError(ctx, ExitUnknown, err, "failed to retrieve key '%s' from '%s': %s", key, name, err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to retrieve key '%s' from '%s': %s", key, name, err)
|
||||
}
|
||||
|
||||
func (s *Action) showPrintQR(ctx context.Context, name, pw string) error {
|
||||
qr, err := qrcon.QRCode(pw)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to encode '%s' as QR: %s", name, err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to encode '%s' as QR: %s", name, err)
|
||||
}
|
||||
fmt.Fprintln(stdout, qr)
|
||||
return nil
|
||||
@ -9,10 +9,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/store/secret"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store/secret"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/store"
|
||||
"github.com/justwatchcom/gopass/utils/notify"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/notify"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/editor"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -29,7 +30,7 @@ const (
|
||||
func (s *Action) TemplatesPrint(ctx context.Context, c *cli.Context) error {
|
||||
tree, err := s.Store.TemplateTree(ctx)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitList, err, "failed to list templates: %s", err)
|
||||
return ExitError(ctx, ExitList, err, "failed to list templates: %s", err)
|
||||
}
|
||||
fmt.Fprintln(stdout, tree.Format(0))
|
||||
return nil
|
||||
@ -41,7 +42,7 @@ func (s *Action) TemplatePrint(ctx context.Context, c *cli.Context) error {
|
||||
|
||||
content, err := s.Store.GetTemplate(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to retrieve template: %s", err)
|
||||
return ExitError(ctx, ExitIO, err, "failed to retrieve template: %s", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(stdout, string(content))
|
||||
@ -58,16 +59,16 @@ func (s *Action) TemplateEdit(ctx context.Context, c *cli.Context) error {
|
||||
var err error
|
||||
content, err = s.Store.GetTemplate(ctx, name)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitIO, err, "failed to retrieve template: %s", err)
|
||||
return ExitError(ctx, ExitIO, err, "failed to retrieve template: %s", err)
|
||||
}
|
||||
} else {
|
||||
content = []byte(templateExample)
|
||||
}
|
||||
|
||||
editor := getEditor(c)
|
||||
nContent, err := s.editor(ctx, editor, content)
|
||||
ed := editor.Path(c)
|
||||
nContent, err := editor.Invoke(ctx, ed, content)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitUnknown, err, "failed to invoke editor %s: %s", editor, err)
|
||||
return ExitError(ctx, ExitUnknown, err, "failed to invoke editor %s: %s", ed, err)
|
||||
}
|
||||
|
||||
// If content is equal, nothing changed, exiting
|
||||
@ -82,11 +83,11 @@ func (s *Action) TemplateEdit(ctx context.Context, c *cli.Context) error {
|
||||
func (s *Action) TemplateRemove(ctx context.Context, c *cli.Context) error {
|
||||
name := c.Args().First()
|
||||
if name == "" {
|
||||
return exitError(ctx, ExitUsage, nil, "usage: %s templates remove [name]", s.Name)
|
||||
return ExitError(ctx, ExitUsage, nil, "usage: %s templates remove [name]", s.Name)
|
||||
}
|
||||
|
||||
if !s.Store.HasTemplate(ctx, name) {
|
||||
return exitError(ctx, ExitNotFound, nil, "template '%s' not found", name)
|
||||
return ExitError(ctx, ExitNotFound, nil, "template '%s' not found", name)
|
||||
}
|
||||
|
||||
return s.Store.RemoveTemplate(ctx, name)
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
23
pkg/action/unclip.go
Normal file
23
pkg/action/unclip.go
Normal file
@ -0,0 +1,23 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/clipboard"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Unclip tries to erase the content of the clipboard
|
||||
func (s *Action) Unclip(ctx context.Context, c *cli.Context) error {
|
||||
force := c.Bool("force")
|
||||
timeout := c.Int("timeout")
|
||||
checksum := os.Getenv("GOPASS_UNCLIP_CHECKSUM")
|
||||
|
||||
time.Sleep(time.Second * time.Duration(timeout))
|
||||
if err := clipboard.Clear(ctx, checksum, force); err != nil {
|
||||
return ExitError(ctx, ExitIO, err, "Failed to clear clipboard: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
24
pkg/action/update.go
Normal file
24
pkg/action/update.go
Normal file
@ -0,0 +1,24 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/updater"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Update will start hte interactive update assistant
|
||||
func (s *Action) Update(ctx context.Context, c *cli.Context) error {
|
||||
pre := c.Bool("pre")
|
||||
|
||||
if s.version.String() == "0.0.0+HEAD" {
|
||||
out.Red(ctx, "Can not check version against HEAD")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updater.Update(ctx, pre, s.version); err != nil {
|
||||
return ExitError(ctx, ExitUnknown, err, "Failed to update gopass: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -9,15 +9,15 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/dominikschulz/github-releases/ghrel"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/updater"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -41,7 +41,7 @@ const testUpdateJSON = `[
|
||||
]`
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
updateMoveAfterQuit = false
|
||||
updater.UpdateMoveAfterQuit = false
|
||||
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
@ -102,30 +102,3 @@ func TestUpdate(t *testing.T) {
|
||||
assert.NoError(t, act.Update(ctx, c))
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
func TestCheckHost(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
in: "https://github.com/justwatchcom/gopass/releases/download/v1.6.8/gopass-1.6.8-linux-amd64.tar.gz",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
in: "http://localhost:8080/foo/bar.tar.gz",
|
||||
ok: true,
|
||||
},
|
||||
} {
|
||||
u, err := url.Parse(tc.in)
|
||||
assert.NoError(t, err)
|
||||
err = updateCheckHost(ctx, u)
|
||||
if tc.ok {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,18 +7,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dominikschulz/github-releases/ghrel"
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/protect"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/protect"
|
||||
"github.com/justwatchcom/gopass/pkg/updater"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
gitHubOrg = "justwatchcom"
|
||||
gitHubRepo = "gopass"
|
||||
)
|
||||
|
||||
// Version prints the gopass version
|
||||
func (s *Action) Version(ctx context.Context, c *cli.Context) error {
|
||||
version := make(chan string, 1)
|
||||
@ -35,13 +30,7 @@ func (s *Action) Version(ctx context.Context, c *cli.Context) error {
|
||||
return
|
||||
}
|
||||
|
||||
var r ghrel.Release
|
||||
var err error
|
||||
if len(s.version.Pre) > 0 {
|
||||
r, err = ghrel.FetchLatestRelease(gitHubOrg, gitHubRepo)
|
||||
} else {
|
||||
r, err = ghrel.FetchLatestStableRelease(gitHubOrg, gitHubRepo)
|
||||
}
|
||||
r, err := updater.LatestRelease(ctx, len(s.version.Pre) > 0)
|
||||
if err != nil {
|
||||
u <- color.RedString("\nError checking latest version: %s", err)
|
||||
return
|
||||
@ -50,7 +39,7 @@ func (s *Action) Version(ctx context.Context, c *cli.Context) error {
|
||||
if s.version.LT(r.Version()) {
|
||||
notice := fmt.Sprintf("\nYour version (%s) of gopass is out of date!\nThe latest version is %s.\n", s.version, r.Version().String())
|
||||
notice += "You can update by downloading from www.justwatch.com/gopass"
|
||||
if err := s.isUpdateable(ctx); err == nil {
|
||||
if err := updater.IsUpdateable(ctx); err == nil {
|
||||
notice += " by running 'gopass update'"
|
||||
}
|
||||
notice += " or via your package manager"
|
||||
@ -79,7 +68,7 @@ func (s *Action) Version(ctx context.Context, c *cli.Context) error {
|
||||
case <-time.After(2 * time.Second):
|
||||
out.Red(ctx, "Version check timed out")
|
||||
case <-ctx.Done():
|
||||
return exitError(ctx, ExitAborted, nil, "user aborted")
|
||||
return ExitError(ctx, ExitAborted, nil, "user aborted")
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -7,9 +7,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
227
pkg/action/xc/xc.go
Normal file
227
pkg/action/xc/xc.go
Normal file
@ -0,0 +1,227 @@
|
||||
package xc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/justwatchcom/gopass/pkg/action"
|
||||
"github.com/justwatchcom/gopass/pkg/agent/client"
|
||||
"github.com/justwatchcom/gopass/pkg/backend/crypto/xc"
|
||||
"github.com/justwatchcom/gopass/pkg/config"
|
||||
"github.com/justwatchcom/gopass/pkg/fsutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/termio"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// ListPrivateKeys list the XC private keys
|
||||
func ListPrivateKeys(ctx context.Context, c *cli.Context) error {
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
kl, err := crypto.ListPrivateKeyIDs(ctx)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to list private keys")
|
||||
}
|
||||
|
||||
out.Print(ctx, "XC Private Keys:")
|
||||
for _, key := range kl {
|
||||
out.Print(ctx, "%s - %s", key, crypto.FormatKey(ctx, key))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListPublicKeys lists the XC public keys
|
||||
func ListPublicKeys(ctx context.Context, c *cli.Context) error {
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
kl, err := crypto.ListPublicKeyIDs(ctx)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to list public keys")
|
||||
}
|
||||
|
||||
out.Print(ctx, "XC Public Keys:")
|
||||
for _, key := range kl {
|
||||
out.Print(ctx, "%s - %s", key, crypto.FormatKey(ctx, key))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateKeypair generates a new XC keypair
|
||||
func GenerateKeypair(ctx context.Context, c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
email := c.String("email")
|
||||
pw := c.String("passphrase")
|
||||
|
||||
if name == "" {
|
||||
var err error
|
||||
name, err = termio.AskForString(ctx, "What is your full name?", "")
|
||||
if err != nil || name == "" {
|
||||
return action.ExitError(ctx, action.ExitNoName, err, "please provide a name")
|
||||
}
|
||||
}
|
||||
if email == "" {
|
||||
var err error
|
||||
email, err = termio.AskForString(ctx, "What is your email?", "")
|
||||
if err != nil || name == "" {
|
||||
return action.ExitError(ctx, action.ExitNoName, err, "please provide a email")
|
||||
}
|
||||
}
|
||||
if pw == "" {
|
||||
var err error
|
||||
pw, err = termio.AskForPassword(ctx, name)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitIO, err, "failed to ask for password: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
return crypto.CreatePrivateKeyBatch(ctx, name, email, pw)
|
||||
}
|
||||
|
||||
// ExportPublicKey exports an XC key
|
||||
func ExportPublicKey(ctx context.Context, c *cli.Context) error {
|
||||
id := c.String("id")
|
||||
file := c.String("file")
|
||||
|
||||
if id == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need id")
|
||||
}
|
||||
if file == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if fsutil.IsFile(file) {
|
||||
return action.ExitError(ctx, action.ExitUnknown, nil, "output file already exists")
|
||||
}
|
||||
|
||||
pk, err := crypto.ExportPublicKey(ctx, id)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to export key: %s", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(file, pk, 0600); err != nil {
|
||||
return action.ExitError(ctx, action.ExitIO, err, "failed to write file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportPublicKey imports an XC key
|
||||
func ImportPublicKey(ctx context.Context, c *cli.Context) error {
|
||||
file := c.String("file")
|
||||
|
||||
if file == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if !fsutil.IsFile(file) {
|
||||
return action.ExitError(ctx, action.ExitNotFound, nil, "input file not found")
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitIO, err, "failed to read file")
|
||||
}
|
||||
return crypto.ImportPublicKey(ctx, buf)
|
||||
}
|
||||
|
||||
// RemoveKey removes a key from the keyring
|
||||
func RemoveKey(ctx context.Context, c *cli.Context) error {
|
||||
id := c.String("id")
|
||||
|
||||
if id == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need id")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
return crypto.RemoveKey(id)
|
||||
}
|
||||
|
||||
// ExportPrivateKey exports an XC key
|
||||
func ExportPrivateKey(ctx context.Context, c *cli.Context) error {
|
||||
id := c.String("id")
|
||||
file := c.String("file")
|
||||
|
||||
if id == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need id")
|
||||
}
|
||||
if file == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if fsutil.IsFile(file) {
|
||||
return action.ExitError(ctx, action.ExitUnknown, nil, "output file already exists")
|
||||
}
|
||||
|
||||
pk, err := crypto.ExportPrivateKey(ctx, id)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to export key: %s", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(file, pk, 0600); err != nil {
|
||||
return action.ExitError(ctx, action.ExitIO, err, "failed to write file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportPrivateKey imports an XC key
|
||||
func ImportPrivateKey(ctx context.Context, c *cli.Context) error {
|
||||
file := c.String("file")
|
||||
|
||||
if file == "" {
|
||||
return action.ExitError(ctx, action.ExitUsage, nil, "need file")
|
||||
}
|
||||
|
||||
cfgdir := config.Directory()
|
||||
crypto, err := xc.New(cfgdir, client.New(cfgdir))
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitUnknown, err, "failed to init XC")
|
||||
}
|
||||
|
||||
if !fsutil.IsFile(file) {
|
||||
return action.ExitError(ctx, action.ExitNotFound, nil, "input file not found")
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return action.ExitError(ctx, action.ExitIO, err, "failed to read file")
|
||||
}
|
||||
return crypto.ImportPrivateKey(ctx, buf)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package action
|
||||
package xc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -7,69 +7,47 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/justwatchcom/gopass/tests/gptest"
|
||||
"github.com/justwatchcom/gopass/utils/ctxutil"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/ctxutil"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestXCListPrivateKeys(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
func TestListPrivateKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
}()
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
c := cli.NewContext(app, fs, nil)
|
||||
assert.NoError(t, ListPrivateKeys(ctx, c))
|
||||
}
|
||||
|
||||
func TestListPublicKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.NoError(t, act.XCListPrivateKeys(ctx, c))
|
||||
}
|
||||
|
||||
func TestXCListPublicKeys(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
c := cli.NewContext(app, fs, nil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.NoError(t, act.XCListPublicKeys(ctx, c))
|
||||
assert.NoError(t, ListPublicKeys(ctx, c))
|
||||
}
|
||||
|
||||
func TestXCGenerateKeypair(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
func TestGenerateKeypair(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
@ -93,23 +71,16 @@ func TestXCGenerateKeypair(t *testing.T) {
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.NoError(t, act.XCGenerateKeypair(ctx, c))
|
||||
assert.NoError(t, GenerateKeypair(ctx, c))
|
||||
}
|
||||
|
||||
func TestXCExportPublicKey(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
@ -128,23 +99,16 @@ func TestXCExportPublicKey(t *testing.T) {
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.Error(t, act.XCExportPublicKey(ctx, c))
|
||||
assert.Error(t, ExportPublicKey(ctx, c))
|
||||
}
|
||||
|
||||
func TestXCImportPublicKey(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
func TestImportPublicKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
@ -158,23 +122,16 @@ func TestXCImportPublicKey(t *testing.T) {
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.Error(t, act.XCImportPublicKey(ctx, c))
|
||||
assert.Error(t, ImportPublicKey(ctx, c))
|
||||
}
|
||||
|
||||
func TestXCExportPrivateKey(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
func TestExportPrivateKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
@ -193,23 +150,16 @@ func TestXCExportPrivateKey(t *testing.T) {
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.Error(t, act.XCExportPrivateKey(ctx, c))
|
||||
assert.Error(t, ExportPrivateKey(ctx, c))
|
||||
}
|
||||
|
||||
func TestXCImportPrivateKey(t *testing.T) {
|
||||
u := gptest.NewUnitTester(t)
|
||||
defer u.Remove()
|
||||
|
||||
func TestImportPrivateKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
||||
act, err := newMock(ctx, u)
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
||||
@ -223,11 +173,9 @@ func TestXCImportPrivateKey(t *testing.T) {
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
out.Stdout = buf
|
||||
stdout = buf
|
||||
defer func() {
|
||||
out.Stdout = os.Stdout
|
||||
stdout = os.Stdout
|
||||
}()
|
||||
|
||||
assert.Error(t, act.XCImportPrivateKey(ctx, c))
|
||||
assert.Error(t, ImportPrivateKey(ctx, c))
|
||||
}
|
||||
@ -10,9 +10,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/agent/client"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/utils/pinentry"
|
||||
"github.com/justwatchcom/gopass/pkg/agent/client"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/pinentry"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
package action
|
||||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/justwatchcom/gopass/utils/notify"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/notify"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/justwatchcom/gopass/pkg/store"
|
||||
"github.com/muesli/crunchy"
|
||||
"github.com/muesli/goprogressbar"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// auditedSecret with its name, content a warning message and a pipeline error.
|
||||
@ -28,43 +29,33 @@ type auditedSecret struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Audit validates passwords against common flaws
|
||||
func (s *Action) Audit(ctx context.Context, c *cli.Context) error {
|
||||
t, err := s.Store.Tree(ctx)
|
||||
if err != nil {
|
||||
return exitError(ctx, ExitList, err, "failed to get store tree: %s", err)
|
||||
}
|
||||
list := t.List(0)
|
||||
type secretGetter interface {
|
||||
Get(context.Context, string) (store.Secret, error)
|
||||
}
|
||||
|
||||
if len(list) < 1 {
|
||||
out.Yellow(ctx, "No secrets found")
|
||||
return nil
|
||||
}
|
||||
|
||||
out.Print(ctx, "Checking %d secrets. This may take some time ...\n", len(list))
|
||||
// Batch runs a password strength audit on multiple secrets
|
||||
func Batch(ctx context.Context, secrets []string, secStore secretGetter) error {
|
||||
out.Print(ctx, "Checking %d secrets. This may take some time ...\n", len(secrets))
|
||||
|
||||
// Secrets that still need auditing.
|
||||
secrets := make(chan string, 100)
|
||||
pending := make(chan string, 100)
|
||||
|
||||
// Secrets that have been audited.
|
||||
checked := make(chan auditedSecret, 100)
|
||||
|
||||
// Spawn workers that run the auditing of all secrets concurrently.
|
||||
validator := crunchy.NewValidator()
|
||||
maxJobs := 1
|
||||
if mj := c.Int("jobs"); mj > 0 {
|
||||
maxJobs = mj
|
||||
}
|
||||
maxJobs := runtime.NumCPU()
|
||||
done := make(chan struct{}, maxJobs)
|
||||
for jobs := 0; jobs < maxJobs; jobs++ {
|
||||
go s.audit(ctx, validator, secrets, checked, done)
|
||||
go audit(ctx, secStore, validator, pending, checked, done)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for _, secret := range list {
|
||||
secrets <- secret
|
||||
for _, secret := range secrets {
|
||||
pending <- secret
|
||||
}
|
||||
close(secrets)
|
||||
close(pending)
|
||||
}()
|
||||
go func() {
|
||||
for i := 0; i < maxJobs; i++ {
|
||||
@ -78,7 +69,7 @@ func (s *Action) Audit(ctx context.Context, c *cli.Context) error {
|
||||
errors := make(map[string][]string)
|
||||
|
||||
bar := &goprogressbar.ProgressBar{
|
||||
Total: int64(len(list)),
|
||||
Total: int64(len(secrets)),
|
||||
Width: 120,
|
||||
}
|
||||
if out.IsHidden(ctx) {
|
||||
@ -105,47 +96,16 @@ func (s *Action) Audit(ctx context.Context, c *cli.Context) error {
|
||||
bar.Text = fmt.Sprintf("%d of %d secrets checked", bar.Current, bar.Total)
|
||||
bar.LazyPrint()
|
||||
|
||||
if i == len(list) {
|
||||
if i == len(secrets) {
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(stdout) // Print empty line after the progressbar.
|
||||
fmt.Fprintln(goprogressbar.Stdout) // Print empty line after the progressbar.
|
||||
|
||||
return s.auditPrintResults(ctx, duplicates, messages, errors)
|
||||
return auditPrintResults(ctx, duplicates, messages, errors)
|
||||
}
|
||||
|
||||
func (s *Action) auditPrintResults(ctx context.Context, duplicates, messages, errors map[string][]string) error {
|
||||
foundDuplicates := false
|
||||
for _, secrets := range duplicates {
|
||||
if len(secrets) > 1 {
|
||||
foundDuplicates = true
|
||||
|
||||
out.Cyan(ctx, "Detected a shared secret for:")
|
||||
for _, secret := range secrets {
|
||||
out.Cyan(ctx, "\t- %s", secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundDuplicates {
|
||||
out.Green(ctx, "No shared secrets found.")
|
||||
}
|
||||
|
||||
foundWeakPasswords := printAuditResults(ctx, messages, "%s:\n", color.CyanString)
|
||||
if !foundWeakPasswords {
|
||||
out.Green(ctx, "No weak secrets detected.")
|
||||
}
|
||||
foundErrors := printAuditResults(ctx, errors, "%s:\n", color.RedString)
|
||||
|
||||
if foundWeakPasswords || foundDuplicates || foundErrors {
|
||||
_ = notify.Notify(ctx, "gopass - audit", "Finished. Found weak passwords and/or duplicates")
|
||||
return exitError(ctx, ExitAudit, nil, "found weak passwords or duplicates")
|
||||
}
|
||||
|
||||
_ = notify.Notify(ctx, "gopass - audit", "Finished. No weak passwords or duplicates found!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Action) audit(ctx context.Context, validator *crunchy.Validator, secrets <-chan string, checked chan<- auditedSecret, done chan struct{}) {
|
||||
func audit(ctx context.Context, secStore secretGetter, validator *crunchy.Validator, secrets <-chan string, checked chan<- auditedSecret, done chan struct{}) {
|
||||
for secret := range secrets {
|
||||
// check for context cancelation
|
||||
select {
|
||||
@ -155,7 +115,7 @@ func (s *Action) audit(ctx context.Context, validator *crunchy.Validator, secret
|
||||
default:
|
||||
}
|
||||
|
||||
sec, err := s.Store.Get(ctx, secret)
|
||||
sec, err := secStore.Get(ctx, secret)
|
||||
if err != nil {
|
||||
pw := ""
|
||||
if sec != nil {
|
||||
@ -180,18 +140,50 @@ func printAuditResults(ctx context.Context, m map[string][]string, format string
|
||||
|
||||
for msg, secrets := range m {
|
||||
b = true
|
||||
fmt.Fprint(stdout, color(format, msg))
|
||||
fmt.Fprint(goprogressbar.Stdout, color(format, msg))
|
||||
for _, secret := range secrets {
|
||||
fmt.Fprint(stdout, color("\t- %s\n", secret))
|
||||
fmt.Fprint(goprogressbar.Stdout, color("\t- %s\n", secret))
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func printAuditResult(ctx context.Context, pw string) {
|
||||
// Single runs a password strength audit on a single password
|
||||
func Single(ctx context.Context, password string) {
|
||||
validator := crunchy.NewValidator()
|
||||
if err := validator.Check(pw); err != nil {
|
||||
if err := validator.Check(password); err != nil {
|
||||
out.Cyan(ctx, fmt.Sprintf("Warning: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func auditPrintResults(ctx context.Context, duplicates, messages, errors map[string][]string) error {
|
||||
foundDuplicates := false
|
||||
for _, secrets := range duplicates {
|
||||
if len(secrets) > 1 {
|
||||
foundDuplicates = true
|
||||
|
||||
out.Cyan(ctx, "Detected a shared secret for:")
|
||||
for _, secret := range secrets {
|
||||
out.Cyan(ctx, "\t- %s", secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundDuplicates {
|
||||
out.Green(ctx, "No shared secrets found.")
|
||||
}
|
||||
|
||||
foundWeakPasswords := printAuditResults(ctx, messages, "%s:\n", color.CyanString)
|
||||
if !foundWeakPasswords {
|
||||
out.Green(ctx, "No weak secrets detected.")
|
||||
}
|
||||
foundErrors := printAuditResults(ctx, errors, "%s:\n", color.RedString)
|
||||
|
||||
if foundWeakPasswords || foundDuplicates || foundErrors {
|
||||
_ = notify.Notify(ctx, "gopass - audit", "Finished. Found weak passwords and/or duplicates")
|
||||
return fmt.Errorf("found weak passwords or duplicates")
|
||||
}
|
||||
|
||||
_ = notify.Notify(ctx, "gopass - audit", "Finished. No weak passwords or duplicates found!")
|
||||
return nil
|
||||
}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"sort"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
)
|
||||
|
||||
// Binary returns the GPG binary location
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"os/exec"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -9,8 +9,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/justwatchcom/gopass/backend/crypto/gpg"
|
||||
"github.com/justwatchcom/gopass/utils/out"
|
||||
"github.com/justwatchcom/gopass/pkg/backend/crypto/gpg"
|
||||
"github.com/justwatchcom/gopass/pkg/out"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/justwatchcom/gopass/utils/fsutil"
|
||||
"github.com/justwatchcom/gopass/pkg/fsutil"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user