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/repository_role_grant_test.go

1935 lines
62 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepository_AddRoleGrants(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)
role := TestRole(t, conn, proj.PublicId)
createGrantsFn := func() []string {
results := []string{}
for i := 0; i < 5; i++ {
g := fmt.Sprintf("ids=hc_%d;actions=*", i)
results = append(results, g)
}
return results
}
type args struct {
roleId string
roleVersion uint32
grants []string
opt []Option
}
tests := []struct {
name string
args args
wantErr bool
wantErrIs error
}{
{
name: "valid",
args: args{
roleId: role.PublicId,
roleVersion: 1,
grants: createGrantsFn(),
},
wantErr: false,
},
{
name: "no-grants",
args: args{
roleId: role.PublicId,
roleVersion: 2,
grants: nil,
},
wantErr: true,
},
{
name: "bad-version",
args: args{
roleId: role.PublicId,
roleVersion: 1000,
grants: createGrantsFn(),
},
wantErr: true,
},
{
name: "zero-version",
args: args{
roleId: role.PublicId,
roleVersion: 0,
grants: createGrantsFn(),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() any { rg := allocRoleGrant(); return &rg }(), "1=1")
got, err := repo.AddRoleGrants(context.Background(), tt.args.roleId, tt.args.roleVersion, tt.args.grants, tt.args.opt...)
if tt.wantErr {
require.Error(err)
if tt.wantErrIs != nil {
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error())
}
return
}
require.NoError(err)
gotRoleGrant := map[string]*RoleGrant{}
for _, r := range got {
gotRoleGrant[r.CanonicalGrant] = r
}
err = db.TestVerifyOplog(t, rw, role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.NoError(err)
foundRoleGrants, err := repo.ListRoleGrants(context.Background(), role.PublicId)
require.NoError(err)
// Create a map of grants to check against
grantSet := make(map[string]bool, len(tt.args.grants))
for _, grant := range tt.args.grants {
grantSet[grant] = true
}
for _, r := range foundRoleGrants {
roleGrant := gotRoleGrant[r.CanonicalGrant]
assert.NotEmpty(roleGrant)
assert.Equal(roleGrant.GetRoleId(), r.GetRoleId())
assert.NotEmpty(grantSet[roleGrant.CanonicalGrant])
delete(grantSet, roleGrant.CanonicalGrant)
}
assert.Empty(grantSet)
})
}
}
func TestRepository_ListRoleGrants(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper, WithLimit(testLimit))
org, proj := TestScopes(t, repo)
type args struct {
withRoleId string
opt []Option
}
tests := []struct {
name string
createCnt int
createGrantScopeId string
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.defaultLimit + 2,
createGrantScopeId: org.PublicId,
args: args{
opt: []Option{WithLimit(-1)},
},
wantCnt: repo.defaultLimit + 2,
wantErr: false,
},
{
name: "no-limit-proj-group",
createCnt: repo.defaultLimit + 2,
createGrantScopeId: proj.PublicId,
args: args{
opt: []Option{WithLimit(-1)},
},
wantCnt: repo.defaultLimit + 2,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.defaultLimit + 2,
createGrantScopeId: org.PublicId,
wantCnt: repo.defaultLimit,
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.defaultLimit + 2,
createGrantScopeId: org.PublicId,
args: args{
opt: []Option{WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-role-id",
createCnt: 2,
createGrantScopeId: org.PublicId,
args: args{
withRoleId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() any { i := allocGlobalRole(); return &i }(), "1=1")
db.TestDeleteWhere(t, conn, func() any { i := allocOrgRole(); return &i }(), "1=1")
db.TestDeleteWhere(t, conn, func() any { i := allocProjectRole(); return &i }(), "1=1")
role := TestRole(t, conn, tt.createGrantScopeId)
roleGrants := make([]string, 0, tt.createCnt)
for i := 0; i < tt.createCnt; i++ {
roleGrants = append(roleGrants, fmt.Sprintf("ids=h_%d;actions=*", i))
}
testRoles, err := repo.AddRoleGrants(context.Background(), role.PublicId, role.Version, roleGrants, tt.args.opt...)
require.NoError(err)
assert.Equal(tt.createCnt, len(testRoles))
var roleId string
switch {
case tt.args.withRoleId != "":
roleId = tt.args.withRoleId
default:
roleId = role.PublicId
}
got, err := repo.ListRoleGrants(context.Background(), roleId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
func TestRepository_DeleteRoleGrants(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, _ := TestScopes(t, repo)
type args struct {
role *Role
roleIdOverride *string
roleVersionOverride *uint32
grantStringsOverride []string
createCnt int
deleteCnt int
opt []Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsErr errors.Code
}{
{
name: "valid",
args: args{
role: TestRole(t, conn, org.PublicId),
createCnt: 5,
deleteCnt: 5,
},
wantRowsDeleted: 5,
wantErr: false,
},
{
name: "valid-keeping-some",
args: args{
role: TestRole(t, conn, org.PublicId),
createCnt: 5,
deleteCnt: 3,
},
wantRowsDeleted: 3,
wantErr: false,
},
{
name: "no-deletes",
args: args{
role: TestRole(t, conn, org.PublicId),
createCnt: 5,
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
role: TestRole(t, conn, org.PublicId),
roleIdOverride: func() *string { id := testId(t); return &id }(),
createCnt: 5,
deleteCnt: 5,
},
wantRowsDeleted: 0,
wantErr: true,
},
{
name: "missing-role-id",
args: args{
role: TestRole(t, conn, org.PublicId),
roleIdOverride: func() *string { id := ""; return &id }(),
createCnt: 5,
deleteCnt: 5,
},
wantRowsDeleted: 0,
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-grant-strings",
args: args{
role: TestRole(t, conn, org.PublicId),
grantStringsOverride: []string{"ids=s_87;actions=*"},
createCnt: 5,
deleteCnt: 3,
},
wantRowsDeleted: 2,
},
{
name: "zero-version",
args: args{
role: TestRole(t, conn, org.PublicId),
createCnt: 5,
deleteCnt: 5,
roleVersionOverride: func() *uint32 { v := uint32(0); return &v }(),
},
wantRowsDeleted: 0,
wantErr: true,
},
{
name: "bad-version",
args: args{
role: TestRole(t, conn, org.PublicId),
createCnt: 5,
deleteCnt: 5,
roleVersionOverride: func() *uint32 { v := uint32(1000); return &v }(),
},
wantRowsDeleted: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() any { rg := allocRoleGrant(); return &rg }(), "1=1")
grants := make([]*RoleGrant, 0, tt.args.createCnt)
grantStrings := make([]string, 0, tt.args.createCnt)
for i := 0; i < tt.args.createCnt; i++ {
g, err := NewRoleGrant(ctx, tt.args.role.PublicId, fmt.Sprintf("actions=*;ids=s_%d", i), tt.args.opt...)
require.NoError(err)
grantStrings = append(grantStrings, g.RawGrant)
grants = append(grants, g)
}
roleGrants, err := repo.AddRoleGrants(context.Background(), tt.args.role.PublicId, 1, grantStrings, tt.args.opt...)
require.NoError(err)
assert.Equal(tt.args.createCnt, len(roleGrants))
deleteCanonicalGrants := make([]string, 0, tt.args.deleteCnt)
deleteGrants := make([]string, 0, tt.args.deleteCnt)
for i := 0; i < tt.args.deleteCnt; i++ {
deleteCanonicalGrants = append(deleteCanonicalGrants, grants[i].CanonicalGrant)
deleteGrants = append(deleteGrants, fmt.Sprintf("ids=s_%d;actions=*", i))
}
for i, override := range tt.args.grantStringsOverride {
deleteCanonicalGrants = deleteCanonicalGrants[1:]
deleteGrants[i] = override
}
var roleId string
switch {
case tt.args.roleIdOverride != nil:
roleId = *tt.args.roleIdOverride
default:
roleId = tt.args.role.PublicId
}
var roleVersion uint32
switch {
case tt.args.roleVersionOverride != nil:
roleVersion = *tt.args.roleVersionOverride
default:
roleVersion = 2
}
deletedRows, err := repo.DeleteRoleGrants(context.Background(), roleId, roleVersion, deleteGrants, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Equal(0, deletedRows)
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)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
roleGrants = []*RoleGrant{}
require.NoError(repo.reader.SearchWhere(context.Background(), &roleGrants, "role_id = ?", []any{roleId}))
found := map[string]bool{}
for _, rg := range roleGrants {
found[rg.CanonicalGrant] = true
}
for _, i := range deleteCanonicalGrants {
assert.False(found[i])
}
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.NoError(err)
})
}
}
func TestRepository_SetRoleGrants_Randomize(t *testing.T) {
t.Parallel()
ctx := context.Background()
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, repo)
role := TestRole(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() any { i := allocRoleGrant(); return &i }(), "1=1")
type roleGrantWrapper struct {
grantString string
enabled bool
}
totalCnt := 30
grants := make([]*roleGrantWrapper, 0, totalCnt)
for i := 0; i < totalCnt; i++ {
g, err := NewRoleGrant(ctx, role.PublicId, fmt.Sprintf("ids=s_%d;actions=*", i))
require.NoError(err)
grants = append(grants, &roleGrantWrapper{
grantString: g.RawGrant,
})
}
// Each loop will set some number of role grants to enabled or disabled
// randomly and then validate after setting that what's set matches
var grantsToSet []string
var expected map[string]bool
for i := 1; i <= totalCnt; i++ {
grantsToSet = make([]string, 0, totalCnt)
expected = make(map[string]bool, totalCnt)
prng := rand.New(rand.NewSource(time.Now().UnixNano()))
for _, rgw := range grants {
rgw.enabled = prng.Int()%2 == 0
if rgw.enabled {
grantsToSet = append(grantsToSet, rgw.grantString)
expected[rgw.grantString] = true
}
}
// First time, run a couple of error conditions
if i == 1 {
_, _, err := repo.SetRoleGrants(ctx, "", 1, []string{})
require.Error(err)
_, _, err = repo.SetRoleGrants(ctx, role.PublicId, 1, nil)
require.Error(err)
}
_, _, err := repo.SetRoleGrants(ctx, role.PublicId, uint32(i), grantsToSet)
require.NoError(err)
roleGrants, err := repo.ListRoleGrants(ctx, role.PublicId)
require.NoError(err)
require.Equal(len(grantsToSet), len(roleGrants))
for _, rg := range roleGrants {
require.Contains(expected, rg.CanonicalGrant)
delete(expected, rg.CanonicalGrant)
}
require.Empty(expected)
}
// At the end, set to explicitly empty and make sure all are cleared out
_, _, err := repo.SetRoleGrants(context.Background(), role.PublicId, uint32(totalCnt+1), []string{})
require.NoError(err)
roleGrants, err := repo.ListRoleGrants(context.Background(), role.PublicId)
require.NoError(err)
require.Empty(roleGrants)
}
func TestRepository_SetRoleGrants_Parameters(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, repo)
role := TestRole(t, conn, org.PublicId)
type args struct {
roleId string
roleVersion uint32
grants []string
opt []Option
}
tests := []struct {
name string
args args
want []*RoleGrant
wantAffectedRows int
wantErr bool
}{
{
name: "missing-roleid",
args: args{
roleId: "",
roleVersion: 1,
grants: []string{"ids=s_1;actions=*"},
},
want: nil,
wantAffectedRows: 0,
wantErr: true,
},
{
name: "bad-roleid",
args: args{
roleId: "bad-roleid",
roleVersion: 1,
grants: []string{"ids=s_1;actions=*"},
},
want: nil,
wantAffectedRows: 0,
wantErr: true,
},
{
name: "nil-grants",
args: args{
roleId: role.PublicId,
roleVersion: 1,
grants: nil,
},
want: nil,
wantAffectedRows: 0,
wantErr: true,
},
{
name: "zero-version",
args: args{
roleId: role.PublicId,
roleVersion: 0,
grants: []string{"ids=s_1;actions=*"},
},
want: nil,
wantAffectedRows: 0,
wantErr: true,
},
{
name: "bad-version",
args: args{
roleId: role.PublicId,
roleVersion: 1000,
grants: []string{"ids=s_1;actions=*"},
},
want: nil,
wantAffectedRows: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rg := allocRoleGrant()
db.TestDeleteWhere(t, conn, &rg, "1=1")
got, gotAffectedRows, err := repo.SetRoleGrants(context.Background(), tt.args.roleId, tt.args.roleVersion, tt.args.grants, tt.args.opt...)
if tt.wantErr {
require.Error(err)
} else {
require.NoError(err)
}
assert.Equal(tt.wantAffectedRows, gotAffectedRows)
assert.Equal(tt.want, got)
})
}
}
// testInput is used to pass test inputs into the various grantsForUser functions
type testInput struct {
userId string
reqScopeId string
resource []resource.Type
}
func TestResolveQuery(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
repo := TestRepo(t, conn, wrap)
scopeSuffix := "_12345"
testcases := []struct {
name string
input testInput
isRecursive bool
wantQuery string
errorMsg string
}{
{
name: "global request scope should return the global query",
input: testInput{
resource: []resource.Type{resource.Alias},
reqScopeId: globals.GlobalPrefix,
},
wantQuery: grantsForUserGlobalResourcesQuery,
},
{
name: "org request scope should return the org query",
input: testInput{
resource: []resource.Type{resource.AuthToken},
reqScopeId: globals.OrgPrefix + scopeSuffix,
},
wantQuery: grantsForUserOrgResourcesQuery,
},
{
name: "project request scope should return the project query",
input: testInput{
resource: []resource.Type{resource.HostCatalog},
reqScopeId: globals.ProjectPrefix + scopeSuffix,
},
wantQuery: grantsForUserProjectResourcesQuery,
},
{
name: "recursive request should return the recursive query regardless of request scope",
input: testInput{
resource: []resource.Type{resource.HostCatalog},
reqScopeId: globals.ProjectPrefix + scopeSuffix,
},
isRecursive: true,
wantQuery: grantsForUserRecursiveQuery,
},
{
name: "missing resource type should return an error",
input: testInput{
reqScopeId: globals.GlobalPrefix,
},
errorMsg: "missing resource type",
},
{
name: "unknown resource type should return an error",
input: testInput{
resource: []resource.Type{resource.Unknown},
reqScopeId: globals.GlobalPrefix,
},
errorMsg: "resource type cannot be unknown",
},
{
name: "all resource type should return an error",
input: testInput{
resource: []resource.Type{resource.All},
reqScopeId: globals.GlobalPrefix,
},
errorMsg: "resource type cannot be all",
},
{
name: "missing request scope should return an error",
input: testInput{
resource: []resource.Type{resource.Alias},
},
errorMsg: "missing request scope id",
},
{
name: "invalid resource type returns error",
input: testInput{
resource: []resource.Type{resource.Type(999)},
reqScopeId: globals.GlobalPrefix,
},
errorMsg: "invalid resource type: 999",
},
{
name: "global resources without a global request scope returns error",
input: testInput{
resource: []resource.Type{resource.Alias, resource.Billing},
reqScopeId: globals.ProjectPrefix + scopeSuffix,
},
errorMsg: fmt.Sprintf("request scope id must be global for %s resources", []resource.Type{resource.Alias, resource.Billing}),
},
{
name: "global and org resources without a global or org request scope returns error",
input: testInput{
resource: []resource.Type{resource.Account, resource.AuthMethod, resource.AuthToken, resource.ManagedGroup, resource.Policy, resource.SessionRecording, resource.StorageBucket, resource.User},
reqScopeId: globals.ProjectPrefix + scopeSuffix,
},
errorMsg: fmt.Sprintf("request scope id must be global or org for %s resources", []resource.Type{resource.Account, resource.AuthMethod, resource.AuthToken, resource.ManagedGroup, resource.Policy, resource.SessionRecording, resource.StorageBucket, resource.User}),
},
{
name: "global and org and project resources without a global or org or project request scope returns error",
input: testInput{
resource: []resource.Type{resource.Group, resource.Role, resource.Scope},
reqScopeId: "junk scope",
},
errorMsg: "invalid scope id junk scope",
},
{
name: "project resources without a project request scope returns error",
input: testInput{
resource: []resource.Type{resource.CredentialLibrary, resource.Credential, resource.CredentialStore, resource.HostCatalog, resource.HostSet, resource.Host, resource.Session, resource.Target},
reqScopeId: globals.GlobalPrefix,
},
errorMsg: fmt.Sprintf("request scope id must be project for %s resources", []resource.Type{resource.CredentialLibrary, resource.Credential, resource.CredentialStore, resource.HostCatalog, resource.HostSet, resource.Host, resource.Session, resource.Target}),
},
{
name: "global resource followed by a project resource returns the global query",
input: testInput{
resource: []resource.Type{resource.Alias, resource.Host},
reqScopeId: globals.GlobalPrefix,
},
wantQuery: grantsForUserGlobalResourcesQuery,
},
{
name: "project resource followed by a global resource returns the project query",
input: testInput{
resource: []resource.Type{resource.Host, resource.Alias},
reqScopeId: globals.ProjectPrefix + scopeSuffix,
},
wantQuery: grantsForUserProjectResourcesQuery,
},
{
name: "global and org resource followed by a global resource returns the org query for an org request scope",
input: testInput{
resource: []resource.Type{resource.AuthMethod, resource.Alias},
reqScopeId: globals.OrgPrefix + scopeSuffix,
},
wantQuery: grantsForUserOrgResourcesQuery,
},
{
name: "global and org and project resource followed by a project resource returns the org query for an org request scope",
input: testInput{
resource: []resource.Type{resource.Group, resource.Host},
reqScopeId: globals.OrgPrefix + scopeSuffix,
},
wantQuery: grantsForUserOrgResourcesQuery,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
gotQuery, err := repo.resolveQuery(ctx, tc.input.resource, tc.input.reqScopeId, tc.isRecursive)
if tc.errorMsg != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorMsg)
return
}
require.NoError(t, err)
assert.Equal(t, gotQuery, tc.wantQuery)
})
}
}
func TestGrantsForUserGlobalResources(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
repo := TestRepo(t, conn, wrap)
user := TestUser(t, repo, globals.GlobalPrefix)
// Create scopes
org1 := TestOrg(t, repo)
org2 := TestOrg(t, repo)
// Create roles
roleThis := TestRole(t, conn, globals.GlobalPrefix)
roleOrg1 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{org1.PublicId}))
roleThisAndOrg2 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, org2.PublicId}))
roleDescendants := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeDescendants}))
roleThisAndChildren := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeChildren}))
// Grant roles
TestRoleGrant(t, conn, roleThis.PublicId, "ids=*;type=*;actions=*")
TestRoleGrant(t, conn, roleOrg1.PublicId, "ids=*;type=alias;actions=create,update,read,list")
TestRoleGrant(t, conn, roleOrg1.PublicId, "ids=*;type=alias;actions=delete")
TestRoleGrant(t, conn, roleThisAndOrg2.PublicId, "ids=*;type=alias;actions=delete")
TestRoleGrant(t, conn, roleThisAndOrg2.PublicId, "ids=*;type=alias;actions=read")
TestRoleGrant(t, conn, roleThisAndOrg2.PublicId, "ids=*;type=alias;actions=update")
TestRoleGrant(t, conn, roleDescendants.PublicId, "ids=*;type=*;actions=update")
TestRoleGrant(t, conn, roleThisAndChildren.PublicId, "ids=*;type=account;actions=create,update")
TestRoleGrant(t, conn, roleThisAndChildren.PublicId, "ids=*;type=group;actions=read;output_fields=id")
// Add users to created roles
for _, role := range []*Role{roleThis, roleOrg1, roleThisAndOrg2, roleDescendants, roleThisAndChildren} {
_, err := repo.AddPrincipalRoles(ctx, role.PublicId, role.Version, []string{user.PublicId})
require.NoError(t, err)
}
testcases := []struct {
name string
input testInput
output []perms.GrantTuple
errorMsg string
}{
{
name: "alias resource should return alias and '*' grants",
input: testInput{
userId: user.PublicId,
resource: []resource.Type{resource.Alias},
},
output: []perms.GrantTuple{
{
RoleId: roleThis.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=*;actions=*",
},
{
RoleId: roleThisAndOrg2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=alias;actions=delete",
},
{
RoleId: roleThisAndOrg2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=alias;actions=read",
},
{
RoleId: roleThisAndOrg2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=alias;actions=update",
},
},
},
{
name: "account resource should return account and '*' grants",
input: testInput{
userId: user.PublicId,
resource: []resource.Type{resource.Account},
},
output: []perms.GrantTuple{
{
RoleId: roleThis.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=*;actions=*",
},
{
RoleId: roleThisAndChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=account;actions=create,update",
},
},
},
{
name: "group resource should return group and '*' grants",
input: testInput{
userId: user.PublicId,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{
{
RoleId: roleThis.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=*;actions=*",
},
{
RoleId: roleThisAndChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=group;actions=read;output_fields=id",
},
},
},
{
name: "u_anon should return no grants",
input: testInput{
userId: globals.AnonymousUserId,
resource: []resource.Type{resource.Alias},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
resource: []resource.Type{resource.Alias},
},
output: []perms.GrantTuple{},
},
{
name: "missing user id should return error",
input: testInput{
resource: []resource.Type{resource.Alias},
},
errorMsg: "missing user id",
},
{
name: "missing resource type should return error",
input: testInput{
userId: user.PublicId,
},
errorMsg: "missing resource type",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := repo.GrantsForUser(ctx, tc.input.userId, tc.input.resource, globals.GlobalPrefix)
if tc.errorMsg != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorMsg)
return
}
require.NoError(t, err)
assert.ElementsMatch(t, got, tc.output)
})
}
}
func TestGrantsForUserOrgResources(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
repo := TestRepo(t, conn, wrap)
user := TestUser(t, repo, globals.GlobalPrefix)
// Create scopes
org1 := TestOrg(t, repo, WithSkipDefaultRoleCreation(true))
org2 := TestOrg(t, repo, WithSkipDefaultRoleCreation(true))
// Create & grant roles
roles := make([]*Role, 0)
globalRoleOrg1 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{org1.PublicId}))
globalRoleThisAndOrg2 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, org2.PublicId}))
globalRoleDescendants := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeDescendants}))
globalRoleThisAndChildren := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeChildren}))
roles = append(roles, globalRoleOrg1, globalRoleThisAndOrg2, globalRoleDescendants, globalRoleThisAndChildren)
TestRoleGrant(t, conn, globalRoleOrg1.PublicId, "ids=*;type=user;actions=create,update")
TestRoleGrant(t, conn, globalRoleOrg1.PublicId, "ids=*;type=user;actions=delete,read")
TestRoleGrant(t, conn, globalRoleOrg1.PublicId, "ids=*;type=policy;actions=list,read")
TestRoleGrant(t, conn, globalRoleThisAndOrg2.PublicId, "ids=*;type=user;actions=*")
TestRoleGrant(t, conn, globalRoleDescendants.PublicId, "ids=*;type=*;actions=update")
TestRoleGrant(t, conn, globalRoleThisAndChildren.PublicId, "ids=*;type=user;actions=set-accounts")
TestRoleGrant(t, conn, globalRoleThisAndChildren.PublicId, "ids=*;type=policy;actions=read;output_fields=id")
org1RoleThis := TestRole(t, conn, org1.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis}))
org1RoleChildren := TestRole(t, conn, org1.PublicId, WithGrantScopeIds([]string{globals.GrantScopeChildren}))
org2RoleThis := TestRole(t, conn, org2.PublicId)
org2RoleThisAndChildren := TestRole(t, conn, org2.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeChildren}))
roles = append(roles, org1RoleThis, org1RoleChildren, org2RoleThis, org2RoleThisAndChildren)
TestRoleGrant(t, conn, org1RoleThis.PublicId, "ids=*;type=*;actions=*")
TestRoleGrant(t, conn, org1RoleChildren.PublicId, "ids=*;type=user;actions=add-accounts")
TestRoleGrant(t, conn, org2RoleThis.PublicId, "ids=*;type=user;actions=list-resolvable-aliases")
TestRoleGrant(t, conn, org2RoleThis.PublicId, "ids=*;type=policy;actions=list,no-op")
TestRoleGrant(t, conn, org2RoleThisAndChildren.PublicId, "ids=*;type=policy;actions=*")
// Add users to created roles
for _, role := range roles {
_, err := repo.AddPrincipalRoles(ctx, role.PublicId, role.Version, []string{user.PublicId})
require.NoError(t, err)
}
testcases := []struct {
name string
input testInput
output []perms.GrantTuple
errorMsg string
}{
{
name: "return grants for user resource at org1 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.User},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleOrg1.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: org1.PublicId,
Grant: "ids=*;type=user;actions=create,update",
},
{
RoleId: globalRoleOrg1.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: org1.PublicId,
Grant: "ids=*;type=user;actions=delete,read",
},
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=update",
},
{
RoleId: globalRoleThisAndChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeChildren,
Grant: "ids=*;type=user;actions=set-accounts",
},
{
RoleId: org1RoleThis.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org1.PublicId,
Grant: "ids=*;type=*;actions=*",
},
},
},
{
name: "return grants for user resource at org2 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.User},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleThisAndOrg2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: org2.PublicId,
Grant: "ids=*;type=user;actions=*",
},
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=update",
},
{
RoleId: globalRoleThisAndChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeChildren,
Grant: "ids=*;type=user;actions=set-accounts",
},
{
RoleId: org2RoleThis.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org2.PublicId,
Grant: "ids=*;type=user;actions=list-resolvable-aliases",
},
},
},
{
name: "return grants for policy resource at org1 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.Policy},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleOrg1.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: org1.PublicId,
Grant: "ids=*;type=policy;actions=list,read",
},
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=update",
},
{
RoleId: globalRoleThisAndChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeChildren,
Grant: "ids=*;type=policy;actions=read;output_fields=id",
},
{
RoleId: org1RoleThis.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org1.PublicId,
Grant: "ids=*;type=*;actions=*",
},
},
},
{
name: "return grants for policy resource at org2 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.Policy},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=update",
},
{
RoleId: globalRoleThisAndChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeChildren,
Grant: "ids=*;type=policy;actions=read;output_fields=id",
},
{
RoleId: org2RoleThis.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org2.PublicId,
Grant: "ids=*;type=policy;actions=list,no-op",
},
{
RoleId: org2RoleThisAndChildren.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org2.PublicId,
Grant: "ids=*;type=policy;actions=*",
},
},
},
{
name: "u_anon should return no grants at org1 request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.User},
},
output: []perms.GrantTuple{},
},
{
name: "u_anon should return no grants at org2 request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.User},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at org1 request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.User},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at org2 request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.User},
},
output: []perms.GrantTuple{},
},
{
name: "missing user id should return error",
input: testInput{
resource: []resource.Type{resource.User},
reqScopeId: org1.PublicId,
},
errorMsg: "missing user id",
},
{
name: "missing scope id should return error",
input: testInput{
userId: user.PublicId,
reqScopeId: "",
resource: []resource.Type{resource.User},
},
errorMsg: "missing request scope id",
},
{
name: "missing resource type should return error",
input: testInput{
userId: user.PublicId,
reqScopeId: org1.PublicId,
},
errorMsg: "missing resource type",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := repo.GrantsForUser(ctx, tc.input.userId, tc.input.resource, tc.input.reqScopeId)
if tc.errorMsg != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorMsg)
return
}
require.NoError(t, err)
assert.ElementsMatch(t, got, tc.output)
})
}
}
func TestGrantsForUserProjectResources(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
repo := TestRepo(t, conn, wrap)
user := TestUser(t, repo, globals.GlobalPrefix)
// Create scopes
org1 := TestOrg(t, repo, WithSkipDefaultRoleCreation(true))
org2 := TestOrg(t, repo, WithSkipDefaultRoleCreation(true))
proj1a := TestProject(t, repo, org1.PublicId, WithSkipDefaultRoleCreation(true))
proj1b := TestProject(t, repo, org1.PublicId, WithSkipDefaultRoleCreation(true))
proj2 := TestProject(t, repo, org2.PublicId, WithSkipDefaultRoleCreation(true))
// Create & grant roles
roles := make([]*Role, 0)
globalRoleDescendants := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeDescendants}))
globalRoleThisAndProj1a := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, proj1a.PublicId}))
globalRoleProj2 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{proj2.PublicId}))
roles = append(roles, globalRoleProj2, globalRoleThisAndProj1a, globalRoleDescendants)
TestRoleGrant(t, conn, globalRoleDescendants.PublicId, "ids=*;type=*;actions=read")
TestRoleGrant(t, conn, globalRoleThisAndProj1a.PublicId, "ids=*;type=target;actions=set-credential-sources")
TestRoleGrant(t, conn, globalRoleProj2.PublicId, "ids=*;type=scope;actions=list,read")
TestRoleGrant(t, conn, globalRoleProj2.PublicId, "ids=*;type=scope;actions=destroy-key-version")
TestRoleGrant(t, conn, globalRoleProj2.PublicId, "ids=*;type=scope;actions=rotate-keys")
TestRoleGrant(t, conn, globalRoleProj2.PublicId, "ids=*;type=target;actions=create,update")
org1RoleProj1b := TestRole(t, conn, org1.PublicId, WithGrantScopeIds([]string{proj1b.PublicId}))
org2RoleThisAndChildren := TestRole(t, conn, org2.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeChildren}))
roles = append(roles, org1RoleProj1b, org2RoleThisAndChildren)
TestRoleGrant(t, conn, org1RoleProj1b.PublicId, "ids=*;type=target;actions=list-resolvable-aliases")
TestRoleGrant(t, conn, org1RoleProj1b.PublicId, "ids=*;type=scope;actions=list,no-op")
TestRoleGrant(t, conn, org2RoleThisAndChildren.PublicId, "ids=*;type=scope;actions=list-keys,read")
proj1bRoleThis := TestRole(t, conn, proj1b.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis}))
proj2RoleThis := TestRole(t, conn, proj2.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis}))
roles = append(roles, proj1bRoleThis, proj2RoleThis)
TestRoleGrant(t, conn, proj1bRoleThis.PublicId, "ids=*;type=*;actions=*")
TestRoleGrant(t, conn, proj2RoleThis.PublicId, "ids=tssh_12345;actions=add-host-sources,remove-host-sources")
TestRoleGrant(t, conn, proj2RoleThis.PublicId, "ids=*;type=scope;actions=attach-storage-policy,detach-storage-policy")
// Add users to created roles
for _, role := range roles {
_, err := repo.AddPrincipalRoles(ctx, role.PublicId, role.Version, []string{user.PublicId})
require.NoError(t, err)
}
testcases := []struct {
name string
input testInput
output []perms.GrantTuple
errorMsg string
}{
{
name: "return grants for target resource at proj1a request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj1a.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=read",
},
{
RoleId: globalRoleThisAndProj1a.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj1a.PublicId,
Grant: "ids=*;type=target;actions=set-credential-sources",
},
},
},
{
name: "return grants for target resource at proj1b request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj1b.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=read",
},
{
RoleId: org1RoleProj1b.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: proj1b.PublicId,
Grant: "ids=*;type=target;actions=list-resolvable-aliases",
},
{
RoleId: proj1bRoleThis.PublicId,
RoleScopeId: proj1b.PublicId,
RoleParentScopeId: org1.PublicId,
GrantScopeId: proj1b.PublicId,
Grant: "ids=*;type=*;actions=*",
},
},
},
{
name: "return grants for target resource at proj2 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj2.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=read",
},
{
RoleId: globalRoleProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=target;actions=create,update",
},
{
RoleId: proj2RoleThis.PublicId,
RoleScopeId: proj2.PublicId,
RoleParentScopeId: org2.PublicId,
GrantScopeId: proj2.PublicId,
Grant: "ids=tssh_12345;actions=add-host-sources,remove-host-sources",
},
},
},
{
name: "return grants for scope resource at proj1a request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj1a.PublicId,
resource: []resource.Type{resource.Scope},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=read",
},
},
},
{
name: "return grants for scope resource at proj1b request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj1b.PublicId,
resource: []resource.Type{resource.Scope},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=read",
},
{
RoleId: org1RoleProj1b.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: proj1b.PublicId,
Grant: "ids=*;type=scope;actions=list,no-op",
},
{
RoleId: proj1bRoleThis.PublicId,
RoleScopeId: proj1b.PublicId,
RoleParentScopeId: org1.PublicId,
GrantScopeId: proj1b.PublicId,
Grant: "ids=*;type=*;actions=*",
},
},
},
{
name: "return grants for scope resource at proj2 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj2.PublicId,
resource: []resource.Type{resource.Scope},
},
output: []perms.GrantTuple{
{
RoleId: globalRoleDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=read",
},
{
RoleId: globalRoleProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=scope;actions=list,read",
},
{
RoleId: globalRoleProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=scope;actions=destroy-key-version",
},
{
RoleId: globalRoleProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=scope;actions=rotate-keys",
},
{
RoleId: org2RoleThisAndChildren.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: globals.GrantScopeChildren,
Grant: "ids=*;type=scope;actions=list-keys,read",
},
{
RoleId: proj2RoleThis.PublicId,
RoleScopeId: proj2.PublicId,
RoleParentScopeId: org2.PublicId,
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=scope;actions=attach-storage-policy,detach-storage-policy",
},
{
RoleId: proj2RoleThis.PublicId,
RoleScopeId: proj2.PublicId,
RoleParentScopeId: org2.PublicId,
GrantScopeId: proj2.PublicId,
Grant: "ids=tssh_12345;actions=add-host-sources,remove-host-sources",
},
},
},
{
name: "u_anon should return no grants at proj1a request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: proj1a.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{},
},
{
name: "u_anon should return no grants at proj1b request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: proj1b.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{},
},
{
name: "u_anon should return no grants at proj2 request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: proj2.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at proj1a request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: proj1a.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at proj1b request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: proj1b.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at proj2 request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: proj2.PublicId,
resource: []resource.Type{resource.Target},
},
output: []perms.GrantTuple{},
},
{
name: "missing user id should return error",
input: testInput{
resource: []resource.Type{resource.Target},
reqScopeId: proj2.PublicId,
},
errorMsg: "missing user id",
},
{
name: "missing scope id should return error",
input: testInput{
userId: user.PublicId,
reqScopeId: "",
resource: []resource.Type{resource.Target},
},
errorMsg: "missing request scope id",
},
{
name: "missing resource type should return error",
input: testInput{
reqScopeId: proj1a.PublicId,
userId: user.PublicId,
},
errorMsg: "missing resource type",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := repo.GrantsForUser(ctx, tc.input.userId, tc.input.resource, tc.input.reqScopeId)
if tc.errorMsg != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorMsg)
return
}
require.NoError(t, err)
assert.ElementsMatch(t, got, tc.output)
})
}
}
func TestGrantsForUserRecursive(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
repo := TestRepo(t, conn, wrap)
user := TestUser(t, repo, globals.GlobalPrefix)
// Create scopes
org1 := TestOrg(t, repo, WithSkipDefaultRoleCreation(true))
org2 := TestOrg(t, repo, WithSkipDefaultRoleCreation(true))
proj1a := TestProject(t, repo, org1.PublicId, WithSkipDefaultRoleCreation(true))
proj1b := TestProject(t, repo, org1.PublicId, WithSkipDefaultRoleCreation(true))
proj2 := TestProject(t, repo, org2.PublicId, WithSkipDefaultRoleCreation(true))
// Create & grant roles
roles := make([]*Role, 0)
globalRoleThis := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis}))
globalRoleChildren := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeChildren}))
globalRoleThisAndDescendants := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeDescendants}))
globalRoleOrg1AndProj2 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{org1.PublicId, proj2.PublicId}))
globalRoleOrg2 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{org2.PublicId}))
globalRoleProj1a := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{proj1a.PublicId}))
globalRoleThisAndProj2 := TestRole(t, conn, globals.GlobalPrefix, WithGrantScopeIds([]string{globals.GrantScopeThis, proj2.PublicId}))
roles = append(roles, globalRoleThis, globalRoleChildren, globalRoleThisAndDescendants, globalRoleOrg1AndProj2, globalRoleOrg2, globalRoleProj1a, globalRoleThisAndProj2)
TestRoleGrant(t, conn, globalRoleThis.PublicId, "ids=*;type=group;actions=create,update")
TestRoleGrant(t, conn, globalRoleChildren.PublicId, "ids=*;type=group;actions=set-members")
TestRoleGrant(t, conn, globalRoleThisAndDescendants.PublicId, "ids=*;type=*;actions=update")
TestRoleGrant(t, conn, globalRoleOrg1AndProj2.PublicId, "ids=*;type=group;actions=list,read")
TestRoleGrant(t, conn, globalRoleOrg2.PublicId, "ids=g_12345;actions=read")
TestRoleGrant(t, conn, globalRoleProj1a.PublicId, "ids=*;type=group;actions=create,delete,read")
TestRoleGrant(t, conn, globalRoleThisAndProj2.PublicId, "ids=g_12345,g_67890;actions=delete")
org1RoleThis := TestRole(t, conn, org1.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis}))
org1RoleChildren := TestRole(t, conn, org1.PublicId, WithGrantScopeIds([]string{globals.GrantScopeChildren}))
org2RoleThisAndChildren := TestRole(t, conn, org2.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeChildren}))
org1RoleProj1b := TestRole(t, conn, org1.PublicId, WithGrantScopeIds([]string{proj1b.PublicId}))
org2RoleProj2 := TestRole(t, conn, org2.PublicId, WithGrantScopeIds([]string{proj2.PublicId}))
roles = append(roles, org1RoleThis, org1RoleChildren, org1RoleProj1b, org2RoleThisAndChildren, org2RoleProj2)
TestRoleGrant(t, conn, org1RoleThis.PublicId, "ids=g_67890;actions=read")
TestRoleGrant(t, conn, org1RoleChildren.PublicId, "ids=*;type=group;actions=read,set-members")
TestRoleGrant(t, conn, org2RoleThisAndChildren.PublicId, "ids=*;type=group;actions=delete")
TestRoleGrant(t, conn, org1RoleProj1b.PublicId, "ids=*;type=group;actions=*")
TestRoleGrant(t, conn, org2RoleProj2.PublicId, "ids=*;type=group;actions=read")
proj1bRoleThis := TestRole(t, conn, proj1b.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis}))
proj2RoleThis := TestRole(t, conn, proj2.PublicId, WithGrantScopeIds([]string{globals.GrantScopeThis}))
roles = append(roles, proj1bRoleThis, proj2RoleThis)
TestRoleGrant(t, conn, proj1bRoleThis.PublicId, "ids=*;type=*;actions=*")
TestRoleGrant(t, conn, proj2RoleThis.PublicId, "ids=g_12345;actions=add-members,remove-members")
TestRoleGrant(t, conn, proj2RoleThis.PublicId, "ids=*;type=group;actions=set-members")
// Add user to created roles (some containing the list action)
for _, role := range roles {
_, err := repo.AddPrincipalRoles(ctx, role.PublicId, role.Version, []string{user.PublicId})
require.NoError(t, err)
}
// If there's a list permission anywhere in the scope tree, the user should be able to perform recursive list at the global scope
listResultSet := []perms.GrantTuple{
{
RoleId: globalRoleOrg1AndProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: org1.PublicId,
Grant: "ids=*;type=group;actions=list,read",
},
{
RoleId: globalRoleThisAndProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=g_12345,g_67890;actions=delete",
},
{
RoleId: globalRoleOrg2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: org2.PublicId,
Grant: "ids=g_12345;actions=read",
},
{
RoleId: globalRoleProj1a.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj1a.PublicId,
Grant: "ids=*;type=group;actions=create,delete,read",
},
{
RoleId: globalRoleThisAndDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=*;actions=update",
},
{
RoleId: org1RoleThis.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org1.PublicId,
Grant: "ids=g_67890;actions=read",
},
{
RoleId: org2RoleThisAndChildren.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: org2.PublicId,
Grant: "ids=*;type=group;actions=delete",
},
{
RoleId: globalRoleChildren.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: "children",
Grant: "ids=*;type=group;actions=set-members",
},
{
RoleId: globalRoleThis.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GlobalPrefix,
Grant: "ids=*;type=group;actions=create,update",
},
{
RoleId: proj1bRoleThis.PublicId,
RoleScopeId: proj1b.PublicId,
RoleParentScopeId: org1.PublicId,
GrantScopeId: proj1b.PublicId,
Grant: "ids=*;type=*;actions=*",
},
{
RoleId: org1RoleChildren.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: "children",
Grant: "ids=*;type=group;actions=read,set-members",
},
{
RoleId: org1RoleProj1b.PublicId,
RoleScopeId: org1.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: proj1b.PublicId,
Grant: "ids=*;type=group;actions=*",
},
{
RoleId: globalRoleThisAndDescendants.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: globals.GrantScopeDescendants,
Grant: "ids=*;type=*;actions=update",
},
{
RoleId: globalRoleOrg1AndProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=group;actions=list,read",
},
{
RoleId: globalRoleThisAndProj2.PublicId,
RoleScopeId: globals.GlobalPrefix,
RoleParentScopeId: "",
GrantScopeId: proj2.PublicId,
Grant: "ids=g_12345,g_67890;actions=delete",
},
{
RoleId: org2RoleThisAndChildren.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: globals.GrantScopeChildren,
Grant: "ids=*;type=group;actions=delete",
},
{
RoleId: org2RoleProj2.PublicId,
RoleScopeId: org2.PublicId,
RoleParentScopeId: globals.GlobalPrefix,
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=group;actions=read",
},
{
RoleId: proj2RoleThis.PublicId,
RoleScopeId: proj2.PublicId,
RoleParentScopeId: org2.PublicId,
GrantScopeId: proj2.PublicId,
Grant: "ids=g_12345;actions=add-members,remove-members",
},
{
RoleId: proj2RoleThis.PublicId,
RoleScopeId: proj2.PublicId,
RoleParentScopeId: org2.PublicId,
GrantScopeId: proj2.PublicId,
Grant: "ids=*;type=group;actions=set-members",
},
}
testcases := []struct {
name string
input testInput
output []perms.GrantTuple
errorMsg string
}{
{
name: "return grants for group resource at global request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: globals.GlobalPrefix,
resource: []resource.Type{resource.Group},
},
output: listResultSet,
},
{
name: "return grants for group resource at org1 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.Group},
},
output: listResultSet,
},
{
name: "return grants for group resource at org2 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.Group},
},
output: listResultSet,
},
{
name: "return grants for group resource at proj1a request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj1a.PublicId,
resource: []resource.Type{resource.Group},
},
output: listResultSet,
},
{
name: "return grants for group resource at proj1b request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj1b.PublicId,
resource: []resource.Type{resource.Group},
},
output: listResultSet,
},
{
name: "return grants for group resource at proj2 request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: proj2.PublicId,
resource: []resource.Type{resource.Group},
},
output: listResultSet,
},
{
name: "return error when trying to recursively list grants at an unknown request scope",
input: testInput{
userId: user.PublicId,
reqScopeId: scope.Unknown.String(),
resource: []resource.Type{resource.Group},
},
errorMsg: "request scope must be global scope, an org scope, or a project scope",
},
{
name: "return no grants for a resource that has no permissions granted for it",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: globals.GlobalPrefix,
resource: []resource.Type{resource.Role},
},
output: []perms.GrantTuple{},
},
{
name: "unknown resource type should return error",
input: testInput{
userId: user.PublicId,
reqScopeId: globals.GlobalPrefix,
resource: []resource.Type{resource.Unknown},
},
errorMsg: "resource type cannot be unknown",
},
{
name: "'*' resource type should return error",
input: testInput{
userId: user.PublicId,
reqScopeId: globals.GlobalPrefix,
resource: []resource.Type{resource.All},
},
errorMsg: "resource type cannot be all",
},
{
name: "u_anon should return no grants at global request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: globals.GlobalPrefix,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{},
},
{
name: "u_anon should return no grants at org1 request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{},
},
{
name: "u_anon should return no grants at org2 request scope",
input: testInput{
userId: globals.AnonymousUserId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at global request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: globals.GlobalPrefix,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at org1 request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: org1.PublicId,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{},
},
{
name: "u_auth should return no grants at org2 request scope",
input: testInput{
userId: globals.AnyAuthenticatedUserId,
reqScopeId: org2.PublicId,
resource: []resource.Type{resource.Group},
},
output: []perms.GrantTuple{},
},
{
name: "missing user id should return error",
input: testInput{
resource: []resource.Type{resource.Group},
reqScopeId: globals.GlobalPrefix,
},
errorMsg: "missing user id",
},
{
name: "missing scope id should return error",
input: testInput{
userId: user.PublicId,
reqScopeId: "",
resource: []resource.Type{resource.Group},
},
errorMsg: "missing request scope id",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := repo.GrantsForUser(ctx, tc.input.userId, tc.input.resource, tc.input.reqScopeId, WithRecursive(true))
if tc.errorMsg != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorMsg)
return
}
require.NoError(t, err)
assert.ElementsMatch(t, got, tc.output)
})
}
}