You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/iam/role_test.go

2150 lines
64 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
)
func TestNewRole(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, proj := TestScopes(t, repo)
id := testId(t)
type args struct {
scopePublicId string
opt []Option
}
tests := []struct {
name string
args args
wantErr bool
wantErrMsg string
wantIsErr errors.Code
wantName string
wantDescription string
}{
{
name: "valid",
args: args{
scopePublicId: org.PublicId,
opt: []Option{WithName(id), WithDescription("description-" + id)},
},
wantErr: false,
wantName: id,
wantDescription: "description-" + id,
},
{
name: "valid-proj",
args: args{
scopePublicId: proj.PublicId,
opt: []Option{WithName(id), WithDescription("description-" + id)},
},
wantErr: false,
wantName: id,
wantDescription: "description-" + id,
},
{
name: "valid-with-no-options" + id,
args: args{
scopePublicId: org.PublicId,
},
wantErr: false,
},
{
name: "no-scope",
args: args{
opt: []Option{WithName(id)},
},
wantErr: true,
wantErrMsg: "iam.NewRole: missing scope id: parameter violation: error #100",
wantIsErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewRole(ctx, tt.args.scopePublicId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrMsg)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.wantName, got.Name)
assert.Equal(tt.wantDescription, got.Description)
assert.Empty(got.PublicId)
})
}
}
func TestRole_Actions(t *testing.T) {
assert := assert.New(t)
r := &Role{}
a := r.Actions()
assert.Equal(a[action.Create.String()], action.Create)
assert.Equal(a[action.Update.String()], action.Update)
assert.Equal(a[action.Read.String()], action.Read)
assert.Equal(a[action.Delete.String()], action.Delete)
assert.Equal(a[action.AddGrants.String()], action.AddGrants)
assert.Equal(a[action.RemoveGrants.String()], action.RemoveGrants)
assert.Equal(a[action.SetGrants.String()], action.SetGrants)
assert.Equal(a[action.AddPrincipals.String()], action.AddPrincipals)
assert.Equal(a[action.RemovePrincipals.String()], action.RemovePrincipals)
assert.Equal(a[action.SetPrincipals.String()], action.SetPrincipals)
}
func TestRole_ResourceType(t *testing.T) {
assert := assert.New(t)
r := &Role{}
ty := r.GetResourceType()
assert.Equal(ty, resource.Role)
}
func TestRole_GetScope(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, proj := TestScopes(t, repo)
t.Run("valid-org", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := db.New(conn)
role := TestRole(t, conn, org.PublicId)
scope, err := role.GetScope(context.Background(), w)
require.NoError(err)
assert.True(proto.Equal(org, scope))
})
t.Run("valid-proj", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := db.New(conn)
role := TestRole(t, conn, proj.PublicId)
scope, err := role.GetScope(context.Background(), w)
require.NoError(err)
assert.True(proto.Equal(proj, scope))
})
}
func Test_globalRole_Create(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
type args struct {
role *globalRole
}
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, proj := TestScopes(t, repo)
rw := db.New(conn)
tests := []struct {
name string
args args
wantErr bool
wantErrMsg string
}{
{
name: "can create valid role in global grants scope individual",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
return r
}(),
},
wantErr: false,
},
{
name: "can create valid role in global grants scope descendants",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
return r
}(),
},
wantErr: false,
},
{
name: "can create valid role in global grants scope children",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
},
}
return r
}(),
},
wantErr: false,
},
{
name: "cannot create role in org",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_global" violates foreign key constraint "iam_scope_global_fkey": integrity violation: error #1003`,
},
{
name: "cannot create role in project",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: proj.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_global" violates foreign key constraint "iam_scope_global_fkey": integrity violation: error #1003`,
},
{
name: "invalid grants scope",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: "invalid-scope",
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_global" violates foreign key constraint "iam_role_global_grant_scope_enm_fkey": integrity violation: error #1003`,
},
{
name: "duplicate name not allowed",
args: args{
role: func() *globalRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
// create a role then return the cloned role with different name
r := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: grpId,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
require.NoError(t, rw.Create(ctx, r))
newId, err := newRoleId(ctx)
require.NoError(t, err)
dupeName := r.Clone().(*globalRole)
dupeName.PublicId = newId
dupeName.GrantThisRoleScope = false
dupeName.GrantScope = globals.GrantScopeChildren
return dupeName
}(),
},
wantErr: true,
wantErrMsg: `db.Create: duplicate key value violates unique constraint "iam_role_global_name_scope_id_uq": unique constraint violation: integrity violation: error #1002`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := tt.args.role.Clone().(*globalRole)
err := rw.Create(ctx, r)
if tt.wantErr {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
return
}
require.NoError(t, err)
require.NotEmpty(t, tt.args.role.PublicId)
foundGrp := allocGlobalRole()
foundGrp.PublicId = tt.args.role.PublicId
err = rw.LookupByPublicId(ctx, &foundGrp)
require.NoError(t, err)
require.Empty(t, cmp.Diff(r, &foundGrp, protocmp.Transform()))
require.NotEmpty(t, foundGrp.GrantScopeUpdateTime)
require.NotEmpty(t, foundGrp.GrantThisRoleScopeUpdateTime)
require.NotZero(t, foundGrp.CreateTime.AsTime())
require.NotZero(t, foundGrp.UpdateTime.AsTime())
baseRole := &testBaseRole{PublicId: tt.args.role.PublicId}
err = rw.LookupByPublicId(ctx, baseRole)
require.NoError(t, err)
require.Equal(t, foundGrp.CreateTime.AsTime(), baseRole.CreateTime.AsTime())
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
})
}
}
func Test_globalRole_Update(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
type arg struct {
updateRole *globalRole
fieldMask []string
nullPath []string
opts []db.Option
}
tests := []struct {
name string
setupOriginal func(t *testing.T) *globalRole
createInput func(t *testing.T, original *globalRole) arg
wantErr bool
wantRowsUpdate int
wantErrMsg string
}{
{
name: "can update name and description",
setupOriginal: func(t *testing.T) *globalRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: roleId,
ScopeId: globals.GlobalPrefix,
Name: testId(t),
Description: testId(t),
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *globalRole) arg {
updated := original.Clone().(*globalRole)
updated.Name = testId(t)
updated.Description = testId(t)
return arg{
updateRole: updated,
fieldMask: []string{"name", "description"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update grant_scope",
setupOriginal: func(t *testing.T) *globalRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: roleId,
ScopeId: globals.GlobalPrefix,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *globalRole) arg {
updated := original.Clone().(*globalRole)
updated.GrantScope = globals.GrantScopeChildren
return arg{
updateRole: updated,
fieldMask: []string{"GrantScope"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can set name and description null",
setupOriginal: func(t *testing.T) *globalRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: roleId,
ScopeId: globals.GlobalPrefix,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *globalRole) arg {
updated := original.Clone().(*globalRole)
updated.Name = ""
updated.Description = ""
return arg{
updateRole: updated,
fieldMask: []string{},
nullPath: []string{"name", "description"},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update grant_this_role_scope",
setupOriginal: func(t *testing.T) *globalRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: roleId,
ScopeId: globals.GlobalPrefix,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *globalRole) arg {
updated := original.Clone().(*globalRole)
updated.GrantThisRoleScope = false
return arg{
updateRole: updated,
fieldMask: []string{"GrantThisRoleScope"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update version",
setupOriginal: func(t *testing.T) *globalRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: roleId,
ScopeId: globals.GlobalPrefix,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *globalRole) arg {
updated := original.Clone().(*globalRole)
// version will be overridden by trigger
updated.Version = 123456
return arg{
updateRole: updated,
fieldMask: []string{"version"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := tt.setupOriginal(t)
args := tt.createInput(t, original)
updatedRows, err := rw.Update(context.Background(), args.updateRole, args.fieldMask, args.nullPath, args.opts...)
if tt.wantErr {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
return
}
require.NoError(t, err)
require.NotEmpty(t, args.updateRole.PublicId)
if tt.wantRowsUpdate == 0 {
require.Zero(t, updatedRows)
return
}
require.Equal(t, tt.wantRowsUpdate, updatedRows)
foundGrp := allocGlobalRole()
foundGrp.PublicId = original.PublicId
err = rw.LookupByPublicId(ctx, &foundGrp)
require.NoError(t, err)
require.Empty(t, cmp.Diff(args.updateRole, &foundGrp, protocmp.Transform()))
baseRole := &testBaseRole{PublicId: original.PublicId}
err = rw.LookupByPublicId(ctx, baseRole)
require.NoError(t, err)
require.Equal(t, original.Version+1, foundGrp.Version)
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
// assert other update time as necessary
if original.GrantThisRoleScope != args.updateRole.GrantThisRoleScope {
require.Greater(t, foundGrp.GrantThisRoleScopeUpdateTime.AsTime(), original.GrantThisRoleScopeUpdateTime.AsTime())
}
if original.GrantScope != args.updateRole.GrantScope {
require.Greater(t, foundGrp.GrantScopeUpdateTime.AsTime(), original.GrantScopeUpdateTime.AsTime())
}
})
}
}
func Test_globalRole_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
tests := []struct {
name string
setupRole func(ctx context.Context, t *testing.T) *globalRole
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
setupRole: func(ctx context.Context, t *testing.T) *globalRole {
role := &globalRole{
GlobalRole: &store.GlobalRole{
ScopeId: globals.GlobalPrefix,
Name: "test-role",
Description: "description",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
id, err := newRoleId(ctx)
require.NoError(t, err)
role.PublicId = id
require.NoError(t, rw.Create(ctx, role))
require.NotEmpty(t, role.PublicId)
return role
},
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "role-does-not-exist",
setupRole: func(ctx context.Context, t *testing.T) *globalRole {
id, err := newRoleId(ctx)
require.NoError(t, err)
role := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: id,
ScopeId: globals.GlobalPrefix,
Name: "test-role",
Description: "description",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
return role
},
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
createdRole := tc.setupRole(context.Background(), t)
deletedRows, err := rw.Delete(context.Background(), &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: createdRole.PublicId,
},
})
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if tc.wantRowsDeleted == 0 {
require.Equal(t, tc.wantRowsDeleted, deletedRows)
return
}
require.Equal(t, tc.wantRowsDeleted, deletedRows)
foundRole := &globalRole{GlobalRole: &store.GlobalRole{PublicId: createdRole.PublicId}}
err = rw.LookupByPublicId(context.Background(), foundRole)
require.Error(t, err)
require.True(t, errors.IsNotFoundError(err))
// also check that base table was deleted
baseRole := &testBaseRole{PublicId: createdRole.PublicId}
err = rw.LookupByPublicId(context.Background(), baseRole)
require.Error(t, err)
require.True(t, errors.IsNotFoundError(err))
})
}
}
func Test_globalRole_Actions(t *testing.T) {
r := allocGlobalRole()
a := r.Actions()
assert.Equal(t, a[action.Create.String()], action.Create)
assert.Equal(t, a[action.Update.String()], action.Update)
assert.Equal(t, a[action.Read.String()], action.Read)
assert.Equal(t, a[action.Delete.String()], action.Delete)
assert.Equal(t, a[action.AddGrants.String()], action.AddGrants)
assert.Equal(t, a[action.RemoveGrants.String()], action.RemoveGrants)
assert.Equal(t, a[action.SetGrants.String()], action.SetGrants)
assert.Equal(t, a[action.AddPrincipals.String()], action.AddPrincipals)
assert.Equal(t, a[action.RemovePrincipals.String()], action.RemovePrincipals)
assert.Equal(t, a[action.SetPrincipals.String()], action.SetPrincipals)
}
func Test_globalRole_ResourceType(t *testing.T) {
t.Parallel()
role := allocGlobalRole()
result := role.GetResourceType()
assert.Equal(t, result, resource.Role)
}
func Test_globalRole_GetScope(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
ctx := context.Background()
rw := db.New(conn)
role := globalRole{
GlobalRole: &store.GlobalRole{
ScopeId: globals.GlobalPrefix,
Name: "test-role",
Description: "description",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
id, err := newRoleId(ctx)
require.NoError(t, err)
role.PublicId = id
require.NoError(t, rw.Create(ctx, role))
require.NotEmpty(t, role.PublicId)
scope, err := role.GetScope(context.Background(), rw)
require.NoError(t, err)
require.Equal(t, scope.GetPublicId(), role.GetScopeId())
}
func Test_globalRole_Clone(t *testing.T) {
t.Parallel()
role1 := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: "r_123456",
ScopeId: "global",
Name: "test-role",
Description: "description",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
GrantThisRoleScopeUpdateTime: timestamp.New(time.Date(2025, 2, 12, 3, 15, 15, 30, time.UTC)),
GrantScopeUpdateTime: timestamp.New(time.Date(2025, 3, 12, 3, 15, 15, 30, time.UTC)),
CreateTime: timestamp.New(time.Date(2025, 4, 12, 3, 15, 15, 30, time.UTC)),
UpdateTime: timestamp.New(time.Date(2025, 5, 12, 3, 15, 15, 30, time.UTC)),
Version: 1,
},
GrantScopes: []*RoleGrantScope{
{
CreateTime: timestamp.New(time.Date(2025, 3, 12, 3, 15, 15, 30, time.UTC)),
RoleId: "r_123456",
ScopeIdOrSpecial: "children",
},
{
CreateTime: timestamp.New(time.Date(2025, 1, 14, 3, 15, 15, 30, time.UTC)),
RoleId: "r_123456",
ScopeIdOrSpecial: "p_123456",
},
},
}
role2 := &globalRole{
GlobalRole: &store.GlobalRole{
PublicId: "r_abcdef",
ScopeId: "o_123456",
Name: "another-role",
Description: "different description",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
GrantThisRoleScopeUpdateTime: timestamp.New(time.Date(2024, 2, 12, 3, 15, 15, 30, time.UTC)),
GrantScopeUpdateTime: timestamp.New(time.Date(2024, 3, 12, 3, 15, 15, 30, time.UTC)),
CreateTime: timestamp.New(time.Date(2024, 4, 12, 3, 15, 15, 30, time.UTC)),
UpdateTime: timestamp.New(time.Date(2024, 5, 12, 3, 15, 15, 30, time.UTC)),
Version: 1,
},
GrantScopes: []*RoleGrantScope{
{
CreateTime: timestamp.New(time.Date(2024, 3, 12, 3, 15, 15, 30, time.UTC)),
RoleId: "r_abcdef",
ScopeIdOrSpecial: "children",
},
{
CreateTime: timestamp.New(time.Date(2024, 1, 14, 3, 15, 15, 30, time.UTC)),
RoleId: "r_abcdef",
ScopeIdOrSpecial: "p_abcdef",
},
},
}
require.NotEqual(t, role1, role2)
t.Run("valid", func(t *testing.T) {
clone := role1.Clone()
assert.True(t, proto.Equal(clone.(*globalRole).GlobalRole, role1.GlobalRole))
assert.Equal(t, clone.(*globalRole).GrantScopes, role1.GrantScopes)
})
t.Run("not-equal", func(t *testing.T) {
role1Clone := role1.Clone()
assert.True(t, !proto.Equal(role1Clone.(*globalRole).GlobalRole, role2.GlobalRole))
assert.NotEqual(t, role1Clone.(*globalRole).GrantScopes, role2.GrantScopes)
})
}
func Test_globalRole_SetTableName(t *testing.T) {
defaultTableName := defaultGlobalRoleTableName
tests := []struct {
name string
initialName string
setNameTo string
want string
}{
{
name: "new-name",
initialName: "",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
initialName: "initial",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
def := allocGlobalRole()
require.Equal(t, defaultTableName, def.TableName())
s := &globalRole{
GlobalRole: &store.GlobalRole{},
tableName: tt.initialName,
}
s.SetTableName(tt.setNameTo)
assert.Equal(t, tt.want, s.TableName())
})
}
}
func Test_orgRole_Create(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
type args struct {
role *orgRole
}
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, proj := TestScopes(t, repo)
org2 := TestOrg(t, repo)
rw := db.New(conn)
tests := []struct {
name string
args args
wantErr bool
wantErrMsg string
}{
{
name: "can create valid role in org grants scope individual",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
return r
}(),
},
wantErr: false,
},
{
name: "can create valid role in org grants scope children",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
},
}
return r
}(),
},
wantErr: false,
},
{
name: "duplicate name different scope allowed",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
// create a role then return the cloned role with different name
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
},
}
require.NoError(t, rw.Create(ctx, r))
newId, err := newRoleId(ctx)
require.NoError(t, err)
dupeName := r.Clone().(*orgRole)
dupeName.PublicId = newId
dupeName.ScopeId = org2.PublicId
dupeName.GrantThisRoleScope = false
dupeName.GrantScope = globals.GrantScopeChildren
return dupeName
}(),
},
wantErr: false,
},
{
name: "cannot create valid role in org grants scope descendants",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_org" violates foreign key constraint "iam_role_org_grant_scope_enm_fkey": integrity violation: error #1003`,
},
{
name: "cannot create role in org with descendants grants",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_org" violates foreign key constraint "iam_role_org_grant_scope_enm_fkey": integrity violation: error #1003`,
},
{
name: "cannot create role in global",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_org" violates foreign key constraint "iam_scope_org_fkey": integrity violation: error #1003`,
},
{
name: "cannot create role in project",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: proj.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeDescendants,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_org" violates foreign key constraint "iam_scope_org_fkey": integrity violation: error #1003`,
},
{
name: "invalid grants scope",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: "invalid-scope",
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_org" violates foreign key constraint "iam_role_org_grant_scope_enm_fkey": integrity violation: error #1003`,
},
{
name: "duplicate name not allowed",
args: args{
role: func() *orgRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
// create a role then return the cloned role with different name
r := &orgRole{
OrgRole: &store.OrgRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
},
}
require.NoError(t, rw.Create(ctx, r))
newId, err := newRoleId(ctx)
require.NoError(t, err)
dupeName := r.Clone().(*orgRole)
dupeName.PublicId = newId
dupeName.GrantThisRoleScope = false
dupeName.GrantScope = globals.GrantScopeChildren
return dupeName
}(),
},
wantErr: true,
wantErrMsg: `db.Create: duplicate key value violates unique constraint "iam_role_org_name_scope_id_uq": unique constraint violation: integrity violation: error #1002`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := tt.args.role.Clone().(*orgRole)
err := rw.Create(ctx, r)
if tt.wantErr {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
return
}
require.NoError(t, err)
require.NotEmpty(t, tt.args.role.PublicId)
foundGrp := allocOrgRole()
foundGrp.PublicId = tt.args.role.PublicId
err = rw.LookupByPublicId(ctx, &foundGrp)
require.NoError(t, err)
require.Empty(t, cmp.Diff(r, &foundGrp, protocmp.Transform()))
require.NotEmpty(t, foundGrp.GrantScopeUpdateTime)
require.NotEmpty(t, foundGrp.GrantThisRoleScopeUpdateTime)
require.NotZero(t, foundGrp.CreateTime.AsTime())
require.NotZero(t, foundGrp.UpdateTime.AsTime())
baseRole := testBaseRole{PublicId: tt.args.role.PublicId}
err = rw.LookupByPublicId(ctx, &baseRole)
require.NoError(t, err)
require.Equal(t, foundGrp.CreateTime.AsTime(), baseRole.CreateTime.AsTime())
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
})
}
}
func Test_orgRole_Update(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org := TestOrg(t, repo)
type arg struct {
updateRole *orgRole
fieldMask []string
nullPath []string
opts []db.Option
}
tests := []struct {
name string
setupOriginal func(t *testing.T) *orgRole
createInput func(t *testing.T, original *orgRole) arg
wantErr bool
wantRowsUpdate int
wantErrMsg string
}{
{
name: "can update name and description",
setupOriginal: func(t *testing.T) *orgRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &orgRole{
OrgRole: &store.OrgRole{
PublicId: roleId,
ScopeId: org.PublicId,
Name: testId(t),
Description: testId(t),
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *orgRole) arg {
updated := original.Clone().(*orgRole)
updated.Name = testId(t)
updated.Description = testId(t)
return arg{
updateRole: updated,
fieldMask: []string{"name", "description"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update grant_scope",
setupOriginal: func(t *testing.T) *orgRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &orgRole{
OrgRole: &store.OrgRole{
PublicId: roleId,
ScopeId: org.PublicId,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *orgRole) arg {
updated := original.Clone().(*orgRole)
updated.GrantScope = globals.GrantScopeChildren
return arg{
updateRole: updated,
fieldMask: []string{"GrantScope"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can set name and description null",
setupOriginal: func(t *testing.T) *orgRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &orgRole{
OrgRole: &store.OrgRole{
PublicId: roleId,
ScopeId: org.PublicId,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *orgRole) arg {
updated := original.Clone().(*orgRole)
updated.Name = ""
updated.Description = ""
return arg{
updateRole: updated,
fieldMask: []string{},
nullPath: []string{"name", "description"},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update grant_this_role_scope",
setupOriginal: func(t *testing.T) *orgRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &orgRole{
OrgRole: &store.OrgRole{
PublicId: roleId,
ScopeId: org.PublicId,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *orgRole) arg {
updated := original.Clone().(*orgRole)
updated.GrantThisRoleScope = false
return arg{
updateRole: updated,
fieldMask: []string{"GrantThisRoleScope"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update version",
setupOriginal: func(t *testing.T) *orgRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &orgRole{
OrgRole: &store.OrgRole{
PublicId: roleId,
ScopeId: org.PublicId,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeIndividual,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *orgRole) arg {
updated := original.Clone().(*orgRole)
updated.Version = uint32(15)
return arg{
updateRole: updated,
fieldMask: []string{"version"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := tt.setupOriginal(t)
args := tt.createInput(t, original)
updatedRows, err := rw.Update(context.Background(), args.updateRole, args.fieldMask, args.nullPath, args.opts...)
if tt.wantErr {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
return
}
require.NoError(t, err)
require.NotEmpty(t, args.updateRole.PublicId)
if tt.wantRowsUpdate == 0 {
require.Zero(t, updatedRows)
return
}
require.Equal(t, tt.wantRowsUpdate, updatedRows)
foundGrp := allocOrgRole()
foundGrp.PublicId = original.PublicId
err = rw.LookupByPublicId(ctx, &foundGrp)
require.NoError(t, err)
require.Empty(t, cmp.Diff(args.updateRole, &foundGrp, protocmp.Transform()))
// also check that base table was deleted
baseRole := &testBaseRole{PublicId: original.PublicId}
err = rw.LookupByPublicId(ctx, baseRole)
require.NoError(t, err)
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
require.Equal(t, original.Version+1, foundGrp.Version)
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
// assert other update time as necessary
if original.GrantThisRoleScope != args.updateRole.GrantThisRoleScope {
require.Greater(t, foundGrp.GrantThisRoleScopeUpdateTime.AsTime(), original.GrantThisRoleScopeUpdateTime.AsTime())
}
if original.GrantScope != args.updateRole.GrantScope {
require.Greater(t, foundGrp.GrantScopeUpdateTime.AsTime(), original.GrantScopeUpdateTime.AsTime())
}
})
}
}
func Test_orgRole_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org := TestOrg(t, repo)
tests := []struct {
name string
setupRole func(ctx context.Context, t *testing.T) *orgRole
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
setupRole: func(ctx context.Context, t *testing.T) *orgRole {
role := &orgRole{
OrgRole: &store.OrgRole{
ScopeId: org.PublicId,
Name: "test-role",
Description: "description",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
id, err := newRoleId(ctx)
require.NoError(t, err)
role.PublicId = id
require.NoError(t, rw.Create(ctx, role))
require.NotEmpty(t, role.PublicId)
return role
},
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "role-does-not-exist",
setupRole: func(ctx context.Context, t *testing.T) *orgRole {
id, err := newRoleId(ctx)
require.NoError(t, err)
role := &orgRole{
OrgRole: &store.OrgRole{
PublicId: id,
ScopeId: org.PublicId,
Name: "test-role",
Description: "description",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
return role
},
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
createdRole := tc.setupRole(context.Background(), t)
deletedRows, err := rw.Delete(context.Background(), &orgRole{
OrgRole: &store.OrgRole{
PublicId: createdRole.PublicId,
},
})
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if tc.wantRowsDeleted == 0 {
require.Equal(t, tc.wantRowsDeleted, deletedRows)
return
}
require.Equal(t, tc.wantRowsDeleted, deletedRows)
foundRole := &orgRole{OrgRole: &store.OrgRole{PublicId: createdRole.PublicId}}
err = rw.LookupByPublicId(context.Background(), foundRole)
require.Error(t, err)
require.True(t, errors.IsNotFoundError(err))
// also check that base table was deleted
baseRole := &testBaseRole{PublicId: createdRole.PublicId}
err = rw.LookupByPublicId(context.Background(), baseRole)
require.Error(t, err)
require.True(t, errors.IsNotFoundError(err))
})
}
}
func Test_orgRole_Actions(t *testing.T) {
r := &orgRole{}
a := r.Actions()
assert.Equal(t, a[action.Create.String()], action.Create)
assert.Equal(t, a[action.Update.String()], action.Update)
assert.Equal(t, a[action.Read.String()], action.Read)
assert.Equal(t, a[action.Delete.String()], action.Delete)
assert.Equal(t, a[action.AddGrants.String()], action.AddGrants)
assert.Equal(t, a[action.RemoveGrants.String()], action.RemoveGrants)
assert.Equal(t, a[action.SetGrants.String()], action.SetGrants)
assert.Equal(t, a[action.AddPrincipals.String()], action.AddPrincipals)
assert.Equal(t, a[action.RemovePrincipals.String()], action.RemovePrincipals)
assert.Equal(t, a[action.SetPrincipals.String()], action.SetPrincipals)
}
func Test_orgRole_ResourceType(t *testing.T) {
t.Parallel()
role := orgRole{}
result := role.GetResourceType()
assert.Equal(t, result, resource.Role)
}
func Test_orgRole_GetScope(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
ctx := context.Background()
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org := TestOrg(t, repo)
role := orgRole{
OrgRole: &store.OrgRole{
ScopeId: org.PublicId,
Name: "test-role",
Description: "description",
GrantThisRoleScope: false,
GrantScope: globals.GrantScopeIndividual,
},
}
id, err := newRoleId(ctx)
require.NoError(t, err)
role.PublicId = id
require.NoError(t, rw.Create(ctx, role))
require.NotEmpty(t, role.PublicId)
scope, err := role.GetScope(context.Background(), rw)
require.NoError(t, err)
require.True(t, proto.Equal(org, scope))
}
func Test_orgRole_Clone(t *testing.T) {
t.Parallel()
role1 := &orgRole{
OrgRole: &store.OrgRole{
PublicId: "r_123456",
ScopeId: "o_123456",
Name: "test-role",
Description: "description",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
GrantThisRoleScopeUpdateTime: timestamp.New(time.Date(2025, 2, 12, 3, 15, 15, 30, time.UTC)),
GrantScopeUpdateTime: timestamp.New(time.Date(2025, 3, 12, 3, 15, 15, 30, time.UTC)),
CreateTime: timestamp.New(time.Date(2025, 4, 12, 3, 15, 15, 30, time.UTC)),
UpdateTime: timestamp.New(time.Date(2025, 5, 12, 3, 15, 15, 30, time.UTC)),
Version: 1,
},
GrantScopes: []*RoleGrantScope{
{
CreateTime: timestamp.New(time.Date(2025, 3, 12, 3, 15, 15, 30, time.UTC)),
RoleId: "r_123456",
ScopeIdOrSpecial: "children",
},
{
CreateTime: timestamp.New(time.Date(2025, 1, 14, 3, 15, 15, 30, time.UTC)),
RoleId: "r_123456",
ScopeIdOrSpecial: "p_123456",
},
},
}
role2 := &orgRole{
OrgRole: &store.OrgRole{
PublicId: "r_abcdef",
ScopeId: "o_abcdef",
Name: "another-role",
Description: "different description",
GrantThisRoleScope: true,
GrantScope: globals.GrantScopeChildren,
GrantThisRoleScopeUpdateTime: timestamp.New(time.Date(2024, 2, 12, 3, 15, 15, 30, time.UTC)),
GrantScopeUpdateTime: timestamp.New(time.Date(2024, 3, 12, 3, 15, 15, 30, time.UTC)),
CreateTime: timestamp.New(time.Date(2024, 4, 12, 3, 15, 15, 30, time.UTC)),
UpdateTime: timestamp.New(time.Date(2024, 5, 12, 3, 15, 15, 30, time.UTC)),
Version: 1,
},
GrantScopes: []*RoleGrantScope{
{
CreateTime: timestamp.New(time.Date(2024, 3, 12, 3, 15, 15, 30, time.UTC)),
RoleId: "r_abcdef",
ScopeIdOrSpecial: "children",
},
{
CreateTime: timestamp.New(time.Date(2024, 1, 14, 3, 15, 15, 30, time.UTC)),
RoleId: "r_abcdef",
ScopeIdOrSpecial: "p_abcdef",
},
},
}
require.NotEqual(t, role1, role2)
t.Run("valid", func(t *testing.T) {
clone := role1.Clone()
assert.True(t, proto.Equal(clone.(*orgRole).OrgRole, role1.OrgRole))
assert.Equal(t, clone.(*orgRole).GrantScopes, role1.GrantScopes)
})
t.Run("not-equal", func(t *testing.T) {
role1Clone := role1.Clone()
assert.True(t, !proto.Equal(role1Clone.(*orgRole).OrgRole, role2.OrgRole))
assert.NotEqual(t, role1Clone.(*orgRole).GrantScopes, role2.GrantScopes)
})
}
func Test_orgRole_SetTableName(t *testing.T) {
defaultTableName := defaultOrgRoleTableName
tests := []struct {
name string
initialName string
setNameTo string
want string
}{
{
name: "new-name",
initialName: "",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
initialName: "initial",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
def := orgRole{}
require.Equal(t, defaultTableName, def.TableName())
s := &orgRole{
OrgRole: &store.OrgRole{},
tableName: tt.initialName,
}
s.SetTableName(tt.setNameTo)
assert.Equal(t, tt.want, s.TableName())
})
}
}
func Test_projectRole_Create(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
type args struct {
role *projectRole
}
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, proj := TestScopes(t, repo)
proj2 := TestProject(t, repo, org.PublicId)
rw := db.New(conn)
tests := []struct {
name string
args args
wantErr bool
wantErrMsg string
}{
{
name: "can create valid role in project",
args: args{
role: func() *projectRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: grpId,
ScopeId: proj.PublicId,
GrantThisRoleScope: false,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
},
}
return r
}(),
},
wantErr: false,
},
{
name: "duplicate name different scope allowed",
args: args{
role: func() *projectRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
// create a role then return the cloned role with different name
r := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: grpId,
ScopeId: proj.PublicId,
GrantThisRoleScope: true,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
},
}
require.NoError(t, rw.Create(ctx, r))
newId, err := newRoleId(ctx)
require.NoError(t, err)
dupeName := r.Clone().(*projectRole)
dupeName.PublicId = newId
dupeName.ScopeId = proj2.PublicId
return dupeName
}(),
},
wantErr: false,
},
{
name: "cannot create role in global",
args: args{
role: func() *projectRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: grpId,
GrantThisRoleScope: true,
ScopeId: globals.GlobalPrefix,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_project" violates foreign key constraint "iam_scope_project_fkey": integrity violation: error #1003`,
},
{
name: "cannot create role in org",
args: args{
role: func() *projectRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
r := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: grpId,
ScopeId: org.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
},
}
return r
}(),
},
wantErr: true,
wantErrMsg: `db.Create: insert or update on table "iam_role_project" violates foreign key constraint "iam_scope_project_fkey": integrity violation: error #1003`,
},
{
name: "duplicate name same scope not allowed",
args: args{
role: func() *projectRole {
randomUuid := testId(t)
grpId, err := newRoleId(ctx)
require.NoError(t, err)
// create a role then return the cloned role with different name
r := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: grpId,
GrantThisRoleScope: true,
ScopeId: proj.PublicId,
Name: "name" + randomUuid,
Description: "desc" + randomUuid,
},
}
require.NoError(t, rw.Create(ctx, r))
newId, err := newRoleId(ctx)
require.NoError(t, err)
dupeName := r.Clone().(*projectRole)
dupeName.PublicId = newId
return dupeName
}(),
},
wantErr: true,
wantErrMsg: `db.Create: duplicate key value violates unique constraint "iam_role_project_name_scope_id_uq": unique constraint violation: integrity violation: error #1002`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := tt.args.role.Clone().(*projectRole)
err := rw.Create(ctx, r)
if tt.wantErr {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
return
}
require.NoError(t, err)
require.NotEmpty(t, tt.args.role.PublicId)
foundGrp := allocProjectRole()
foundGrp.PublicId = tt.args.role.PublicId
err = rw.LookupByPublicId(ctx, &foundGrp)
require.NoError(t, err)
require.Empty(t, cmp.Diff(r, &foundGrp, protocmp.Transform()))
require.NotZero(t, foundGrp.CreateTime.AsTime())
require.NotZero(t, foundGrp.UpdateTime.AsTime())
baseRole := &testBaseRole{PublicId: tt.args.role.PublicId}
err = rw.LookupByPublicId(ctx, baseRole)
require.NoError(t, err)
require.Equal(t, foundGrp.CreateTime.AsTime(), baseRole.CreateTime.AsTime())
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
})
}
}
func Test_projectRole_Update(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
_, proj := TestScopes(t, repo)
type arg struct {
updateRole *projectRole
fieldMask []string
nullPath []string
opts []db.Option
}
tests := []struct {
name string
setupOriginal func(t *testing.T) *projectRole
createInput func(t *testing.T, original *projectRole) arg
wantErr bool
wantRowsUpdate int
wantErrMsg string
}{
{
name: "can update name and description",
setupOriginal: func(t *testing.T) *projectRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: roleId,
ScopeId: proj.PublicId,
Name: testId(t),
Description: testId(t),
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *projectRole) arg {
updated := original.Clone().(*projectRole)
updated.Name = testId(t)
updated.Description = testId(t)
return arg{
updateRole: updated,
fieldMask: []string{"name", "description"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can set name and description null",
setupOriginal: func(t *testing.T) *projectRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: roleId,
ScopeId: proj.PublicId,
Name: testId(t),
Description: "desc",
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *projectRole) arg {
updated := original.Clone().(*projectRole)
updated.Name = ""
updated.Description = ""
return arg{
updateRole: updated,
fieldMask: []string{},
nullPath: []string{"name", "description"},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update grant_this_role_scope",
setupOriginal: func(t *testing.T) *projectRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: roleId,
ScopeId: proj.PublicId,
Name: testId(t),
Description: "desc",
GrantThisRoleScope: true,
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *projectRole) arg {
updated := original.Clone().(*projectRole)
updated.GrantThisRoleScope = false
return arg{
updateRole: updated,
fieldMask: []string{"GrantThisRoleScope"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
{
name: "can update version",
setupOriginal: func(t *testing.T) *projectRole {
roleId, err := newRoleId(ctx)
require.NoError(t, err)
original := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: roleId,
ScopeId: proj.PublicId,
Name: testId(t),
Description: "desc",
},
}
require.NoError(t, rw.Create(ctx, original))
return original
},
createInput: func(t *testing.T, original *projectRole) arg {
updated := original.Clone().(*projectRole)
updated.Version = uint32(15)
return arg{
updateRole: updated,
fieldMask: []string{"version"},
nullPath: []string{},
opts: []db.Option{},
}
},
wantRowsUpdate: 1,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := tt.setupOriginal(t)
args := tt.createInput(t, original)
updatedRows, err := rw.Update(context.Background(), args.updateRole, args.fieldMask, args.nullPath, args.opts...)
if tt.wantErr {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
return
}
require.NoError(t, err)
require.NotEmpty(t, args.updateRole.PublicId)
if tt.wantRowsUpdate == 0 {
require.Zero(t, updatedRows)
return
}
require.Equal(t, tt.wantRowsUpdate, updatedRows)
foundGrp := allocProjectRole()
foundGrp.PublicId = original.PublicId
err = rw.LookupByPublicId(ctx, &foundGrp)
require.NoError(t, err)
require.Empty(t, cmp.Diff(args.updateRole, &foundGrp, protocmp.Transform()))
baseRole := &testBaseRole{PublicId: original.PublicId}
err = rw.LookupByPublicId(ctx, baseRole)
require.NoError(t, err)
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
require.Equal(t, original.Version+1, foundGrp.Version)
require.Equal(t, foundGrp.UpdateTime.AsTime(), baseRole.UpdateTime.AsTime())
})
}
}
func Test_projectRole_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
_, proj := TestScopes(t, repo)
tests := []struct {
name string
setupRole func(ctx context.Context, t *testing.T) *projectRole
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
setupRole: func(ctx context.Context, t *testing.T) *projectRole {
role := &projectRole{
ProjectRole: &store.ProjectRole{
ScopeId: proj.PublicId,
Name: "test-role",
Description: "description",
},
}
id, err := newRoleId(ctx)
require.NoError(t, err)
role.PublicId = id
require.NoError(t, rw.Create(ctx, role))
require.NotEmpty(t, role.PublicId)
return role
},
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "role-does-not-exist",
setupRole: func(ctx context.Context, t *testing.T) *projectRole {
id, err := newRoleId(ctx)
require.NoError(t, err)
role := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: id,
ScopeId: proj.PublicId,
Name: "test-role",
Description: "description",
},
}
return role
},
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
createdRole := tc.setupRole(context.Background(), t)
deletedRows, err := rw.Delete(context.Background(), &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: createdRole.PublicId,
},
})
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if tc.wantRowsDeleted == 0 {
require.Equal(t, tc.wantRowsDeleted, deletedRows)
return
}
require.Equal(t, tc.wantRowsDeleted, deletedRows)
foundRole := &projectRole{ProjectRole: &store.ProjectRole{PublicId: createdRole.PublicId}}
err = rw.LookupByPublicId(context.Background(), foundRole)
require.Error(t, err)
require.True(t, errors.IsNotFoundError(err))
// also check that base table was deleted
baseRole := &testBaseRole{PublicId: createdRole.PublicId}
err = rw.LookupByPublicId(context.Background(), baseRole)
require.Error(t, err)
require.True(t, errors.IsNotFoundError(err))
})
}
}
func Test_projectRole_Actions(t *testing.T) {
r := &projectRole{}
a := r.Actions()
assert.Equal(t, a[action.Create.String()], action.Create)
assert.Equal(t, a[action.Update.String()], action.Update)
assert.Equal(t, a[action.Read.String()], action.Read)
assert.Equal(t, a[action.Delete.String()], action.Delete)
assert.Equal(t, a[action.AddGrants.String()], action.AddGrants)
assert.Equal(t, a[action.RemoveGrants.String()], action.RemoveGrants)
assert.Equal(t, a[action.SetGrants.String()], action.SetGrants)
assert.Equal(t, a[action.AddPrincipals.String()], action.AddPrincipals)
assert.Equal(t, a[action.RemovePrincipals.String()], action.RemovePrincipals)
assert.Equal(t, a[action.SetPrincipals.String()], action.SetPrincipals)
}
func Test_projectRole_ResourceType(t *testing.T) {
t.Parallel()
role := projectRole{}
result := role.GetResourceType()
assert.Equal(t, result, resource.Role)
}
func Test_projectRole_GetScope(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
ctx := context.Background()
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
_, proj := TestScopes(t, repo)
role := projectRole{
ProjectRole: &store.ProjectRole{
ScopeId: proj.PublicId,
Name: "test-role",
Description: "description",
},
}
id, err := newRoleId(ctx)
require.NoError(t, err)
role.PublicId = id
require.NoError(t, rw.Create(ctx, role))
require.NotEmpty(t, role.PublicId)
scope, err := role.GetScope(context.Background(), rw)
require.NoError(t, err)
require.True(t, proto.Equal(proj, scope))
}
func Test_projectRole_Clone(t *testing.T) {
t.Parallel()
role1 := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: "r_123456",
ScopeId: "p_123456",
Name: "test-role",
Description: "description",
CreateTime: timestamp.New(time.Date(2025, 4, 12, 3, 15, 15, 30, time.UTC)),
UpdateTime: timestamp.New(time.Date(2025, 5, 12, 3, 15, 15, 30, time.UTC)),
Version: 1,
},
GrantScopes: []*RoleGrantScope{
{
CreateTime: timestamp.New(time.Date(2025, 3, 12, 3, 15, 15, 30, time.UTC)),
RoleId: "r_123456",
ScopeIdOrSpecial: "this",
},
},
}
role2 := &projectRole{
ProjectRole: &store.ProjectRole{
PublicId: "r_abcdef",
ScopeId: "o_123456",
Name: "another-role",
Description: "different description",
CreateTime: timestamp.New(time.Date(2024, 4, 12, 3, 15, 15, 30, time.UTC)),
UpdateTime: timestamp.New(time.Date(2024, 5, 12, 3, 15, 15, 30, time.UTC)),
Version: 1,
},
GrantScopes: []*RoleGrantScope{
{
CreateTime: timestamp.New(time.Date(2024, 3, 12, 3, 15, 15, 30, time.UTC)),
RoleId: "r_abcdef",
ScopeIdOrSpecial: "children",
},
{
CreateTime: timestamp.New(time.Date(2024, 1, 14, 3, 15, 15, 30, time.UTC)),
RoleId: "r_abcdef",
ScopeIdOrSpecial: "p_abcdef",
},
},
}
require.NotEqual(t, role1, role2)
t.Run("valid", func(t *testing.T) {
clone := role1.Clone()
assert.True(t, proto.Equal(clone.(*projectRole).ProjectRole, role1.ProjectRole))
assert.Equal(t, clone.(*projectRole).GrantScopes, role1.GrantScopes)
})
t.Run("not-equal", func(t *testing.T) {
role1Clone := role1.Clone()
assert.True(t, !proto.Equal(role1Clone.(*projectRole).ProjectRole, role2.ProjectRole))
assert.NotEqual(t, role1Clone.(*projectRole).GrantScopes, role2.GrantScopes)
})
}
func Test_projectRole_SetTableName(t *testing.T) {
defaultTableName := defaultProjectRoleTableName
tests := []struct {
name string
initialName string
setNameTo string
want string
}{
{
name: "new-name",
initialName: "",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
initialName: "initial",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
def := projectRole{}
require.Equal(t, defaultTableName, def.TableName())
s := &projectRole{
ProjectRole: &store.ProjectRole{},
tableName: tt.initialName,
}
s.SetTableName(tt.setNameTo)
assert.Equal(t, tt.want, s.TableName())
})
}
}
// testBaseRole is used to interact with `iam_role` table for testing purposes
type testBaseRole struct {
PublicId string `gorm:"primary_key"`
ScopeId string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
}
func (t *testBaseRole) GetPublicId() string {
if t == nil {
return ""
}
return t.PublicId
}
func (t *testBaseRole) TableName() string {
return "iam_role"
}