Refactor and cleanup codebase (#715)

This commit is contained in:
Dominik Schulz 2018-03-16 14:22:47 +01:00 committed by GitHub
parent 997e830057
commit da436e6a79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
344 changed files with 1679 additions and 1655 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,9 +0,0 @@
// +build !linux
package action
import "context"
func (s *Action) clearClipboardHistory(ctx context.Context) error {
return nil
}

View File

@ -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)
}

View File

@ -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
View File

@ -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"
)

View File

@ -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{

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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

View File

@ -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
View 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)
}

View File

@ -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"
)

View File

@ -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
}

View File

@ -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
View 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)
}

View File

@ -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

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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
}

View File

@ -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"
)

View File

@ -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

View File

@ -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"
)

View File

@ -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)

View File

@ -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()

View File

@ -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
}

View File

@ -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
View 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
}

View File

@ -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)
}

View File

@ -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")

View File

@ -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")
}
}

View File

@ -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"
)

View File

@ -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
}

View File

@ -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"
)

View File

@ -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

View File

@ -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"
)

View File

@ -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 {

View File

@ -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"
)

View File

@ -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")
}

View File

@ -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"
)

View File

@ -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 {

View File

@ -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"
)

View File

@ -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

View File

@ -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"
)

View File

@ -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
}

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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

View File

@ -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"
)

View File

@ -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)

View File

@ -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"
)

View File

@ -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

View File

@ -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
View 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
}

View File

@ -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"
)

View File

@ -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")
}
}

View File

@ -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"

View File

@ -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

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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)

View File

@ -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
View 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
View 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
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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
View 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)
}

View File

@ -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))
}

View File

@ -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"
)

View File

@ -8,7 +8,7 @@ import (
"os/exec"
"syscall"
"github.com/justwatchcom/gopass/utils/out"
"github.com/justwatchcom/gopass/pkg/out"
"github.com/pkg/errors"
)

View File

@ -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
}

View File

@ -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

View File

@ -4,7 +4,7 @@ import (
"context"
"os/exec"
"github.com/justwatchcom/gopass/utils/out"
"github.com/justwatchcom/gopass/pkg/out"
"github.com/pkg/errors"
)

View File

@ -6,7 +6,7 @@ import (
"os"
"os/exec"
"github.com/justwatchcom/gopass/utils/out"
"github.com/justwatchcom/gopass/pkg/out"
"github.com/pkg/errors"
)

View File

@ -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 (

View File

@ -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