mirror of
https://github.com/gopasspw/gopass.git
synced 2025-12-08 19:24:54 +00:00
Mostly to remind myself how those are supposed to operate and document it for my future self. The lookup part is a little bit error prone, i.e. if the rules for these domains change the test will break. But I promise to take care of that once that happens for the first time. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
446 lines
12 KiB
Go
446 lines
12 KiB
Go
package action
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/gopasspw/gopass/internal/config"
|
|
"github.com/gopasspw/gopass/internal/out"
|
|
"github.com/gopasspw/gopass/pkg/ctxutil"
|
|
"github.com/gopasspw/gopass/pkg/pwgen/pwrules"
|
|
"github.com/gopasspw/gopass/tests/gptest"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
func TestRuleLookup(t *testing.T) {
|
|
domain, _ := hasPwRuleForSecret(config.NewContextInMemory(), "foo/gopass.pw")
|
|
assert.Empty(t, domain)
|
|
}
|
|
|
|
func TestGenerate(t *testing.T) {
|
|
u := gptest.NewUnitTester(t)
|
|
|
|
ctx := t.Context()
|
|
ctx = ctxutil.WithAlwaysYes(ctx, true)
|
|
ctx = ctxutil.WithInteractive(ctx, false)
|
|
|
|
act, err := newMock(ctx, u.StoreDir(""))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, act)
|
|
ctx = act.cfg.WithConfig(ctx)
|
|
|
|
require.NoError(t, act.cfg.Set("", "generate.autoclip", "false"))
|
|
|
|
buf := &bytes.Buffer{}
|
|
out.Stdout = buf
|
|
out.Stderr = buf
|
|
defer func() {
|
|
out.Stdout = os.Stdout
|
|
out.Stderr = os.Stderr
|
|
}()
|
|
color.NoColor = true
|
|
|
|
// generate
|
|
t.Run("generate", func(t *testing.T) {
|
|
require.Error(t, act.Generate(gptest.CliCtx(ctx, t)))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate foobar
|
|
t.Run("generate foobar", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtx(ctx, t, "foobar")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate foobar
|
|
// should succeed because of always yes
|
|
t.Run("generate foobar again", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtx(ctx, t, "foobar")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --edit foobar
|
|
t.Run("generate --edit foobar", func(t *testing.T) {
|
|
if testing.Short() || runtime.GOOS == "windows" {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"edit": "true", "editor": "/usr/bin/env cat"}, "foobar")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force foobar
|
|
t.Run("generate --force foobar", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true"}, "foobar")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force foobar 32
|
|
t.Run("generate --force foobar 32", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true"}, "foobar", "32")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force --symbols foobar 32
|
|
t.Run("generate --force --symbols foobar 32", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "print": "true", "symbols": "true"}, "foobar", "32")))
|
|
passIsAlphaNum(t, buf.String(), false)
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force --symbols=false foobar 32
|
|
t.Run("generate --force --symbols=False foobar 32", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "print": "true", "symbols": "false"}, "foobar", "32")))
|
|
passIsAlphaNum(t, buf.String(), true)
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force --xkcd foobar 32
|
|
t.Run("generate --force --xkcd foobar 32", func(t *testing.T) {
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "xkcd": "true", "lang": "en"}, "foobar", "32")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force --xkcd foobar baz 32
|
|
t.Run("generate --force --xkcd foobar baz 32", func(t *testing.T) {
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "xkcd": "true", "lang": "en"}, "foobar", "baz", "32")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force --xkcd foobar baz
|
|
t.Run("generate --force --xkcd foobar baz", func(t *testing.T) {
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "xkcd": "true", "lang": "en"}, "foobar", "baz")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force --xkcd --print foobar baz
|
|
t.Run("generate --force --xkcd --print foobar baz", func(t *testing.T) {
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "xkcd": "true", "print": "true", "lang": "en"}, "foobar", "baz")))
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force foobar 24 w/ autoclip and output redirection
|
|
t.Run("generate --force foobar 24", func(t *testing.T) {
|
|
ov := act.cfg.Get("generate.autoclip")
|
|
defer func() {
|
|
require.NoError(t, act.cfg.Set("", "generate.autoclip", ov))
|
|
}()
|
|
require.NoError(t, act.cfg.Set("", "generate.autoclip", "true"))
|
|
ctx := ctxutil.WithTerminal(ctx, false)
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true"}, "foobar", "24")))
|
|
assert.Contains(t, buf.String(), "Not printing secrets by default")
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force foobar 24 w/ autoclip and no output redirection
|
|
t.Run("generate --force foobar 24", func(t *testing.T) {
|
|
ov := act.cfg.Get("generate.autoclip")
|
|
defer func() {
|
|
require.NoError(t, act.cfg.Set("", "generate.autoclip", ov))
|
|
}()
|
|
require.NoError(t, act.cfg.Set("", "generate.autoclip", "true"))
|
|
ctx := ctxutil.WithTerminal(ctx, true)
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true"}, "foobar", "24")))
|
|
assert.Contains(t, buf.String(), "Copied to clipboard")
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force foobar w/ pw length set via env variable (42 chars)
|
|
t.Run("generate --force foobar", func(t *testing.T) {
|
|
t.Setenv("GOPASS_PW_DEFAULT_LENGTH", "42")
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "print": "true", "symbols": "false"}, "foobar")))
|
|
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
|
assert.Len(t, lines[3], 42)
|
|
buf.Reset()
|
|
})
|
|
|
|
// generate --force foobar w/ pw length set via env variable to invalid value, fallback mechanism
|
|
t.Run("generate --force foobar", func(t *testing.T) {
|
|
t.Setenv("GOPASS_PW_DEFAULT_LENGTH", "0")
|
|
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode.")
|
|
}
|
|
|
|
require.NoError(t, act.Generate(gptest.CliCtxWithFlags(ctx, t, map[string]string{"force": "true", "print": "true", "symbols": "false"}, "foobar")))
|
|
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
|
assert.Len(t, lines[3], 24) // 24 = default value used as fallback
|
|
buf.Reset()
|
|
})
|
|
}
|
|
|
|
func passIsAlphaNum(t *testing.T, buf string, want bool) {
|
|
t.Helper()
|
|
|
|
reAlphaNum := regexp.MustCompile(`^[A-Za-z0-9]+$`)
|
|
lines := strings.Split(strings.TrimSpace(buf), "\n")
|
|
if len(lines) < 1 {
|
|
t.Errorf("buffer empty (no lines)")
|
|
}
|
|
line := strings.TrimSpace(lines[len(lines)-1])
|
|
if reAlphaNum.MatchString(line) != want {
|
|
t.Errorf("buffer did not match alpha num re: %s (%s)", line, buf)
|
|
}
|
|
}
|
|
|
|
func TestKeyAndLength(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in []string
|
|
key string
|
|
length string
|
|
}{
|
|
{
|
|
in: []string{"32"},
|
|
key: "",
|
|
length: "32",
|
|
},
|
|
{
|
|
in: []string{"baz"},
|
|
key: "baz",
|
|
length: "",
|
|
},
|
|
{
|
|
in: []string{"baz", "32"},
|
|
key: "baz",
|
|
length: "32",
|
|
},
|
|
{
|
|
in: []string{},
|
|
key: "",
|
|
length: "",
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("%v", tc.in), func(t *testing.T) {
|
|
app := cli.NewApp()
|
|
fs := flag.NewFlagSet("default", flag.ContinueOnError)
|
|
require.NoError(t, fs.Parse(append([]string{"foobar"}, tc.in...)))
|
|
c := cli.NewContext(app, fs, nil)
|
|
args, _ := parseArgs(c)
|
|
k, l := keyAndLength(args)
|
|
assert.Equal(t, tc.key, k, "Key from %+v", tc.in)
|
|
assert.Equal(t, tc.length, l, "Length from %+v", tc.in)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractEmails(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in []string
|
|
out []string
|
|
}{
|
|
{
|
|
out: []string{},
|
|
},
|
|
{
|
|
in: []string{"some/mount/gmail.com/john.doe@example.org", "example.com/user@example.org"},
|
|
out: []string{"john.doe@example.org", "user@example.org"},
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("%v", tc.in), func(t *testing.T) {
|
|
assert.Equal(t, tc.out, extractEmails(tc.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractDomains(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in []string
|
|
out []string
|
|
}{
|
|
{
|
|
out: []string{},
|
|
},
|
|
{
|
|
in: []string{"websites/gmail.com", "live.com", "some/mount/websites/web.de"},
|
|
out: []string{"gmail.com", "live.com", "web.de"},
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("%v", tc.in), func(t *testing.T) {
|
|
assert.Equal(t, tc.out, extractDomains(tc.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUniq(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in []string
|
|
out []string
|
|
}{
|
|
{
|
|
out: []string{},
|
|
},
|
|
{
|
|
in: []string{"foo", "foo", "bar"},
|
|
out: []string{"bar", "foo"},
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("%v", tc.in), func(t *testing.T) {
|
|
assert.Equal(t, tc.out, uniq(tc.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilterPrefix(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in []string
|
|
prefix string
|
|
out []string
|
|
}{
|
|
{
|
|
out: []string{},
|
|
},
|
|
{
|
|
in: []string{"foo", "bar", "baz"},
|
|
prefix: "foo",
|
|
out: []string{"foo"},
|
|
},
|
|
{
|
|
in: []string{"foo/bar", "foo/baz", "bar/foo"},
|
|
prefix: "foo/",
|
|
out: []string{"foo/bar", "foo/baz"},
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("%v", tc.in), func(t *testing.T) {
|
|
assert.Equal(t, tc.out, filterPrefix(tc.in, tc.prefix))
|
|
})
|
|
}
|
|
}
|
|
|
|
// NOTE: Do not use t.Parallel because environment variables are being used
|
|
// which can leak into other tests that run in parallel.
|
|
func TestDefaultLengthFromEnv(t *testing.T) {
|
|
const pwLengthEnvName = "GOPASS_PW_DEFAULT_LENGTH"
|
|
|
|
ctx := config.NewContextInMemory()
|
|
|
|
t.Run("use default value if no environment variable is set", func(t *testing.T) {
|
|
actual, isCustom := config.DefaultPasswordLengthFromEnv(ctx)
|
|
expected := config.DefaultPasswordLength
|
|
assert.Equal(t, expected, actual)
|
|
assert.False(t, isCustom)
|
|
})
|
|
|
|
t.Run("interpretetion of various inputs for environment variable", func(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
in string
|
|
expected int
|
|
custom bool
|
|
}{
|
|
{in: "42", expected: 42, custom: true},
|
|
{in: "1", expected: 1, custom: true},
|
|
{in: "0", expected: config.DefaultPasswordLength, custom: false},
|
|
{in: "abc", expected: config.DefaultPasswordLength, custom: false},
|
|
{in: "-1", expected: config.DefaultPasswordLength, custom: false},
|
|
} {
|
|
t.Setenv(pwLengthEnvName, tc.in)
|
|
actual, isCustom := config.DefaultPasswordLengthFromEnv(ctx)
|
|
assert.Equal(t, tc.expected, actual)
|
|
assert.Equal(t, isCustom, tc.custom)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestHasPwRuleForSecret(t *testing.T) {
|
|
ctx := t.Context()
|
|
|
|
wantRule := pwrules.Rule{
|
|
Minlen: 8,
|
|
Maxlen: 63,
|
|
Required: []string{"digit", "lower", "upper"},
|
|
Allowed: []string{"ascii-printable"},
|
|
Exact: false,
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
input string
|
|
domain string
|
|
want pwrules.Rule
|
|
}{
|
|
{
|
|
name: "domain only",
|
|
input: "websites/apple.com",
|
|
domain: "apple.com",
|
|
want: wantRule,
|
|
},
|
|
{
|
|
name: "domain and username",
|
|
input: "websites/apple.com/gopass",
|
|
domain: "apple.com",
|
|
want: wantRule,
|
|
},
|
|
{
|
|
name: "domain and email",
|
|
input: "websites/apple.com/gopass@gopass.pw",
|
|
domain: "apple.com",
|
|
want: wantRule,
|
|
},
|
|
{
|
|
name: "domain and user that looks like a domain",
|
|
input: "websites/apple.com/gopass.pw",
|
|
domain: "apple.com",
|
|
want: wantRule,
|
|
},
|
|
{
|
|
name: "empty input",
|
|
input: "",
|
|
domain: "",
|
|
want: pwrules.Rule{},
|
|
},
|
|
{
|
|
// domains are search for starting from the end of the string, so
|
|
// the the further right the domain is, the more specific it is.
|
|
name: "double domains",
|
|
input: "websites/apple.com/google.com",
|
|
domain: "google.com",
|
|
want: pwrules.Rule{
|
|
Minlen: 8,
|
|
Maxlen: 0,
|
|
Required: []string{},
|
|
Allowed: []string{"", "digit", "lower", "upper"},
|
|
Maxconsec: 0,
|
|
Exact: false,
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
domain, rule := hasPwRuleForSecret(ctx, tc.input)
|
|
assert.Equal(t, tc.domain, domain, tc.name)
|
|
assert.Equal(t, tc.want, rule, tc.name)
|
|
})
|
|
}
|
|
}
|