ICU-739/iam domain error refactor (#841)

* Refactor iam errors

* Update db and host doTx error ops

* Changelog
This commit is contained in:
Louis Ruch authored and GitHub committed 2020-12-23 11:00:00 -08:00
1 parent bfbb179741
commit 70a5899e99
41 files changed
+825 -714

No files matched your search

+2
View File
@@ -8,6 +8,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
### New and Improved
* controller: Improved error handling in iam repo
([PR](https://github.com/hashicorp/boundary/pull/841))
* controller: Improved error handling in db
([PR](https://github.com/hashicorp/boundary/pull/815))
+1 -1
View File
@@ -33,7 +33,7 @@ func UpdateFields(i interface{}, fieldMaskPaths []string, setToNullPaths []strin
inter, maskPaths, nullPaths, err := Intersection(fieldMaskPaths, setToNullPaths)
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
if len(inter) != 0 {
return nil, errors.New(errors.InvalidParameter, op, "fieldMashPaths and setToNullPaths cannot intersect")
+4 -4
View File
@@ -264,7 +264,7 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error {
if !opts.withSkipVetForWrite {
if vetter, ok := i.(VetForWriter); ok {
if err := vetter.VetForWrite(ctx, rw, CreateOp); err != nil {
return errors.Wrap(err, op, errors.WithMsg("vet for write failed"))
return errors.Wrap(err, op)
}
}
}
@@ -435,7 +435,7 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
if !opts.withSkipVetForWrite {
if vetter, ok := i.(VetForWriter); ok {
if err := vetter.VetForWrite(ctx, rw, UpdateOp, WithFieldMaskPaths(fieldMaskPaths), WithNullPaths(setToNullPaths)); err != nil {
return NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("vet for write failed"))
return NoRowsAffected, errors.Wrap(err, op)
}
}
}
@@ -791,7 +791,7 @@ func (rw *Db) addOplog(ctx context.Context, opType OpType, opts Options, ticket
ticketer,
)
if err != nil {
return err
return errors.Wrap(err, op)
}
msg, err := rw.newOplogMessage(ctx, opType, i, WithFieldMaskPaths(opts.WithFieldMaskPaths), WithNullPaths(opts.WithNullPaths))
if err != nil {
@@ -985,7 +985,7 @@ func (rw *Db) LookupWhere(_ context.Context, resource interface{}, where string,
if err == gorm.ErrRecordNotFound {
return errors.E(errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
return err
return errors.Wrap(err, op)
}
return nil
}
+3 -3
View File
@@ -17,7 +17,7 @@ func newHostCatalogId() (string, error) {
if err != nil {
return "", errors.Wrap(err, "static.newHostCatalogId")
}
return id, err
return id, nil
}
func newHostId() (string, error) {
@@ -25,7 +25,7 @@ func newHostId() (string, error) {
if err != nil {
return "", errors.Wrap(err, "static.newHostId")
}
return id, err
return id, nil
}
func newHostSetId() (string, error) {
@@ -33,5 +33,5 @@ func newHostSetId() (string, error) {
if err != nil {
return "", errors.Wrap(err, "static.newHostSetId")
}
return id, err
return id, nil
}
+11 -5
View File
@@ -133,7 +133,7 @@ func (r *Repository) UpdateHost(ctx context.Context, scopeId string, h *Host, ve
return nil, db.NoRowsAffected, errors.New(errors.InvalidAddress, op, "invalid address")
}
default:
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("field: %s", f))
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
var dbMask, nullFields []string
@@ -164,10 +164,13 @@ func (r *Repository) UpdateHost(ctx context.Context, scopeId string, h *Host, ve
rowsUpdated, err = w.Update(ctx, returnedHost, dbMask, nullFields,
db.WithOplog(oplogWrapper, h.oplog(oplog.OpType_OP_TYPE_UPDATE)),
db.WithVersion(&version))
if err == nil && rowsUpdated > 1 {
if err != nil {
return errors.Wrap(err, op)
}
if rowsUpdated > 1 {
return errors.E(errors.WithCode(errors.MultipleRecords))
}
return err
return nil
},
)
@@ -250,10 +253,13 @@ func (r *Repository) DeleteHost(ctx context.Context, scopeId string, publicId st
func(_ db.Reader, w db.Writer) (err error) {
dh := h.clone()
rowsDeleted, err = w.Delete(ctx, dh, db.WithOplog(oplogWrapper, h.oplog(oplog.OpType_OP_TYPE_DELETE)))
if err == nil && rowsDeleted > 1 {
if err != nil {
return errors.Wrap(err, op)
}
if rowsDeleted > 1 {
return errors.E(errors.WithCode(errors.MultipleRecords))
}
return err
return nil
},
)
@@ -128,7 +128,7 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version
dbMask = append(dbMask, "description")
default:
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("field: %s", f))
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
@@ -158,10 +158,13 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version
db.WithOplog(oplogWrapper, metadata),
db.WithVersion(&version),
)
if err == nil && rowsUpdated > 1 {
if err != nil {
return errors.Wrap(err, op)
}
if rowsUpdated > 1 {
return errors.E(errors.WithCode(errors.MultipleRecords))
}
return err
return nil
},
)
@@ -253,10 +256,13 @@ func (r *Repository) DeleteCatalog(ctx context.Context, id string, opt ...Option
deleteCatalog,
db.WithOplog(oplogWrapper, metadata),
)
if err == nil && rowsDeleted > 1 {
if err != nil {
return errors.Wrap(err, op)
}
if rowsDeleted > 1 {
return errors.E(errors.WithCode(errors.MultipleRecords))
}
return err
return nil
},
)
+22 -10
View File
@@ -116,7 +116,7 @@ func (r *Repository) UpdateSet(ctx context.Context, scopeId string, s *HostSet,
case strings.EqualFold("Name", f):
case strings.EqualFold("Description", f):
default:
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("field: %s", f))
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
var dbMask, nullFields []string
@@ -154,14 +154,17 @@ func (r *Repository) UpdateSet(ctx context.Context, scopeId string, s *HostSet,
rowsUpdated, err = w.Update(ctx, returnedHostSet, dbMask, nullFields,
db.WithOplog(oplogWrapper, s.oplog(oplog.OpType_OP_TYPE_UPDATE)),
db.WithVersion(&version))
if err == nil && rowsUpdated > 1 {
if err != nil {
return errors.Wrap(err, op)
}
if rowsUpdated > 1 {
return errors.E(errors.WithCode(errors.MultipleRecords))
}
if err != nil {
return err
}
hosts, err = getHosts(ctx, reader, s.PublicId, limit)
return err
if err != nil {
return errors.Wrap(err, op)
}
return nil
},
)
@@ -201,11 +204,17 @@ func (r *Repository) LookupSet(ctx context.Context, publicId string, opt ...Opti
s = nil
return nil
}
return err
if err != nil {
return errors.Wrap(err, op)
}
return nil
}
var err error
hosts, err = getHosts(ctx, reader, s.PublicId, limit)
return err
if err != nil {
return errors.Wrap(err, op)
}
return nil
})
if err != nil {
@@ -261,10 +270,13 @@ func (r *Repository) DeleteSet(ctx context.Context, scopeId string, publicId str
func(_ db.Reader, w db.Writer) (err error) {
ds := s.clone()
rowsDeleted, err = w.Delete(ctx, ds, db.WithOplog(oplogWrapper, s.oplog(oplog.OpType_OP_TYPE_DELETE)))
if err == nil && rowsDeleted > 1 {
if err != nil {
return errors.Wrap(err, op)
}
if rowsDeleted > 1 {
return errors.E(errors.WithCode(errors.MultipleRecords))
}
return err
return nil
},
)
@@ -51,15 +51,18 @@ func (r *Repository) AddSetMembers(ctx context.Context, scopeId string, setId st
// Create host set members
msgs, err := createMembers(ctx, w, members)
if err != nil {
return err
return errors.Wrap(err, op)
}
// Update host set version
if err := updateVersion(ctx, w, wrapper, metadata, msgs, set, version); err != nil {
return err
return errors.Wrap(err, op)
}
hosts, err = getHosts(ctx, reader, setId, unlimited)
return err
if err != nil {
return errors.Wrap(err, op)
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, op)
@@ -188,11 +191,15 @@ func (r *Repository) DeleteSetMembers(ctx context.Context, scopeId string, setId
// Delete host set members
msgs, err := deleteMembers(ctx, w, members)
if err != nil {
return err
return errors.Wrap(err, op)
}
// Update host set version
return updateVersion(ctx, w, wrapper, metadata, msgs, set, version)
err = updateVersion(ctx, w, wrapper, metadata, msgs, set, version)
if err != nil {
return errors.Wrap(err, op)
}
return nil
})
if err != nil {
@@ -278,7 +285,7 @@ func (r *Repository) SetSetMembers(ctx context.Context, scopeId string, setId st
if len(deletions) > 0 {
deletedMsgs, err := deleteMembers(ctx, w, deletions)
if err != nil {
return err
return errors.Wrap(err, op)
}
msgs = append(msgs, deletedMsgs...)
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String())
@@ -296,11 +303,14 @@ func (r *Repository) SetSetMembers(ctx context.Context, scopeId string, setId st
// Update host set version
if err := updateVersion(ctx, w, wrapper, metadata, msgs, set, version); err != nil {
return err
return errors.Wrap(err, op)
}
hosts, err = getHosts(ctx, reader, setId, unlimited)
return err
if err != nil {
return errors.Wrap(err, op)
}
return nil
})
if err != nil {
+3 -3
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
"fmt"
authStore "github.com/hashicorp/boundary/internal/auth/store"
"github.com/hashicorp/boundary/internal/db"
@@ -43,11 +42,12 @@ func (a *authAccount) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface.
func (a *authAccount) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(authAccount).VetForWrite"
if a.PublicId == "" {
return fmt.Errorf("error public id is empty string for auth account write: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing public id")
}
if err := validateScopeForWrite(ctx, r, a, opType, opt...); err != nil {
return err
return errors.Wrap(err, op)
}
return nil
}
+5 -4
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -31,8 +30,9 @@ var _ db.VetForWriter = (*Group)(nil)
// NewGroup creates a new in memory group with a scope (project/org)
// and allowed options include: withDescripion, WithName.
func NewGroup(scopeId string, opt ...Option) (*Group, error) {
const op = "iam.NewGroup"
if scopeId == "" {
return nil, fmt.Errorf("new group: missing scope id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
g := &Group{
@@ -62,11 +62,12 @@ func allocGroup() Group {
// VetForWrite implements db.VetForWrite() interface and validates the group
// before it's written.
func (g *Group) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(Group).VetForWrite"
if g.PublicId == "" {
return fmt.Errorf("group vet for write: missing public id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing public id")
}
if err := validateScopeForWrite(ctx, r, g, opType, opt...); err != nil {
return err
return errors.Wrap(err, op)
}
return nil
}
+7 -6
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -69,12 +68,13 @@ var _ db.VetForWriter = (*GroupMemberUser)(nil)
// NewGroupMemberUser creates a new in memory user member of the group. No
// options are currently supported.
func NewGroupMemberUser(groupId, userId string, opt ...Option) (*GroupMemberUser, error) {
func NewGroupMemberUser(groupId, userId string, _ ...Option) (*GroupMemberUser, error) {
const op = "iam.NewGroupMemberUser"
if groupId == "" {
return nil, fmt.Errorf("new group member: missing group id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing group id")
}
if userId == "" {
return nil, fmt.Errorf("new group member: missing user id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
return &GroupMemberUser{
GroupMemberUser: &store.GroupMemberUser{
@@ -100,11 +100,12 @@ func (m *GroupMemberUser) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface for group members.
func (m *GroupMemberUser) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(GroupMemberUser).VetForWrite"
if m.GroupId == "" {
return fmt.Errorf("group member: missing group id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing group id")
}
if m.MemberId == "" {
return fmt.Errorf("group member: missing member id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing member id")
}
return nil
}
+4 -4
View File
@@ -35,7 +35,7 @@ func Test_NewGroupMember(t *testing.T) {
args args
want *GroupMemberUser
wantErr bool
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid-org",
@@ -83,7 +83,7 @@ func Test_NewGroupMember(t *testing.T) {
},
want: nil,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "missing-user",
@@ -92,7 +92,7 @@ func Test_NewGroupMember(t *testing.T) {
},
want: nil,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -102,7 +102,7 @@ func Test_NewGroupMember(t *testing.T) {
got, err := NewGroupMemberUser(tt.args.groupId, tt.args.userId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
+7 -7
View File
@@ -34,7 +34,7 @@ func TestNewGroup(t *testing.T) {
args args
wantErr bool
wantErrMsg string
wantIsErr error
wantIsErr errors.Code
wantName string
wantDescription string
}{
@@ -71,8 +71,8 @@ func TestNewGroup(t *testing.T) {
opt: []Option{WithName(id)},
},
wantErr: true,
wantErrMsg: "new group: missing scope id invalid parameter:",
wantIsErr: errors.ErrInvalidParameter,
wantErrMsg: "iam.NewGroup: missing scope id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -82,8 +82,8 @@ func TestNewGroup(t *testing.T) {
if tt.wantErr {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrMsg)
if tt.wantIsErr != nil {
assert.True(errors.Is(err, tt.wantIsErr))
if tt.wantIsErr != 0 {
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
}
return
}
@@ -171,7 +171,7 @@ func Test_GroupCreate(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "db.Create: vet for write failed: iam.validateScopeForWrite: scope is not found: search issue: error #1100",
wantErrMsg: "db.Create: iam.(Group).VetForWrite: iam.validateScopeForWrite: scope is not found: search issue: error #1100",
},
}
@@ -247,7 +247,7 @@ func Test_GroupUpdate(t *testing.T) {
ScopeId: proj.PublicId,
},
wantErr: true,
wantErrMsg: "db.Update: vet for write failed: iam.validateScopeForWrite: not allowed to change a resource's scope: parameter violation: error #100",
wantErrMsg: "db.Update: iam.(Group).VetForWrite: iam.validateScopeForWrite: not allowed to change a resource's scope: parameter violation: error #100",
},
{
name: "proj-scope-id-not-in-mask",
+6 -5
View File
@@ -18,7 +18,7 @@ const (
func newRoleId() (string, error) {
id, err := db.NewPublicId(RolePrefix)
if err != nil {
return "", fmt.Errorf("new role id: %w", err)
return "", errors.Wrap(err, "iam.newRoleId")
}
return id, nil
}
@@ -26,7 +26,7 @@ func newRoleId() (string, error) {
func newUserId() (string, error) {
id, err := db.NewPublicId(UserPrefix)
if err != nil {
return "", fmt.Errorf("new user id: %w", err)
return "", errors.Wrap(err, "iam.newUserId")
}
return id, nil
}
@@ -34,18 +34,19 @@ func newUserId() (string, error) {
func newGroupId() (string, error) {
id, err := db.NewPublicId(GroupPrefix)
if err != nil {
return "", fmt.Errorf("new group id: %w", err)
return "", errors.Wrap(err, "iam.newGroupId")
}
return id, nil
}
func newScopeId(scopeType scope.Type) (string, error) {
const op = "iam.newScopeId"
if scopeType == scope.Unknown {
return "", fmt.Errorf("new scope id: unknown is not supported %w", errors.ErrInvalidParameter)
return "", errors.New(errors.InvalidParameter, op, "unknown scope is not supported")
}
id, err := db.NewPublicId(scopeType.Prefix())
if err != nil {
return "", fmt.Errorf("new %s id: %w", scopeType.String(), err)
return "", errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("scope type: %s", scopeType.String())))
}
return id, nil
}
+1 -1
View File
@@ -38,6 +38,6 @@ func Test_PublicIds(t *testing.T) {
id, err = newScopeId(scope.Unknown)
require.Error(t, err)
assert.Empty(t, id)
assert.True(t, errors.Is(err, errors.ErrInvalidParameter))
assert.True(t, errors.Match(errors.T(errors.InvalidParameter), err))
})
}
+14 -11
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -73,12 +72,13 @@ var _ db.VetForWriter = (*UserRole)(nil)
// NewUserRole creates a new user role in memory. No options are supported
// currently.
func NewUserRole(roleId, userId string, opt ...Option) (*UserRole, error) {
func NewUserRole(roleId, userId string, _ ...Option) (*UserRole, error) {
const op = "iam.NewUserRole"
if roleId == "" {
return nil, fmt.Errorf("new user role: missing role id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
if userId == "" {
return nil, fmt.Errorf("new user role: missing user id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
return &UserRole{
UserRole: &store.UserRole{
@@ -103,12 +103,13 @@ func (r *UserRole) Clone() interface{} {
}
// VetForWrite implements db.VetForWrite() interface for user roles.
func (role *UserRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
func (role *UserRole) VetForWrite(_ context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error {
const op = "iam.(UserRole).VetForWrite"
if role.RoleId == "" {
return fmt.Errorf("new user role: missing role id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing role id")
}
if role.PrincipalId == "" {
return fmt.Errorf("new user role: missing user id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing user id")
}
return nil
}
@@ -147,11 +148,12 @@ var _ db.VetForWriter = (*GroupRole)(nil)
// NewGroupRole creates a new group role in memory. No options are supported
// currently.
func NewGroupRole(roleId, groupId string, opt ...Option) (*GroupRole, error) {
const op = "iam.NewGroupRole"
if roleId == "" {
return nil, fmt.Errorf("new group role: missing role id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
if groupId == "" {
return nil, fmt.Errorf("new group role: missing group id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing group id")
}
return &GroupRole{
GroupRole: &store.GroupRole{
@@ -177,11 +179,12 @@ func (r *GroupRole) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface for group roles.
func (role *GroupRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(GroupRole).VetForWrite"
if role.RoleId == "" {
return fmt.Errorf("new group role: missing role id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing role id")
}
if role.PrincipalId == "" {
return fmt.Errorf("new group role: missing user id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing user id")
}
return nil
}
+22 -22
View File
@@ -32,7 +32,7 @@ func TestNewUserRole(t *testing.T) {
args args
want *UserRole
wantErr bool
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid-org",
@@ -68,7 +68,7 @@ func TestNewUserRole(t *testing.T) {
},
want: nil,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-user-id",
@@ -78,7 +78,7 @@ func TestNewUserRole(t *testing.T) {
},
want: nil,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -87,7 +87,7 @@ func TestNewUserRole(t *testing.T) {
got, err := NewUserRole(tt.args.roleId, tt.args.userId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
@@ -112,7 +112,7 @@ func TestUserRole_Create(t *testing.T) {
wantDup bool
wantErr bool
wantErrMsg string
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid-with-org",
@@ -206,8 +206,8 @@ func TestUserRole_Create(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "db.Create: vet for write failed: new user role: missing role id invalid parameter",
wantIsErr: errors.ErrInvalidParameter,
wantErrMsg: "db.Create: iam.(UserRole).VetForWrite: missing role id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
{
name: "missing-user-id",
@@ -223,8 +223,8 @@ func TestUserRole_Create(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "db.Create: vet for write failed: new user role: missing user id invalid parameter",
wantIsErr: errors.ErrInvalidParameter,
wantErrMsg: "db.Create: iam.(UserRole).VetForWrite: missing user id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
{
name: "dup-at-org",
@@ -257,8 +257,8 @@ func TestUserRole_Create(t *testing.T) {
if tt.wantErr {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrMsg)
if tt.wantIsErr != nil {
assert.True(errors.Is(err, tt.wantIsErr))
if tt.wantIsErr != 0 {
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
}
return
}
@@ -395,7 +395,7 @@ func TestNewGroupRole(t *testing.T) {
args args
want *GroupRole
wantErr bool
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid-org",
@@ -431,7 +431,7 @@ func TestNewGroupRole(t *testing.T) {
},
want: nil,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-group-id",
@@ -441,7 +441,7 @@ func TestNewGroupRole(t *testing.T) {
},
want: nil,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -450,7 +450,7 @@ func TestNewGroupRole(t *testing.T) {
got, err := NewGroupRole(tt.args.roleId, tt.args.groupId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
@@ -475,7 +475,7 @@ func TestGroupRole_Create(t *testing.T) {
wantDup bool
wantErr bool
wantErrMsg string
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid-with-org",
@@ -569,8 +569,8 @@ func TestGroupRole_Create(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "db.Create: vet for write failed: new group role: missing role id invalid parameter",
wantIsErr: errors.ErrInvalidParameter,
wantErrMsg: "db.Create: iam.(GroupRole).VetForWrite: missing role id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
{
name: "missing-user-id",
@@ -586,8 +586,8 @@ func TestGroupRole_Create(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "db.Create: vet for write failed: new group role: missing user id invalid parameter",
wantIsErr: errors.ErrInvalidParameter,
wantErrMsg: "db.Create: iam.(GroupRole).VetForWrite: missing user id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
{
name: "dup-at-org",
@@ -635,8 +635,8 @@ func TestGroupRole_Create(t *testing.T) {
if tt.wantErr {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrMsg)
if tt.wantIsErr != nil {
assert.True(errors.Is(err, tt.wantIsErr))
if tt.wantIsErr != 0 {
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
}
return
}
+58 -38
View File
@@ -2,18 +2,18 @@ package iam
import (
"context"
"errors"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/types/scope"
)
var (
ErrMetadataScopeNotFound = errors.New("scope not found for metadata")
ErrMetadataScopeNotFound = errors.New(errors.RecordNotFound, "iam", "scope not found for metadata")
)
// Repository is the iam database repository
@@ -29,14 +29,15 @@ type Repository struct {
// NewRepository creates a new iam Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) {
const op = "iam.NewRepository"
if r == nil {
return nil, errors.New("error creating db repository with nil reader")
return nil, errors.New(errors.InvalidParameter, op, "nil reader")
}
if w == nil {
return nil, errors.New("error creating db repository with nil writer")
return nil, errors.New(errors.InvalidParameter, op, "nil writer")
}
if kms == nil {
return nil, errors.New("error creating db repository with nil kms")
return nil, errors.New(errors.InvalidParameter, op, "nil kms")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
@@ -64,27 +65,28 @@ func (r *Repository) list(ctx context.Context, resources interface{}, where stri
}
// create will create a new iam resource in the db repository with an oplog entry
func (r *Repository) create(ctx context.Context, resource Resource, opt ...Option) (Resource, error) {
func (r *Repository) create(ctx context.Context, resource Resource, _ ...Option) (Resource, error) {
const op = "iam.(Repository).create"
if resource == nil {
return nil, errors.New("error creating resource that is nil")
return nil, errors.New(errors.InvalidParameter, op, "missing resource")
}
resourceCloner, ok := resource.(Cloneable)
if !ok {
return nil, errors.New("error resource is not Cloneable for create")
return nil, errors.New(errors.InvalidParameter, op, "resource is not Cloneable")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return nil, fmt.Errorf("error getting metadata for create: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("error getting metadata"))
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_CREATE.String()}
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return nil, fmt.Errorf("unable to get scope: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get scope"))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var returnedResource interface{}
@@ -101,24 +103,28 @@ func (r *Repository) create(ctx context.Context, resource Resource, opt ...Optio
)
},
)
return returnedResource.(Resource), err
if err != nil {
return nil, errors.Wrap(err, op)
}
return returnedResource.(Resource), nil
}
// update will update an iam resource in the db repository with an oplog entry
func (r *Repository) update(ctx context.Context, resource Resource, version uint32, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (Resource, int, error) {
const op = "iam.(Repository).update"
if version == 0 {
return nil, db.NoRowsAffected, errors.New("resource version cannot be zero during update")
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
if resource == nil {
return nil, db.NoRowsAffected, errors.New("error updating resource that is nil")
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing resource")
}
resourceCloner, ok := resource.(Cloneable)
if !ok {
return nil, db.NoRowsAffected, errors.New("error resource is not Cloneable for update")
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "resource is not Cloneable")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("error getting metadata for update: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("error getting metadata"))
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_UPDATE.String()}
@@ -137,12 +143,12 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint
default:
scope, err = resource.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("unable to get scope: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get scope"))
}
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("unable to get oplog wrapper: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata))
@@ -161,38 +167,45 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint
setToNullPaths,
dbOpts...,
)
if err == nil && rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return errors.New("error more than 1 resource would have been updated ")
if err != nil {
return errors.Wrap(err, op)
}
return err
if rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
return nil
},
)
return returnedResource.(Resource), rowsUpdated, err
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
return returnedResource.(Resource), rowsUpdated, nil
}
// delete will delete an iam resource in the db repository with an oplog entry
func (r *Repository) delete(ctx context.Context, resource Resource, opt ...Option) (int, error) {
func (r *Repository) delete(ctx context.Context, resource Resource, _ ...Option) (int, error) {
const op = "iam.(Repository).delete"
if resource == nil {
return db.NoRowsAffected, errors.New("error deleting resource that is nil")
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing resource")
}
resourceCloner, ok := resource.(Cloneable)
if !ok {
return db.NoRowsAffected, errors.New("error resource is not Cloneable for delete")
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "resource is not Cloneable")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("error getting metadata for delete: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("error getting metadata"))
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_DELETE.String()}
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("unable to get scope: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get scope"))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("unable to get oplog wrapper: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var rowsDeleted int
@@ -208,24 +221,31 @@ func (r *Repository) delete(ctx context.Context, resource Resource, opt ...Optio
deleteResource,
db.WithOplog(oplogWrapper, metadata),
)
if err == nil && rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return errors.New("error more than 1 resource would have been deleted ")
if err != nil {
return errors.Wrap(err, op)
}
return err
if rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
return rowsDeleted, err
if err != nil {
return db.NoRowsAffected, errors.Wrap(err, op)
}
return rowsDeleted, nil
}
func (r *Repository) stdMetadata(ctx context.Context, resource Resource) (oplog.Metadata, error) {
const op = "iam.(Repository).stdMetadata"
if s, ok := resource.(*Scope); ok {
newScope := allocScope()
newScope.PublicId = s.PublicId
newScope.Type = s.Type
if newScope.Type == "" {
if err := r.reader.LookupByPublicId(ctx, &newScope); err != nil {
return nil, ErrMetadataScopeNotFound
return nil, errors.Wrap(ErrMetadataScopeNotFound, op)
}
}
switch newScope.Type {
@@ -244,16 +264,16 @@ func (r *Repository) stdMetadata(ctx context.Context, resource Resource) (oplog.
"resource-type": []string{resource.ResourceType().String()},
}, nil
default:
return nil, fmt.Errorf("not a supported scope for metadata: %s", s.Type)
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("not a supported scope for metadata: %s", s.Type))
}
}
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return nil, fmt.Errorf("unable to get scope for standard metadata: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get scope"))
}
if scope == nil {
return nil, errors.New("scope was nil for standard metadata")
return nil, errors.E(errors.WithOp(op), errors.WithMsg("nil scope"))
}
return oplog.Metadata{
"resource-public-id": []string{resource.GetPublicId()},
+95 -85
View File
@@ -14,33 +14,34 @@ import (
// CreateGroup will create a group in the repository and return the written
// group. No options are currently supported.
func (r *Repository) CreateGroup(ctx context.Context, group *Group, opt ...Option) (*Group, error) {
func (r *Repository) CreateGroup(ctx context.Context, group *Group, _ ...Option) (*Group, error) {
const op = "iam.(Repository).CreateGroup"
if group == nil {
return nil, fmt.Errorf("create group: missing group %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing group")
}
if group.Group == nil {
return nil, fmt.Errorf("create group: missing group store %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing group store")
}
if group.PublicId != "" {
return nil, fmt.Errorf("create group: public id not empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "public id not empty")
}
if group.ScopeId == "" {
return nil, fmt.Errorf("create group: missing group scope id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
id, err := newGroupId()
if err != nil {
return nil, fmt.Errorf("create group: %w", err)
return nil, errors.Wrap(err, op)
}
g := group.Clone().(*Group)
g.PublicId = id
resource, err := r.create(ctx, g)
if err != nil {
if errors.IsUniqueError(err) {
return nil, fmt.Errorf("create group: group %s already exists in scope %s: %w", group.Name, group.ScopeId, errors.ErrNotUnique)
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf("group %s already exists in scope %s", group.Name, group.ScopeId))
}
return nil, fmt.Errorf("create group: %w for %s", err, g.PublicId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for group %s", g.PublicId)))
}
return resource.(*Group), err
return resource.(*Group), nil
}
// UpdateGroup will update a group in the repository and return the written
@@ -48,22 +49,23 @@ func (r *Repository) CreateGroup(ctx context.Context, group *Group, opt ...Optio
// be updated. Fields will be set to NULL if the field is a zero value and
// included in fieldMask. Name and Description are the only updatable fields,
// If no updatable fields are included in the fieldMaskPaths, then an error is returned.
func (r *Repository) UpdateGroup(ctx context.Context, group *Group, version uint32, fieldMaskPaths []string, opt ...Option) (*Group, []*GroupMember, int, error) {
func (r *Repository) UpdateGroup(ctx context.Context, group *Group, version uint32, fieldMaskPaths []string, _ ...Option) (*Group, []*GroupMember, int, error) {
const op = "iam.(Repository).UpdateGroup"
if group == nil {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: missing group %w", errors.ErrInvalidParameter)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing group")
}
if group.Group == nil {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: missing group store %w", errors.ErrInvalidParameter)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing group store")
}
if group.PublicId == "" {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: missing group public id %w", errors.ErrInvalidParameter)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold("name", f):
case strings.EqualFold("description", f):
default:
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: field: %s: %w", f, errors.ErrInvalidFieldMask)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
var dbMask, nullFields []string
@@ -76,7 +78,7 @@ func (r *Repository) UpdateGroup(ctx context.Context, group *Group, version uint
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: %w", errors.ErrEmptyFieldMask)
return nil, nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "empty field mask")
}
var resource Resource
var rowsUpdated int
@@ -90,33 +92,34 @@ func (r *Repository) UpdateGroup(ctx context.Context, group *Group, version uint
g := group.Clone().(*Group)
resource, rowsUpdated, err = r.update(ctx, g, version, dbMask, nullFields)
if err != nil {
return err
return errors.Wrap(err, op)
}
repo, err := NewRepository(read, w, r.kms)
if err != nil {
return fmt.Errorf("update group: failed creating inner repo: %w for %s", err, group.PublicId)
return errors.Wrap(err, op)
}
members, err = repo.ListGroupMembers(ctx, group.PublicId)
if err != nil {
return fmt.Errorf("update group: listing group members: %w for %s", err, group.PublicId)
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: group %s already exists in org %s: %w", group.Name, group.ScopeId, errors.ErrNotUnique)
return nil, nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("group %s already exists in scope %s", group.Name, group.ScopeId))
}
return nil, nil, db.NoRowsAffected, fmt.Errorf("update group: %w for %s", err, group.PublicId)
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for group %s", group.PublicId)))
}
return resource.(*Group), members, rowsUpdated, err
return resource.(*Group), members, rowsUpdated, nil
}
// LookupGroup will look up a group in the repository. If the group is not
// found, it will return nil, nil.
func (r *Repository) LookupGroup(ctx context.Context, withPublicId string, opt ...Option) (*Group, []*GroupMember, error) {
func (r *Repository) LookupGroup(ctx context.Context, withPublicId string, _ ...Option) (*Group, []*GroupMember, error) {
const op = "iam.(Repository).LookupGroup"
if withPublicId == "" {
return nil, nil, fmt.Errorf("lookup group: missing public id %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
g := allocGroup()
g.PublicId = withPublicId
@@ -127,15 +130,15 @@ func (r *Repository) LookupGroup(ctx context.Context, withPublicId string, opt .
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
if err := read.LookupByPublicId(ctx, &g); err != nil {
return fmt.Errorf("lookup group: failed %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
repo, err := NewRepository(read, w, r.kms)
if err != nil {
return fmt.Errorf("lookup group: failed creating inner repo: %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
members, err = repo.ListGroupMembers(ctx, withPublicId)
if err != nil {
return fmt.Errorf("lookup group: listing group members: %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
return nil
},
@@ -144,49 +147,52 @@ func (r *Repository) LookupGroup(ctx context.Context, withPublicId string, opt .
if errors.IsNotFoundError(err) {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("lookup group: failed %w for %s", err, withPublicId)
return nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for group %s", withPublicId)))
}
return &g, members, nil
}
// DeleteGroup will delete a group from the repository.
func (r *Repository) DeleteGroup(ctx context.Context, withPublicId string, opt ...Option) (int, error) {
func (r *Repository) DeleteGroup(ctx context.Context, withPublicId string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteGroup"
if withPublicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete group: missing public id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
g := allocGroup()
g.PublicId = withPublicId
if err := r.reader.LookupByPublicId(ctx, &g); err != nil {
return db.NoRowsAffected, fmt.Errorf("delete group: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for group %s", withPublicId)))
}
rowsDeleted, err := r.delete(ctx, &g)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete group: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for group %s", withPublicId)))
}
return rowsDeleted, nil
}
// ListGroups in a scope and supports WithLimit option.
func (r *Repository) ListGroups(ctx context.Context, withScopeId string, opt ...Option) ([]*Group, error) {
const op = "iam.(Repository).ListGroups"
if withScopeId == "" {
return nil, fmt.Errorf("list groups: missing scope id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
var grps []*Group
err := r.list(ctx, &grps, "scope_id = ?", []interface{}{withScopeId}, opt...)
if err != nil {
return nil, fmt.Errorf("list groups: %w", err)
return nil, errors.Wrap(err, op)
}
return grps, nil
}
// ListGroupMembers of a group and supports WithLimit option.
func (r *Repository) ListGroupMembers(ctx context.Context, withGroupId string, opt ...Option) ([]*GroupMember, error) {
const op = "iam.(Repository).ListGroups"
if withGroupId == "" {
return nil, fmt.Errorf("list group members: missing group id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing group id")
}
members := []*GroupMember{}
if err := r.list(ctx, &members, "group_id = ?", []interface{}{withGroupId}, opt...); err != nil {
return nil, fmt.Errorf("list group members: %w", err)
return nil, errors.Wrap(err, op)
}
return members, nil
}
@@ -195,35 +201,36 @@ func (r *Repository) ListGroupMembers(ctx context.Context, withGroupId string, o
// (groupId). The group's current db version must match the groupVersion or an
// error will be returned. Zero is not a valid value for the WithVersion option
// and will return an error.
func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupVersion uint32, userIds []string, opt ...Option) ([]*GroupMember, error) {
func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupVersion uint32, userIds []string, _ ...Option) ([]*GroupMember, error) {
const op = "iam.(Repository).AddGroupMembers"
if groupId == "" {
return nil, fmt.Errorf("add group members: missing group id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing group id")
}
if len(userIds) == 0 {
return nil, fmt.Errorf("add group members: missing user ids to add %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user ids")
}
if groupVersion == 0 {
return nil, fmt.Errorf("add group members: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing version")
}
group := allocGroup()
group.PublicId = groupId
scope, err := group.GetScope(ctx, r.reader)
if err != nil {
return nil, fmt.Errorf("add group members: unable to get group %s scope: %w", groupId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get group members %s scope", groupId)))
}
newGroupMembers := make([]interface{}, 0, len(userIds))
for _, id := range userIds {
gm, err := NewGroupMemberUser(groupId, id)
if err != nil {
return nil, fmt.Errorf("add group members: unable to create in memory group member: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group member"))
}
newGroupMembers = append(newGroupMembers, gm)
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("add group members: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentMembers []*GroupMember
@@ -235,7 +242,7 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV
msgs := make([]*oplog.Message, 0, 2)
groupTicket, err := w.GetTicket(&group)
if err != nil {
return fmt.Errorf("add group members: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedGroup := allocGroup()
updatedGroup.PublicId = groupId
@@ -243,15 +250,15 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV
var groupOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedGroup, []string{"Version"}, nil, db.NewOplogMsg(&groupOplogMsg), db.WithVersion(&groupVersion))
if err != nil {
return fmt.Errorf("add group members: unable to update group version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update group version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("add group members: updated group and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated group and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &groupOplogMsg)
memberOplogMsgs := make([]*oplog.Message, 0, len(newGroupMembers))
if err := w.CreateItems(ctx, newGroupMembers, db.NewOplogMsgs(&memberOplogMsgs)); err != nil {
return fmt.Errorf("add group members: unable to add users: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add users"))
}
msgs = append(msgs, memberOplogMsgs...)
metadata := oplog.Metadata{
@@ -261,7 +268,7 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV
"resource-public-id": []string{groupId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, groupTicket, metadata, msgs); err != nil {
return fmt.Errorf("add group members: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := Repository{
@@ -273,13 +280,13 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV
}
currentMembers, err = txRepo.ListGroupMembers(ctx, groupId)
if err != nil {
return fmt.Errorf("set group members: unable to retrieve current group members after sets: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current group members after sets"))
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("add group members: error adding members: %w", err)
return nil, errors.Wrap(err, op)
}
return currentMembers, nil
}
@@ -287,35 +294,36 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV
// DeleteGroupMembers (userIds) from a group (groupId). The group's current db version
// must match the groupVersion or an error will be returned. Zero is not a valid
// value for the WithVersion option and will return an error.
func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, groupVersion uint32, userIds []string, opt ...Option) (int, error) {
func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, groupVersion uint32, userIds []string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteGroupMembers"
if groupId == "" {
return db.NoRowsAffected, fmt.Errorf("delete group members: missing group id: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing group id")
}
if len(userIds) == 0 {
return db.NoRowsAffected, fmt.Errorf("delete group members: missing either user or groups to delete %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing either user or groups to delete")
}
if groupVersion == 0 {
return db.NoRowsAffected, fmt.Errorf("delete group members: version cannot be zero: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
group := allocGroup()
group.PublicId = groupId
scope, err := group.GetScope(ctx, r.reader)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete group members: unable to get group %s scope: %w", groupId, err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get group members %s scope", groupId)))
}
deleteMembers := make([]interface{}, 0, len(userIds))
for _, id := range userIds {
member, err := NewGroupMemberUser(groupId, id)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete group members: unable to create in memory group member: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group member"))
}
deleteMembers = append(deleteMembers, member)
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("add group members: unable to get oplog wrapper: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var totalRowsDeleted int
@@ -327,7 +335,7 @@ func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, gro
msgs := make([]*oplog.Message, 0, 2)
groupTicket, err := w.GetTicket(&group)
if err != nil {
return fmt.Errorf("delete group members: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedGroup := allocGroup()
updatedGroup.PublicId = groupId
@@ -335,19 +343,19 @@ func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, gro
var groupOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedGroup, []string{"Version"}, nil, db.NewOplogMsg(&groupOplogMsg), db.WithVersion(&groupVersion))
if err != nil {
return fmt.Errorf("delete group members: unable to update group version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update group version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("delete group members: updated group and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated group and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &groupOplogMsg)
userOplogMsgs := make([]*oplog.Message, 0, len(deleteMembers))
rowsDeleted, err := w.DeleteItems(ctx, deleteMembers, db.NewOplogMsgs(&userOplogMsgs))
if err != nil {
return fmt.Errorf("delete group members: unable to delete group members: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete group members"))
}
if rowsDeleted != len(deleteMembers) {
return fmt.Errorf("delete group members: group members deleted %d did not match request for %d", rowsDeleted, len(deleteMembers))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("group members deleted %d did not match request for %d", rowsDeleted, len(deleteMembers)))
}
totalRowsDeleted += rowsDeleted
msgs = append(msgs, userOplogMsgs...)
@@ -358,13 +366,13 @@ func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, gro
"resource-public-id": []string{groupId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, groupTicket, metadata, msgs); err != nil {
return fmt.Errorf("delete group members: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete group members: error deleting members: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op)
}
return totalRowsDeleted, nil
}
@@ -372,23 +380,24 @@ func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, gro
// SetGroupMembers will set the group's members. If userIds is empty, the
// members will be cleared. Zero is not a valid value for the WithVersion option
// and will return an error.
func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupVersion uint32, userIds []string, opt ...Option) ([]*GroupMember, int, error) {
func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupVersion uint32, userIds []string, _ ...Option) ([]*GroupMember, int, error) {
const op = "iam.(Repository).SetGroupMembers"
if groupId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("set group members: missing group id: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing group id")
}
if groupVersion == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("set group members: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
group := allocGroup()
group.PublicId = groupId
scope, err := group.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set group members: unable to get members %s scope: %w", groupId, err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get group members %s scope", groupId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("add group members: unable to get oplog wrapper: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentMembers []*GroupMember
@@ -408,13 +417,13 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV
}
addMembers, deleteMembers, err := groupMemberChanges(ctx, reader, groupId, userIds)
if err != nil {
return fmt.Errorf("set associated accounts: unable to determine changes: %w", err)
return errors.Wrap(err, op)
}
// handle no change to existing group members
if len(addMembers) == 0 && len(deleteMembers) == 0 {
currentMembers, err = txRepo.ListGroupMembers(ctx, groupId)
if err != nil {
return fmt.Errorf("set group members: unable to retrieve current group members after sets: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current group members after sets"))
}
return nil
}
@@ -431,7 +440,7 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV
// we need to write oplog entries for deletes and adds
groupTicket, err := w.GetTicket(&group)
if err != nil {
return fmt.Errorf("set group members: unable to get ticket for group: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedGroup := allocGroup()
updatedGroup.PublicId = groupId
@@ -439,19 +448,19 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV
var groupOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedGroup, []string{"Version"}, nil, db.NewOplogMsg(&groupOplogMsg), db.WithVersion(&groupVersion))
if err != nil {
return fmt.Errorf("set group members: unable to update group version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update group verison"))
}
if rowsUpdated != 1 {
return fmt.Errorf("set group members: updated group and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated group and %d rows updated", rowsUpdated))
}
if len(deleteMembers) > 0 {
userOplogMsgs := make([]*oplog.Message, 0, len(deleteMembers))
rowsDeleted, err := w.DeleteItems(ctx, deleteMembers, db.NewOplogMsgs(&userOplogMsgs))
if err != nil {
return fmt.Errorf("set group members: unable to delete group member: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete group member"))
}
if rowsDeleted != len(deleteMembers) {
return fmt.Errorf("set group members: members deleted %d did not match request for %d", rowsDeleted, len(deleteMembers))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("members deleted %d did not match request for %d", rowsDeleted, len(deleteMembers)))
}
totalRowsAffected += rowsDeleted
msgs = append(msgs, userOplogMsgs...)
@@ -460,7 +469,7 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV
if len(addMembers) > 0 {
userOplogMsgs := make([]*oplog.Message, 0, len(addMembers))
if err := w.CreateItems(ctx, addMembers, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
return fmt.Errorf("set group members: unable to add users: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add users"))
}
totalRowsAffected += len(addMembers)
msgs = append(msgs, userOplogMsgs...)
@@ -470,22 +479,23 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV
// we're done with all the membership writes, so let's write the
// group's update oplog message
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, groupTicket, metadata, msgs); err != nil {
return fmt.Errorf("set group members: unable to write oplog for additions: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
currentMembers, err = txRepo.ListGroupMembers(ctx, groupId)
if err != nil {
return fmt.Errorf("set group members: unable to retrieve current group members after sets: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current group members after set"))
}
return nil
})
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set group members: unable to set group members: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
return currentMembers, totalRowsAffected, nil
}
// groupMemberChanges returns two slices: members to add and delete
func groupMemberChanges(ctx context.Context, reader db.Reader, groupId string, userIds []string) ([]interface{}, []interface{}, error) {
const op = "iam.groupMemberChanges"
var inClauseSpots []string
// starts at 2 because there is already a $1 in the query
for i := 2; i < len(userIds)+2; i++ {
@@ -505,7 +515,7 @@ func groupMemberChanges(ctx context.Context, reader db.Reader, groupId string, u
// fmt.Println(query, params)
rows, err := reader.Query(ctx, query, params)
if err != nil {
return nil, nil, fmt.Errorf("changes: query failed: %w", err)
return nil, nil, errors.Wrap(err, op)
}
defer rows.Close()
@@ -517,7 +527,7 @@ func groupMemberChanges(ctx context.Context, reader db.Reader, groupId string, u
for rows.Next() {
var chg change
if err := reader.ScanRows(rows, &chg); err != nil {
return nil, nil, fmt.Errorf("changes: scan row failed: %w", err)
return nil, nil, errors.Wrap(err, op)
}
changes = append(changes, &chg)
}
@@ -525,23 +535,23 @@ func groupMemberChanges(ctx context.Context, reader db.Reader, groupId string, u
deleteMembers := []interface{}{}
for _, c := range changes {
if c.MemberId == "" {
return nil, nil, fmt.Errorf("changes: missing user id in change result")
return nil, nil, errors.New(errors.InvalidParameter, op, "missing user id in change result")
}
switch c.Action {
case "add":
gm, err := NewGroupMemberUser(groupId, c.MemberId)
if err != nil {
return nil, nil, fmt.Errorf("set group members: unable to create in memory group member for add: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group member for add"))
}
addMembers = append(addMembers, gm)
case "delete":
gm, err := NewGroupMemberUser(groupId, c.MemberId)
if err != nil {
return nil, nil, fmt.Errorf("set group members: unable to create in memory group member for delete: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group member for delete"))
}
deleteMembers = append(deleteMembers, gm)
default:
return nil, nil, fmt.Errorf("changes: unknown action %s for %s", c.Action, c.MemberId)
return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("unknown action %s for %s", c.Action, c.MemberId))
}
}
+32 -30
View File
@@ -36,7 +36,7 @@ func TestRepository_CreateGroup(t *testing.T) {
wantDup bool
wantErr bool
wantErrMsg string
wantIsError error
wantIsError errors.Code
}{
{
name: "valid-org",
@@ -70,8 +70,8 @@ func TestRepository_CreateGroup(t *testing.T) {
return g
}(),
},
wantErrMsg: "create group: public id not empty: invalid parameter",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).CreateGroup: public id not empty: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
wantErr: true,
},
{
@@ -80,8 +80,8 @@ func TestRepository_CreateGroup(t *testing.T) {
group: nil,
},
wantErr: true,
wantErrMsg: "create group: missing group invalid parameter",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).CreateGroup: missing group: parameter violation: error #10",
wantIsError: errors.InvalidParameter,
},
{
name: "nil-store",
@@ -93,8 +93,8 @@ func TestRepository_CreateGroup(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "create group: missing group store invalid parameter",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).CreateGroup: missing group store: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
},
{
name: "bad-scope-id",
@@ -106,8 +106,8 @@ func TestRepository_CreateGroup(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "create group: error getting metadata for create: unable to get scope for standard metadata: db.LookupWhere: record not found",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).create: error getting metadata: iam.(Repository).stdMetadata: unable to get scope: iam.LookupScope: db.LookupWhere: record not found, search issue: error #1100",
wantIsError: errors.RecordNotFound,
},
{
name: "dup-name",
@@ -122,7 +122,7 @@ func TestRepository_CreateGroup(t *testing.T) {
wantDup: true,
wantErr: true,
wantErrMsg: "already exists in scope ",
wantIsError: errors.ErrNotUnique,
wantIsError: errors.NotUnique,
},
{
name: "dup-name-but-diff-scope",
@@ -154,6 +154,7 @@ func TestRepository_CreateGroup(t *testing.T) {
assert.Error(err)
assert.Nil(grp)
assert.Contains(err.Error(), tt.wantErrMsg)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
assert.NoError(err)
@@ -237,7 +238,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update group: db.DoTx: db.DoTx: db.Update: db.lookupAfterWrite: db.LookupById: record not found",
wantErrMsg: "db.DoTx: iam.(Repository).UpdateGroup: iam.(Repository).update: db.DoTx: iam.(Repository).update: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100",
wantIsError: errors.RecordNotFound,
},
{
@@ -274,7 +275,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update group: empty field mask",
wantErrMsg: "iam.(Repository).UpdateGroup: empty field mask: parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
@@ -287,7 +288,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update group: empty field mask",
wantErrMsg: "iam.(Repository).UpdateGroup: empty field mask: parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
@@ -300,7 +301,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update group: field: CreateTime: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateGroup: invalid field mask: CreateTime: parameter violation: error #103",
wantIsError: errors.InvalidFieldMask,
},
{
@@ -313,7 +314,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update group: field: Alice: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateGroup: invalid field mask: Alice: parameter violation: error #103",
wantIsError: errors.InvalidFieldMask,
},
{
@@ -326,7 +327,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: "update group: missing group public id invalid parameter",
wantErrMsg: "iam.(Repository).UpdateGroup: missing public id: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
wantRowsUpdate: 0,
},
@@ -338,7 +339,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: "update group: empty field mask",
wantErrMsg: "iam.(Repository).UpdateGroup: empty field mask: parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
@@ -375,7 +376,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantDup: true,
wantErrMsg: " already exists in org " + org.PublicId,
wantErrMsg: " already exists in scope " + org.PublicId,
wantIsError: errors.NotUnique,
},
{
@@ -388,7 +389,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: `db.DoTx: db.Update: immutable column: iam_group.scope_id`,
wantErrMsg: `iam.(Repository).update: db.DoTx: iam.(Repository).update: db.Update: immutable column: iam_group.scope_id: integrity violation: error #1003`,
directUpdate: true,
},
}
@@ -474,6 +475,9 @@ func TestRepository_DeleteGroup(t *testing.T) {
repo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, repo)
grpId, err := newGroupId()
a.NoError(err)
type args struct {
group *Group
opt []Option
@@ -503,23 +507,21 @@ func TestRepository_DeleteGroup(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete group: missing public id invalid parameter",
wantErrMsg: "iam.(Repository).DeleteGroup: missing public id: parameter violation: error #100",
},
{
name: "not-found",
args: args{
group: func() *Group {
id, err := newGroupId()
a.NoError(err)
g, err := NewGroup(org.PublicId)
g.PublicId = id
g.PublicId = grpId
a.NoError(err)
return g
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete group: failed db.LookupById: record not found",
wantErrMsg: "iam.(Repository).DeleteGroup: for group " + grpId + ": db.LookupById: record not found, search issue: error #1100",
},
}
for _, tt := range tests {
@@ -727,7 +729,7 @@ func TestRepository_ListMembers(t *testing.T) {
got, err := repo.ListGroupMembers(context.Background(), "")
require.Error(err)
require.Nil(got)
require.Truef(errors.Is(err, errors.ErrInvalidParameter), "unexpected error %s", err.Error())
require.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error())
})
}
@@ -879,7 +881,7 @@ func TestRepository_DeleteGroupMembers(t *testing.T) {
args args
wantRowsDeleted int
wantErr bool
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid",
@@ -926,7 +928,7 @@ func TestRepository_DeleteGroupMembers(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "bad-version",
@@ -949,7 +951,7 @@ func TestRepository_DeleteGroupMembers(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -979,8 +981,8 @@ func TestRepository_DeleteGroupMembers(t *testing.T) {
if tt.wantErr {
assert.Error(err)
assert.Equal(0, deletedRows)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "unexpected error %s", err.Error())
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "unexpected error %s", err.Error())
}
err = db.TestVerifyOplog(t, rw, tt.args.group.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
+70 -64
View File
@@ -16,26 +16,27 @@ import (
// roleVersion or an error will be returned. The list of current PrincipalRoles
// after the adds will be returned on success. Zero is not a valid value for
// the WithVersion option and will return an error.
func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, opt ...Option) ([]PrincipalRole, error) {
func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, _ ...Option) ([]PrincipalRole, error) {
const op = "iam.(Repository).AddPrincipalRoles"
if roleId == "" {
return nil, fmt.Errorf("add principal roles: missing role id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
if roleVersion == 0 {
return nil, fmt.Errorf("add principal roles: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing version")
}
userIds, groupIds, err := splitPrincipals(principalIds)
if err != nil {
return nil, fmt.Errorf("add principal roles: error parsing principals: %w", err)
return nil, errors.Wrap(err, op)
}
if len(userIds) == 0 && len(groupIds) == 0 {
return nil, fmt.Errorf("add principal roles: missing either user or groups to add: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing either user or groups to add")
}
newUserRoles := make([]interface{}, 0, len(userIds))
for _, id := range userIds {
usrRole, err := NewUserRole(roleId, id)
if err != nil {
return nil, fmt.Errorf("add principal roles: unable to create in memory user role: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory user role"))
}
newUserRoles = append(newUserRoles, usrRole)
}
@@ -43,7 +44,7 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
for _, id := range groupIds {
grpRole, err := NewGroupRole(roleId, id)
if err != nil {
return nil, fmt.Errorf("add principal roles: unable to create in memory group role: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group role"))
}
newGrpRoles = append(newGrpRoles, grpRole)
}
@@ -52,12 +53,12 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
role.PublicId = roleId
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, fmt.Errorf("add principal roles: unable to get role %s scope: %w", roleId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("add principal roles: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentPrincipals []PrincipalRole
@@ -69,7 +70,7 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(&role)
if err != nil {
return fmt.Errorf("add principal roles: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
@@ -77,23 +78,23 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return fmt.Errorf("add principal roles: unable to update role version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("add principal roles: updated role and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
if len(newUserRoles) > 0 {
userOplogMsgs := make([]*oplog.Message, 0, len(newUserRoles))
if err := w.CreateItems(ctx, newUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
return fmt.Errorf("add principal roles: unable to add users: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add users"))
}
msgs = append(msgs, userOplogMsgs...)
}
if len(newGrpRoles) > 0 {
grpOplogMsgs := make([]*oplog.Message, 0, len(newGrpRoles))
if err := w.CreateItems(ctx, newGrpRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil {
return fmt.Errorf("add principal roles: unable to add groups: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add groups"))
}
msgs = append(msgs, grpOplogMsgs...)
}
@@ -104,7 +105,7 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("add principal roles: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
@@ -116,13 +117,13 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
}
currentPrincipals, err = txRepo.ListPrincipalRoles(ctx, roleId)
if err != nil {
return fmt.Errorf("add principal roles: unable to retrieve current principal roles after adds: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current principal roles after adds"))
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("add principal roles: error creating roles: %w", err)
return nil, errors.Wrap(err, op)
}
return currentPrincipals, nil
}
@@ -132,12 +133,13 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV
// requested. If both userIds and groupIds are empty, the principal roles will
// be cleared. Zero is not a valid value for the WithVersion option and will
// return an error.
func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, opt ...Option) ([]PrincipalRole, int, error) {
func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, _ ...Option) ([]PrincipalRole, int, error) {
const op = "iam.(Repository).SetPrincipalRoles"
if roleId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: missing role id: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing role id")
}
if roleVersion == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
role := allocRole()
role.PublicId = roleId
@@ -147,11 +149,11 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
// changing.
userIds, groupIds, err := splitPrincipals(principalIds)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: error parsing principals: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
toSet, err := r.principalsToSet(ctx, &role, userIds, groupIds)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to determine set: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
// handle no change to existing principal roles
@@ -161,11 +163,11 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get role %s scope: %w", roleId, err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get oplog wrapper: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentPrincipals []PrincipalRole
@@ -180,7 +182,7 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
// we need to write oplog entries for deletes and adds
roleTicket, err := w.GetTicket(&role)
if err != nil {
return fmt.Errorf("set principal roles: unable to get ticket for role: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket for role"))
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
@@ -188,10 +190,10 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return fmt.Errorf("set principal roles: unable to update role version: %w", err)
return errors.Wrap(err, op)
}
if rowsUpdated != 1 {
return fmt.Errorf("set principal roles: updated role and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs := make([]*oplog.Message, 0, 5)
metadata := oplog.Metadata{
@@ -208,10 +210,10 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
userOplogMsgs := make([]*oplog.Message, 0, len(toSet.deleteUserRoles))
rowsDeleted, err := w.DeleteItems(ctx, toSet.deleteUserRoles, db.NewOplogMsgs(&userOplogMsgs))
if err != nil {
return fmt.Errorf("set principal roles: unable to delete user roles: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete user roles"))
}
if rowsDeleted != len(toSet.deleteUserRoles) {
return fmt.Errorf("set principal roles: user roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteUserRoles))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("user roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteUserRoles)))
}
totalRowsAffected += rowsDeleted
msgs = append(msgs, userOplogMsgs...)
@@ -220,10 +222,10 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
grpOplogMsgs := make([]*oplog.Message, 0, len(toSet.deleteGroupRoles))
rowsDeleted, err := w.DeleteItems(ctx, toSet.deleteGroupRoles, db.NewOplogMsgs(&grpOplogMsgs))
if err != nil {
return fmt.Errorf("set principal roles: unable to delete groups: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete groups"))
}
if rowsDeleted != len(toSet.deleteGroupRoles) {
return fmt.Errorf("set principal roles: group roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteGroupRoles))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("group roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteGroupRoles)))
}
totalRowsAffected += rowsDeleted
msgs = append(msgs, grpOplogMsgs...)
@@ -234,7 +236,7 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
if len(toSet.addUserRoles) > 0 {
userOplogMsgs := make([]*oplog.Message, 0, len(toSet.addUserRoles))
if err := w.CreateItems(ctx, toSet.addUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
return fmt.Errorf("set principal roles: unable to add users: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add users"))
}
totalRowsAffected += len(toSet.addUserRoles)
msgs = append(msgs, userOplogMsgs...)
@@ -242,14 +244,14 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
if len(toSet.addGroupRoles) > 0 {
grpOplogMsgs := make([]*oplog.Message, 0, len(toSet.addGroupRoles))
if err := w.CreateItems(ctx, toSet.addGroupRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil {
return fmt.Errorf("set principal roles: unable to add groups: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add groups"))
}
totalRowsAffected += len(toSet.addGroupRoles)
msgs = append(msgs, grpOplogMsgs...)
}
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("set principal roles: unable to write oplog for additions: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
@@ -261,12 +263,12 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
}
currentPrincipals, err = txRepo.ListPrincipalRoles(ctx, roleId)
if err != nil {
return fmt.Errorf("set principal roles: unable to retrieve current principal roles after sets: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current principal roles after sets"))
}
return nil
})
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to set principals: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
return currentPrincipals, totalRowsAffected, nil
}
@@ -275,19 +277,20 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV
// (roleId). The role's current db version must match the roleVersion or an
// error will be returned. Zero is not a valid value for the WithVersion option
// and will return an error.
func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, opt ...Option) (int, error) {
func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, roleVersion uint32, principalIds []string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeletePrincipalRoles"
if roleId == "" {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: missing role id: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing role id")
}
userIds, groupIds, err := splitPrincipals(principalIds)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("remove principal roles: error parsing principals: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op)
}
if len(userIds) == 0 && len(groupIds) == 0 {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: missing either user or groups to delete: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing either user or groups to delete")
}
if roleVersion == 0 {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: version cannot be zero: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
role := allocRole()
role.PublicId = roleId
@@ -296,7 +299,7 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
for _, id := range userIds {
usrRole, err := NewUserRole(roleId, id)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to create in memory user role: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory user role"))
}
deleteUserRoles = append(deleteUserRoles, usrRole)
}
@@ -304,18 +307,18 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
for _, id := range groupIds {
grpRole, err := NewGroupRole(roleId, id)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to create in memory group role: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group role"))
}
deleteGrpRoles = append(deleteGrpRoles, grpRole)
}
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to get role %s scope to create metadata: %w", roleId, err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope to create metadata", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to get oplog wrapper: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var totalRowsDeleted int
@@ -327,7 +330,7 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(&role)
if err != nil {
return fmt.Errorf("delete principal roles: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
@@ -335,20 +338,20 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return fmt.Errorf("delete principal roles: unable to update role version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("delete principal roles: updated role and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
if len(deleteUserRoles) > 0 {
userOplogMsgs := make([]*oplog.Message, 0, len(deleteUserRoles))
rowsDeleted, err := w.DeleteItems(ctx, deleteUserRoles, db.NewOplogMsgs(&userOplogMsgs))
if err != nil {
return fmt.Errorf("delete principal roles: unable to delete user roles: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete user roles"))
}
if rowsDeleted != len(deleteUserRoles) {
return fmt.Errorf("delete principal roles: user roles deleted %d did not match request for %d", rowsDeleted, len(deleteUserRoles))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("user roles deleted %d did not match request for %d", rowsDeleted, len(deleteUserRoles)))
}
totalRowsDeleted += rowsDeleted
msgs = append(msgs, userOplogMsgs...)
@@ -357,10 +360,10 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
grpOplogMsgs := make([]*oplog.Message, 0, len(deleteGrpRoles))
rowsDeleted, err := w.DeleteItems(ctx, deleteGrpRoles, db.NewOplogMsgs(&grpOplogMsgs))
if err != nil {
return fmt.Errorf("delete principal roles: unable to delete groups: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete groups"))
}
if rowsDeleted != len(deleteGrpRoles) {
return fmt.Errorf("delete principal roles: group roles deleted %d did not match request for %d", rowsDeleted, len(deleteGrpRoles))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("group roles deleted %d did not match request for %d", rowsDeleted, len(deleteGrpRoles)))
}
totalRowsDeleted += rowsDeleted
msgs = append(msgs, grpOplogMsgs...)
@@ -372,25 +375,26 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("delete principal roles: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete principal roles: error deleting principal roles: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op)
}
return totalRowsDeleted, nil
}
// ListPrincipalRoles returns the principal roles for the roleId and supports the WithLimit option.
func (r *Repository) ListPrincipalRoles(ctx context.Context, roleId string, opt ...Option) ([]PrincipalRole, error) {
const op = "iam.(Repository).ListPrincipalRoles"
if roleId == "" {
return nil, fmt.Errorf("lookup principal roles: missing role id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
var roles []PrincipalRole
if err := r.list(ctx, &roles, "role_id = ?", []interface{}{roleId}, opt...); err != nil {
return nil, fmt.Errorf("lookup principal role: unable to lookup roles: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to lookup roles"))
}
principals := make([]PrincipalRole, 0, len(roles))
principals = append(principals, roles...)
@@ -409,13 +413,14 @@ type principalSet struct {
// TODO: Should this be moved inside the transaction, at this point?
func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, groupIds []string) (*principalSet, error) {
const op = "iam.(Repository).principalsToSet"
// TODO(mgaffney) 08/2020: Use SQL to calculate changes.
if role == nil {
return nil, fmt.Errorf("missing role: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role")
}
existing, err := r.ListPrincipalRoles(ctx, role.PublicId)
if err != nil {
return nil, fmt.Errorf("unable to list existing principal role %s: %w", role.PublicId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to list existing principal role %s", role.PublicId)))
}
existingUsers := map[string]PrincipalRole{}
existingGroups := map[string]PrincipalRole{}
@@ -426,7 +431,7 @@ func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, g
case GroupRoleType.String():
existingGroups[p.PrincipalId] = p
default:
return nil, fmt.Errorf("%s is unknown principal type %s", p.PrincipalId, p.GetType())
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is unknown principal type %s", p.PrincipalId, p.GetType()))
}
}
var newUserRoles []interface{}
@@ -436,7 +441,7 @@ func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, g
if _, ok := existingUsers[id]; !ok {
usrRole, err := NewUserRole(role.PublicId, id)
if err != nil {
return nil, fmt.Errorf("unable to create in memory user role for add: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory user role for add"))
}
newUserRoles = append(newUserRoles, usrRole)
}
@@ -448,7 +453,7 @@ func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, g
if _, ok := existingGroups[id]; !ok {
grpRole, err := NewGroupRole(role.PublicId, id)
if err != nil {
return nil, fmt.Errorf("unable to create in memory group role for add: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group role for add"))
}
newGrpRoles = append(newGrpRoles, grpRole)
}
@@ -458,7 +463,7 @@ func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, g
if _, ok := userIdsMap[p.PrincipalId]; !ok {
usrRole, err := NewUserRole(p.GetRoleId(), p.GetPrincipalId())
if err != nil {
return nil, fmt.Errorf("unable to create in memory user role for delete: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory user role for delete"))
}
deleteUserRoles = append(deleteUserRoles, usrRole)
}
@@ -468,7 +473,7 @@ func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, g
if _, ok := groupIdsMap[p.PrincipalId]; !ok {
grpRole, err := NewGroupRole(p.GetRoleId(), p.GetPrincipalId())
if err != nil {
return nil, fmt.Errorf("unable to create in memory group role for delete: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory group role for delete"))
}
deleteGrpRoles = append(deleteGrpRoles, grpRole)
}
@@ -489,6 +494,7 @@ func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, g
}
func splitPrincipals(principals []string) ([]string, []string, error) {
const op = "iam.splitPrincipals"
var users, groups []string
for _, principal := range principals {
switch {
@@ -498,7 +504,7 @@ func splitPrincipals(principals []string) ([]string, []string, error) {
case strings.HasPrefix(principal, GroupPrefix):
groups = append(groups, principal)
default:
return nil, nil, fmt.Errorf("invalid principal ID %q: %w", principal, errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("invalid principal ID %q", principal))
}
}
@@ -320,7 +320,7 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) {
args args
wantRowsDeleted int
wantErr bool
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid",
@@ -355,7 +355,7 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "just-user-roles",
@@ -402,7 +402,7 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "zero-version",
@@ -416,7 +416,7 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "bad-version",
@@ -494,8 +494,8 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) {
if tt.wantErr {
assert.Error(err)
assert.Equal(0, deletedRows)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "unexpected error %s", err.Error())
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "unexpected error %s", err.Error())
}
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
@@ -772,7 +772,7 @@ func TestRepository_principalsToSet(t *testing.T) {
got, err := repo.principalsToSet(context.Background(), nil, users, grps)
require.Error(err)
assert.Nil(got)
assert.Truef(errors.Is(err, errors.ErrInvalidParameter), "unexpected error %s", err.Error())
assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error())
})
t.Run("no change", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
+40 -35
View File
@@ -12,33 +12,34 @@ import (
// CreateRole will create a role in the repository and return the written
// role. No options are currently supported.
func (r *Repository) CreateRole(ctx context.Context, role *Role, opt ...Option) (*Role, error) {
func (r *Repository) CreateRole(ctx context.Context, role *Role, _ ...Option) (*Role, error) {
const op = "iam.(Repository).CreateRole"
if role == nil {
return nil, fmt.Errorf("create role: missing role %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role")
}
if role.Role == nil {
return nil, fmt.Errorf("create role: missing role store %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role store")
}
if role.PublicId != "" {
return nil, fmt.Errorf("create role: public id not empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "public id not empty")
}
if role.ScopeId == "" {
return nil, fmt.Errorf("create role: missing role scope id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
id, err := newRoleId()
if err != nil {
return nil, fmt.Errorf("create role: %w", err)
return nil, errors.Wrap(err, op)
}
c := role.Clone().(*Role)
c.PublicId = id
resource, err := r.create(ctx, c)
if err != nil {
if errors.IsUniqueError(err) {
return nil, fmt.Errorf("create role: role %s already exists in scope %s: %w", role.Name, role.ScopeId, errors.ErrNotUnique)
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf("role %s already exists in scope %s", role.Name, role.ScopeId))
}
return nil, fmt.Errorf("create role: %w for %s", err, c.PublicId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", c.PublicId)))
}
return resource.(*Role), err
return resource.(*Role), nil
}
// UpdateRole will update a role in the repository and return the written role.
@@ -47,15 +48,16 @@ func (r *Repository) CreateRole(ctx context.Context, role *Role, opt ...Option)
// included in fieldMask. Name, Description, and GrantScopeId are the only
// updatable fields, If no updatable fields are included in the fieldMaskPaths,
// then an error is returned.
func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, fieldMaskPaths []string, opt ...Option) (*Role, []PrincipalRole, []*RoleGrant, int, error) {
func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, fieldMaskPaths []string, _ ...Option) (*Role, []PrincipalRole, []*RoleGrant, int, error) {
const op = "iam.(Repository).UpdateRole"
if role == nil {
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: missing role %w", errors.ErrInvalidParameter)
return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing role")
}
if role.Role == nil {
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: missing role store %w", errors.ErrInvalidParameter)
return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing role store")
}
if role.PublicId == "" {
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: missing role public id %w", errors.ErrInvalidParameter)
return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
for _, f := range fieldMaskPaths {
switch {
@@ -63,7 +65,7 @@ func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32,
case strings.EqualFold("description", f):
case strings.EqualFold("grantscopeid", f):
default:
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: field: %s: %w", f, errors.ErrInvalidFieldMask)
return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
var dbMask, nullFields []string
@@ -77,7 +79,7 @@ func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32,
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: %w", errors.ErrEmptyFieldMask)
return nil, nil, nil, db.NoRowsAffected, errors.E(errors.WithCode(errors.EmptyFieldMask), errors.WithOp(op))
}
var resource Resource
var rowsUpdated int
@@ -92,37 +94,38 @@ func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32,
c := role.Clone().(*Role)
resource, rowsUpdated, err = r.update(ctx, c, version, dbMask, nullFields)
if err != nil {
return err
return errors.Wrap(err, op)
}
repo, err := NewRepository(read, w, r.kms)
if err != nil {
return fmt.Errorf("update role: failed creating inner repo: %w for %s", err, role.PublicId)
return errors.Wrap(err, op)
}
pr, err = repo.ListPrincipalRoles(ctx, role.PublicId)
if err != nil {
return fmt.Errorf("update role: listing principal roles: %w for %s", err, role.PublicId)
return errors.Wrap(err, op)
}
rg, err = repo.ListRoleGrants(ctx, role.PublicId)
if err != nil {
return fmt.Errorf("update role: listing principal roles: %w for %s", err, role.PublicId)
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: role %s already exists in org %s: %w", role.Name, role.ScopeId, errors.ErrNotUnique)
return nil, nil, nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("role %s already exists in org %s", role.Name, role.ScopeId))
}
return nil, nil, nil, db.NoRowsAffected, fmt.Errorf("update role: %w for %s", err, role.PublicId)
return nil, nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", role.PublicId)))
}
return resource.(*Role), pr, rg, rowsUpdated, err
return resource.(*Role), pr, rg, rowsUpdated, nil
}
// LookupRole will look up a role in the repository. If the role is not
// found, it will return nil, nil.
func (r *Repository) LookupRole(ctx context.Context, withPublicId string, opt ...Option) (*Role, []PrincipalRole, []*RoleGrant, error) {
func (r *Repository) LookupRole(ctx context.Context, withPublicId string, _ ...Option) (*Role, []PrincipalRole, []*RoleGrant, error) {
const op = "iam.(Repository).LookupRole"
if withPublicId == "" {
return nil, nil, nil, fmt.Errorf("lookup role: missing public id %w", errors.ErrInvalidParameter)
return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
role := allocRole()
role.PublicId = withPublicId
@@ -134,19 +137,19 @@ func (r *Repository) LookupRole(ctx context.Context, withPublicId string, opt ..
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
if err := read.LookupByPublicId(ctx, &role); err != nil {
return fmt.Errorf("lookup role: failed %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
repo, err := NewRepository(read, w, r.kms)
if err != nil {
return fmt.Errorf("lookup role: failed creating inner repo: %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
pr, err = repo.ListPrincipalRoles(ctx, withPublicId)
if err != nil {
return fmt.Errorf("lookup role: listing principal roles: %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
rg, err = repo.ListRoleGrants(ctx, withPublicId)
if err != nil {
return fmt.Errorf("lookup role: listing principal roles: %w for %s", err, withPublicId)
return errors.Wrap(err, op)
}
return nil
},
@@ -155,37 +158,39 @@ func (r *Repository) LookupRole(ctx context.Context, withPublicId string, opt ..
if errors.IsNotFoundError(err) {
return nil, nil, nil, nil
}
return nil, nil, nil, err
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", withPublicId)))
}
return &role, pr, rg, nil
}
// DeleteRole will delete a role from the repository.
func (r *Repository) DeleteRole(ctx context.Context, withPublicId string, opt ...Option) (int, error) {
func (r *Repository) DeleteRole(ctx context.Context, withPublicId string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteRole"
if withPublicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete role: missing public id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
role := allocRole()
role.PublicId = withPublicId
if err := r.reader.LookupByPublicId(ctx, &role); err != nil {
return db.NoRowsAffected, fmt.Errorf("delete role: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", withPublicId)))
}
rowsDeleted, err := r.delete(ctx, &role)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete role: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", withPublicId)))
}
return rowsDeleted, nil
}
// ListRoles in a scope and supports WithLimit option.
func (r *Repository) ListRoles(ctx context.Context, withScopeId string, opt ...Option) ([]*Role, error) {
const op = "iam.(Repository).ListRoles"
if withScopeId == "" {
return nil, fmt.Errorf("list roles: missing scope id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
var roles []*Role
err := r.list(ctx, &roles, "scope_id = ?", []interface{}{withScopeId}, opt...)
if err != nil {
return nil, fmt.Errorf("list roles: %w", err)
return nil, errors.Wrap(err, op)
}
return roles, nil
}
+58 -53
View File
@@ -14,15 +14,16 @@ import (
// AddRoleGrant will add role grants associated with the role ID in the
// repository. No options are currently supported. Zero is not a valid value for
// the WithVersion option and will return an error.
func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, opt ...Option) ([]*RoleGrant, error) {
func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) ([]*RoleGrant, error) {
const op = "iam.(Repository).AddRoleGrants"
if roleId == "" {
return nil, fmt.Errorf("add role grants: missing role id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
if len(grants) == 0 {
return nil, fmt.Errorf("add role grants: missing grants: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing grants")
}
if roleVersion == 0 {
return nil, fmt.Errorf("add role grants: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing version")
}
role := allocRole()
role.PublicId = roleId
@@ -31,18 +32,18 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi
for _, grant := range grants {
roleGrant, err := NewRoleGrant(roleId, grant)
if err != nil {
return nil, fmt.Errorf("add role grants: unable to create in memory role grant: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
newRoleGrants = append(newRoleGrants, roleGrant)
}
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, fmt.Errorf("add role grants: unable to get role %s scope: %w", roleId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("add role grants: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
_, err = r.writer.DoTx(
@@ -53,7 +54,7 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(&role)
if err != nil {
return fmt.Errorf("unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
// We need to update the role version as that's the aggregate
@@ -63,15 +64,15 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return fmt.Errorf("unable to update role version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("updated role and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(newRoleGrants))
if err := w.CreateItems(ctx, newRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
return fmt.Errorf("unable to add grants: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add grants"))
}
msgs = append(msgs, roleGrantOplogMsgs...)
@@ -82,14 +83,14 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("add role grants: error creating grants: %w", err)
return nil, errors.Wrap(err, op)
}
roleGrants := make([]*RoleGrant, 0, len(newRoleGrants))
for _, grant := range newRoleGrants {
@@ -102,26 +103,27 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi
// current db version must match the roleVersion or an error will be returned.
// Zero is not a valid value for the WithVersion option and will return an
// error.
func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, opt ...Option) (int, error) {
func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteRoleGrants"
if roleId == "" {
return 0, fmt.Errorf("delete role grants: missing role id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing role id")
}
if len(grants) == 0 {
return 0, fmt.Errorf("delete role grants: missing grants: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing grants")
}
if roleVersion == 0 {
return 0, fmt.Errorf("delete role grants: version cannot be zero: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
role := allocRole()
role.PublicId = roleId
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete role grants: unable to get role %s scope to create metadata: %w", roleId, err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope to create metadata", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete role grants: unable to get oplog wrapper: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var totalRowsDeleted int
@@ -133,7 +135,7 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(&role)
if err != nil {
return fmt.Errorf("delete role grants: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
@@ -141,17 +143,17 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return fmt.Errorf("delete role grants: unable to update role version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("delete roles grants: updated role and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
// Find existing grants
roleGrants := []*RoleGrant{}
if err := reader.SearchWhere(ctx, &roleGrants, "role_id = ?", []interface{}{roleId}); err != nil {
return fmt.Errorf("delete role grants: unable to search for grants: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to search for grants"))
}
found := map[string]bool{}
for _, rg := range roleGrants {
@@ -165,7 +167,7 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
// Use a fake scope, just want to get out a canonical string
perm, err := perms.Parse("o_abcd1234", grant, perms.WithSkipFinalValidation(true))
if err != nil {
return fmt.Errorf("delete role grants: error parsing grant string: %w", err)
return errors.Wrap(err, op, errors.WithMsg("parsing grant string"))
}
// We don't have what they want to delete, so ignore it
if !found[perm.CanonicalString()] {
@@ -174,7 +176,7 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
roleGrant, err := NewRoleGrant(roleId, grant)
if err != nil {
return fmt.Errorf("delete role grants: unable to create in memory role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
deleteRoleGrants = append(deleteRoleGrants, roleGrant)
}
@@ -186,10 +188,10 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(deleteRoleGrants))
rowsDeleted, err := w.DeleteItems(ctx, deleteRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs))
if err != nil {
return fmt.Errorf("delete role grants: unable to delete role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete role grant"))
}
if rowsDeleted != len(deleteRoleGrants) {
return fmt.Errorf("delete role grants: role grants deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrants))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("role grants deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrants)))
}
totalRowsDeleted = rowsDeleted
msgs = append(msgs, roleGrantOplogMsgs...)
@@ -201,14 +203,14 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("delete role grants: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete role grants: error deleting role grants: %w", err)
return db.NoRowsAffected, errors.Wrap(err, op)
}
return totalRowsDeleted, nil
}
@@ -216,17 +218,18 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
// SetRoleGrants sets grants on a role (roleId). The role's current db version
// must match the roleVersion or an error will be returned. Zero is not a valid
// value for the WithVersion option and will return an error.
func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, opt ...Option) ([]*RoleGrant, int, error) {
func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) ([]*RoleGrant, int, error) {
const op = "iam.(Repository).SetRoleGrants"
if roleId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: missing role id %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing role id")
}
if roleVersion == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: version cannot be zero: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
// Explicitly set to zero clears, but treat nil as a mistake
if grants == nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: nil grants: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing grants")
}
role := allocRole()
@@ -241,7 +244,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
// Find existing grants
roleGrants := []*RoleGrant{}
if err := r.reader.SearchWhere(ctx, &roleGrants, "role_id = ?", []interface{}{roleId}); err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to search for grants: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to search for grants"))
}
found := map[string]*RoleGrant{}
for _, rg := range roleGrants {
@@ -256,7 +259,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
// Use a fake scope, just want to get out a canonical string
perm, err := perms.Parse("o_abcd1234", grant, perms.WithSkipFinalValidation(true))
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: error parsing grant string: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("error parsing grant string"))
}
canonicalString := perm.CanonicalString()
@@ -272,7 +275,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
// Not found, so add
rg, err = NewRoleGrant(roleId, grant)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to create in memory role grant: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
addRoleGrants = append(addRoleGrants, rg)
currentRoleGrants = append(currentRoleGrants, rg)
@@ -290,11 +293,11 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to get role %s scope: %w", roleId, err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to get oplog wrapper: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var totalRowsDeleted int
@@ -306,7 +309,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(&role)
if err != nil {
return fmt.Errorf("set role grants: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
@@ -314,10 +317,10 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return fmt.Errorf("set role grants: unable to update role version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("set roles grants: updated role and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
@@ -325,7 +328,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
if len(addRoleGrants) > 0 {
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(addRoleGrants))
if err := w.CreateItems(ctx, addRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
return fmt.Errorf("unable to add grants during set: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add grants during set"))
}
msgs = append(msgs, roleGrantOplogMsgs...)
}
@@ -335,10 +338,10 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(deleteRoleGrants))
rowsDeleted, err := w.DeleteItems(ctx, deleteRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs))
if err != nil {
return fmt.Errorf("set role grants: unable to delete role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to delete role grant"))
}
if rowsDeleted != len(deleteRoleGrants) {
return fmt.Errorf("set role grants: role grants deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrants))
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("role grants deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrants)))
}
totalRowsDeleted = rowsDeleted
msgs = append(msgs, roleGrantOplogMsgs...)
@@ -351,19 +354,19 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("set role grants: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
currentRoleGrants, err = r.ListRoleGrants(ctx, roleId)
if err != nil {
return fmt.Errorf("set role grants: unable to retrieve current role grants after set: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current role grants after set"))
}
return nil
},
)
if err != nil {
return nil, db.NoRowsAffected, fmt.Errorf("set role grants: error set role grants: %w", err)
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
return currentRoleGrants, totalRowsDeleted, nil
}
@@ -371,19 +374,21 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
// ListRoleGrants returns the grants for the roleId and supports the WithLimit
// option.
func (r *Repository) ListRoleGrants(ctx context.Context, roleId string, opt ...Option) ([]*RoleGrant, error) {
const op = "iam.(Repository).ListRoleGrants"
if roleId == "" {
return nil, fmt.Errorf("add role grants: missing role id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
var roleGrants []*RoleGrant
if err := r.list(ctx, &roleGrants, "role_id = ?", []interface{}{roleId}, opt...); err != nil {
return nil, fmt.Errorf("lookup role grants: unable to lookup role grants: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to lookup role grants"))
}
return roleGrants, nil
}
func (r *Repository) GrantsForUser(ctx context.Context, userId string, opt ...Option) ([]perms.GrantPair, error) {
func (r *Repository) GrantsForUser(ctx context.Context, userId string, _ ...Option) ([]perms.GrantPair, error) {
const op = "iam.(Repository).GrantsForUser"
if userId == "" {
return nil, fmt.Errorf("get grants for user: missing user id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
const (
@@ -451,13 +456,13 @@ select role_scope as scope_id, role_grant as grant from final;
var grants []perms.GrantPair
rows, err := r.reader.Query(ctx, query, []interface{}{userId})
if err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
defer rows.Close()
for rows.Next() {
var g perms.GrantPair
if err := r.reader.ScanRows(rows, &g); err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
grants = append(grants, g)
}
+5 -5
View File
@@ -240,7 +240,7 @@ func TestRepository_DeleteRoleGrants(t *testing.T) {
args args
wantRowsDeleted int
wantErr bool
wantIsErr error
wantIsErr errors.Code
}{
{
name: "valid",
@@ -271,7 +271,7 @@ func TestRepository_DeleteRoleGrants(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
@@ -295,7 +295,7 @@ func TestRepository_DeleteRoleGrants(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-grant-strings",
@@ -375,8 +375,8 @@ func TestRepository_DeleteRoleGrants(t *testing.T) {
if tt.wantErr {
assert.Error(err)
assert.Equal(0, deletedRows)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "unexpected error %s", err.Error())
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "unexpected error %s", err.Error())
}
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
+24 -22
View File
@@ -37,7 +37,7 @@ func TestRepository_CreateRole(t *testing.T) {
wantDup bool
wantErr bool
wantErrMsg string
wantIsError error
wantIsError errors.Code
}{
{
name: "valid-org",
@@ -71,8 +71,8 @@ func TestRepository_CreateRole(t *testing.T) {
return r
}(),
},
wantErrMsg: "create role: public id not empty: invalid parameter",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "am.(Repository).CreateRole: public id not empty: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
wantErr: true,
},
{
@@ -81,8 +81,8 @@ func TestRepository_CreateRole(t *testing.T) {
role: nil,
},
wantErr: true,
wantErrMsg: "create role: missing role invalid parameter",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).CreateRole: missing role: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
},
{
name: "nil-store",
@@ -94,8 +94,8 @@ func TestRepository_CreateRole(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "create role: missing role store invalid parameter",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).CreateRole: missing role store: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
},
{
name: "bad-scope-id",
@@ -107,8 +107,8 @@ func TestRepository_CreateRole(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "create role: error getting metadata for create: unable to get scope for standard metadata: db.LookupWhere: record not found",
wantIsError: errors.ErrInvalidParameter,
wantErrMsg: "iam.(Repository).create: error getting metadata: iam.(Repository).stdMetadata: unable to get scope: iam.LookupScope: db.LookupWhere: record not found, search issue: error #1100",
wantIsError: errors.RecordNotFound,
},
{
name: "dup-name",
@@ -123,7 +123,7 @@ func TestRepository_CreateRole(t *testing.T) {
wantDup: true,
wantErr: true,
wantErrMsg: "already exists in scope ",
wantIsError: errors.ErrNotUnique,
wantIsError: errors.NotUnique,
},
{
name: "dup-name-but-diff-scope",
@@ -155,6 +155,7 @@ func TestRepository_CreateRole(t *testing.T) {
assert.Error(err)
assert.Nil(grp)
assert.Contains(err.Error(), tt.wantErrMsg)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
assert.NoError(err)
@@ -238,7 +239,7 @@ func TestRepository_UpdateRole(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update role: db.DoTx: db.DoTx: db.Update: db.lookupAfterWrite: db.LookupById: record not found",
wantErrMsg: "db.DoTx: iam.(Repository).UpdateRole: iam.(Repository).update: db.DoTx: iam.(Repository).update: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100",
wantIsError: errors.RecordNotFound,
},
{
@@ -275,7 +276,7 @@ func TestRepository_UpdateRole(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update role: empty field mask",
wantErrMsg: "iam.(Repository).UpdateRole: empty field mask, parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
@@ -288,7 +289,7 @@ func TestRepository_UpdateRole(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update role: empty field mask",
wantErrMsg: "iam.(Repository).UpdateRole: empty field mask, parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
@@ -301,7 +302,7 @@ func TestRepository_UpdateRole(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update role: field: CreateTime: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateRole: invalid field mask: CreateTime: parameter violation: error #103",
wantIsError: errors.InvalidFieldMask,
},
{
@@ -314,7 +315,7 @@ func TestRepository_UpdateRole(t *testing.T) {
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update role: field: Alice: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateRole: invalid field mask: Alice: parameter violation: error #103",
wantIsError: errors.InvalidFieldMask,
},
{
@@ -327,7 +328,7 @@ func TestRepository_UpdateRole(t *testing.T) {
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: "update role: missing role public id invalid parameter",
wantErrMsg: "iam.(Repository).UpdateRole: missing public id: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
wantRowsUpdate: 0,
},
@@ -339,7 +340,7 @@ func TestRepository_UpdateRole(t *testing.T) {
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: "update role: empty field mask",
wantErrMsg: "iam.(Repository).UpdateRole: empty field mask, parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
@@ -471,6 +472,9 @@ func TestRepository_DeleteRole(t *testing.T) {
repo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, repo)
roleId, err := newRoleId()
require.NoError(t, err)
type args struct {
role *Role
opt []Option
@@ -500,24 +504,22 @@ func TestRepository_DeleteRole(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete role: missing public id invalid parameter",
wantErrMsg: "iam.(Repository).DeleteRole: missing public id: parameter violation: error #100",
},
{
name: "not-found",
args: args{
role: func() *Role {
id, err := newRoleId()
require.NoError(t, err)
r, err := NewRole(org.PublicId)
r.PublicId = id
r.PublicId = roleId
require.NoError(t, err)
return r
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete role: failed db.LookupById: record not found",
wantErrMsg: "iam.(Repository).DeleteRole: failed for " + roleId + ": db.LookupById: record not found, search issue: error #1100",
},
}
for _, tt := range tests {
+64 -58
View File
@@ -19,14 +19,15 @@ import (
// CreateScope will create a scope in the repository and return the written
// scope. Supported options include: WithPublicId and WithRandomReader.
func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, opt ...Option) (*Scope, error) {
const op = "iam.(Repository).CreateScope"
if s == nil {
return nil, fmt.Errorf("create scope: missing scope %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope")
}
if s.Scope == nil {
return nil, fmt.Errorf("create scope: missing scope store %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope store")
}
if s.PublicId != "" {
return nil, fmt.Errorf("create scope: public id not empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "public id not empty")
}
var parentOplogWrapper wrapping.Wrapper
@@ -34,13 +35,13 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
var err error
switch s.Type {
case scope.Unknown.String():
return nil, fmt.Errorf("create scope: unknown type: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "unknown type")
case scope.Global.String():
return nil, fmt.Errorf("create scope: invalid type: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "invalid type")
default:
switch s.ParentId {
case "":
return nil, fmt.Errorf("create scope: missing parent id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing parent id")
case scope.Global.String():
parentOplogWrapper, err = r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeOplog)
default:
@@ -49,7 +50,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
externalWrappers = r.kms.GetExternalWrappers()
}
if err != nil {
return nil, fmt.Errorf("create scope: unable to get oplog wrapper: %w", err)
return nil, errors.New(errors.InvalidParameter, op, "unable to get oplog wrapper")
}
opts := getOpts(opt...)
@@ -61,13 +62,13 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
scopeType := scope.Map[s.Type]
if opts.withPublicId != "" {
if !strings.HasPrefix(opts.withPublicId, scopeType.Prefix()+"_") {
return nil, fmt.Errorf("create scope: passed-in public ID %q has wrong prefix for type %q which uses prefix %q", opts.withPublicId, scopeType.String(), scopeType.Prefix())
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("passed-in public ID %q has wrong prefix for type %q which uses prefix %q", opts.withPublicId, scopeType.String(), scopeType.Prefix()))
}
scopePublicId = opts.withPublicId
} else {
scopePublicId, err = newScopeId(scopeType)
if err != nil {
return nil, fmt.Errorf("create scope: error generating public id for new scope: %w", err)
return nil, errors.Wrap(err, op)
}
}
sc := s.Clone().(*Scope)
@@ -75,7 +76,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
scopeRaw = sc
scopeMetadata, err = r.stdMetadata(ctx, sc)
if err != nil {
return nil, fmt.Errorf("create scope: error getting metadata for scope create: %w", err)
return nil, errors.Wrap(err, op)
}
scopeMetadata["op-type"] = []string{oplog.OpType_OP_TYPE_CREATE.String()}
}
@@ -102,11 +103,11 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
default:
adminRole, err = NewRole(scopePublicId)
if err != nil {
return nil, fmt.Errorf("create scope: error instantiating new admin role: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("error instantiating new admin role"))
}
adminRolePublicId, err = newRoleId()
if err != nil {
return nil, fmt.Errorf("create scope: error generating public id for new admin role: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("error generating public id for new admin role"))
}
adminRole.PublicId = adminRolePublicId
adminRole.Name = "Administration"
@@ -128,11 +129,11 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
if !opts.withSkipDefaultRoleCreation && s.Type == scope.Org.String() {
defaultRole, err = NewRole(scopePublicId)
if err != nil {
return nil, fmt.Errorf("create scope: error instantiating new default role: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("error instantiating new default role"))
}
defaultRolePublicId, err = newRoleId()
if err != nil {
return nil, fmt.Errorf("create scope: error generating public id for new default role: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("error generating public id for new default role"))
}
defaultRole.PublicId = defaultRolePublicId
defaultRole.Name = "Login and Default Grants"
@@ -162,7 +163,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
scopeRaw,
db.WithOplog(parentOplogWrapper, scopeMetadata),
); err != nil {
return fmt.Errorf("error creating scope: %w", err)
return errors.Wrap(err, op, errors.WithMsg("error creating scope"))
}
s := scopeRaw.(*Scope)
@@ -170,16 +171,16 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
// Create the scope's keys
_, err = kms.CreateKeysTx(ctx, dbr, w, externalWrappers.Root(), reader, s.PublicId)
if err != nil {
return fmt.Errorf("error creating scope keys: %w", err)
return errors.Wrap(err, op, errors.WithMsg("error creating scope keys"))
}
kmsRepo, err := kms.NewRepository(dbr, w)
if err != nil {
return fmt.Errorf("error creating new kms repo: %w", err)
return errors.Wrap(err, op, errors.WithMsg("error creating new kms repo"))
}
childOplogWrapper, err := r.kms.GetWrapper(ctx, s.PublicId, kms.KeyPurposeOplog, kms.WithRepository(kmsRepo))
if err != nil {
return fmt.Errorf("error fetching new scope oplog wrapper: %w", err)
return errors.New(errors.InvalidParameter, op, "unable to get oplog wrapper")
}
// We create a new role, then set grants and principals on it. This
@@ -191,7 +192,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
adminRoleRaw,
db.WithOplog(childOplogWrapper, adminRoleMetadata),
); err != nil {
return fmt.Errorf("error creating role: %w", err)
return errors.Wrap(err, op, errors.WithMsg("error creating role"))
}
adminRole = adminRoleRaw.(*Role)
@@ -199,38 +200,38 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
msgs := make([]*oplog.Message, 0, 3)
roleTicket, err := w.GetTicket(adminRole)
if err != nil {
return fmt.Errorf("unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
// We need to update the role version as that's the aggregate
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, adminRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&adminRole.Version))
if err != nil {
return fmt.Errorf("unable to update role version for adding grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version for adding grant"))
}
if rowsUpdated != 1 {
return fmt.Errorf("updated role but %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role but %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
roleGrant, err := NewRoleGrant(adminRolePublicId, "id=*;type=*;actions=*")
if err != nil {
return fmt.Errorf("unable to create in memory role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
roleGrantOplogMsgs := make([]*oplog.Message, 0, 1)
if err := w.CreateItems(ctx, []interface{}{roleGrant}, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
return fmt.Errorf("unable to add grants: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add grants"))
}
msgs = append(msgs, roleGrantOplogMsgs...)
rolePrincipal, err := NewUserRole(adminRolePublicId, userId)
if err != nil {
return fmt.Errorf("unable to create in memory role user: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role user"))
}
roleUserOplogMsgs := make([]*oplog.Message, 0, 1)
if err := w.CreateItems(ctx, []interface{}{rolePrincipal}, db.NewOplogMsgs(&roleUserOplogMsgs)); err != nil {
return fmt.Errorf("unable to add grants: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add grants"))
}
msgs = append(msgs, roleUserOplogMsgs...)
@@ -241,7 +242,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
"resource-public-id": []string{adminRole.PublicId},
}
if err := w.WriteOplogEntryWith(ctx, childOplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
}
@@ -254,7 +255,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
defaultRoleRaw,
db.WithOplog(childOplogWrapper, defaultRoleMetadata),
); err != nil {
return fmt.Errorf("error creating role: %w", err)
return errors.Wrap(err, op, errors.WithMsg("error creating role"))
}
defaultRole = defaultRoleRaw.(*Role)
@@ -262,17 +263,17 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
msgs := make([]*oplog.Message, 0, 6)
roleTicket, err := w.GetTicket(defaultRole)
if err != nil {
return fmt.Errorf("unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
// We need to update the role version as that's the aggregate
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, defaultRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&defaultRole.Version))
if err != nil {
return fmt.Errorf("unable to update role version for adding grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update role version for adding grant"))
}
if rowsUpdated != 1 {
return fmt.Errorf("updated role but %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated role but %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
@@ -281,24 +282,24 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
grants := []interface{}{}
roleGrant, err := NewRoleGrant(defaultRolePublicId, "type=scope;actions=list")
if err != nil {
return fmt.Errorf("unable to create in memory role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id=*;type=auth-method;actions=authenticate,list")
if err != nil {
return fmt.Errorf("unable to create in memory role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id={{account.id}};actions=read,change-password")
if err != nil {
return fmt.Errorf("unable to create in memory role grant: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrantOplogMsgs := make([]*oplog.Message, 0, 3)
if err := w.CreateItems(ctx, grants, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
return fmt.Errorf("unable to add grants: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add grants"))
}
msgs = append(msgs, roleGrantOplogMsgs...)
}
@@ -308,13 +309,13 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
principals := []interface{}{}
rolePrincipal, err := NewUserRole(defaultRolePublicId, "u_anon")
if err != nil {
return fmt.Errorf("unable to create in memory role user: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role user"))
}
principals = append(principals, rolePrincipal)
roleUserOplogMsgs := make([]*oplog.Message, 0, 2)
if err := w.CreateItems(ctx, principals, db.NewOplogMsgs(&roleUserOplogMsgs)); err != nil {
return fmt.Errorf("unable to add grants: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to add grants"))
}
msgs = append(msgs, roleUserOplogMsgs...)
}
@@ -326,7 +327,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
"resource-public-id": []string{defaultRole.PublicId},
}
if err := w.WriteOplogEntryWith(ctx, childOplogWrapper, roleTicket, metadata, msgs); err != nil {
return fmt.Errorf("unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
}
@@ -336,9 +337,9 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
if err != nil {
if errors.IsUniqueError(err) {
return nil, fmt.Errorf("create scope: scope %s/%s already exists: %w", scopePublicId, s.Name, errors.ErrNotUnique)
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf("scope %s/%s already exists", scopePublicId, s.Name))
}
return nil, fmt.Errorf("create scope: id %s got error: %w", scopePublicId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", scopePublicId)))
}
return scopeRaw.(*Scope), nil
}
@@ -349,15 +350,16 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
// included in fieldMask. Name and Description are the only updatable fields,
// and everything else is ignored. If no updatable fields are included in the
// fieldMaskPaths, then an error is returned.
func (r *Repository) UpdateScope(ctx context.Context, scope *Scope, version uint32, fieldMaskPaths []string, opt ...Option) (*Scope, int, error) {
func (r *Repository) UpdateScope(ctx context.Context, scope *Scope, version uint32, fieldMaskPaths []string, _ ...Option) (*Scope, int, error) {
const op = "iam.(Repository).UpdateScope"
if scope == nil {
return nil, db.NoRowsAffected, fmt.Errorf("update scope: missing scope: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope")
}
if scope.PublicId == "" {
return nil, db.NoRowsAffected, fmt.Errorf("update scope: missing public id: %w", errors.ErrInvalidParameter)
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
if contains(fieldMaskPaths, "ParentId") {
return nil, db.NoRowsAffected, fmt.Errorf("update scope: you cannot change a scope's parent: %w", errors.ErrInvalidFieldMask)
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, "you cannot change a scope's parent")
}
var dbMask, nullFields []string
dbMask, nullFields = dbcommon.BuildUpdatePaths(
@@ -370,24 +372,25 @@ func (r *Repository) UpdateScope(ctx context.Context, scope *Scope, version uint
)
// nada to update, so reload scope from db and return it
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, fmt.Errorf("update scope: %w", errors.ErrEmptyFieldMask)
return nil, db.NoRowsAffected, errors.E(errors.WithCode(errors.EmptyFieldMask), errors.WithOp(op))
}
resource, rowsUpdated, err := r.update(ctx, scope, version, dbMask, nullFields)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, fmt.Errorf("update scope: %s name %s already exists: %w", scope.PublicId, scope.Name, errors.ErrNotUnique)
return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("%s name %s already exists", scope.PublicId, scope.Name))
}
return nil, db.NoRowsAffected, fmt.Errorf("update scope: failed for public id %s: %w", scope.PublicId, err)
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for public id %s", scope.PublicId)))
}
return resource.(*Scope), rowsUpdated, err
return resource.(*Scope), rowsUpdated, nil
}
// LookupScope will look up a scope in the repository. If the scope is not
// found, it will return nil, nil.
func (r *Repository) LookupScope(ctx context.Context, withPublicId string, opt ...Option) (*Scope, error) {
func (r *Repository) LookupScope(ctx context.Context, withPublicId string, _ ...Option) (*Scope, error) {
const op = "iam.(Repository).LookupScope"
if withPublicId == "" {
return nil, fmt.Errorf("lookup scope: missing public id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
scope := allocScope()
scope.PublicId = withPublicId
@@ -395,18 +398,19 @@ func (r *Repository) LookupScope(ctx context.Context, withPublicId string, opt .
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, fmt.Errorf("lookup scope: failed %w fo %s", err, withPublicId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", withPublicId)))
}
return &scope, nil
}
// DeleteScope will delete a scope from the repository
func (r *Repository) DeleteScope(ctx context.Context, withPublicId string, opt ...Option) (int, error) {
func (r *Repository) DeleteScope(ctx context.Context, withPublicId string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteScope"
if withPublicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete scope: missing public id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
if withPublicId == scope.Global.String() {
return db.NoRowsAffected, fmt.Errorf("delete scope: invalid to delete global scope: %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "invalid to delete global scope")
}
scope := allocScope()
scope.PublicId = withPublicId
@@ -415,30 +419,32 @@ func (r *Repository) DeleteScope(ctx context.Context, withPublicId string, opt .
if errors.Is(err, ErrMetadataScopeNotFound) {
return 0, nil
}
return db.NoRowsAffected, fmt.Errorf("delete scope: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", withPublicId)))
}
return rowsDeleted, nil
}
// ListProjects in an org and supports the WithLimit option.
func (r *Repository) ListProjects(ctx context.Context, withOrgId string, opt ...Option) ([]*Scope, error) {
const op = "iam.(Repository).ListProjects"
if withOrgId == "" {
return nil, fmt.Errorf("list projects: missing org id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing org id")
}
var projects []*Scope
err := r.list(ctx, &projects, "parent_id = ? and type = ?", []interface{}{withOrgId, scope.Project.String()}, opt...)
if err != nil {
return nil, fmt.Errorf("list projects: %w", err)
return nil, errors.Wrap(err, op)
}
return projects, nil
}
// ListOrgs and supports the WithLimit option.
func (r *Repository) ListOrgs(ctx context.Context, opt ...Option) ([]*Scope, error) {
const op = "iam.(Repository).ListOrgs"
var orgs []*Scope
err := r.list(ctx, &orgs, "parent_id = ? and type = ?", []interface{}{"global", scope.Org.String()}, opt...)
if err != nil {
return nil, fmt.Errorf("list orgs: %w", err)
return nil, errors.Wrap(err, op)
}
return orgs, nil
}
+5 -5
View File
@@ -197,7 +197,7 @@ func Test_Repository_Scope_Update(t *testing.T) {
require.Error(err)
assert.Nil(project)
assert.Equal(0, updatedRows)
assert.Contains(err.Error(), "update scope: you cannot change a scope's parent: invalid field mask")
assert.Contains(err.Error(), "iam.(Repository).UpdateScope: you cannot change a scope's parent: parameter violation: error #103")
})
}
@@ -315,7 +315,7 @@ func TestRepository_UpdateScope(t *testing.T) {
},
wantUpdatedRows: 0,
wantErr: true,
wantErrMsg: "update scope: missing scope: invalid parameter",
wantErrMsg: "iam.(Repository).UpdateScope: missing scope: parameter violation: error #100",
wantNullFields: nil,
},
{
@@ -334,7 +334,7 @@ func TestRepository_UpdateScope(t *testing.T) {
},
wantUpdatedRows: 0,
wantErr: true,
wantErrMsg: "update scope: empty field mask",
wantErrMsg: "iam.(Repository).UpdateScope: empty field mask, parameter violation: error #104",
wantNullFields: nil,
},
{
@@ -398,7 +398,7 @@ func TestRepository_UpdateScope(t *testing.T) {
wantDescription: "orig-" + id,
wantUpdatedRows: 0,
wantErr: true,
wantErrMsg: "update scope: you cannot change a scope's parent: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateScope: you cannot change a scope's parent: parameter violation: error #103",
wantNullFields: nil,
},
{
@@ -418,7 +418,7 @@ func TestRepository_UpdateScope(t *testing.T) {
},
wantUpdatedRows: 0,
wantErr: true,
wantErrMsg: "update scope: empty field mask",
wantErrMsg: "am.(Repository).UpdateScope: empty field mask, parameter violation: error #104",
wantNullFields: nil,
},
}
+8 -8
View File
@@ -61,7 +61,7 @@ func TestNewRepository(t *testing.T) {
},
want: nil,
wantErr: true,
wantErrString: "error creating db repository with nil kms",
wantErrString: "iam.NewRepository: nil kms: parameter violation: error #100",
},
{
name: "nil-writer",
@@ -72,7 +72,7 @@ func TestNewRepository(t *testing.T) {
},
want: nil,
wantErr: true,
wantErrString: "error creating db repository with nil writer",
wantErrString: "iam.NewRepository: nil writer: parameter violation: error #100",
},
{
name: "nil-reader",
@@ -83,7 +83,7 @@ func TestNewRepository(t *testing.T) {
},
want: nil,
wantErr: true,
wantErrString: "error creating db repository with nil reader",
wantErrString: "iam.NewRepository: nil reader: parameter violation: error #100",
},
}
for _, tt := range tests {
@@ -92,7 +92,7 @@ func TestNewRepository(t *testing.T) {
got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms)
if tt.wantErr {
require.Error(err)
assert.Equal(err.Error(), tt.wantErrString)
assert.Equal(tt.wantErrString, err.Error())
return
}
require.NoError(err)
@@ -136,7 +136,7 @@ func Test_Repository_create(t *testing.T) {
resource, err := repo.create(context.Background(), nil)
require.Error(err)
assert.Nil(resource)
assert.Equal(err.Error(), "error creating resource that is nil")
assert.Equal("iam.(Repository).create: missing resource: parameter violation: error #100", err.Error())
})
}
@@ -165,7 +165,7 @@ func Test_Repository_delete(t *testing.T) {
deletedRows, err := repo.delete(context.Background(), nil, nil)
require.Error(err)
assert.Equal(0, deletedRows)
assert.Equal(err.Error(), "error deleting resource that is nil")
assert.Equal("iam.(Repository).delete: missing resource: parameter violation: error #100", err.Error())
})
}
@@ -223,7 +223,7 @@ func TestRepository_update(t *testing.T) {
},
wantUpdatedRows: 0,
wantErr: true,
wantErrMsg: "error updating resource that is nil",
wantErrMsg: "iam.(Repository).update: missing resource: parameter violation: error #100",
},
{
name: "intersection",
@@ -242,7 +242,7 @@ func TestRepository_update(t *testing.T) {
},
wantUpdatedRows: 0,
wantErr: true,
wantErrMsg: "db.DoTx: db.Update: getting update fields failed: common.UpdateFields: fieldMashPaths and setToNullPaths cannot intersect: parameter violation: error #100",
wantErrMsg: "iam.(Repository).update: db.DoTx: iam.(Repository).update: db.Update: getting update fields failed: common.UpdateFields: fieldMashPaths and setToNullPaths cannot intersect: parameter violation: error #100",
},
{
name: "only-field-masks",
+136 -127
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
stdErrors "errors"
"fmt"
"strings"
@@ -16,11 +15,12 @@ import (
// CreateUser will create a user in the repository and return the written user
func (r *Repository) CreateUser(ctx context.Context, user *User, opt ...Option) (*User, error) {
const op = "iam.(Repository).CreateUser"
if user == nil {
return nil, fmt.Errorf("create user: missing user %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user")
}
if user.PublicId != "" {
return nil, fmt.Errorf("create user: public id is not empty %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "public id is not empty")
}
u := user.Clone().(*User)
@@ -28,13 +28,13 @@ func (r *Repository) CreateUser(ctx context.Context, user *User, opt ...Option)
if opts.withPublicId != "" {
if !strings.HasPrefix(opts.withPublicId, UserPrefix+"_") {
return nil, fmt.Errorf("create user: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, UserPrefix, errors.ErrInvalidPublicId)
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("passed-in public ID %q has wrong prefix, should be %q", opts.withPublicId, UserPrefix))
}
u.PublicId = opts.withPublicId
} else {
id, err := newUserId()
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
return nil, errors.Wrap(err, op)
}
u.PublicId = id
}
@@ -42,11 +42,11 @@ func (r *Repository) CreateUser(ctx context.Context, user *User, opt ...Option)
resource, err := r.create(ctx, u)
if err != nil {
if errors.IsUniqueError(err) {
return nil, fmt.Errorf("create user: user %s already exists in org %s: %w", user.Name, user.ScopeId, err)
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf("user %s already exists in org %s", user.Name, user.ScopeId))
}
return nil, fmt.Errorf("create user: %w for %s", err, u.PublicId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", u.PublicId)))
}
return resource.(*User), err
return resource.(*User), nil
}
// UpdateUser will update a user in the repository and return the written user
@@ -56,18 +56,19 @@ func (r *Repository) CreateUser(ctx context.Context, user *User, opt ...Option)
// only updatable fields, if no updatable fields are included in the
// fieldMaskPaths, then an error is returned.
func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32, fieldMaskPaths []string, opt ...Option) (*User, []string, int, error) {
const op = "iam.(Repository).UpdateUser"
if user == nil {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: missing user %w", errors.ErrInvalidParameter)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing user")
}
if user.PublicId == "" {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: missing user public id %w", errors.ErrInvalidParameter)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold("name", f):
case strings.EqualFold("description", f):
default:
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: field: %s: %w", f, errors.ErrInvalidFieldMask)
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
var dbMask, nullFields []string
@@ -80,13 +81,13 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32,
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: %w", errors.ErrEmptyFieldMask)
return nil, nil, db.NoRowsAffected, errors.E(errors.WithCode(errors.EmptyFieldMask), errors.WithOp(op))
}
u := user.Clone().(*User)
metadata, err := r.stdMetadata(ctx, u)
if err != nil {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: error getting metadata for update: %w", err)
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op)
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_UPDATE.String()}
@@ -100,12 +101,12 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32,
scope, err := u.GetScope(ctx, r.reader)
if err != nil {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: unable to get scope: %w", err)
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get scope"))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: unable to get oplog wrapper: %w", err)
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata))
@@ -125,12 +126,12 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32,
nullFields,
dbOpts...,
)
if err == nil && rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return stdErrors.New("error more than 1 resource would have been updated ")
}
if err != nil {
return err
return errors.Wrap(err, op)
}
if rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return errors.New(errors.MultipleRecords, op, "error more than 1 resource would have been updated")
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
@@ -142,25 +143,26 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32,
}
currentAccountIds, err = txRepo.ListUserAccounts(ctx, user.PublicId)
if err != nil {
return fmt.Errorf("unable to retrieve current account ids after update: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids after update"))
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: user %s already exists in org %s", user.Name, user.ScopeId)
return nil, nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("user %s already exists in org %s", user.Name, user.ScopeId))
}
return nil, nil, db.NoRowsAffected, fmt.Errorf("update user: %w for %s", err, user.PublicId)
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", user.PublicId)))
}
return returnedUser, currentAccountIds, rowsUpdated, err
return returnedUser, currentAccountIds, rowsUpdated, nil
}
// LookupUser will look up a user and its associated account ids in the
// repository. If the user is not found, it will return nil, nil, nil.
func (r *Repository) LookupUser(ctx context.Context, userId string, opt ...Option) (*User, []string, error) {
func (r *Repository) LookupUser(ctx context.Context, userId string, _ ...Option) (*User, []string, error) {
const op = "iam.(Repository).LookupUser"
if userId == "" {
return nil, nil, fmt.Errorf("lookup user: missing public id %w", errors.ErrInvalidParameter)
return nil, nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
user := allocUser()
@@ -169,41 +171,43 @@ func (r *Repository) LookupUser(ctx context.Context, userId string, opt ...Optio
if errors.IsNotFoundError(err) {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("lookup user: failed %w for %s", err, userId)
return nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", userId)))
}
currentAccountIds, err := r.ListUserAccounts(ctx, userId)
if err != nil {
return nil, nil, fmt.Errorf("lookup user: unable to retrieve current account ids: %w", err)
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids"))
}
return &user, currentAccountIds, nil
}
// DeleteUser will delete a user from the repository
func (r *Repository) DeleteUser(ctx context.Context, withPublicId string, opt ...Option) (int, error) {
func (r *Repository) DeleteUser(ctx context.Context, withPublicId string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteUser"
if withPublicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete user: missing public id %w", errors.ErrInvalidParameter)
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
}
user := allocUser()
user.PublicId = withPublicId
if err := r.reader.LookupByPublicId(ctx, &user); err != nil {
return db.NoRowsAffected, fmt.Errorf("delete user: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", withPublicId)))
}
rowsDeleted, err := r.delete(ctx, &user)
if err != nil {
return db.NoRowsAffected, fmt.Errorf("delete user: failed %w for %s", err, withPublicId)
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", withPublicId)))
}
return rowsDeleted, nil
}
// ListUsers in an org and supports the WithLimit option.
func (r *Repository) ListUsers(ctx context.Context, withOrgId string, opt ...Option) ([]*User, error) {
const op = "iam.(Repository).ListUsers"
if withOrgId == "" {
return nil, fmt.Errorf("list users: missing org id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing org id")
}
var users []*User
err := r.list(ctx, &users, "scope_id = ?", []interface{}{withOrgId}, opt...)
if err != nil {
return nil, fmt.Errorf("list users: %w", err)
return nil, errors.Wrap(err, op)
}
return users, nil
}
@@ -215,26 +219,27 @@ func (r *Repository) ListUsers(ctx context.Context, withOrgId string, opt ...Opt
// account. If a new user is auto vivified, then the WithName and
// WithDescription options are supported as well.
func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string, opt ...Option) (*User, error) {
const op = "iam.(Repository).LookupUserWithLogin"
opts := getOpts(opt...)
if accountId == "" {
return nil, fmt.Errorf("lookup user with login: missing account id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing account id")
}
u, err := r.getUserWithAccount(ctx, accountId)
if err != nil {
return nil, fmt.Errorf("lookup user with login: %w", err)
return nil, errors.Wrap(err, op)
}
if u != nil {
return u, nil
}
if !opts.withAutoVivify {
return nil, fmt.Errorf("lookup user with login: user not found for account %s: %w", accountId, errors.ErrRecordNotFound)
return nil, errors.New(errors.RecordNotFound, op, fmt.Sprintf("user not found for account %s", accountId))
}
acct := allocAccount()
acct.PublicId = accountId
err = r.reader.LookupByPublicId(context.Background(), &acct)
if err != nil {
return nil, fmt.Errorf("lookup user with login: unable to lookup account %s: %w", accountId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup account %s", accountId)))
}
metadata := oplog.Metadata{
@@ -246,7 +251,7 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string,
oplogWrapper, err := r.kms.GetWrapper(ctx, acct.GetScopeId(), kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("lookup user with login: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
// We will create a new user and associate the user with the account
@@ -260,21 +265,21 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string,
msgs := make([]*oplog.Message, 0, 2)
ticket, err := w.GetTicket(&acct)
if err != nil {
return err
return errors.Wrap(err, op)
}
obtainedUser, err = NewUser(acct.ScopeId, opt...)
if err != nil {
return err
return errors.Wrap(err, op)
}
id, err := newUserId()
if err != nil {
return err
return errors.Wrap(err, op)
}
var createMsg oplog.Message
obtainedUser.PublicId = id
err = w.Create(ctx, obtainedUser, db.NewOplogMsg(&createMsg))
if err != nil {
return err
return errors.Wrap(err, op)
}
msgs = append(msgs, &createMsg)
@@ -283,60 +288,59 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string,
updateAcct.IamUserId = id
updatedRows, err := w.Update(ctx, updateAcct, []string{"IamUserId"}, nil, db.NewOplogMsg(&updateMsg))
if err != nil {
return err
return errors.Wrap(err, op)
}
if updatedRows != 1 {
return fmt.Errorf("account update affected %d rows", updatedRows)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("account update affected %d rows", updatedRows))
}
msgs = append(msgs, &updateMsg)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return err
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("lookup user with login: unable to associate user and account: %w", err)
return nil, errors.Wrap(err, op)
}
return obtainedUser, nil
}
func (r *Repository) getUserWithAccount(ctx context.Context, withAccountId string, opt ...Option) (*User, error) {
func (r *Repository) getUserWithAccount(ctx context.Context, withAccountId string, _ ...Option) (*User, error) {
const op = "iam.(Repository).getUserWithAccount"
if withAccountId == "" {
return nil, fmt.Errorf("missing account id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing account id")
}
rows, err := r.reader.Query(ctx, whereUserAccount, []interface{}{withAccountId})
if err != nil {
return nil, fmt.Errorf("unable to query account %s", withAccountId)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to query account %s", withAccountId)))
}
defer rows.Close()
u := allocUser()
if rows.Next() {
err = r.reader.ScanRows(rows, &u)
if err != nil {
return nil, fmt.Errorf("unable to scan rows for account %s: %w", withAccountId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to scan rows for account %s", withAccountId)))
}
} else {
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("unable to get next account: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get next account"))
}
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("unable to get next row for accounts %s: %w", withAccountId, err)
}
return &u, nil
}
// ListUserAccounts returns the account ids for the userId and supports the
// WithLimit option. Returns nil, nil when no associated accounts are found.
func (r *Repository) ListUserAccounts(ctx context.Context, userId string, opt ...Option) ([]string, error) {
const op = "iam.(Repository).ListUserAccounts"
if userId == "" {
return nil, fmt.Errorf("list auth account ids: missing user id: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
var accounts []*authAccount
if err := r.list(ctx, &accounts, "iam_user_id = ?", []interface{}{userId}, opt...); err != nil {
return nil, fmt.Errorf("list auth account ids: %w", err)
return nil, errors.Wrap(err, op)
}
if len(accounts) == 0 {
return nil, nil
@@ -352,15 +356,16 @@ func (r *Repository) ListUserAccounts(ctx context.Context, userId string, opt ..
// return a list of all associated account ids for the user. The accounts must
// not already be associated with different users. No options are currently
// supported.
func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVersion uint32, accountIds []string, opt ...Option) ([]string, error) {
func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVersion uint32, accountIds []string, _ ...Option) ([]string, error) {
const op = "iam.(Repository).AddUserAccounts"
if userId == "" {
return nil, fmt.Errorf("associate accounts: missing user public id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user id")
}
if userVersion == 0 {
return nil, fmt.Errorf("associate accounts: missing user version %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user version")
}
if len(accountIds) == 0 {
return nil, fmt.Errorf("associate accounts: missing account id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing account ids")
}
user := allocUser()
@@ -368,11 +373,11 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
err := r.reader.LookupById(ctx, &user)
if err != nil {
return nil, fmt.Errorf("associate accounts: unable to lookup user %s: %w", userId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup user %s", userId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, user.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("associate accounts: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentAccountIds []string
@@ -383,7 +388,7 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
func(reader db.Reader, w db.Writer) error {
userTicket, err := w.GetTicket(&user)
if err != nil {
return fmt.Errorf("associate accounts: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedUser := allocUser()
updatedUser.PublicId = userId
@@ -391,13 +396,13 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
var userOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedUser, []string{"Version"}, nil, db.NewOplogMsg(&userOplogMsg), db.WithVersion(&userVersion))
if err != nil {
return fmt.Errorf("associate accounts: unable to update user version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get user version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("associate accounts: updated user and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated user and %d rows updated", rowsUpdated))
}
if err := associateUserWithAccounts(ctx, r.kms, reader, w, user.PublicId, accountIds); err != nil {
return fmt.Errorf("associate accounts: %w", err)
return errors.Wrap(err, op)
}
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
@@ -406,7 +411,7 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
"resource-public-id": []string{user.PublicId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, userTicket, metadata, []*oplog.Message{&userOplogMsg}); err != nil {
return fmt.Errorf("associate accounts: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
@@ -418,13 +423,13 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
}
currentAccountIds, err = txRepo.ListUserAccounts(ctx, user.PublicId)
if err != nil {
return fmt.Errorf("associate accounts: unable to retrieve current account ids after adds: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids after adds"))
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("associate accounts: error associating account ids: %w", err)
return nil, errors.Wrap(err, op)
}
return currentAccountIds, nil
}
@@ -433,26 +438,27 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
// return a list of all associated account ids for the user. The accounts must
// not be associated with different users. No options are currently
// supported.
func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, userVersion uint32, accountIds []string, opt ...Option) ([]string, error) {
func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, userVersion uint32, accountIds []string, _ ...Option) ([]string, error) {
const op = "iam.(Repository).DeleteUserAccounts"
if userId == "" {
return nil, fmt.Errorf("disassociate accounts: missing user public id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
if userVersion == 0 {
return nil, fmt.Errorf("disassociate accounts: missing user version %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing user version")
}
if len(accountIds) == 0 {
return nil, fmt.Errorf("disassociate accounts: missing account id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing account ids")
}
user := allocUser()
user.PublicId = userId
err := r.reader.LookupById(ctx, &user)
if err != nil {
return nil, fmt.Errorf("disassociate accounts: unable to lookup user %s: %w", userId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup user %s", userId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, user.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("disassociate accounts: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentAccountIds []string
@@ -463,7 +469,7 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
func(reader db.Reader, w db.Writer) error {
userTicket, err := w.GetTicket(&user)
if err != nil {
return fmt.Errorf("disassociate accounts: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedUser := allocUser()
updatedUser.PublicId = userId
@@ -471,13 +477,13 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
var userOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedUser, []string{"Version"}, nil, db.NewOplogMsg(&userOplogMsg), db.WithVersion(&userVersion))
if err != nil {
return fmt.Errorf("disassociate accounts: unable to update user version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update user version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("disassociate accounts: updated user and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated user and %d rows updated", rowsUpdated))
}
if err := dissociateUserFromAccounts(ctx, r.kms, reader, w, user.PublicId, accountIds); err != nil {
return fmt.Errorf("disassociate accounts: %w", err)
return errors.Wrap(err, op)
}
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
@@ -486,7 +492,7 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
"resource-public-id": []string{user.PublicId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, userTicket, metadata, []*oplog.Message{&userOplogMsg}); err != nil {
return fmt.Errorf("disassociate accounts: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
@@ -498,13 +504,13 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
}
currentAccountIds, err = txRepo.ListUserAccounts(ctx, user.PublicId)
if err != nil {
return fmt.Errorf("disassociate accounts: unable to retrieve current account ids after adds: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids after adds"))
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("disassociate accounts: error associating account ids: %w", err)
return nil, errors.Wrap(err, op)
}
return currentAccountIds, nil
}
@@ -513,23 +519,24 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
// return a list of all associated account ids for the user. The accounts must
// not already be associated with different users. No options are currently
// supported.
func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVersion uint32, accountIds []string, opt ...Option) ([]string, error) {
func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVersion uint32, accountIds []string, _ ...Option) ([]string, error) {
const op = "iam.(Repository).SetUserAccounts"
if userId == "" {
return nil, fmt.Errorf("set associated accounts: missing user public id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
if userVersion == 0 {
return nil, fmt.Errorf("set associated accounts: missing user version %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing version")
}
user := allocUser()
user.PublicId = userId
err := r.reader.LookupById(ctx, &user)
if err != nil {
return nil, fmt.Errorf("set associated accounts: unable to lookup user %s: %w", userId, err)
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup user %s", userId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, user.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, fmt.Errorf("set associated accounts: unable to get oplog wrapper: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var currentAccountIds []string
_, err = r.writer.DoTx(
@@ -539,7 +546,7 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
func(reader db.Reader, w db.Writer) error {
associateIds, disassociateIds, err := associationChanges(ctx, reader, userId, accountIds)
if err != nil {
return fmt.Errorf("set associated accounts: unable to determine changes: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to determine changes"))
}
// just in case we've got nothing to do...
@@ -554,13 +561,13 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
}
currentAccountIds, err = txRepo.ListUserAccounts(ctx, userId)
if err != nil {
return fmt.Errorf("set associated accounts: unable to retrieve current account ids after set: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids after set"))
}
return nil
}
userTicket, err := w.GetTicket(&user)
if err != nil {
return fmt.Errorf("set associated accounts: unable to get ticket: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
updatedUser := allocUser()
updatedUser.PublicId = userId
@@ -568,21 +575,21 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
var userOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedUser, []string{"Version"}, nil, db.NewOplogMsg(&userOplogMsg), db.WithVersion(&userVersion))
if err != nil {
return fmt.Errorf("set associated accounts: unable to update user version: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to update user version"))
}
if rowsUpdated != 1 {
return fmt.Errorf("set associated accounts: updated user and %d rows updated", rowsUpdated)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated user and %d rows updated", rowsUpdated))
}
if len(associateIds) > 0 {
if err := associateUserWithAccounts(ctx, r.kms, reader, w, userId, associateIds); err != nil {
return fmt.Errorf("set associated accounts: unable to associate ids: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to associate ids"))
}
}
if len(disassociateIds) > 0 {
if err := dissociateUserFromAccounts(ctx, r.kms, reader, w, userId, disassociateIds); err != nil {
return fmt.Errorf("set associated accounts: unable to disassociate ids: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to disassociate ids"))
}
}
@@ -593,7 +600,7 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
"resource-public-id": []string{userId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, userTicket, metadata, []*oplog.Message{&userOplogMsg}); err != nil {
return fmt.Errorf("set associated accounts: unable to write oplog: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
// we need a new repo, that's using the same reader/writer as this TxHandler
txRepo := &Repository{
@@ -605,34 +612,35 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
}
currentAccountIds, err = txRepo.ListUserAccounts(ctx, user.PublicId)
if err != nil {
return fmt.Errorf("set associated accounts: unable to retrieve current account ids after set: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids after set"))
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("set associated accounts: %w", err)
return nil, errors.Wrap(err, op)
}
return currentAccountIds, nil
}
// associateUserWithAccounts will associate the accounts (accountIds) with
// the user (userId) within the writer's database
func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.Reader, writer db.Writer, userId string, accountIds []string, opt ...Option) error {
func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.Reader, writer db.Writer, userId string, accountIds []string, _ ...Option) error {
const op = "iam.associateUserWithAccounts"
if repoKms == nil {
return fmt.Errorf("associate user with accounts: kms is nil: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "nil kms")
}
if reader == nil {
return fmt.Errorf("associate user with accounts: db reader is nil: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "nil reader")
}
if writer == nil {
return fmt.Errorf("associate user with accounts: db writer is nil: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "nil writer")
}
if userId == "" {
return fmt.Errorf("associate user with accounts: user id is empty: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing user id")
}
if len(accountIds) == 0 {
return fmt.Errorf("associate user with accounts: missing account id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing account ids")
}
authAccounts := make([]*authAccount, 0, len(accountIds))
for _, accountId := range accountIds {
@@ -640,10 +648,10 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.
acct.PublicId = accountId
err := reader.LookupByPublicId(context.Background(), &acct)
if err != nil {
return fmt.Errorf("associate user with accounts: unable to lookup account %s: %w", accountId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup account %s", accountId)))
}
if acct.IamUserId != "" && acct.IamUserId != userId {
return fmt.Errorf("associate user with accounts: %s account is associated with a user %s: %w", accountId, acct.IamUserId, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s account is associated with a user %s", accountId, acct.IamUserId))
}
authAccounts = append(authAccounts, &acct)
}
@@ -652,7 +660,7 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.
// wrapper could be different for each authAccount depending on it's scope
oplogWrapper, err := repoKms.GetWrapper(ctx, aa.GetScopeId(), kms.KeyPurposeOplog)
if err != nil {
return fmt.Errorf("associate user with accounts: unable to get oplog wrapper: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
metadata := oplog.Metadata{
@@ -666,13 +674,13 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.
updatedAcct.IamUserId = userId
updatedRows, err = writer.Update(ctx, updatedAcct, []string{"IamUserId"}, nil, db.WithOplog(oplogWrapper, metadata), db.WithWhere("iam_user_id is NULL or iam_user_id = ?", userId))
if err != nil {
return fmt.Errorf("associate user with accouts: failed to associate %s account: %w", aa.PublicId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed to associate %s account", aa.PublicId)))
}
if updatedRows == 0 {
return fmt.Errorf("associate user with accounts: failed to associate %s account: it is already associated with another user", aa.PublicId)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to associate %s account: it is already associated with another user", aa.PublicId))
}
if updatedRows > 1 {
return fmt.Errorf("associate user with accounts: failed to associate %s account: would have updated too many accounts %d", aa.PublicId, updatedRows)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to associate %s account: would have updated too many accounts %d", aa.PublicId, updatedRows))
}
}
return nil
@@ -681,21 +689,22 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.
// dissociateUserFromAccounts will dissociate a user with its existing accounts
// (accountIds). An error is returned if account is associated with a different
// user. No options are currently supported.
func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db.Reader, writer db.Writer, userId string, accountIds []string, opt ...Option) error {
func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db.Reader, writer db.Writer, userId string, accountIds []string, _ ...Option) error {
const op = "iam.dissociateUserFromAccounts"
if repoKms == nil {
return fmt.Errorf("dissociate user from accounts: kms is nil: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "nil kms")
}
if reader == nil {
return fmt.Errorf("dissociate user from accounts: db reader is nil: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "nil reader")
}
if writer == nil {
return fmt.Errorf("dissociate user from accounts: db writer is nil: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "nil writer")
}
if userId == "" {
return fmt.Errorf("dissociate user from accounts: missing user public id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing public id")
}
if len(accountIds) == 0 {
return fmt.Errorf("dissociate user from accounts: missing account id %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing account ids")
}
authAccounts := make([]*authAccount, 0, len(accountIds))
for _, accountId := range accountIds {
@@ -703,10 +712,10 @@ func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db
acct.PublicId = accountId
err := reader.LookupByPublicId(context.Background(), &acct)
if err != nil {
return fmt.Errorf("dissociate user from accounts: unable to lookup account %s: %w", accountId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup account %s", accountId)))
}
if acct.IamUserId != userId {
return fmt.Errorf("dissociate user from accounts: %s account is not associated with user %s: %w", accountId, userId, errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s account is not associated with user %s", accountId, userId))
}
authAccounts = append(authAccounts, &acct)
}
@@ -715,7 +724,7 @@ func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db
// wrapper could be different for each authAccount depending on it's scope
oplogWrapper, err := repoKms.GetWrapper(ctx, aa.GetScopeId(), kms.KeyPurposeOplog)
if err != nil {
return fmt.Errorf("dissociate user from accounts: unable to get oplog wrapper: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
metadata := oplog.Metadata{
"resource-public-id": []string{aa.PublicId},
@@ -729,13 +738,13 @@ func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db
// update IamUserId to null
updatedRows, err = writer.Update(ctx, updatedAcct, nil, []string{"IamUserId"}, db.WithOplog(oplogWrapper, metadata), db.WithWhere("iam_user_id is NULL or iam_user_id = ?", userId))
if err != nil {
return fmt.Errorf("dissociate user from accounts: failed to disassociate %s account: %w", aa.PublicId, err)
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed to disassociate %s account", aa.PublicId)))
}
if updatedRows == 0 {
return fmt.Errorf("dissociate user from accounts: failed to disassociate %s account: it is associated with another user", aa.PublicId)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to disassociate %s account: it is already associated with another user", aa.PublicId))
}
if updatedRows > 1 {
return fmt.Errorf("dissociate user from accounts: failed to disassociate %s account: would have updated too many accounts %d", aa.PublicId, updatedRows)
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to disassociate %s account: would have updated too many accounts %d", aa.PublicId, updatedRows))
}
}
return nil
@@ -743,6 +752,7 @@ func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db
// associationChanges returns two slices: accounts to associate and disassociate
func associationChanges(ctx context.Context, reader db.Reader, userId string, accountIds []string) ([]string, []string, error) {
const op = "iam.associationChanges"
var inClauseSpots []string
// starts at 2 because there is already a $1 in the query
for i := 2; i < len(accountIds)+2; i++ {
@@ -761,7 +771,7 @@ func associationChanges(ctx context.Context, reader db.Reader, userId string, ac
}
rows, err := reader.Query(ctx, query, params)
if err != nil {
return nil, nil, fmt.Errorf("changes: query failed: %w", err)
return nil, nil, errors.Wrap(err, op)
}
defer rows.Close()
@@ -773,14 +783,14 @@ func associationChanges(ctx context.Context, reader db.Reader, userId string, ac
for rows.Next() {
var chg change
if err := reader.ScanRows(rows, &chg); err != nil {
return nil, nil, fmt.Errorf("changes: scan row failed: %w", err)
return nil, nil, errors.Wrap(err, op)
}
changes = append(changes, &chg)
}
var associateIds, disassociateIds []string
for _, c := range changes {
if c.AccountId == "" {
return nil, nil, fmt.Errorf("changes: missing account id in change result")
return nil, nil, errors.New(errors.InvalidParameter, op, "missing account id in change result")
}
switch c.Action {
case "associate":
@@ -788,9 +798,8 @@ func associationChanges(ctx context.Context, reader db.Reader, userId string, ac
case "disassociate":
disassociateIds = append(disassociateIds, c.AccountId)
default:
return nil, nil, fmt.Errorf("changes: unknown action %s for %s", c.Action, c.AccountId)
return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("unknown action %s for %s", c.Action, c.AccountId))
}
}
return associateIds, disassociateIds, nil
}
+13 -13
View File
@@ -59,7 +59,7 @@ func TestRepository_CreateUser(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "create user: error getting metadata for create: unable to get scope for standard metadata: db.LookupWhere: record not found",
wantErrMsg: "iam.(Repository).create: error getting metadata: iam.(Repository).stdMetadata: unable to get scope: iam.LookupScope: db.LookupWhere: record not found",
},
{
name: "dup-name",
@@ -72,7 +72,7 @@ func TestRepository_CreateUser(t *testing.T) {
},
wantDup: true,
wantErr: true,
wantErrMsg: "create user: user %s already exists in org %s",
wantErrMsg: "iam.(Repository).CreateUser: user %s already exists in org %s: integrity violation: error #1002",
},
}
for _, tt := range tests {
@@ -170,7 +170,7 @@ func TestRepository_UpdateUser(t *testing.T) {
},
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update user: db.DoTx: db.Update: db.lookupAfterWrite: db.LookupById: record not found",
wantErrMsg: "iam.(Repository).UpdateUser: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100",
wantIsErr: errors.RecordNotFound,
},
{
@@ -204,7 +204,7 @@ func TestRepository_UpdateUser(t *testing.T) {
},
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update user: empty field mask",
wantErrMsg: "iam.(Repository).UpdateUser: empty field mask, parameter violation: error #104",
},
{
name: "nil-fieldmask",
@@ -215,7 +215,7 @@ func TestRepository_UpdateUser(t *testing.T) {
},
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update user: empty field mask",
wantErrMsg: "iam.(Repository).UpdateUser: empty field mask, parameter violation: error #104",
},
{
name: "read-only-fields",
@@ -226,7 +226,7 @@ func TestRepository_UpdateUser(t *testing.T) {
},
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update user: field: CreateTime: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateUser: invalid field mask: CreateTime: parameter violation: error #103",
},
{
name: "unknown-fields",
@@ -237,7 +237,7 @@ func TestRepository_UpdateUser(t *testing.T) {
},
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "update user: field: Alice: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateUser: invalid field mask: Alice: parameter violation: error #103",
},
{
name: "no-public-id",
@@ -248,7 +248,7 @@ func TestRepository_UpdateUser(t *testing.T) {
PublicId: pubId(""),
},
wantErr: true,
wantErrMsg: "update user: missing user public id invalid parameter",
wantErrMsg: "iam.(Repository).UpdateUser: missing public id: parameter violation: error #100",
wantRowsUpdate: 0,
},
{
@@ -259,7 +259,7 @@ func TestRepository_UpdateUser(t *testing.T) {
ScopeId: proj.PublicId,
},
wantErr: true,
wantErrMsg: "update user: field: ScopeId: invalid field mask",
wantErrMsg: "iam.(Repository).UpdateUser: invalid field mask: ScopeId: parameter violation: error #103",
},
{
name: "empty-scope-id-with-name-mask",
@@ -280,7 +280,7 @@ func TestRepository_UpdateUser(t *testing.T) {
},
wantErr: true,
wantDup: true,
wantErrMsg: `update user: user %s already exists in org %s`,
wantErrMsg: `iam.(Repository).UpdateUser: user %s already exists in org %s: integrity violation: error #1002`,
},
{
name: "modified-scope",
@@ -291,7 +291,7 @@ func TestRepository_UpdateUser(t *testing.T) {
opt: []Option{WithSkipVetForWrite(true)},
},
wantErr: true,
wantErrMsg: `db.DoTx: db.Update: immutable column: iam_user.scope_id:`,
wantErrMsg: `iam.(Repository).update: db.DoTx: iam.(Repository).update: db.Update: immutable column: iam_user.scope_id: integrity violation: error #1003`,
directUpdate: true,
},
}
@@ -418,7 +418,7 @@ func TestRepository_DeleteUser(t *testing.T) {
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "delete user: missing public id invalid parameter",
wantErrMsg: "iam.(Repository).DeleteUser: missing public id: parameter violation: error #100",
},
{
name: "not-found",
@@ -434,7 +434,7 @@ func TestRepository_DeleteUser(t *testing.T) {
},
wantRowsDeleted: 1,
wantErr: true,
wantErrMsg: "delete user: failed db.LookupById: record not found",
wantErrMsg: "db.LookupById: record not found, search issue: error #1100",
},
}
for _, tt := range tests {
+8 -8
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
stderrors "errors"
"fmt"
"github.com/hashicorp/boundary/internal/db"
@@ -49,28 +48,29 @@ type ResourceWithScope interface {
// LookupScope looks up the resource's scope
func LookupScope(ctx context.Context, reader db.Reader, resource ResourceWithScope) (*Scope, error) {
const op = "iam.LookupScope"
if reader == nil {
return nil, stderrors.New("error reader is nil for LookupScope")
return nil, errors.New(errors.InvalidParameter, op, "nil reader")
}
if resource == nil {
return nil, stderrors.New("error resource is nil for LookupScope")
return nil, errors.New(errors.InvalidParameter, op, "missing resource")
}
if resource.GetPublicId() == "" {
return nil, fmt.Errorf("LookupScope: scope id is unset %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
if resource.GetScopeId() == "" {
// try to retrieve it from db with it's scope id
if err := reader.LookupById(ctx, resource); err != nil {
return nil, fmt.Errorf("unable to get resource by public id: %w", err)
return nil, errors.Wrap(err, op)
}
// if it's still not set after getting it from the db...
if resource.GetScopeId() == "" {
return nil, stderrors.New("error scope is unset for LookupScope")
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
}
var p Scope
if err := reader.LookupWhere(ctx, &p, "public_id = ?", resource.GetScopeId()); err != nil {
return nil, err
return nil, errors.Wrap(err, op)
}
return &p, nil
}
@@ -89,7 +89,7 @@ func validateScopeForWrite(ctx context.Context, r db.Reader, resource ResourceWi
if errors.IsNotFoundError(err) {
return errors.New(errors.RecordNotFound, op, "scope is not found")
}
return err
return errors.Wrap(err, op)
}
validScopeType := false
for _, t := range resource.validScopeTypes() {
+3 -3
View File
@@ -40,15 +40,15 @@ func Test_LookupScope(t *testing.T) {
s, err := LookupScope(context.Background(), nil, user)
require.Error(err)
assert.Nil(s)
assert.Equal("error reader is nil for LookupScope", err.Error())
assert.Equal("iam.LookupScope: nil reader: parameter violation: error #100", err.Error())
s, err = LookupScope(context.Background(), w, nil)
assert.Nil(s)
assert.Equal("error resource is nil for LookupScope", err.Error())
assert.Equal("iam.LookupScope: missing resource: parameter violation: error #100", err.Error())
user2 := allocUser()
s, err = LookupScope(context.Background(), w, &user2)
assert.Nil(s)
assert.Contains(err.Error(), "LookupScope: scope id is unset invalid parameter")
assert.Contains(err.Error(), "iam.LookupScope: missing public id: parameter violation: error #100")
})
}
+5 -5
View File
@@ -2,8 +2,6 @@ package iam
import (
"context"
stderrors "errors"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -32,8 +30,9 @@ var _ db.VetForWriter = (*Role)(nil)
// NewRole creates a new in memory role with a scope (project/org)
// allowed options include: withDescripion, WithName, withGrantScopeId.
func NewRole(scopeId string, opt ...Option) (*Role, error) {
const op = "iam.NewRole"
if scopeId == "" {
return nil, fmt.Errorf("new role: missing scope id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
r := &Role{
@@ -63,11 +62,12 @@ func (r *Role) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface.
func (role *Role) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(Role).VetForWrite"
if role.PublicId == "" {
return stderrors.New("error public id is empty string for role write")
return errors.New(errors.InvalidParameter, op, "missing public id")
}
if err := validateScopeForWrite(ctx, r, role, opType, opt...); err != nil {
return err
return errors.Wrap(err, op)
}
return nil
}
+10 -9
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -24,12 +23,13 @@ var _ Cloneable = (*RoleGrant)(nil)
var _ db.VetForWriter = (*RoleGrant)(nil)
// NewRoleGrant creates a new in memory role grant
func NewRoleGrant(roleId string, grant string, opt ...Option) (*RoleGrant, error) {
func NewRoleGrant(roleId string, grant string, _ ...Option) (*RoleGrant, error) {
const op = "iam.NewRoleGrant"
if roleId == "" {
return nil, fmt.Errorf("new role grant: role id is not set: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing role id")
}
if grant == "" {
return nil, fmt.Errorf("new role grant: grant is empty: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing grant")
}
// Validate that the grant parses successfully. Note that we fake the scope
@@ -37,7 +37,7 @@ func NewRoleGrant(roleId string, grant string, opt ...Option) (*RoleGrant, error
// checking time and we just care that it parses correctly.
perm, err := perms.Parse("o_abcd1234", grant)
if err != nil {
return nil, fmt.Errorf("new role grant: error parsing grant string: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("parsing grant string"))
}
rg := &RoleGrant{
RoleGrant: &store.RoleGrant{
@@ -64,9 +64,10 @@ func (g *RoleGrant) Clone() interface{} {
}
// VetForWrite implements db.VetForWrite() interface
func (g *RoleGrant) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
func (g *RoleGrant) VetForWrite(_ context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error {
const op = "iam.(RoleGrant).VetForWrite"
if g.RawGrant == "" {
return fmt.Errorf("vet role grant for writing: grant is empty: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing grant")
}
// Validate that the grant parses successfully. Note that we fake the scope
@@ -76,11 +77,11 @@ func (g *RoleGrant) VetForWrite(ctx context.Context, r db.Reader, opType db.OpTy
// anyways because it should still be part of the vetting process.
perm, err := perms.Parse("o_abcd1234", g.RawGrant)
if err != nil {
return fmt.Errorf("vet role grant for writing: error parsing grant string: %w", err)
return errors.Wrap(err, op, errors.WithMsg("parsing grant string"))
}
canonical := perm.CanonicalString()
if g.CanonicalGrant != "" && g.CanonicalGrant != canonical {
return fmt.Errorf("vet role grant for writing: existing canonical grant and derived one do not match: %w", err)
return errors.Wrap(err, op, errors.WithMsg("existing canonical grant and derived one do not match"))
}
g.CanonicalGrant = canonical
+4 -4
View File
@@ -31,7 +31,7 @@ func TestRoleGrant_Create(t *testing.T) {
args args
want *RoleGrant
wantErr bool
wantIsErr error
wantIsErr errors.Code
create bool
}{
{
@@ -41,7 +41,7 @@ func TestRoleGrant_Create(t *testing.T) {
grant: "id=*;type=*;actions=*",
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
},
{
name: "nil-grant",
@@ -50,7 +50,7 @@ func TestRoleGrant_Create(t *testing.T) {
grant: "",
},
wantErr: true,
wantIsErr: errors.ErrInvalidParameter,
wantIsErr: errors.InvalidParameter,
create: true,
},
{
@@ -91,7 +91,7 @@ func TestRoleGrant_Create(t *testing.T) {
got, err := NewRoleGrant(tt.args.roleId, tt.args.grant, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Is(err, tt.wantIsErr))
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
+7 -7
View File
@@ -34,7 +34,7 @@ func TestNewRole(t *testing.T) {
args args
wantErr bool
wantErrMsg string
wantIsErr error
wantIsErr errors.Code
wantName string
wantDescription string
}{
@@ -71,8 +71,8 @@ func TestNewRole(t *testing.T) {
opt: []Option{WithName(id)},
},
wantErr: true,
wantErrMsg: "new role: missing scope id invalid parameter:",
wantIsErr: errors.ErrInvalidParameter,
wantErrMsg: "iam.NewRole: missing scope id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
@@ -82,8 +82,8 @@ func TestNewRole(t *testing.T) {
if tt.wantErr {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrMsg)
if tt.wantIsErr != nil {
assert.True(errors.Is(err, tt.wantIsErr))
if tt.wantIsErr != 0 {
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
}
return
}
@@ -171,7 +171,7 @@ func Test_RoleCreate(t *testing.T) {
}(),
},
wantErr: true,
wantErrMsg: "db.Create: vet for write failed: iam.validateScopeForWrite: scope is not found: search issue: error #1100",
wantErrMsg: "db.Create: iam.(Role).VetForWrite: iam.validateScopeForWrite: scope is not found: search issue: error #1100",
},
}
@@ -252,7 +252,7 @@ func Test_RoleUpdate(t *testing.T) {
scopeIdOverride: org.PublicId,
},
wantErr: true,
wantErrMsg: "db.Update: vet for write failed: iam.validateScopeForWrite: not allowed to change a resource's scope: parameter violation: error #100",
wantErrMsg: "db.Update: iam.(Role).VetForWrite: iam.validateScopeForWrite: not allowed to change a resource's scope: parameter violation: error #100",
},
{
name: "proj-scope-id-not-in-mask",
+21 -19
View File
@@ -2,8 +2,6 @@ package iam
import (
"context"
stderrors "errors"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
@@ -41,11 +39,12 @@ func NewOrg(opt ...Option) (*Scope, error) {
}
func NewProject(orgPublicId string, opt ...Option) (*Scope, error) {
const op = "iam.NewProject"
org := allocScope()
org.PublicId = orgPublicId
p, err := newScope(&org, opt...)
if err != nil {
return nil, fmt.Errorf("error creating new project: %w", err)
return nil, errors.Wrap(err, op)
}
return p, nil
}
@@ -55,8 +54,9 @@ func NewProject(orgPublicId string, opt ...Option) (*Scope, error) {
// specifies the Scope's parent and must be filled in. The type of the parent is
// used to determine the type of the child.
func newScope(parent *Scope, opt ...Option) (*Scope, error) {
const op = "iam.newScope"
if parent == nil || parent.PublicId == "" {
return nil, fmt.Errorf("new scope: child scope is missing its parent: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "child scope is missing its parent")
}
var typ scope.Type
switch {
@@ -66,7 +66,7 @@ func newScope(parent *Scope, opt ...Option) (*Scope, error) {
typ = scope.Project
}
if typ == scope.Unknown {
return nil, fmt.Errorf("new scope: unknown type of scope to create: %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "unknown scope type")
}
opts := getOpts(opt...)
@@ -100,41 +100,42 @@ func (s *Scope) Clone() interface{} {
// this function is intended to be callled by a db.Writer (Create and Update) to validate
// the scope before writing it to the db.
func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(Scope).VetForWrite"
if s.Type == scope.Unknown.String() {
return stderrors.New("unknown scope type for scope write")
return errors.New(errors.InvalidParameter, op, "unknown scope type")
}
if s.PublicId == "" {
return stderrors.New("public id is empty string for scope write")
return errors.New(errors.InvalidParameter, op, "missing public id")
}
if opType == db.UpdateOp {
dbOptions := db.GetOpts(opt...)
for _, path := range dbOptions.WithFieldMaskPaths {
switch path {
case "ParentId":
return stderrors.New("you cannot change a scope's parent")
return errors.New(errors.InvalidParameter, op, "you cannot change a scope's parent")
case "Type":
return stderrors.New("you cannot change a scope's type")
return errors.New(errors.InvalidParameter, op, "you cannot change a scope's type")
}
}
}
if opType == db.CreateOp {
switch {
case s.Type == scope.Global.String():
return stderrors.New("global scope cannot be created")
return errors.New(errors.InvalidParameter, op, "you cannot create a global scope")
case s.ParentId == "":
return stderrors.New("scope must have a parent")
return errors.New(errors.InvalidParameter, op, "scope must have a parent")
case s.Type == scope.Org.String():
if s.ParentId != "global" {
return stderrors.New(`org's parent must be "global"`)
return errors.New(errors.InvalidParameter, op, `org's parent must be "global"`)
}
case s.Type == scope.Project.String():
parentScope := allocScope()
parentScope.PublicId = s.ParentId
if err := r.LookupByPublicId(ctx, &parentScope, opt...); err != nil {
return fmt.Errorf("unable to verify project's org scope: %w", err)
return errors.Wrap(err, op, errors.WithMsg("unable to verify project's org scope"))
}
if parentScope.Type != scope.Org.String() {
return stderrors.New("project parent scope is not an org")
return errors.New(errors.InvalidParameter, op, "project parent scope is not an org")
}
}
}
@@ -153,15 +154,16 @@ func (*Scope) Actions() map[string]action.Type {
// GetScope returns the scope for the "scope" if there is one defined
func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
const op = "iam.(Scope).GetScope"
if r == nil {
return nil, stderrors.New("error db is nil for get scope")
return nil, errors.New(errors.InvalidParameter, op, "nil reader")
}
if s.PublicId == "" {
return nil, stderrors.New("unable to get scope with unset public id")
return nil, errors.New(errors.InvalidParameter, op, "missing public id")
}
if s.Type == "" && s.ParentId == "" {
if err := r.LookupByPublicId(ctx, s); err != nil {
return nil, fmt.Errorf("unable to get scope by public id: %w", err)
return nil, errors.Wrap(err, op)
}
}
// HANDLE_GLOBAL
@@ -179,11 +181,11 @@ func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
// handled at HANDLE_GLOBAL
where := "public_id in (select parent_id from iam_scope where public_id = ?)"
if err := r.LookupWhere(ctx, &p, where, s.PublicId); err != nil {
return nil, fmt.Errorf("unable to lookup parent public id from public id: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to lookup parent public id from public id"))
}
default:
if err := r.LookupWhere(ctx, &p, "public_id = ?", s.ParentId); err != nil {
return nil, fmt.Errorf("unable to lookup parent from public id: %w", err)
return nil, errors.Wrap(err, op, errors.WithMsg("unable to lookup parent from public id"))
}
}
return &p, nil
+3 -3
View File
@@ -42,14 +42,14 @@ func TestScope_New(t *testing.T) {
s, err := newScope(nil)
require.Error(err)
require.Nil(s)
assert.Contains(err.Error(), "new scope: child scope is missing its parent: invalid parameter")
assert.Contains(err.Error(), "iam.newScope: child scope is missing its parent: parameter violation: error #100")
})
t.Run("proj-scope-with-no-org", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewProject("")
require.Error(err)
require.Nil(s)
assert.Contains(err.Error(), "error creating new project: new scope: child scope is missing its parent: invalid parameter")
assert.Contains(err.Error(), "iam.NewProject: iam.newScope: child scope is missing its parent: parameter violation: error #100")
})
}
func TestScope_Create(t *testing.T) {
@@ -216,7 +216,7 @@ func TestScope_GlobalErrors(t *testing.T) {
s.PublicId = "global"
err := s.VetForWrite(context.Background(), nil, db.CreateOp)
require.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "global scope cannot be created"))
assert.True(t, strings.Contains(err.Error(), "iam.(Scope).VetForWrite: you cannot create a global scope: parameter violation: error #100"))
})
t.Run("check org parent at vet time", func(t *testing.T) {
// Org must have global parent
+5 -4
View File
@@ -2,7 +2,6 @@ package iam
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -32,9 +31,10 @@ var _ db.VetForWriter = (*User)(nil)
// WithName - to specify the user's friendly name and WithDescription - to
// specify a user description
func NewUser(scopeId string, opt ...Option) (*User, error) {
const op = "iam.NewUser"
opts := getOpts(opt...)
if scopeId == "" {
return nil, fmt.Errorf("new user: missing scope id %w", errors.ErrInvalidParameter)
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
}
u := &User{
User: &store.User{
@@ -63,11 +63,12 @@ func (u *User) Clone() interface{} {
// VetForWrite implements db.VetForWrite() interface and validates the user
// before it's written.
func (u *User) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(User).VetForWrite"
if u.PublicId == "" {
return fmt.Errorf("user vet for write: missing public id: %w", errors.ErrInvalidParameter)
return errors.New(errors.InvalidParameter, op, "missing public id")
}
if err := validateScopeForWrite(ctx, r, u, opType, opt...); err != nil {
return err
return errors.Wrap(err, op)
}
return nil
}
+3 -3
View File
@@ -55,7 +55,7 @@ func TestNewUser(t *testing.T) {
opt: []Option{WithName(id)},
},
wantErr: true,
wantErrMsg: "new user: missing scope id invalid parameter",
wantErrMsg: "iam.NewUser: missing scope id: parameter violation: error #100",
},
}
for _, tt := range tests {
@@ -123,7 +123,7 @@ func Test_UserCreate(t *testing.T) {
user.PublicId = id
err = w.Create(context.Background(), user)
require.Error(err)
assert.Equal("db.Create: vet for write failed: iam.validateScopeForWrite: scope is not found: search issue: error #1100", err.Error())
assert.Equal("db.Create: iam.(User).VetForWrite: iam.validateScopeForWrite: scope is not found: search issue: error #1100", err.Error())
})
}
@@ -168,7 +168,7 @@ func Test_UserUpdate(t *testing.T) {
ScopeId: proj.PublicId,
},
wantErr: true,
wantErrMsg: "db.Update: vet for write failed: iam.validateScopeForWrite: not allowed to change a resource's scope: parameter violation: error #100",
wantErrMsg: "db.Update: iam.(User).VetForWrite: iam.validateScopeForWrite: not allowed to change a resource's scope: parameter violation: error #100",
},
{
name: "proj-scope-id-not-in-mask",