Lookup and List Repo Methods for Password Auth Accounts (#216)
This commit is contained in:
1 parent
7897c1cf52
commit
37d8f78091
7 files changed
+295
-8
No files matched your search
@@ -19,6 +19,12 @@ type Account struct {
|
||||
CredentialID string `gorm:"-"`
|
||||
}
|
||||
|
||||
func allocAccount() Account {
|
||||
return Account{
|
||||
Account: &store.Account{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccount creates a new in memory Account with userName assigned to
|
||||
// authMethodId. Name and description are the only valid options. All other
|
||||
// options are ignored.
|
||||
|
||||
@@ -16,6 +16,7 @@ type Option func(*options)
|
||||
type options struct {
|
||||
withName string
|
||||
withDescription string
|
||||
withLimit int
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -35,3 +36,12 @@ func WithName(name string) Option {
|
||||
o.withName = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithLimit provides an option to provide a limit. Intentionally allowing
|
||||
// negative integers. If WithLimit < 0, then unlimited results are returned.
|
||||
// If WithLimit == 0, then default limits are used for results.
|
||||
func WithLimit(l int) Option {
|
||||
return func(o *options) {
|
||||
o.withLimit = l
|
||||
}
|
||||
}
|
||||
@@ -20,4 +20,10 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withDescription = "test desc"
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithLimit", func(t *testing.T) {
|
||||
opts := getOpts(WithLimit(5))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withLimit = 5
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -13,12 +13,15 @@ type Repository struct {
|
||||
reader db.Reader
|
||||
writer db.Writer
|
||||
wrapper wrapping.Wrapper
|
||||
// defaultLimit provides a default for limiting the number of results returned from the repo
|
||||
defaultLimit int
|
||||
}
|
||||
|
||||
// NewRepository creates a new Repository. The returned repository should
|
||||
// only be used for one transaction and it is not safe for concurrent go
|
||||
// routines to access it.
|
||||
func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Repository, error) {
|
||||
// routines to access it. WithLimit option is used as a repo wide default
|
||||
// limit applied to all ListX methods.
|
||||
func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) {
|
||||
switch {
|
||||
case r == nil:
|
||||
return nil, fmt.Errorf("db.Reader: %w", db.ErrNilParameter)
|
||||
@@ -28,9 +31,16 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Reposit
|
||||
return nil, fmt.Errorf("wrapping.Wrapper: %w", db.ErrNilParameter)
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
if opts.withLimit == 0 {
|
||||
// zero signals the watchtower defaults should be used.
|
||||
opts.withLimit = db.DefaultLimit
|
||||
}
|
||||
|
||||
return &Repository{
|
||||
reader: r,
|
||||
writer: w,
|
||||
wrapper: wrapper,
|
||||
reader: r,
|
||||
writer: w,
|
||||
wrapper: wrapper,
|
||||
defaultLimit: opts.withLimit,
|
||||
}, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package password
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
@@ -62,6 +63,42 @@ func (r *Repository) CreateAccount(ctx context.Context, a *Account, opt ...Optio
|
||||
return newAccount, nil
|
||||
}
|
||||
|
||||
// LookupAccount will look up an account in the repository. If the account is not
|
||||
// found, it will return nil, nil. All options are ignored.
|
||||
func (r *Repository) LookupAccount(ctx context.Context, withPublicId string, opt ...Option) (*Account, error) {
|
||||
if withPublicId == "" {
|
||||
return nil, fmt.Errorf("lookup: password account: missing public id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
a := allocAccount()
|
||||
a.PublicId = withPublicId
|
||||
if err := r.reader.LookupByPublicId(ctx, &a); err != nil {
|
||||
if errors.Is(err, db.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("lookup: password account: failed %w for %s", err, withPublicId)
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
// ListAccounts in an auth method and supports WithLimit option.
|
||||
func (r *Repository) ListAccounts(ctx context.Context, withAuthMethodId string, opt ...Option) ([]*Account, error) {
|
||||
if withAuthMethodId == "" {
|
||||
return nil, fmt.Errorf("list: password account: missing auth method id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
var accts []*Account
|
||||
err := r.reader.SearchWhere(ctx, &accts, "auth_method_id = ?", []interface{}{withAuthMethodId}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list: password account: %w", err)
|
||||
}
|
||||
return accts, nil
|
||||
}
|
||||
|
||||
var reInvalidUserName = regexp.MustCompile("[^a-z0-9.]")
|
||||
|
||||
func validUserName(u string) bool {
|
||||
|
||||
@@ -3,10 +3,12 @@ package password
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/auth/password/store"
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -31,6 +33,35 @@ func TestCheckUserName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testAccounts(t *testing.T, conn *gorm.DB, scopeId, authMethodId string, count int) []*Account {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
var auts []*Account
|
||||
for i := 0; i < count; i++ {
|
||||
cat, err := NewAccount(authMethodId, fmt.Sprintf("name%d", i))
|
||||
assert.NoError(err)
|
||||
require.NotNil(cat)
|
||||
id, err := newAuthMethodId()
|
||||
assert.NoError(err)
|
||||
require.NotEmpty(id)
|
||||
cat.PublicId = id
|
||||
|
||||
ctx := context.Background()
|
||||
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
return iw.Create(ctx, cat)
|
||||
},
|
||||
)
|
||||
|
||||
require.NoError(err2)
|
||||
// TODO(toddknight): Figure out why the iw.Create call doesn't populate the scope id from the DB.
|
||||
cat.ScopeId = scopeId
|
||||
auts = append(auts, cat)
|
||||
}
|
||||
return auts
|
||||
}
|
||||
|
||||
func TestRepository_CreateAccount(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
@@ -255,3 +286,173 @@ func TestRepository_CreateAccount(t *testing.T) {
|
||||
assert.Equal(got2.CreateTime, got2.UpdateTime)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepository_LookupAccount(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
authMethod := testAuthMethods(t, conn, 1)[0]
|
||||
account := testAccounts(t, conn, authMethod.GetScopeId(), authMethod.GetPublicId(), 1)[0]
|
||||
|
||||
newAcctId, err := newAccountId()
|
||||
require.NoError(t, err)
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
want *Account
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "With no public id",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "With non existing account id",
|
||||
in: newAcctId,
|
||||
},
|
||||
{
|
||||
name: "With existing account id",
|
||||
in: account.GetPublicId(),
|
||||
want: account,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.LookupAccount(context.Background(), tt.in)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListAccounts(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
authMethods := testAuthMethods(t, conn, 3)
|
||||
accounts1 := testAccounts(t, conn, authMethods[0].GetScopeId(), authMethods[0].GetPublicId(), 3)
|
||||
accounts2 := testAccounts(t, conn, authMethods[1].GetScopeId(), authMethods[1].GetPublicId(), 4)
|
||||
_ = accounts2
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
opts []Option
|
||||
want []*Account
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "With no auth method id",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "With no accounts id",
|
||||
in: authMethods[2].GetPublicId(),
|
||||
want: []*Account{},
|
||||
},
|
||||
{
|
||||
name: "With first auth method id",
|
||||
in: authMethods[0].GetPublicId(),
|
||||
want: accounts1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListAccounts(context.Background(), tt.in, tt.opts...)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListAccounts_Limits(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
am := testAuthMethods(t, conn, 1)[0]
|
||||
|
||||
accountCount := 10
|
||||
_ = testAccounts(t, conn, am.GetScopeId(), am.GetPublicId(), accountCount)
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
repoOpts []Option
|
||||
listOpts []Option
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "With no limits",
|
||||
wantLen: accountCount,
|
||||
},
|
||||
{
|
||||
name: "With repo limit",
|
||||
repoOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "With negative repo limit",
|
||||
repoOpts: []Option{WithLimit(-1)},
|
||||
wantLen: accountCount,
|
||||
},
|
||||
{
|
||||
name: "With List limit",
|
||||
listOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "With negative List limit",
|
||||
listOpts: []Option{WithLimit(-1)},
|
||||
wantLen: accountCount,
|
||||
},
|
||||
{
|
||||
name: "With repo smaller than list limit",
|
||||
repoOpts: []Option{WithLimit(2)},
|
||||
listOpts: []Option{WithLimit(6)},
|
||||
wantLen: 6,
|
||||
},
|
||||
{
|
||||
name: "With repo larger than list limit",
|
||||
repoOpts: []Option{WithLimit(6)},
|
||||
listOpts: []Option{WithLimit(2)},
|
||||
wantLen: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper, tt.repoOpts...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListAccounts(context.Background(), am.GetPublicId(), tt.listOpts...)
|
||||
require.NoError(err)
|
||||
assert.Len(got, tt.wantLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func TestRepository_New(t *testing.T) {
|
||||
r db.Reader
|
||||
w db.Writer
|
||||
wrapper wrapping.Wrapper
|
||||
opts []Option
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
@@ -36,9 +37,25 @@ func TestRepository_New(t *testing.T) {
|
||||
wrapper: wrapper,
|
||||
},
|
||||
want: &Repository{
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
wrapper: wrapper,
|
||||
defaultLimit: db.DefaultLimit,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid with limit",
|
||||
args: args{
|
||||
r: rw,
|
||||
w: rw,
|
||||
wrapper: wrapper,
|
||||
opts: []Option{WithLimit(5)},
|
||||
},
|
||||
want: &Repository{
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
wrapper: wrapper,
|
||||
defaultLimit: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -86,7 +103,7 @@ func TestRepository_New(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper)
|
||||
got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper, tt.args.opts...)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Nil(got)
|
||||
|
||||
Reference in new issue
Block a user