Refactor internal/auth to return domain specific errors (#905)

* Refactor internal/auth to return domain specific errors
This commit is contained in:
s-christoff authored and GitHub committed 2021-02-11 13:32:45 -06:00
1 parent 22462cdd84
commit 6623be8bfb
22 files changed
+502 -365

No files matched your search

+9 -8
View File
@@ -2,13 +2,13 @@ package auth
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
"github.com/hashicorp/boundary/internal/kms"
@@ -321,8 +321,9 @@ func (v *verifier) decryptToken() {
}
func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string, scopeInfo *scopes.ScopeInfo, retAcl perms.ACL, retErr error) {
const op = "auth.(verifier).performAuthCheck"
// Ensure we return an error by default if we forget to set this somewhere
retErr = errors.New("unknown")
retErr = errors.New(errors.Unknown, op, "default auth error")
// Make the linter happy
_ = retErr
scopeInfo = new(scopes.ScopeInfo)
@@ -346,7 +347,7 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string
}
tokenRepo, err := v.authTokenRepoFn()
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to get authtoken repo: %w", err)
retErr = errors.Wrap(err, op)
return
}
at, err := tokenRepo.ValidateToken(v.ctx, v.requestInfo.PublicId, v.requestInfo.Token)
@@ -369,7 +370,7 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string
iamRepo, err := v.iamRepoFn()
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to get iam repo: %w", err)
retErr = errors.Wrap(err, op, errors.WithMsg("failed to get iam repo"))
return
}
@@ -388,11 +389,11 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string
default:
scp, err := iamRepo.LookupScope(v.ctx, v.res.ScopeId)
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to lookup scope: %w", err)
retErr = errors.Wrap(err, op)
return
}
if scp == nil {
retErr = fmt.Errorf("perform auth check: non-existent scope %q", v.res.ScopeId)
retErr = errors.New(errors.InvalidParameter, op, fmt.Sprint("non-existent scope $q", v.res.ScopeId))
return
}
scopeInfo = &scopes.ScopeInfo{
@@ -418,7 +419,7 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string
// u_anon and u_auth)
grantPairs, err = iamRepo.GrantsForUser(v.ctx, userId)
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to query for user grants: %w", err)
retErr = errors.Wrap(err, op)
return
}
parsedGrants = make([]perms.Grant, 0, len(grantPairs))
@@ -433,7 +434,7 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string
perms.WithAccountId(accountId),
perms.WithSkipFinalValidation(true))
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to parse grant %#v: %w", pair.Grant, err)
retErr = errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed to parse grant %#v", pair.Grant)))
return
}
parsedGrants = append(parsedGrants, parsed)
+2 -3
View File
@@ -1,8 +1,6 @@
package password
import (
"fmt"
"github.com/hashicorp/boundary/internal/auth/password/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
@@ -28,10 +26,11 @@ func allocAccount() *Account {
// NewAccount creates a new in memory Account. LoginName, name, and
// description are the only valid options. All other options are ignored.
func NewAccount(authMethodId string, opt ...Option) (*Account, error) {
const op = "password.NewAccount"
// NOTE(mgaffney): The scopeId in the embedded *store.Account is
// populated by a trigger in the database.
if authMethodId == "" {
return nil, fmt.Errorf("new: password account: no auth method id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
}
opts := getOpts(opt...)
+10 -8
View File
@@ -3,7 +3,6 @@ package password
import (
"context"
"crypto/rand"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/auth/password/store"
@@ -116,19 +115,20 @@ type Argon2Credential struct {
}
func newArgon2Credential(accountId string, password string, conf *Argon2Configuration) (*Argon2Credential, error) {
const op = "password.newArgon2Credential"
if accountId == "" {
return nil, fmt.Errorf("new: password argon2 credential: no accountId: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing accountId")
}
if password == "" {
return nil, fmt.Errorf("new: password argon2 credential: no password: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing password")
}
if conf == nil {
return nil, fmt.Errorf("new: password argon2 credential: no argon2 configuration: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing argon2 configuration")
}
id, err := newArgon2CredentialId()
if err != nil {
return nil, fmt.Errorf("new: password argon2 credential: %w", err)
return nil, errors.Wrap(err, op)
}
c := &Argon2Credential{
@@ -142,7 +142,7 @@ func newArgon2Credential(accountId string, password string, conf *Argon2Configur
salt := make([]byte, conf.SaltLength)
if _, err := rand.Read(salt); err != nil {
return nil, fmt.Errorf("new: password argon2 credential: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Io))
}
c.Salt = salt
c.DerivedKey = argon2.IDKey([]byte(password), c.Salt, conf.Iterations, conf.Memory, uint8(conf.Threads), conf.KeyLength)
@@ -170,16 +170,18 @@ func (c *Argon2Credential) SetTableName(n string) {
}
func (c *Argon2Credential) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "password.(Argon2Credential).encrypt"
if err := structwrapping.WrapStruct(ctx, cipher, c.Argon2Credential, nil); err != nil {
return fmt.Errorf("error encrypting argon2 credential: %w", err)
return errors.Wrap(err, op, errors.WithCode(errors.Encrypt))
}
c.KeyId = cipher.KeyID()
return nil
}
func (c *Argon2Credential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "password.(Argon2Credential).decrypt"
if err := structwrapping.UnwrapStruct(ctx, cipher, c.Argon2Credential, nil); err != nil {
return fmt.Errorf("error decrypting argon2 credential: %w", err)
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
+17 -13
View File
@@ -315,10 +315,11 @@ func TestArgon2Credential_New(t *testing.T) {
conf *Argon2Configuration
}
tests := []struct {
name string
args args
want *Argon2Credential
wantIsErr error
name string
args args
want *Argon2Credential
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "blank-accountId",
@@ -327,8 +328,9 @@ func TestArgon2Credential_New(t *testing.T) {
password: "foobarcity",
conf: confs[0],
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.newArgon2Credential: missing accountId: parameter violation: error #100",
},
{
name: "blank-password",
@@ -337,8 +339,9 @@ func TestArgon2Credential_New(t *testing.T) {
password: "",
conf: confs[0],
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.newArgon2Credential: missing password: parameter violation: error #100",
},
{
name: "nil-configuration",
@@ -347,8 +350,9 @@ func TestArgon2Credential_New(t *testing.T) {
password: "foobarcity",
conf: nil,
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.newArgon2Credential: missing argon2 configuration: parameter violation: error #100",
},
{
name: "valid-password",
@@ -398,9 +402,9 @@ func TestArgon2Credential_New(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := newArgon2Credential(tt.args.accountId, tt.args.password, tt.args.conf)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
+2 -3
View File
@@ -1,8 +1,6 @@
package password
import (
"fmt"
"github.com/hashicorp/boundary/internal/auth/password/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
@@ -27,8 +25,9 @@ func allocAuthMethod() AuthMethod {
// ignored. MinLoginNameLength and MinPasswordLength are pre-set to the
// default values of 5 and 8 respectively.
func NewAuthMethod(scopeId string, opt ...Option) (*AuthMethod, error) {
const op = "password.NewAuthMethod"
if scopeId == "" {
return nil, fmt.Errorf("new: password auth method: no scope id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
+5 -4
View File
@@ -1,9 +1,8 @@
package password
import (
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
// Prefixes for private ids for types in the password package.
@@ -13,17 +12,19 @@ const (
)
func newArgon2ConfigurationId() (string, error) {
const op = "password.newArgon2ConfigurationId"
id, err := db.NewPrivateId(argon2ConfigurationPrefix)
if err != nil {
return "", fmt.Errorf("new password argon2 configuration id: %w", err)
return "", errors.Wrap(err, op)
}
return id, err
}
func newArgon2CredentialId() (string, error) {
const op = "password.newArgon2CredentialId"
id, err := db.NewPrivateId(argon2CredentialPrefix)
if err != nil {
return "", fmt.Errorf("new password argon2 configuration id: %w", err)
return "", errors.Wrap(err, op)
}
return id, err
}
+5 -4
View File
@@ -1,9 +1,8 @@
package password
import (
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
// PublicId prefixes for the resources in the password package.
@@ -13,17 +12,19 @@ const (
)
func newAuthMethodId() (string, error) {
const op = "password.newAuthMethodId"
id, err := db.NewPublicId(AuthMethodPrefix)
if err != nil {
return "", fmt.Errorf("new password auth method id: %w", err)
return "", errors.Wrap(err, op)
}
return id, err
}
func newAccountId() (string, error) {
const op = "password.newAccountId"
id, err := db.NewPublicId(AccountPrefix)
if err != nil {
return "", fmt.Errorf("new password account id: %w", err)
return "", errors.Wrap(err, op)
}
return id, err
}
+4 -4
View File
@@ -1,7 +1,6 @@
package password
import (
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
@@ -24,13 +23,14 @@ type Repository struct {
// 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, kms *kms.Kms, opt ...Option) (*Repository, error) {
const op = "password.NewRepository"
switch {
case r == nil:
return nil, fmt.Errorf("db.Reader: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader")
case w == nil:
return nil, fmt.Errorf("db.Writer: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
case kms == nil:
return nil, fmt.Errorf("kms: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing kms")
}
opts := getOpts(opt...)
+48 -38
View File
@@ -26,38 +26,39 @@ import (
// Both a.Name and a.Description are optional. If a.Name is set, it must be
// unique within a.AuthMethodId.
func (r *Repository) CreateAccount(ctx context.Context, scopeId string, a *Account, opt ...Option) (*Account, error) {
const op = "password.(Repository).CreateAccount"
if a == nil {
return nil, fmt.Errorf("create: password account: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing Account")
}
if a.Account == nil {
return nil, fmt.Errorf("create: password account: embedded Account: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing embedded Account")
}
if a.AuthMethodId == "" {
return nil, fmt.Errorf("create: password account: no auth method id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
}
if a.PublicId != "" {
return nil, fmt.Errorf("create: password account: public id not empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "public id must be empty")
}
if scopeId == "" {
return nil, fmt.Errorf("create: password account: scope id empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
if !validLoginName(a.LoginName) {
return nil, fmt.Errorf("create: password account: invalid login name; must be all-lowercase alphanumeric, period or hyphen: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("login name must be all-lowercase alphanumeric, period or hyphen. got: %s", a.LoginName))
}
cc, err := r.currentConfig(ctx, a.AuthMethodId)
if err != nil {
return nil, fmt.Errorf("create: password account: retrieve current configuration: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("retrieve current configuration"))
}
if cc.MinLoginNameLength > len(a.LoginName) {
return nil, fmt.Errorf("create: password account: user name %q: %w", a.LoginName, ErrTooShort)
return nil, errors.New(errors.TooShort, op, fmt.Sprintf("username: %s, must be longer than %d", a.LoginName, cc.MinLoginNameLength))
}
a = a.clone()
id, err := newAccountId()
if err != nil {
return nil, fmt.Errorf("create: password account: %w", err)
return nil, errors.Wrap(err, op)
}
a.PublicId = id
@@ -66,20 +67,20 @@ func (r *Repository) CreateAccount(ctx context.Context, scopeId string, a *Accou
var cred *Argon2Credential
if opts.withPassword {
if cc.MinPasswordLength > len(opts.password) {
return nil, fmt.Errorf("create: password account: password: %w", ErrTooShort)
return nil, errors.New(errors.PasswordTooShort, op, fmt.Sprintf("must be longer than %v", cc.MinPasswordLength))
}
if cred, err = newArgon2Credential(id, opts.password, cc.argon2()); err != nil {
return nil, fmt.Errorf("create: password account: %w", err)
return nil, errors.Wrap(err, op)
}
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("create: password account: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"), errors.WithCode(errors.Encrypt))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, fmt.Errorf("create: password account: unable to get database wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"), errors.WithCode(errors.Encrypt))
}
var newCred *Argon2Credential
@@ -106,10 +107,10 @@ func (r *Repository) CreateAccount(ctx context.Context, scopeId string, a *Accou
if err != nil {
if errors.IsUniqueError(err) {
return nil, fmt.Errorf("create: password account: in auth method: %s: name %q or loginName %q already exists: %w",
a.AuthMethodId, a.Name, a.LoginName, errors.ErrNotUnique)
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf("in auth method %s: name %q or loginName %q already exists",
a.AuthMethodId, a.Name, a.LoginName))
}
return nil, fmt.Errorf("create: password account: in auth method: %s: %w", a.AuthMethodId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(a.AuthMethodId))
}
return newAccount, nil
}
@@ -117,8 +118,9 @@ func (r *Repository) CreateAccount(ctx context.Context, scopeId string, a *Accou
// 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) {
const op = "password.(Repository).LookupAccount"
if withPublicId == "" {
return nil, fmt.Errorf("lookup: password account: missing public id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
}
a := allocAccount()
a.PublicId = withPublicId
@@ -126,15 +128,16 @@ func (r *Repository) LookupAccount(ctx context.Context, withPublicId string, opt
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, fmt.Errorf("lookup: password account: failed %w for %s", err, withPublicId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", 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) {
const op = "password.(Repository).ListAccounts"
if withAuthMethodId == "" {
return nil, fmt.Errorf("list: password account: missing auth method id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
}
opts := getOpts(opt...)
limit := r.defaultLimit
@@ -145,7 +148,7 @@ func (r *Repository) ListAccounts(ctx context.Context, withAuthMethodId string,
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 nil, errors.Wrap(err, op)
}
return accts, nil
}
@@ -153,18 +156,19 @@ func (r *Repository) ListAccounts(ctx context.Context, withAuthMethodId string,
// DeleteAccount deletes the account for the provided id from the repository returning a count of the
// number of records deleted. All options are ignored.
func (r *Repository) DeleteAccount(ctx context.Context, scopeId, withPublicId string, opt ...Option) (int, error) {
const op = "password.(Repository).DeleteAccount"
if withPublicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete: password account: missing public id: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
}
if scopeId == "" {
return db.NoRowsAffected, fmt.Errorf("delete: password account: scope id empty: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
}
ac := allocAccount()
ac.PublicId = withPublicId
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete: password account: unable to get oplog wrapper: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
}
var rowsDeleted int
@@ -184,7 +188,7 @@ func (r *Repository) DeleteAccount(ctx context.Context, scopeId, withPublicId st
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete: password account: %s: %w", withPublicId, err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(withPublicId))
}
return rowsDeleted, nil
@@ -213,20 +217,22 @@ func validLoginName(u string) bool {
// in a is the zero value and it is included in fieldMaskPaths. a.LoginName
// cannot be set to NULL.
func (r *Repository) UpdateAccount(ctx context.Context, scopeId string, a *Account, version uint32, fieldMaskPaths []string, opt ...Option) (*Account, int, error) {
const op = "password.(Repository).UpdateAccount"
if a == nil {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing Account")
}
if a.Account == nil {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: embedded Account: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing embedded Account")
}
if a.PublicId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: missing public id: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
}
if version == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: no version supplied: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
if scopeId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: scope id empty: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
}
var changeLoginName bool
@@ -236,11 +242,12 @@ func (r *Repository) UpdateAccount(ctx context.Context, scopeId string, a *Accou
case strings.EqualFold("Description", f):
case strings.EqualFold("LoginName", f):
if !validLoginName(a.LoginName) {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: invalid user name: must be all-lowercase alphanumeric, period or hyphen %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op,
fmt.Sprintf("invalid username: must be all-lowercase alphanumeric, period or hyphen, got %s", a.LoginName))
}
changeLoginName = true
default:
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: field: %s: %w", f, errors.ErrInvalidFieldMask)
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f)
}
}
var dbMask, nullFields []string
@@ -254,22 +261,24 @@ func (r *Repository) UpdateAccount(ctx context.Context, scopeId string, a *Accou
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: %w", errors.ErrEmptyFieldMask)
return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "missing field mask")
}
if changeLoginName {
cc, err := r.currentConfigForAccount(ctx, a.PublicId)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: retrieve current configuration: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("retrieve current configuration"))
}
if cc.MinLoginNameLength > len(a.LoginName) {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: user name %q: %w", a.LoginName, ErrTooShort)
return nil, db.NoRowsAffected, errors.New(errors.TooShort, op,
fmt.Sprintf("username: %s, must be longer than %v", a.LoginName, cc.MinLoginNameLength))
}
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: unable to get oplog wrapper: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt),
errors.WithMsg(("unable to get oplog wrapper")))
}
a = a.clone()
@@ -292,10 +301,11 @@ func (r *Repository) UpdateAccount(ctx context.Context, scopeId string, a *Accou
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: %s: name %s already exists: %w",
a.PublicId, a.Name, errors.ErrNotUnique)
return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op,
fmt.Sprintf("name %s already exists: %s", a.Name, a.PublicId))
}
return nil, db.NoRowsAffected, fmt.Errorf("update: password account: %s: %w", a.PublicId, err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(a.PublicId))
}
return returnedAccount, rowsUpdated, nil
+135 -88
View File
@@ -51,27 +51,31 @@ func TestRepository_CreateAccount(t *testing.T) {
authMethod := authMethods[0]
tests := []struct {
name string
in *Account
opts []Option
want *Account
wantIsErr error
name string
in *Account
opts []Option
want *Account
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "nil-Account",
wantIsErr: errors.ErrInvalidParameter,
name: "nil-Account",
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: missing Account: parameter violation: error #100",
},
{
name: "nil-embedded-Account",
in: &Account{},
wantIsErr: errors.ErrInvalidParameter,
name: "nil-embedded-Account",
in: &Account{},
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: missing embedded Account: parameter violation: error #100",
},
{
name: "invalid-no-scope-id",
in: &Account{
Account: &store.Account{},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: missing auth method id: parameter violation: error #100",
},
{
name: "invalid-public-id-set",
@@ -81,7 +85,8 @@ func TestRepository_CreateAccount(t *testing.T) {
PublicId: "hcst_OOOOOOOOOO",
},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: public id must be empty: parameter violation: error #100",
},
{
name: "invalid-loginname-uppercase",
@@ -91,7 +96,8 @@ func TestRepository_CreateAccount(t *testing.T) {
LoginName: "KaZmiErcZak11",
},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: login name must be all-lowercase alphanumeric, period or hyphen. got: KaZmiErcZak11: parameter violation: error #100",
},
{
name: "invalid-loginname-leading-space",
@@ -101,7 +107,8 @@ func TestRepository_CreateAccount(t *testing.T) {
LoginName: " kazmierczak12",
},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: login name must be all-lowercase alphanumeric, period or hyphen. got: kazmierczak12: parameter violation: error #100",
},
{
name: "invalid-loginname-trailing-space",
@@ -111,7 +118,8 @@ func TestRepository_CreateAccount(t *testing.T) {
LoginName: "kazmierczak13 ",
},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: login name must be all-lowercase alphanumeric, period or hyphen. got: kazmierczak13 : parameter violation: error #100",
},
{
name: "invalid-loginname-space-in-name",
@@ -121,7 +129,8 @@ func TestRepository_CreateAccount(t *testing.T) {
LoginName: "kazmier czak14",
},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAccount: login name must be all-lowercase alphanumeric, period or hyphen. got: kazmier czak14: parameter violation: error #100",
},
{
name: "invalid-loginname-too-short",
@@ -131,7 +140,8 @@ func TestRepository_CreateAccount(t *testing.T) {
LoginName: "ka",
},
},
wantIsErr: ErrTooShort,
wantIsErr: errors.TooShort,
wantErrMsg: "password.(Repository).CreateAccount: username: ka, must be longer than 3: integrity violation: error #113",
},
{
name: "invalid-password-too-short",
@@ -144,7 +154,8 @@ func TestRepository_CreateAccount(t *testing.T) {
opts: []Option{
WithPassword("a"),
},
wantIsErr: ErrTooShort,
wantIsErr: errors.PasswordTooShort,
wantErrMsg: "password.(Repository).CreateAccount: must be longer than 8: password violation: error #200",
},
{
name: "valid-no-options",
@@ -223,9 +234,9 @@ func TestRepository_CreateAccount(t *testing.T) {
assert.NoError(err)
require.NotNil(repo)
got, err := repo.CreateAccount(context.Background(), org.GetPublicId(), 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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -247,6 +258,14 @@ func TestRepository_CreateAccount(t *testing.T) {
}
})
}
}
func TestRepository_CreateAccount_DuplicateNames(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
t.Run("invalid-duplicate-names", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -276,7 +295,7 @@ func TestRepository_CreateAccount(t *testing.T) {
assert.Equal(got.CreateTime, got.UpdateTime)
got2, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in)
assert.Truef(errors.Is(err, errors.ErrNotUnique), "want err: %v got: %v", errors.ErrNotUnique, err)
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "Unexpected error %s", err)
assert.Nil(got2)
})
@@ -334,14 +353,16 @@ func TestRepository_LookupAccount(t *testing.T) {
newAcctId, err := newAccountId()
require.NoError(t, err)
tests := []struct {
name string
in string
want *Account
wantIsErr error
name string
in string
want *Account
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "With no public id",
wantIsErr: errors.ErrInvalidParameter,
name: "With no public id",
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "password.(Repository).LookupAccount: missing public id: parameter violation: error #102",
},
{
name: "With non existing account id",
@@ -362,9 +383,9 @@ func TestRepository_LookupAccount(t *testing.T) {
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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -388,14 +409,16 @@ func TestRepository_DeleteAccount(t *testing.T) {
newAcctId, err := newAccountId()
require.NoError(t, err)
tests := []struct {
name string
in string
want int
wantIsErr error
name string
in string
want int
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "With no public id",
wantIsErr: errors.ErrInvalidParameter,
name: "With no public id",
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "password.(Repository).DeleteAccount: missing public id: parameter violation: error #102",
},
{
name: "With non existing account id",
@@ -417,9 +440,9 @@ func TestRepository_DeleteAccount(t *testing.T) {
assert.NoError(err)
require.NotNil(repo)
got, err := repo.DeleteAccount(context.Background(), org.GetPublicId(), tt.in)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
assert.Zero(got)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -443,15 +466,17 @@ func TestRepository_ListAccounts(t *testing.T) {
_ = accounts2
tests := []struct {
name string
in string
opts []Option
want []*Account
wantIsErr error
name string
in string
opts []Option
want []*Account
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "With no auth method id",
wantIsErr: errors.ErrInvalidParameter,
name: "With no auth method id",
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).ListAccounts: missing auth method id: parameter violation: error #100",
},
{
name: "With no accounts id",
@@ -473,9 +498,9 @@ func TestRepository_ListAccounts(t *testing.T) {
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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -620,40 +645,44 @@ func TestRepository_UpdateAccount(t *testing.T) {
}
tests := []struct {
name string
orig *Account
chgFn func(*Account) *Account
masks []string
want *Account
wantCount int
wantIsErr errors.Code
name string
orig *Account
chgFn func(*Account) *Account
masks []string
want *Account
wantCount int
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "nil-Account",
orig: &Account{
Account: &store.Account{},
},
chgFn: makeNil(),
masks: []string{"Name", "Description"},
wantIsErr: errors.InvalidParameter,
chgFn: makeNil(),
masks: []string{"Name", "Description"},
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).UpdateAccount: missing Account: parameter violation: error #100",
},
{
name: "nil-embedded-Account",
orig: &Account{
Account: &store.Account{},
},
chgFn: makeEmbeddedNil(),
masks: []string{"Name", "Description"},
wantIsErr: errors.InvalidParameter,
chgFn: makeEmbeddedNil(),
masks: []string{"Name", "Description"},
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).UpdateAccount: missing embedded Account: parameter violation: error #100",
},
{
name: "no-public-id",
orig: &Account{
Account: &store.Account{},
},
chgFn: deletePublicId(),
masks: []string{"Name", "Description"},
wantIsErr: errors.InvalidParameter,
chgFn: deletePublicId(),
masks: []string{"Name", "Description"},
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "password.(Repository).UpdateAccount: missing public id: parameter violation: error #102",
},
{
name: "updating-non-existent-Account",
@@ -662,9 +691,10 @@ func TestRepository_UpdateAccount(t *testing.T) {
Name: "test-name-repo",
},
},
chgFn: combine(nonExistentPublicId(), changeName("test-update-name-repo")),
masks: []string{"Name"},
wantIsErr: errors.RecordNotFound,
chgFn: combine(nonExistentPublicId(), changeName("test-update-name-repo")),
masks: []string{"Name"},
wantIsErr: errors.RecordNotFound,
wantErrMsg: "password.(Repository).UpdateAccount: abcd_OOOOOOOOOO: db.DoTx: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100",
},
{
name: "empty-field-mask",
@@ -673,8 +703,9 @@ func TestRepository_UpdateAccount(t *testing.T) {
Name: "test-name-repo",
},
},
chgFn: changeName("test-update-name-repo"),
wantIsErr: errors.EmptyFieldMask,
chgFn: changeName("test-update-name-repo"),
wantIsErr: errors.EmptyFieldMask,
wantErrMsg: "password.(Repository).UpdateAccount: missing field mask: parameter violation: error #104",
},
{
name: "read-only-fields-in-field-mask",
@@ -683,9 +714,10 @@ func TestRepository_UpdateAccount(t *testing.T) {
Name: "test-name-repo",
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"PublicId", "CreateTime", "UpdateTime", "AuthMethodId"},
wantIsErr: errors.InvalidFieldMask,
chgFn: changeName("test-update-name-repo"),
masks: []string{"PublicId", "CreateTime", "UpdateTime", "AuthMethodId"},
wantIsErr: errors.InvalidFieldMask,
wantErrMsg: "password.(Repository).UpdateAccount: PublicId: parameter violation: error #103",
},
{
name: "unknown-field-in-field-mask",
@@ -694,9 +726,10 @@ func TestRepository_UpdateAccount(t *testing.T) {
Name: "test-name-repo",
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"Bilbo"},
wantIsErr: errors.InvalidFieldMask,
chgFn: changeName("test-update-name-repo"),
masks: []string{"Bilbo"},
wantIsErr: errors.InvalidFieldMask,
wantErrMsg: "password.(Repository).UpdateAccount: Bilbo: parameter violation: error #103",
},
{
name: "change-name",
@@ -841,9 +874,10 @@ func TestRepository_UpdateAccount(t *testing.T) {
LoginName: "kazmierczak",
},
},
chgFn: changeLoginName("KaZmIeRcZaK"),
masks: []string{"LoginName"},
wantIsErr: errors.InvalidParameter,
chgFn: changeLoginName("KaZmIeRcZaK"),
masks: []string{"LoginName"},
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).UpdateAccount: invalid username: must be all-lowercase alphanumeric, period or hyphen, got KaZmIeRcZaK: parameter violation: error #100",
},
{
name: "change-login-name-to-short",
@@ -852,9 +886,10 @@ func TestRepository_UpdateAccount(t *testing.T) {
LoginName: "kazmierczak",
},
},
chgFn: changeLoginName("ka"),
masks: []string{"LoginName"},
wantIsErr: errors.PasswordTooShort,
chgFn: changeLoginName("ka"),
masks: []string{"LoginName"},
wantIsErr: errors.TooShort,
wantErrMsg: "password.(Repository).UpdateAccount: username: ka, must be longer than 3: integrity violation: error #113",
},
{
name: "delete-login-name",
@@ -863,9 +898,10 @@ func TestRepository_UpdateAccount(t *testing.T) {
LoginName: "kazmierczak",
},
},
chgFn: changeLoginName(""),
masks: []string{"LoginName"},
wantIsErr: errors.InvalidParameter,
chgFn: changeLoginName(""),
masks: []string{"LoginName"},
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).UpdateAccount: invalid username: must be all-lowercase alphanumeric, period or hyphen, got : parameter violation: error #100",
},
}
@@ -894,6 +930,7 @@ func TestRepository_UpdateAccount(t *testing.T) {
got, gotCount, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), orig, 1, tt.masks)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Equal(tt.wantErrMsg, err.Error())
assert.Equal(tt.wantCount, gotCount, "row count")
assert.Nil(got)
return
@@ -927,6 +964,15 @@ func TestRepository_UpdateAccount(t *testing.T) {
})
}
}
func TestRepository_UpdateAccount_DupeNames(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
t.Run("invalid-duplicate-names", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms)
@@ -951,7 +997,7 @@ func TestRepository_UpdateAccount(t *testing.T) {
ab.Name = name
got2, gotCount2, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), ab, 1, []string{"name"})
assert.Truef(errors.Is(err, errors.ErrNotUnique), "want err: %v got: %v", errors.ErrNotUnique, err)
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "Unexpected error %s", err)
assert.Nil(got2)
assert.Equal(db.NoRowsAffected, gotCount2, "row count")
err = db.TestVerifyOplog(t, rw, ab.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
@@ -1029,7 +1075,7 @@ func TestRepository_UpdateAccount(t *testing.T) {
ab.LoginName = loginName
got2, gotCount2, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), ab, 1, []string{"LoginName"})
assert.Truef(errors.Is(err, errors.ErrNotUnique), "want err: %v got: %v", errors.ErrNotUnique, err)
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "Unexpected error %s", err)
assert.Nil(got2)
assert.Equal(db.NoRowsAffected, gotCount2, "row count")
err = db.TestVerifyOplog(t, rw, ab.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
@@ -1108,4 +1154,5 @@ func TestRepository_UpdateAccount(t *testing.T) {
assert.Equal(1, gotCount1, "row count")
assert.NoError(db.TestVerifyOplog(t, rw, aa.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
})
}
+34 -28
View File
@@ -23,17 +23,18 @@ import (
// Both m.Name and m.Description are optional. If m.Name is set, it must be
// unique within m.ScopeId.
func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ...Option) (*AuthMethod, error) {
const op = "password.(Repository).CreateAuthMethod"
if m == nil {
return nil, fmt.Errorf("create: password auth method: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing AuthMethod")
}
if m.AuthMethod == nil {
return nil, fmt.Errorf("create: password auth method: embedded AuthMethod: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing embedded AuthMethod")
}
if m.ScopeId == "" {
return nil, fmt.Errorf("create: password auth method: no scope id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
if m.PublicId != "" {
return nil, fmt.Errorf("create: password auth method: public id not empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "public id not empty")
}
m = m.clone()
@@ -41,35 +42,35 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ..
if opts.withPublicId != "" {
if !strings.HasPrefix(opts.withPublicId, AuthMethodPrefix+"_") {
return nil, fmt.Errorf("create: password auth method: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, AuthMethodPrefix, errors.ErrInvalidPublicId)
return nil, errors.New(errors.InvalidPublicId, op, fmt.Sprintf("passed-in public ID %q has wrong prefix, should be %q", opts.withPublicId, AuthMethodPrefix))
}
m.PublicId = opts.withPublicId
} else {
id, err := newAuthMethodId()
if err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
return nil, errors.Wrap(err, op)
}
m.PublicId = id
}
c, ok := opts.withConfig.(*Argon2Configuration)
if !ok {
return nil, fmt.Errorf("create: password auth method: unknown configuration: %w", ErrUnsupportedConfiguration)
return nil, errors.New(errors.PasswordUnsupportedConfiguration, op, "unknown configuration")
}
if err := c.validate(); err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
return nil, errors.Wrap(err, op)
}
var err error
c.PrivateId, err = newArgon2ConfigurationId()
if err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
return nil, errors.Wrap(err, op)
}
m.PasswordConfId, c.PasswordMethodId = c.PrivateId, m.PublicId
oplogWrapper, err := r.kms.GetWrapper(ctx, m.GetScopeId(), kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("create: password auth method: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
}
var newAuthMethod *AuthMethod
@@ -87,10 +88,9 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ..
if err != nil {
if errors.IsUniqueError(err) {
return nil, fmt.Errorf("create: password auth method: in scope: %s: name %s already exists: %w",
m.ScopeId, m.Name, errors.ErrNotUnique)
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf("in scope: %s: name %s already exists", m.ScopeId, m.Name))
}
return nil, fmt.Errorf("create: password auth method: in scope: %s: %w", m.ScopeId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(m.ScopeId))
}
return newAuthMethod, nil
}
@@ -98,8 +98,9 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ..
// LookupAuthMethod will look up an auth method in the repository. If the auth method is not
// found, it will return nil, nil. All options are ignored.
func (r *Repository) LookupAuthMethod(ctx context.Context, publicId string, opt ...Option) (*AuthMethod, error) {
const op = "password.(Repository).LookupAuthMethod"
if publicId == "" {
return nil, fmt.Errorf("lookup: password auth method: missing public id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
}
a := allocAuthMethod()
a.PublicId = publicId
@@ -107,15 +108,16 @@ func (r *Repository) LookupAuthMethod(ctx context.Context, publicId string, opt
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, fmt.Errorf("lookup: password auth method: failed %w for %s", err, publicId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
}
return &a, nil
}
// ListAuthMethods returns a slice of AuthMethods for the scopeId. WithLimit is the only option supported.
func (r *Repository) ListAuthMethods(ctx context.Context, scopeIds []string, opt ...Option) ([]*AuthMethod, error) {
const op = "password.(Repository).ListAuthMethods"
if len(scopeIds) == 0 {
return nil, fmt.Errorf("list: password auth method: missing scope id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
limit := r.defaultLimit
@@ -126,7 +128,7 @@ func (r *Repository) ListAuthMethods(ctx context.Context, scopeIds []string, opt
var authMethods []*AuthMethod
err := r.reader.SearchWhere(ctx, &authMethods, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit))
if err != nil {
return nil, fmt.Errorf("list: password auth method: %w", err)
return nil, errors.Wrap(err, op)
}
return authMethods, nil
}
@@ -134,15 +136,17 @@ func (r *Repository) ListAuthMethods(ctx context.Context, scopeIds []string, opt
// DeleteAuthMethod deletes the auth method for the provided id from the repository returning a count of the
// number of records deleted. All options are ignored.
func (r *Repository) DeleteAuthMethod(ctx context.Context, scopeId, publicId string, opt ...Option) (int, error) {
const op = "password.(Repository).DeleteAuthMethod"
if publicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete: password auth method: missing public id: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
}
am := allocAuthMethod()
am.PublicId = publicId
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete: password auth method: unable to get oplog wrapper: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt),
errors.WithMsg("unable to get oplog wrapper"))
}
var rowsDeleted int
@@ -162,7 +166,7 @@ func (r *Repository) DeleteAuthMethod(ctx context.Context, scopeId, publicId str
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete: password auth method: %s: %w", publicId, err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(publicId))
}
return rowsDeleted, nil
@@ -181,14 +185,15 @@ func (r *Repository) DeleteAuthMethod(ctx context.Context, scopeId, publicId str
// and MinLoginNameLength are the only updatable fields, If no updatable fields
// are included in the fieldMaskPaths, then an error is returned.
func (r *Repository) UpdateAuthMethod(ctx context.Context, authMethod *AuthMethod, version uint32, fieldMaskPaths []string, opt ...Option) (*AuthMethod, int, error) {
const op = "password.(Repository).UpdateAuthMethod"
if authMethod == nil {
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: missing authMethod: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing authMethod")
}
if authMethod.PublicId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: missing authMethod public id: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing authMethod public id")
}
if authMethod.ScopeId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: scope id empty: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
}
for _, f := range fieldMaskPaths {
switch {
@@ -197,7 +202,7 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, authMethod *AuthMetho
case strings.EqualFold("MinLoginNameLength", f):
case strings.EqualFold("MinPasswordLength", f):
default:
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: field: %s: %w", f, errors.ErrInvalidFieldMask)
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f)
}
}
var dbMask, nullFields []string
@@ -212,12 +217,13 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, authMethod *AuthMetho
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: %w", errors.ErrEmptyFieldMask)
return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "field mask must not be empty")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, authMethod.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: unable to get oplog wrapper: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt),
errors.WithMsg("unable to get oplog wrapper"))
}
upAuthMethod := authMethod.clone()
@@ -247,9 +253,9 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, authMethod *AuthMetho
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: authMethod %s already exists in scope %s: %w", authMethod.Name, authMethod.ScopeId, errors.ErrNotUnique)
return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("authMethod %s already exists in scope %s", authMethod.Name, authMethod.ScopeId))
}
return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: %w for %s", err, authMethod.PublicId)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(authMethod.PublicId))
}
return upAuthMethod, rowsUpdated, err
}
@@ -29,27 +29,31 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
org, _ := iam.TestScopes(t, iamRepo)
tests := []struct {
name string
in *AuthMethod
opts []Option
want *AuthMethod
wantIsErr error
name string
in *AuthMethod
opts []Option
want *AuthMethod
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "nil-AuthMethod",
wantIsErr: errors.ErrInvalidParameter,
name: "nil-AuthMethod",
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAuthMethod: missing AuthMethod: parameter violation: error #100",
},
{
name: "nil-embedded-AuthMethod",
in: &AuthMethod{},
wantIsErr: errors.ErrInvalidParameter,
name: "nil-embedded-AuthMethod",
in: &AuthMethod{},
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAuthMethod: missing embedded AuthMethod: parameter violation: error #100",
},
{
name: "invalid-no-scope-id",
in: &AuthMethod{
AuthMethod: &store.AuthMethod{},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAuthMethod: missing scope id: parameter violation: error #100",
},
{
name: "invalid-public-id-set",
@@ -59,7 +63,8 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
PublicId: "hcst_OOOOOOOOOO",
},
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).CreateAuthMethod: public id not empty: parameter violation: error #100",
},
{
name: "valid-no-options",
@@ -114,7 +119,8 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
opts: []Option{
WithConfiguration(&Argon2Configuration{}),
},
wantIsErr: ErrInvalidConfiguration,
wantIsErr: errors.PasswordInvalidConfiguration,
wantErrMsg: "password.(Repository).CreateAuthMethod: invalid parameters in password configuration, password violation: error #202",
},
{
name: "invalid-with-config-unknown-config-type",
@@ -126,7 +132,8 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
opts: []Option{
WithConfiguration(tconf(0)),
},
wantIsErr: ErrUnsupportedConfiguration,
wantIsErr: errors.PasswordUnsupportedConfiguration,
wantErrMsg: "password.(Repository).CreateAuthMethod: unknown configuration: password violation: error #201",
},
}
@@ -138,9 +145,9 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
require.NoError(err)
require.NotNil(repo)
got, err := repo.CreateAuthMethod(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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -153,6 +160,14 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
assert.Equal(got.CreateTime, got.UpdateTime)
})
}
}
func TestRepository_CreateAuthMethod_DupeNames(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
t.Run("invalid-duplicate-names", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -178,7 +193,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
assert.Equal(got.CreateTime, got.UpdateTime)
got2, err := repo.CreateAuthMethod(context.Background(), in)
assert.Truef(errors.Is(err, errors.ErrNotUnique), "want err: %v got: %v", errors.ErrNotUnique, err)
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "Unexpected error %s", err)
assert.Nil(got2)
})
@@ -217,6 +232,14 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
assert.Equal(in2.Description, got2.Description)
assert.Equal(got2.CreateTime, got2.UpdateTime)
})
}
func TestRepository_CreateAuthMethod_PublicId(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
t.Run("valid-with-publicid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -251,7 +274,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
got, err := repo.CreateAuthMethod(context.Background(), &in, WithPublicId("invalid_idwithabadprefix"))
assert.Error(err)
assert.Nil(got)
assert.True(errors.Is(err, errors.ErrInvalidPublicId))
assert.Truef(errors.Match(errors.T(errors.InvalidPublicId), err), "Unexpected error %s", err)
})
}
@@ -267,14 +290,16 @@ func TestRepository_LookupAuthMethod(t *testing.T) {
amId, err := newAuthMethodId()
require.NoError(t, err)
tests := []struct {
name string
in string
want *AuthMethod
wantIsErr error
name string
in string
want *AuthMethod
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "With no public id",
wantIsErr: errors.ErrInvalidParameter,
name: "With no public id",
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "password.(Repository).LookupAuthMethod: missing public id: parameter violation: error #102",
},
{
name: "With non existing auth method id",
@@ -295,9 +320,9 @@ func TestRepository_LookupAuthMethod(t *testing.T) {
assert.NoError(err)
require.NotNil(repo)
got, err := repo.LookupAuthMethod(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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -318,14 +343,16 @@ func TestRepository_DeleteAuthMethod(t *testing.T) {
newAuthMethodId, err := newAuthMethodId()
require.NoError(t, err)
tests := []struct {
name string
in string
want int
wantIsErr error
name string
in string
want int
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "With no public id",
wantIsErr: errors.ErrInvalidParameter,
name: "With no public id",
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "password.(Repository).DeleteAuthMethod: missing public id: parameter violation: error #102",
},
{
name: "With non existing auth method id",
@@ -347,9 +374,9 @@ func TestRepository_DeleteAuthMethod(t *testing.T) {
assert.NoError(err)
require.NotNil(repo)
got, err := repo.DeleteAuthMethod(context.Background(), o.GetPublicId(), tt.in)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
assert.Zero(got)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -369,15 +396,17 @@ func TestRepository_ListAuthMethods(t *testing.T) {
authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 3)
tests := []struct {
name string
in []string
opts []Option
want []*AuthMethod
wantIsErr error
name string
in []string
opts []Option
want []*AuthMethod
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "With no scope id",
wantIsErr: errors.ErrInvalidParameter,
name: "With no scope id",
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).ListAuthMethods: missing scope id: parameter violation: error #100",
},
{
name: "Scope with no auth methods",
@@ -399,9 +428,9 @@ func TestRepository_ListAuthMethods(t *testing.T) {
assert.NoError(err)
require.NotNil(repo)
got, err := repo.ListAuthMethods(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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -2,7 +2,6 @@ package password
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/auth/password/store"
"github.com/hashicorp/boundary/internal/db"
@@ -21,12 +20,13 @@ type Configuration interface {
// GetConfiguration returns the current configuration for authMethodId.
func (r *Repository) GetConfiguration(ctx context.Context, authMethodId string) (Configuration, error) {
const op = "password.(Repository).GetConfiguration"
if authMethodId == "" {
return nil, fmt.Errorf("get password configuration: no auth method id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
}
cc, err := r.currentConfig(ctx, authMethodId)
if err != nil {
return nil, fmt.Errorf("get password configuration: %w", err)
return nil, errors.Wrap(err, op)
}
return cc.argon2(), nil
}
@@ -42,29 +42,31 @@ func (r *Repository) GetConfiguration(ctx context.Context, authMethodId string)
// equal to a previous configuration for c.AuthMethodId, SetConfiguration
// updates AuthMethod to use the previous configuration.
func (r *Repository) SetConfiguration(ctx context.Context, scopeId string, c Configuration) (Configuration, error) {
const op = "password.(Repository).SetConfiguration"
if c == nil {
return nil, fmt.Errorf("set password configuration: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing configuration")
}
if c.AuthMethodId() == "" {
return nil, fmt.Errorf("set password configuration: no auth method id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
}
if err := c.validate(); err != nil {
return nil, fmt.Errorf("set password configuration: %w", err)
return nil, errors.Wrap(err, op)
}
switch v := c.(type) {
case *Argon2Configuration:
out, err := r.setArgon2Conf(ctx, scopeId, v)
if err != nil {
return nil, fmt.Errorf("set password configuration: %w", err)
return nil, errors.Wrap(err, op)
}
return out, nil
default:
return nil, fmt.Errorf("set password configuration: %w", ErrUnsupportedConfiguration)
return nil, errors.New(errors.PasswordUnsupportedConfiguration, op, "unknown configuration")
}
}
func (r *Repository) setArgon2Conf(ctx context.Context, scopeId string, c *Argon2Configuration) (*Argon2Configuration, error) {
const op = "password.(Repository).setArgon2Conf"
c = c.clone()
id, err := newArgon2ConfigurationId()
@@ -81,7 +83,7 @@ func (r *Repository) setArgon2Conf(ctx context.Context, scopeId string, c *Argon
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("update: password account: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
}
newArgon2Conf := &Argon2Configuration{Argon2Configuration: &store.Argon2Configuration{}}
@@ -134,6 +136,7 @@ func (r *Repository) currentConfig(ctx context.Context, authMethodId string) (*c
}
func (r *Repository) currentConfigForAccount(ctx context.Context, accountId string) (*currentConfig, error) {
const op = "password.(Repository).currentConfigForAccount"
var confs []currentConfig
rows, err := r.reader.Query(ctx, currentConfigForAccountQuery, []interface{}{accountId})
@@ -155,7 +158,7 @@ func (r *Repository) currentConfigForAccount(ctx context.Context, accountId stri
return nil, nil
case len(confs) > 1:
// this should never happen
return nil, fmt.Errorf("multiple current configs returned for account")
return nil, errors.New(errors.Unknown, op, "multiple current configs returned for account")
default:
cc = confs[0]
}
+49 -45
View File
@@ -32,27 +32,28 @@ type authAccount struct {
// password settings for authMethodId if authentication is successful and
// the stored values are not using the current password settings.
func (r *Repository) Authenticate(ctx context.Context, scopeId, authMethodId, loginName, password string) (*Account, error) {
const op = "password.(Repository).Authenticate"
if authMethodId == "" {
return nil, fmt.Errorf("password authenticate: no authMethodId: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing authMethodId")
}
if loginName == "" {
return nil, fmt.Errorf("password authenticate: no loginName: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing loginName")
}
if password == "" {
return nil, fmt.Errorf("password authenticate: no password: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing password")
}
if scopeId == "" {
return nil, fmt.Errorf("password authenticate: no scopeId: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scopeId")
}
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, fmt.Errorf("password authenticate: unable to get database wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get database wrapper"))
}
acct, err := r.authenticate(ctx, scopeId, authMethodId, loginName, password)
if err != nil {
return nil, fmt.Errorf("password authenticate: %w", err)
return nil, errors.Wrap(err, op)
}
if acct == nil {
return nil, nil
@@ -61,22 +62,22 @@ func (r *Repository) Authenticate(ctx context.Context, scopeId, authMethodId, lo
if !acct.IsCurrentConf {
cc, err := r.currentConfig(ctx, authMethodId)
if err != nil {
return acct.Account, fmt.Errorf("password authenticate: retrieve current password configuration: %w", err)
return acct.Account, errors.Wrap(err, op, errors.WithMsg("retrieve current password configuration"))
}
cred, err := newArgon2Credential(acct.PublicId, password, cc.argon2())
if err != nil {
return acct.Account, fmt.Errorf("password authenticate: update credential: %w", err)
return acct.Account, errors.Wrap(err, op, errors.WithCode(errors.PasswordInvalidConfiguration))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return acct.Account, fmt.Errorf("password authenticate: unable to get oplog wrapper: %w", err)
return acct.Account, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplong wrapper"))
}
// do not change the Credential Id
cred.PrivateId = acct.CredentialId
if err := cred.encrypt(ctx, databaseWrapper); err != nil {
return acct.Account, fmt.Errorf("password authenticate: update credential: encrypt: %w", err)
return acct.Account, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("update credential"))
}
fields := []string{"CtSalt", "DerivedKey", "PasswordConfId"}
@@ -92,7 +93,7 @@ func (r *Repository) Authenticate(ctx context.Context, scopeId, authMethodId, lo
},
)
if err != nil {
return acct.Account, fmt.Errorf("password authenticate: update credential: %w", err)
return acct.Account, errors.Wrap(err, op, errors.WithMsg("update credential"))
}
}
return acct.Account, nil
@@ -106,45 +107,46 @@ func (r *Repository) Authenticate(ctx context.Context, scopeId, authMethodId, lo
// Returns nil, nil if old does not match the stored password for accountId.
// Returns nil, ErrPasswordsEqual if old and new are equal.
func (r *Repository) ChangePassword(ctx context.Context, scopeId, accountId, old, new string, version uint32) (*Account, error) {
const op = "password.(Repository).ChangePassword"
if accountId == "" {
return nil, fmt.Errorf("change password: no account id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing account id")
}
if old == "" {
return nil, fmt.Errorf("change password: no old password: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing old password")
}
if new == "" {
return nil, fmt.Errorf("change password: no new password: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing new password")
}
if old == new {
return nil, fmt.Errorf("change password: %w", ErrPasswordsEqual)
return nil, errors.New(errors.PasswordsEqual, op, "passwords must not equal")
}
if version == 0 {
return nil, fmt.Errorf("change password: no version supplied: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing version")
}
if scopeId == "" {
return nil, fmt.Errorf("change password: no scopeId: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scopeId")
}
authAccount, err := r.LookupAccount(ctx, accountId)
if err != nil {
return nil, fmt.Errorf("change password: lookup account: %w", err)
return nil, errors.Wrap(err, op)
}
if authAccount == nil {
return nil, fmt.Errorf("change password: lookup account: account not found: %w", errors.ErrRecordNotFound)
return nil, errors.New(errors.RecordNotFound, op, "account not found")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("change password: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, fmt.Errorf("change password: unable to get database wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get database wrapper"))
}
acct, err := r.authenticate(ctx, scopeId, authAccount.GetAuthMethodId(), authAccount.GetLoginName(), old)
if err != nil {
return nil, fmt.Errorf("change password: %w", err)
return nil, errors.Wrap(err, op)
}
if acct == nil {
return nil, nil
@@ -152,18 +154,18 @@ func (r *Repository) ChangePassword(ctx context.Context, scopeId, accountId, old
cc, err := r.currentConfig(ctx, authAccount.GetAuthMethodId())
if err != nil {
return nil, fmt.Errorf("change password: retrieve current password configuration: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("retrieve current password configuration"))
}
if cc.MinPasswordLength > len(new) {
return nil, fmt.Errorf("change password: %w", ErrTooShort)
return nil, errors.New(errors.PasswordTooShort, op, fmt.Sprintf("must be at least %d", cc.MinPasswordLength))
}
newCred, err := newArgon2Credential(accountId, new, cc.argon2())
if err != nil {
return nil, fmt.Errorf("change password: %w", err)
return nil, errors.Wrap(err, op)
}
if err := newCred.encrypt(ctx, databaseWrapper); err != nil {
return nil, fmt.Errorf("change password: encrypt: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt))
}
oldCred := acct.Argon2Credential
@@ -176,10 +178,10 @@ func (r *Repository) ChangePassword(ctx context.Context, scopeId, accountId, old
updatedAccount.Version = version + 1
rowsUpdated, err := w.Update(ctx, updatedAccount, []string{"Version"}, nil, db.WithOplog(oplogWrapper, acct.Account.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version))
if err != nil {
return fmt.Errorf("change password: unable to update account version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update account version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("change password: updated account and %d rows updated", rowsUpdated)
return errors.New(db.NoRowsAffected, op, fmt.Sprintf("updated account and %d rows updated", rowsUpdated))
}
rowsDeleted, err := w.Delete(ctx, oldCred, db.WithOplog(oplogWrapper, oldCred.oplog(oplog.OpType_OP_TYPE_DELETE)))
@@ -193,7 +195,7 @@ func (r *Repository) ChangePassword(ctx context.Context, scopeId, accountId, old
},
)
if err != nil {
return nil, fmt.Errorf("change password: %w", err)
return nil, errors.Wrap(err, op)
}
// change the Credential Id
@@ -202,6 +204,7 @@ func (r *Repository) ChangePassword(ctx context.Context, scopeId, accountId, old
}
func (r *Repository) authenticate(ctx context.Context, scopeId, authMethodId, loginName, password string) (*authAccount, error) {
const op = "password.(Repository).authenticate"
var accts []authAccount
rows, err := r.reader.Query(ctx, authenticateQuery, []interface{}{authMethodId, loginName})
@@ -223,7 +226,7 @@ func (r *Repository) authenticate(ctx context.Context, scopeId, authMethodId, lo
return nil, nil
case len(accts) > 1:
// this should never happen
return nil, fmt.Errorf("multiple accounts returned for user name")
return nil, errors.New(errors.Unknown, op, "multiple accounts returned for user name")
default:
acct = accts[0]
}
@@ -231,11 +234,11 @@ func (r *Repository) authenticate(ctx context.Context, scopeId, authMethodId, lo
// We don't pass a wrapper in here because for ecryption we want to indicate the expected key ID
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase, kms.WithKeyId(acct.GetKeyId()))
if err != nil {
return nil, fmt.Errorf("unable to get database wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get database wrapper"))
}
if err := acct.decrypt(ctx, databaseWrapper); err != nil {
return nil, fmt.Errorf("cannot decrypt credential: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("unable to decrypt credential"))
}
inputKey := argon2.IDKey([]byte(password), acct.Salt, acct.Iterations, acct.Memory, uint8(acct.Threads), acct.KeyLength)
@@ -249,43 +252,44 @@ func (r *Repository) authenticate(ctx context.Context, scopeId, authMethodId, lo
// SetPassword sets the password for accountId to password. If password
// contains an empty string, the password for accountId will be deleted.
func (r *Repository) SetPassword(ctx context.Context, scopeId, accountId, password string, version uint32) (*Account, error) {
const op = "password.(Repository).SetPassword"
if accountId == "" {
return nil, fmt.Errorf("set password: no accountId: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing accountId")
}
if version == 0 {
return nil, fmt.Errorf("set password: no version supplied: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing version")
}
if scopeId == "" {
return nil, fmt.Errorf("set password: no scopeId: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scopeId")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("set password: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, fmt.Errorf("set password: unable to get database wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get database wrapper"))
}
var newCred *Argon2Credential
if password != "" {
cc, err := r.currentConfigForAccount(ctx, accountId)
if err != nil {
return nil, fmt.Errorf("set password: retrieve current configuration: %w", err)
return nil, errors.Wrap(err, op)
}
if cc == nil {
return nil, fmt.Errorf("set password: retrieve current configuration: %w", errors.ErrRecordNotFound)
return nil, errors.New(errors.RecordNotFound, op, "unable to retrieve current configuration")
}
if cc.MinPasswordLength > len(password) {
return nil, fmt.Errorf("set password: new password: %w", ErrTooShort)
return nil, errors.New(errors.PasswordTooShort, op, fmt.Sprintf("password must be at least %v", cc.MinPasswordLength))
}
newCred, err = newArgon2Credential(accountId, password, cc.argon2())
if err != nil {
return nil, fmt.Errorf("set password: %w", err)
return nil, errors.Wrap(err, op)
}
if err := newCred.encrypt(ctx, databaseWrapper); err != nil {
return nil, fmt.Errorf("set password: encrypt: %w", err)
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt))
}
}
@@ -297,10 +301,10 @@ func (r *Repository) SetPassword(ctx context.Context, scopeId, accountId, passwo
updatedAccount.Version = version + 1
rowsUpdated, err := w.Update(ctx, updatedAccount, []string{"Version"}, nil, db.WithOplog(oplogWrapper, updatedAccount.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version))
if err != nil {
return fmt.Errorf("set password: unable to update account version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update account version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("set password: updated account and %d rows updated", rowsUpdated)
return errors.New(db.NoRowsAffected, op, fmt.Sprintf("updated account and %d rows updated", rowsUpdated))
}
acct = updatedAccount
@@ -327,7 +331,7 @@ func (r *Repository) SetPassword(ctx context.Context, scopeId, accountId, passwo
},
)
if err != nil {
return nil, fmt.Errorf("set password: %w", err)
return nil, errors.Wrap(err, op)
}
return acct, nil
}
@@ -46,10 +46,11 @@ func TestRepository_Authenticate(t *testing.T) {
}
tests := []struct {
name string
args args
want *Account
wantIsErr error
name string
args args
want *Account
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "invalid-no-authMethodId",
@@ -58,7 +59,8 @@ func TestRepository_Authenticate(t *testing.T) {
loginName: inAcct.LoginName,
password: passwd,
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).Authenticate: missing authMethodId: parameter violation: error #100",
},
{
name: "invalid-no-loginName",
@@ -67,7 +69,8 @@ func TestRepository_Authenticate(t *testing.T) {
loginName: "",
password: passwd,
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).Authenticate: missing loginName: parameter violation: error #100",
},
{
name: "invalid-no-password",
@@ -76,7 +79,8 @@ func TestRepository_Authenticate(t *testing.T) {
loginName: inAcct.LoginName,
password: "",
},
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).Authenticate: missing password: parameter violation: error #100",
},
{
name: "valid-authenticate",
@@ -95,7 +99,7 @@ func TestRepository_Authenticate(t *testing.T) {
password: "foobar",
},
want: nil,
wantIsErr: nil,
wantIsErr: errors.Unknown,
},
}
for _, tt := range tests {
@@ -103,9 +107,9 @@ func TestRepository_Authenticate(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
authAcct, err := repo.Authenticate(context.Background(), o.GetPublicId(), tt.args.authMethodId, tt.args.loginName, tt.args.password)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(authAcct, "returned account")
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
require.NoError(err)
@@ -277,6 +281,7 @@ func TestRepository_ChangePassword(t *testing.T) {
args args
wantAccount bool
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "invalid-no-accountId",
@@ -285,7 +290,8 @@ func TestRepository_ChangePassword(t *testing.T) {
old: passwd,
new: "12345678-changed",
},
wantIsErr: errors.InvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).ChangePassword: missing account id: parameter violation: error #100",
},
{
name: "invalid-no-current-password",
@@ -294,7 +300,8 @@ func TestRepository_ChangePassword(t *testing.T) {
old: "",
new: "12345678-changed",
},
wantIsErr: errors.InvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).ChangePassword: missing old password: parameter violation: error #100",
},
{
name: "invalid-no-new-password",
@@ -303,7 +310,8 @@ func TestRepository_ChangePassword(t *testing.T) {
old: passwd,
new: "",
},
wantIsErr: errors.InvalidParameter,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).ChangePassword: missing new password: parameter violation: error #100",
},
{
name: "invalid-same-passwords",
@@ -312,7 +320,8 @@ func TestRepository_ChangePassword(t *testing.T) {
old: passwd,
new: passwd,
},
wantIsErr: errors.PasswordsEqual,
wantIsErr: errors.PasswordsEqual,
wantErrMsg: "password.(Repository).ChangePassword: passwords must not equal: password violation: error #203",
},
{
name: "auth-failure-unknown-accountId",
@@ -323,6 +332,7 @@ func TestRepository_ChangePassword(t *testing.T) {
},
wantAccount: false,
wantIsErr: errors.RecordNotFound,
wantErrMsg: "password.(Repository).ChangePassword: account not found: search issue: error #1100",
},
{
name: "auth-failure-wrong-current-password",
@@ -334,13 +344,14 @@ func TestRepository_ChangePassword(t *testing.T) {
wantAccount: false,
},
{
name: "password-to-short",
name: "password-too-short",
args: args{
acctId: acct.PublicId,
old: passwd,
new: "1",
},
wantIsErr: errors.PasswordTooShort,
wantIsErr: errors.PasswordTooShort,
wantErrMsg: "password.(Repository).ChangePassword: must be at least 8: password violation: error #200",
},
{
name: "valid",
@@ -369,8 +380,8 @@ func TestRepository_ChangePassword(t *testing.T) {
chgAuthAcct, err := repo.ChangePassword(context.Background(), o.GetPublicId(), tt.args.acctId, tt.args.old, tt.args.new, authAcct1.Version)
if tt.wantIsErr != 0 {
assert.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
assert.Nil(chgAuthAcct, "returned account")
authAcct2 := authFn(passwd, "error changing password: using old password")
assert.Equal(authAcct1.CredentialId, authAcct2.CredentialId, "CredentialId should not change")
@@ -501,30 +512,34 @@ func TestRepository_SetPassword(t *testing.T) {
badInputAcct := createAccount("badinputusername")("")
badInputCases := []struct {
name string
accountId string
pw string
version uint32
wantError error
name string
accountId string
pw string
version uint32
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "no id",
pw: "anylongpassword",
version: 1,
wantError: errors.ErrInvalidParameter,
name: "no id",
pw: "anylongpassword",
version: 1,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).SetPassword: missing accountId: parameter violation: error #100",
},
{
name: "short pw",
accountId: badInputAcct.PublicId,
pw: "c",
version: 1,
wantError: ErrTooShort,
name: "short pw",
accountId: badInputAcct.PublicId,
pw: "c",
version: 1,
wantIsErr: errors.PasswordTooShort,
wantErrMsg: "password.(Repository).SetPassword: password must be at least 8: password violation: error #200",
},
{
name: "no version",
accountId: badInputAcct.PublicId,
pw: "anylongpassword",
wantError: errors.ErrInvalidParameter,
name: "no version",
accountId: badInputAcct.PublicId,
pw: "anylongpassword",
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.(Repository).SetPassword: missing version: parameter violation: error #100",
},
}
for _, tt := range badInputCases {
@@ -533,7 +548,8 @@ func TestRepository_SetPassword(t *testing.T) {
acct, err := repo.SetPassword(context.Background(), o.GetPublicId(), tt.accountId, tt.pw, tt.version)
assert.Error(err)
assert.Truef(errors.Is(err, tt.wantError), "want err: %q got: %q", tt.wantError, err)
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
assert.Nil(acct)
})
}
+20 -15
View File
@@ -25,10 +25,11 @@ func TestRepository_New(t *testing.T) {
}
tests := []struct {
name string
args args
want *Repository
wantIsErr error
name string
args args
want *Repository
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "valid",
@@ -66,8 +67,9 @@ func TestRepository_New(t *testing.T) {
w: rw,
kms: kmsCache,
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.NewRepository: missing db.Reader: parameter violation: error #100",
},
{
name: "nil-writer",
@@ -76,8 +78,9 @@ func TestRepository_New(t *testing.T) {
w: nil,
kms: kmsCache,
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.NewRepository: missing db.Writer: parameter violation: error #100",
},
{
name: "nil-wrapper",
@@ -86,8 +89,9 @@ func TestRepository_New(t *testing.T) {
w: rw,
kms: nil,
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.NewRepository: missing kms: parameter violation: error #100",
},
{
name: "all-nils",
@@ -96,8 +100,9 @@ func TestRepository_New(t *testing.T) {
w: nil,
kms: nil,
},
want: nil,
wantIsErr: errors.ErrInvalidParameter,
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "password.NewRepository: missing db.Reader: parameter violation: error #100",
},
}
for _, tt := range tests {
@@ -105,9 +110,9 @@ func TestRepository_New(t *testing.T) {
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.kms, 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)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
assert.NoError(err)
+4 -4
View File
@@ -138,7 +138,7 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUser *iam.User,
func (r *Repository) LookupAuthToken(ctx context.Context, id string, opt ...Option) (*AuthToken, error) {
const op = "authtoken.(Repository).LookupAuthToken"
if id == "" {
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
}
opts := getOpts(opt...)
@@ -157,7 +157,7 @@ func (r *Repository) LookupAuthToken(ctx context.Context, id string, opt ...Opti
if opts.withTokenValue {
databaseWrapper, err := r.kms.GetWrapper(ctx, at.GetScopeId(), kms.KeyPurposeDatabase, kms.WithKeyId(at.GetKeyId()))
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get database wrapper"))
}
if err := at.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(err, op)
@@ -182,7 +182,7 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt ..
return nil, errors.New(errors.InvalidParameter, op, "missing token")
}
if id == "" {
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
}
retAT, err := r.LookupAuthToken(ctx, id, withTokenValue())
@@ -301,7 +301,7 @@ func (r *Repository) ListAuthTokens(ctx context.Context, withScopeIds []string,
func (r *Repository) DeleteAuthToken(ctx context.Context, id string, opt ...Option) (int, error) {
const op = "authtoken.(Repository).DeleteAuthToken"
if id == "" {
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
return db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
}
at, err := r.LookupAuthToken(ctx, id)
+4 -4
View File
@@ -305,8 +305,8 @@ func TestRepository_LookupAuthToken(t *testing.T) {
name: "bad-public-id",
id: "",
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.(Repository).LookupAuthToken: missing public id: parameter violation: error #100",
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "authtoken.(Repository).LookupAuthToken: missing public id: parameter violation: error #102",
},
}
@@ -575,8 +575,8 @@ func TestRepository_DeleteAuthToken(t *testing.T) {
name: "empty-public-id",
id: "",
want: 0,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.(Repository).DeleteAuthToken: missing public id: parameter violation: error #100",
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "authtoken.(Repository).DeleteAuthToken: missing public id: parameter violation: error #102",
},
}
+1 -1
View File
@@ -23,7 +23,7 @@ func newId(prefix string) (string, error) {
}
publicId, err := base62.Random(10)
if err != nil {
return "", errors.Wrap(err, op, errors.WithCode(errors.Io))
return "", errors.Wrap(err, op, errors.WithMsg("unable to generate id"), errors.WithCode(errors.Io))
}
return fmt.Sprintf("%s_%s", prefix, publicId), nil
}
+1
View File
@@ -34,6 +34,7 @@ const (
SessionNotFound Code = 110 // SessionNotFound represents that the session was not found
InvalidSessionState Code = 111 // InvalidSessionState represents that the session was in an invalid state
TokenMismatch Code = 112 // TokenMismatch represents that there was a token mismatch
TooShort Code = 113 // TooShort represents an error that means the provided input is not meeting minimum length requirements
// PasswordTooShort results from attempting to set a password which is to short.
PasswordTooShort Code = 200
+5
View File
@@ -92,6 +92,11 @@ func TestCode_Both_String_Info(t *testing.T) {
c: TokenMismatch,
want: TokenMismatch,
},
{
name: "TooShort",
c: TooShort,
want: TooShort,
},
{
name: "PasswordTooShort",
c: PasswordTooShort,
+4
View File
@@ -68,6 +68,10 @@ var errorCodeInfo = map[Code]Info{
Message: "token mismatch",
Kind: Integrity,
},
TooShort: {
Message: "too short",
Kind: Integrity,
},
PasswordTooShort: {
Message: "too short",
Kind: Password,