diff --git a/internal/iam/options.go b/internal/iam/options.go index 990da7ace0..34c6834556 100644 --- a/internal/iam/options.go +++ b/internal/iam/options.go @@ -42,7 +42,7 @@ type options struct { withReader db.Reader withWriter db.Writer withStartPageAfterItem pagination.Item - withTestCacheMultiGrantTuples *[]multiGrantTuple + withTestCacheMultiGrantTuples *[]MultiGrantTuple } func getDefaultOptions() options { @@ -177,7 +177,7 @@ func WithStartPageAfterItem(item pagination.Item) Option { } } -func withTestCacheMultiGrantTuples(cache *[]multiGrantTuple) Option { +func WithTestCacheMultiGrantTuples(cache *[]MultiGrantTuple) Option { return func(o *options) { o.withTestCacheMultiGrantTuples = cache } diff --git a/internal/iam/repository_role_grant.go b/internal/iam/repository_role_grant.go index d9a8d63172..7de9637215 100644 --- a/internal/iam/repository_role_grant.go +++ b/internal/iam/repository_role_grant.go @@ -412,7 +412,7 @@ func (r *Repository) ListRoleGrantScopes(ctx context.Context, roleIds []string, return roleGrantScopes, nil } -type multiGrantTuple struct { +type MultiGrantTuple struct { RoleId string RoleScopeId string RoleParentScopeId string @@ -441,7 +441,7 @@ func (r *Repository) GrantsForUser(ctx context.Context, userId string, opt ...Op query = fmt.Sprintf(grantsForUserQuery, authUser) } - var grants []multiGrantTuple + var grants []MultiGrantTuple rows, err := r.reader.Query(ctx, query, []any{userId}) if err != nil { return nil, errors.Wrap(ctx, err, op) @@ -477,7 +477,7 @@ func (r *Repository) GrantsForUser(ctx context.Context, userId string, opt ...Op if opts.withTestCacheMultiGrantTuples != nil { for i, grant := range grants { - grant.testStableSort() + grant.TestStableSort() grants[i] = grant } *opts.withTestCacheMultiGrantTuples = grants @@ -486,7 +486,7 @@ func (r *Repository) GrantsForUser(ctx context.Context, userId string, opt ...Op return ret, nil } -func (m *multiGrantTuple) testStableSort() { +func (m *MultiGrantTuple) TestStableSort() { grantScopeIds := strings.Split(m.GrantScopeIds, "^") sort.Strings(grantScopeIds) m.GrantScopeIds = strings.Join(grantScopeIds, "^") diff --git a/internal/iam/repository_role_grant_ext_test.go b/internal/iam/repository_role_grant_ext_test.go index f618fce8d7..8846e2b9b9 100644 --- a/internal/iam/repository_role_grant_ext_test.go +++ b/internal/iam/repository_role_grant_ext_test.go @@ -8,14 +8,19 @@ import ( "encoding/json" "fmt" mathrand "math/rand" + "strings" "testing" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/ldap" "github.com/hashicorp/boundary/internal/auth/ldap/store" "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/types/action" + "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -389,3 +394,1547 @@ func TestGrantsForUserRandomized(t *testing.T) { ", roles from ldap managed groups", rolesFromLdapManagedGroups) } } + +func TestGrantsForUser_DirectAssociation(t *testing.T) { + ctx := context.Background() + + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + + repo := iam.TestRepo(t, conn, wrap) + user := iam.TestUser(t, repo, "global") + + // Create a series of scopes with roles in each. We'll create two of each + // kind to ensure we're not just picking up the first role in each. + + // The first org/project set contains direct grants, but without + // inheritance. We create two roles in each project. + directGrantOrg1, directGrantProj1a, directGrantProj1b := iam.SetupDirectGrantScopes(t, conn, repo) + directGrantOrg1Role := iam.TestRole(t, conn, directGrantOrg1.PublicId) + iam.TestUserRole(t, conn, directGrantOrg1Role.PublicId, user.PublicId) + directGrantOrg1RoleGrant1 := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant1) + directGrantOrg1RoleGrant2 := "ids=*;type=group;actions=create,list" + iam.TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant2) + + directGrantProj1aRole := iam.TestRole(t, conn, directGrantProj1a.PublicId) + iam.TestUserRole(t, conn, directGrantProj1aRole.PublicId, user.PublicId) + directGrantProj1aRoleGrant := "ids=*;type=group;actions=add-members,read" + iam.TestRoleGrant(t, conn, directGrantProj1aRole.PublicId, directGrantProj1aRoleGrant) + directGrantProj1bRole := iam.TestRole(t, conn, directGrantProj1b.PublicId) + iam.TestUserRole(t, conn, directGrantProj1bRole.PublicId, user.PublicId) + directGrantProj1bRoleGrant := "ids=*;type=group;actions=list,read" + iam.TestRoleGrant(t, conn, directGrantProj1bRole.PublicId, directGrantProj1bRoleGrant) + + directGrantOrg2, directGrantProj2a, directGrantProj2b := iam.SetupDirectGrantScopes(t, conn, repo) + directGrantOrg2Role := iam.TestRole(t, conn, directGrantOrg2.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeThis, + directGrantProj2a.PublicId, + })) + iam.TestUserRole(t, conn, directGrantOrg2Role.PublicId, user.PublicId) + directGrantOrg2RoleGrant1 := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant1) + directGrantOrg2RoleGrant2 := "ids=*;type=group;actions=list,read" + iam.TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant2) + + directGrantProj2aRole := iam.TestRole(t, conn, directGrantProj2a.PublicId) + iam.TestUserRole(t, conn, directGrantProj2aRole.PublicId, user.PublicId) + directGrantProj2aRoleGrant := "ids=hcst_abcd1234,hcst_1234abcd;actions=*" + iam.TestRoleGrant(t, conn, directGrantProj2aRole.PublicId, directGrantProj2aRoleGrant) + directGrantProj2bRole := iam.TestRole(t, conn, directGrantProj2b.PublicId) + iam.TestUserRole(t, conn, directGrantProj2bRole.PublicId, user.PublicId) + directGrantProj2bRoleGrant := "ids=cs_abcd1234;actions=read,update" + iam.TestRoleGrant(t, conn, directGrantProj2bRole.PublicId, directGrantProj2bRoleGrant) + + // For the second set we create a couple of orgs/projects and then use + // globals.GrantScopeChildren. + childGrantOrg1, _ := iam.SetupChildGrantScopes(t, conn, repo) + childGrantOrg1Role := iam.TestRole(t, conn, childGrantOrg1.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestUserRole(t, conn, childGrantOrg1Role.PublicId, user.PublicId) + childGrantOrg1RoleGrant := "ids=*;type=group;actions=add-members,remove-members" + iam.TestRoleGrant(t, conn, childGrantOrg1Role.PublicId, childGrantOrg1RoleGrant) + + childGrantOrg2, _ := iam.SetupChildGrantScopes(t, conn, repo) + childGrantOrg2Role := iam.TestRole(t, conn, childGrantOrg2.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestUserRole(t, conn, childGrantOrg2Role.PublicId, user.PublicId) + childGrantOrg2RoleGrant1 := "ids=*;type=group;actions=set-members" + iam.TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant1) + childGrantOrg2RoleGrant2 := "ids=*;type=group;actions=delete" + iam.TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant2) + + // Finally, let's create some roles at global scope with children and + // descendants grants + childGrantGlobalRole := iam.TestRole(t, conn, scope.Global.String(), + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestUserRole(t, conn, childGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + childGrantGlobalRoleGrant := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, childGrantGlobalRole.PublicId, childGrantGlobalRoleGrant) + descendantGrantGlobalRole := iam.TestRole(t, conn, scope.Global.String(), + iam.WithGrantScopeIds([]string{ + globals.GrantScopeDescendants, + })) + iam.TestUserRole(t, conn, descendantGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + descendantGrantGlobalRoleGrant := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, descendantGrantGlobalRole.PublicId, descendantGrantGlobalRoleGrant) + + t.Run("db-grants", func(t *testing.T) { + // Here we should see exactly what the DB has returned, before we do some + // local exploding of grants and grant scopes + expMultiGrantTuples := []iam.MultiGrantTuple{ + // No grants from noOrg/noProj + // Direct org1/2: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeThis, + Grants: strings.Join([]string{directGrantOrg1RoleGrant1, directGrantOrg1RoleGrant2}, "^"), + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: strings.Join([]string{globals.GrantScopeThis, directGrantProj2a.PublicId}, "^"), + Grants: strings.Join([]string{directGrantOrg2RoleGrant1, directGrantOrg2RoleGrant2}, "^"), + }, + // Proj orgs 1/2: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1aRoleGrant, + }, + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1bRoleGrant, + }, + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2aRoleGrant, + }, + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2bRoleGrant, + }, + // Child grants from orgs 1/2: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantOrg1RoleGrant, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: strings.Join([]string{childGrantOrg2RoleGrant1, childGrantOrg2RoleGrant2}, "^"), + }, + // Children of global and descendants of global + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeDescendants, + Grants: descendantGrantGlobalRoleGrant, + }, + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantGlobalRoleGrant, + }, + } + for i, tuple := range expMultiGrantTuples { + tuple.TestStableSort() + expMultiGrantTuples[i] = tuple + } + multiGrantTuplesCache := new([]iam.MultiGrantTuple) + _, err := repo.GrantsForUser(ctx, user.PublicId, iam.WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + + // log.Println("multiGrantTuplesCache", pretty.Sprint(*multiGrantTuplesCache)) + assert.ElementsMatch(t, *multiGrantTuplesCache, expMultiGrantTuples) + }) + + t.Run("exploded-grants", func(t *testing.T) { + // We expect to see: + // + // * No grants from noOrg/noProj + // * Grants from direct orgs/projs: + // * directGrantOrg1/directGrantOrg2 on org and respective projects (6 grants total per org) + // * directGrantProj on respective projects (4 grants total) + expGrantTuples := []perms.GrantTuple{ + // No grants from noOrg/noProj + // Grants from direct org1 to org1/proj1a/proj1b: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant1, + }, + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant2, + }, + // Grants from direct org 1 proj 1a: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Grant: directGrantProj1aRoleGrant, + }, + // Grant from direct org 1 proj 1 b: + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Grant: directGrantProj1bRoleGrant, + }, + + // Grants from direct org2 to org2/proj2a/proj2b: + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + // Grants from direct org 2 proj 2a: + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantProj2aRoleGrant, + }, + // Grant from direct org 2 proj 2 b: + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Grant: directGrantProj2bRoleGrant, + }, + // Child grants from child org1 to proj1a/proj1b: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg1RoleGrant, + }, + // Child grants from child org2 to proj2a/proj2b: + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant1, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant2, + }, + + // Grants from global to every org: + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantGlobalRoleGrant, + }, + + // Grants from global to every org and project: + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Grant: descendantGrantGlobalRoleGrant, + }, + } + + multiGrantTuplesCache := new([]iam.MultiGrantTuple) + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId, iam.WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + assert.ElementsMatch(t, grantTuples, expGrantTuples) + }) + + t.Run("acl-grants", func(t *testing.T) { + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId) + require.NoError(t, err) + grants := make([]perms.Grant, 0, len(grantTuples)) + for _, gt := range grantTuples { + grant, err := perms.Parse(ctx, gt) + require.NoError(t, err) + grants = append(grants, grant) + } + acl := perms.NewACL(grants...) + + t.Run("descendant-grants", func(t *testing.T) { + descendantGrants := acl.DescendantsGrants() + expDescendantGrants := []perms.AclGrant{ + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + } + assert.ElementsMatch(t, descendantGrants, expDescendantGrants) + }) + + t.Run("child-grants", func(t *testing.T) { + childrenGrants := acl.ChildrenScopeGrantMap() + expChildrenGrants := map[string][]perms.AclGrant{ + childGrantOrg1.PublicId: { + { + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.AddMembers: true, action.RemoveMembers: true}, + }, + }, + childGrantOrg2.PublicId: { + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.SetMembers: true}, + }, + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.Delete: true}, + }, + }, + scope.Global.String(): { + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + } + assert.Len(t, childrenGrants, len(expChildrenGrants)) + for k, v := range childrenGrants { + assert.ElementsMatch(t, v, expChildrenGrants[k]) + } + }) + + t.Run("direct-grants", func(t *testing.T) { + directGrants := acl.DirectScopeGrantMap() + expDirectGrants := map[string][]perms.AclGrant{ + directGrantOrg1.PublicId: { + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.Create: true, action.List: true}, + }, + }, + directGrantProj1a.PublicId: { + { + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.AddMembers: true, action.Read: true}, + }, + }, + directGrantProj1b.PublicId: { + { + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantOrg2.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantProj2a.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_1234abcd", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + directGrantProj2b.PublicId: { + { + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Id: "cs_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.Update: true, action.Read: true}, + }, + }, + } + /* + log.Println("org1", directGrantOrg1.PublicId) + log.Println("proj1a", directGrantProj1a.PublicId) + log.Println("proj1b", directGrantProj1b.PublicId) + log.Println("org2", directGrantOrg2.PublicId) + log.Println("proj2a", directGrantProj2a.PublicId) + log.Println("proj2b", directGrantProj2b.PublicId) + */ + assert.Len(t, directGrants, len(expDirectGrants)) + for k, v := range directGrants { + assert.ElementsMatch(t, v, expDirectGrants[k]) + } + }) + }) +} + +func TestGrantsForUser_Group(t *testing.T) { + ctx := context.Background() + + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + + repo := iam.TestRepo(t, conn, wrap) + user := iam.TestUser(t, repo, "global") + group := iam.TestGroup(t, conn, "global") + iam.TestGroupMember(t, conn, group.PublicId, user.PublicId) + + // Create a series of scopes with roles in each. We'll create two of each + // kind to ensure we're not just picking up the first role in each. + + // The first org/project set contains direct grants, but without + // inheritance. We create two roles in each project. + directGrantOrg1, directGrantProj1a, directGrantProj1b := iam.SetupDirectGrantScopes(t, conn, repo) + directGrantOrg1Role := iam.TestRole(t, conn, directGrantOrg1.PublicId) + iam.TestGroupRole(t, conn, directGrantOrg1Role.PublicId, group.PublicId) + directGrantOrg1RoleGrant1 := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant1) + directGrantOrg1RoleGrant2 := "ids=*;type=group;actions=create,list" + iam.TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant2) + + directGrantProj1aRole := iam.TestRole(t, conn, directGrantProj1a.PublicId) + iam.TestGroupRole(t, conn, directGrantProj1aRole.PublicId, group.PublicId) + directGrantProj1aRoleGrant := "ids=*;type=group;actions=add-members,read" + iam.TestRoleGrant(t, conn, directGrantProj1aRole.PublicId, directGrantProj1aRoleGrant) + directGrantProj1bRole := iam.TestRole(t, conn, directGrantProj1b.PublicId) + iam.TestGroupRole(t, conn, directGrantProj1bRole.PublicId, group.PublicId) + directGrantProj1bRoleGrant := "ids=*;type=group;actions=list,read" + iam.TestRoleGrant(t, conn, directGrantProj1bRole.PublicId, directGrantProj1bRoleGrant) + + directGrantOrg2, directGrantProj2a, directGrantProj2b := iam.SetupDirectGrantScopes(t, conn, repo) + directGrantOrg2Role := iam.TestRole(t, conn, directGrantOrg2.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeThis, + directGrantProj2a.PublicId, + })) + iam.TestGroupRole(t, conn, directGrantOrg2Role.PublicId, group.PublicId) + directGrantOrg2RoleGrant1 := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant1) + directGrantOrg2RoleGrant2 := "ids=*;type=group;actions=list,read" + iam.TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant2) + + directGrantProj2aRole := iam.TestRole(t, conn, directGrantProj2a.PublicId) + iam.TestGroupRole(t, conn, directGrantProj2aRole.PublicId, group.PublicId) + directGrantProj2aRoleGrant := "ids=hcst_abcd1234,hcst_1234abcd;actions=*" + iam.TestRoleGrant(t, conn, directGrantProj2aRole.PublicId, directGrantProj2aRoleGrant) + directGrantProj2bRole := iam.TestRole(t, conn, directGrantProj2b.PublicId) + iam.TestGroupRole(t, conn, directGrantProj2bRole.PublicId, group.PublicId) + directGrantProj2bRoleGrant := "ids=cs_abcd1234;actions=read,update" + iam.TestRoleGrant(t, conn, directGrantProj2bRole.PublicId, directGrantProj2bRoleGrant) + + // For the second set we create a couple of orgs/projects and then use + // globals.GrantScopeChildren. + childGrantOrg1, _ := iam.SetupChildGrantScopes(t, conn, repo) + childGrantOrg1Role := iam.TestRole(t, conn, childGrantOrg1.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestGroupRole(t, conn, childGrantOrg1Role.PublicId, group.PublicId) + childGrantOrg1RoleGrant := "ids=*;type=group;actions=add-members,remove-members" + iam.TestRoleGrant(t, conn, childGrantOrg1Role.PublicId, childGrantOrg1RoleGrant) + + childGrantOrg2, _ := iam.SetupChildGrantScopes(t, conn, repo) + childGrantOrg2Role := iam.TestRole(t, conn, childGrantOrg2.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestGroupRole(t, conn, childGrantOrg2Role.PublicId, group.PublicId) + childGrantOrg2RoleGrant1 := "ids=*;type=group;actions=set-members" + iam.TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant1) + childGrantOrg2RoleGrant2 := "ids=*;type=group;actions=delete" + iam.TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant2) + + // Finally, let's create some roles at global scope with children and + // descendants grants + childGrantGlobalRole := iam.TestRole(t, conn, scope.Global.String(), + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestUserRole(t, conn, childGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + childGrantGlobalRoleGrant := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, childGrantGlobalRole.PublicId, childGrantGlobalRoleGrant) + descendantGrantGlobalRole := iam.TestRole(t, conn, scope.Global.String(), + iam.WithGrantScopeIds([]string{ + globals.GrantScopeDescendants, + })) + iam.TestUserRole(t, conn, descendantGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + descendantGrantGlobalRoleGrant := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, descendantGrantGlobalRole.PublicId, descendantGrantGlobalRoleGrant) + + t.Run("db-grants", func(t *testing.T) { + // Here we should see exactly what the DB has returned, before we do some + // local exploding of grants and grant scopes + expMultiGrantTuples := []iam.MultiGrantTuple{ + // No grants from noOrg/noProj + // Direct org1/2: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeThis, + Grants: strings.Join([]string{directGrantOrg1RoleGrant1, directGrantOrg1RoleGrant2}, "^"), + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: strings.Join([]string{globals.GrantScopeThis, directGrantProj2a.PublicId}, "^"), + Grants: strings.Join([]string{directGrantOrg2RoleGrant1, directGrantOrg2RoleGrant2}, "^"), + }, + // Proj orgs 1/2: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1aRoleGrant, + }, + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1bRoleGrant, + }, + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2aRoleGrant, + }, + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2bRoleGrant, + }, + // Child grants from orgs 1/2: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantOrg1RoleGrant, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: strings.Join([]string{childGrantOrg2RoleGrant1, childGrantOrg2RoleGrant2}, "^"), + }, + // Children of global and descendants of global + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeDescendants, + Grants: descendantGrantGlobalRoleGrant, + }, + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantGlobalRoleGrant, + }, + } + for i, tuple := range expMultiGrantTuples { + tuple.TestStableSort() + expMultiGrantTuples[i] = tuple + } + multiGrantTuplesCache := new([]iam.MultiGrantTuple) + _, err := repo.GrantsForUser(ctx, user.PublicId, iam.WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + + // log.Println("multiGrantTuplesCache", pretty.Sprint(*multiGrantTuplesCache)) + assert.ElementsMatch(t, *multiGrantTuplesCache, expMultiGrantTuples) + }) + + t.Run("exploded-grants", func(t *testing.T) { + // We expect to see: + // + // * No grants from noOrg/noProj + // * Grants from direct orgs/projs: + // * directGrantOrg1/directGrantOrg2 on org and respective projects (6 grants total per org) + // * directGrantProj on respective projects (4 grants total) + expGrantTuples := []perms.GrantTuple{ + // No grants from noOrg/noProj + // Grants from direct org1 to org1/proj1a/proj1b: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant1, + }, + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant2, + }, + // Grants from direct org 1 proj 1a: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Grant: directGrantProj1aRoleGrant, + }, + // Grant from direct org 1 proj 1 b: + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Grant: directGrantProj1bRoleGrant, + }, + + // Grants from direct org2 to org2/proj2a/proj2b: + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + // Grants from direct org 2 proj 2a: + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantProj2aRoleGrant, + }, + // Grant from direct org 2 proj 2 b: + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Grant: directGrantProj2bRoleGrant, + }, + // Child grants from child org1 to proj1a/proj1b: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg1RoleGrant, + }, + // Child grants from child org2 to proj2a/proj2b: + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant1, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant2, + }, + + // Grants from global to every org: + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantGlobalRoleGrant, + }, + + // Grants from global to every org and project: + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Grant: descendantGrantGlobalRoleGrant, + }, + } + + multiGrantTuplesCache := new([]iam.MultiGrantTuple) + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId, iam.WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + assert.ElementsMatch(t, grantTuples, expGrantTuples) + }) + + t.Run("acl-grants", func(t *testing.T) { + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId) + require.NoError(t, err) + grants := make([]perms.Grant, 0, len(grantTuples)) + for _, gt := range grantTuples { + grant, err := perms.Parse(ctx, gt) + require.NoError(t, err) + grants = append(grants, grant) + } + acl := perms.NewACL(grants...) + + t.Run("descendant-grants", func(t *testing.T) { + descendantGrants := acl.DescendantsGrants() + expDescendantGrants := []perms.AclGrant{ + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + } + assert.ElementsMatch(t, descendantGrants, expDescendantGrants) + }) + + t.Run("child-grants", func(t *testing.T) { + childrenGrants := acl.ChildrenScopeGrantMap() + expChildrenGrants := map[string][]perms.AclGrant{ + childGrantOrg1.PublicId: { + { + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.AddMembers: true, action.RemoveMembers: true}, + }, + }, + childGrantOrg2.PublicId: { + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.SetMembers: true}, + }, + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.Delete: true}, + }, + }, + scope.Global.String(): { + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + } + assert.Len(t, childrenGrants, len(expChildrenGrants)) + for k, v := range childrenGrants { + assert.ElementsMatch(t, v, expChildrenGrants[k]) + } + }) + + t.Run("direct-grants", func(t *testing.T) { + directGrants := acl.DirectScopeGrantMap() + expDirectGrants := map[string][]perms.AclGrant{ + directGrantOrg1.PublicId: { + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.Create: true, action.List: true}, + }, + }, + directGrantProj1a.PublicId: { + { + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.AddMembers: true, action.Read: true}, + }, + }, + directGrantProj1b.PublicId: { + { + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantOrg2.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantProj2a.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_1234abcd", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + directGrantProj2b.PublicId: { + { + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Id: "cs_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.Update: true, action.Read: true}, + }, + }, + } + /* + log.Println("org1", directGrantOrg1.PublicId) + log.Println("proj1a", directGrantProj1a.PublicId) + log.Println("proj1b", directGrantProj1b.PublicId) + log.Println("org2", directGrantOrg2.PublicId) + log.Println("proj2a", directGrantProj2a.PublicId) + log.Println("proj2b", directGrantProj2b.PublicId) + */ + assert.Len(t, directGrants, len(expDirectGrants)) + for k, v := range directGrants { + assert.ElementsMatch(t, v, expDirectGrants[k]) + } + }) + }) +} + +func TestGrantsForUser_ManagedGroup(t *testing.T) { + ctx := context.Background() + + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + + repo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + + o, _ := iam.TestScopes( + t, + repo, + iam.WithSkipAdminRoleCreation(true), + iam.WithSkipDefaultRoleCreation(true), + ) + databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + t.Run("oidc", func(t *testing.T) { + oidcAuthMethod := oidc.TestAuthMethod( + t, conn, databaseWrapper, "global", oidc.ActivePrivateState, + "alice-rp", "fido", + oidc.WithSigningAlgs(oidc.RS256), + oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]), + oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]), + ) + oidcAcct := oidc.TestAccount(t, conn, oidcAuthMethod, "sub") + oidcManagedGroup := oidc.TestManagedGroup(t, conn, oidcAuthMethod, `"/token/sub" matches ".*"`) + + user := iam.TestUser(t, repo, "global", iam.WithAccountIds(oidcAcct.PublicId)) + oidc.TestManagedGroupMember(t, conn, oidcManagedGroup.GetPublicId(), oidcAcct.GetPublicId()) + + // Create a series of scopes with roles in each. We'll create two of each + // kind to ensure we're not just picking up the first role in each. + + // The first org/project set contains direct grants, but without + // inheritance. We create two roles in each project. + directGrantOrg1, directGrantProj1a, directGrantProj1b := iam.SetupDirectGrantScopes(t, conn, repo) + directGrantOrg1Role := iam.TestRole(t, conn, directGrantOrg1.PublicId) + iam.TestManagedGroupRole(t, conn, directGrantOrg1Role.PublicId, oidcManagedGroup.PublicId) + directGrantOrg1RoleGrant1 := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant1) + directGrantOrg1RoleGrant2 := "ids=*;type=group;actions=create,list" + iam.TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant2) + + directGrantProj1aRole := iam.TestRole(t, conn, directGrantProj1a.PublicId) + iam.TestManagedGroupRole(t, conn, directGrantProj1aRole.PublicId, oidcManagedGroup.PublicId) + directGrantProj1aRoleGrant := "ids=*;type=group;actions=add-members,read" + iam.TestRoleGrant(t, conn, directGrantProj1aRole.PublicId, directGrantProj1aRoleGrant) + directGrantProj1bRole := iam.TestRole(t, conn, directGrantProj1b.PublicId) + iam.TestManagedGroupRole(t, conn, directGrantProj1bRole.PublicId, oidcManagedGroup.PublicId) + directGrantProj1bRoleGrant := "ids=*;type=group;actions=list,read" + iam.TestRoleGrant(t, conn, directGrantProj1bRole.PublicId, directGrantProj1bRoleGrant) + + directGrantOrg2, directGrantProj2a, directGrantProj2b := iam.SetupDirectGrantScopes(t, conn, repo) + directGrantOrg2Role := iam.TestRole(t, conn, directGrantOrg2.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeThis, + directGrantProj2a.PublicId, + })) + iam.TestManagedGroupRole(t, conn, directGrantOrg2Role.PublicId, oidcManagedGroup.PublicId) + directGrantOrg2RoleGrant1 := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant1) + directGrantOrg2RoleGrant2 := "ids=*;type=group;actions=list,read" + iam.TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant2) + + directGrantProj2aRole := iam.TestRole(t, conn, directGrantProj2a.PublicId) + iam.TestManagedGroupRole(t, conn, directGrantProj2aRole.PublicId, oidcManagedGroup.PublicId) + directGrantProj2aRoleGrant := "ids=hcst_abcd1234,hcst_1234abcd;actions=*" + iam.TestRoleGrant(t, conn, directGrantProj2aRole.PublicId, directGrantProj2aRoleGrant) + directGrantProj2bRole := iam.TestRole(t, conn, directGrantProj2b.PublicId) + iam.TestManagedGroupRole(t, conn, directGrantProj2bRole.PublicId, oidcManagedGroup.PublicId) + directGrantProj2bRoleGrant := "ids=cs_abcd1234;actions=read,update" + iam.TestRoleGrant(t, conn, directGrantProj2bRole.PublicId, directGrantProj2bRoleGrant) + + // For the second set we create a couple of orgs/projects and then use + // globals.GrantScopeChildren. + childGrantOrg1, _ := iam.SetupChildGrantScopes(t, conn, repo) + childGrantOrg1Role := iam.TestRole(t, conn, childGrantOrg1.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestManagedGroupRole(t, conn, childGrantOrg1Role.PublicId, oidcManagedGroup.PublicId) + childGrantOrg1RoleGrant := "ids=*;type=group;actions=add-members,remove-members" + iam.TestRoleGrant(t, conn, childGrantOrg1Role.PublicId, childGrantOrg1RoleGrant) + + childGrantOrg2, _ := iam.SetupChildGrantScopes(t, conn, repo) + childGrantOrg2Role := iam.TestRole(t, conn, childGrantOrg2.PublicId, + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestManagedGroupRole(t, conn, childGrantOrg2Role.PublicId, oidcManagedGroup.PublicId) + childGrantOrg2RoleGrant1 := "ids=*;type=group;actions=set-members" + iam.TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant1) + childGrantOrg2RoleGrant2 := "ids=*;type=group;actions=delete" + iam.TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant2) + + // Finally, let's create some roles at global scope with children and + // descendants grants + childGrantGlobalRole := iam.TestRole(t, conn, scope.Global.String(), + iam.WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + iam.TestUserRole(t, conn, childGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + childGrantGlobalRoleGrant := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, childGrantGlobalRole.PublicId, childGrantGlobalRoleGrant) + descendantGrantGlobalRole := iam.TestRole(t, conn, scope.Global.String(), + iam.WithGrantScopeIds([]string{ + globals.GrantScopeDescendants, + })) + iam.TestUserRole(t, conn, descendantGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + descendantGrantGlobalRoleGrant := "ids=*;type=group;actions=*" + iam.TestRoleGrant(t, conn, descendantGrantGlobalRole.PublicId, descendantGrantGlobalRoleGrant) + + t.Run("db-grants", func(t *testing.T) { + // Here we should see exactly what the DB has returned, before we do some + // local exploding of grants and grant scopes + expMultiGrantTuples := []iam.MultiGrantTuple{ + // No grants from noOrg/noProj + // Direct org1/2: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeThis, + Grants: strings.Join([]string{directGrantOrg1RoleGrant1, directGrantOrg1RoleGrant2}, "^"), + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: strings.Join([]string{globals.GrantScopeThis, directGrantProj2a.PublicId}, "^"), + Grants: strings.Join([]string{directGrantOrg2RoleGrant1, directGrantOrg2RoleGrant2}, "^"), + }, + // Proj orgs 1/2: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1aRoleGrant, + }, + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1bRoleGrant, + }, + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2aRoleGrant, + }, + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2bRoleGrant, + }, + // Child grants from orgs 1/2: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantOrg1RoleGrant, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: strings.Join([]string{childGrantOrg2RoleGrant1, childGrantOrg2RoleGrant2}, "^"), + }, + // Children of global and descendants of global + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeDescendants, + Grants: descendantGrantGlobalRoleGrant, + }, + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantGlobalRoleGrant, + }, + } + for i, tuple := range expMultiGrantTuples { + tuple.TestStableSort() + expMultiGrantTuples[i] = tuple + } + multiGrantTuplesCache := new([]iam.MultiGrantTuple) + _, err := repo.GrantsForUser(ctx, user.PublicId, iam.WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + + // log.Println("multiGrantTuplesCache", pretty.Sprint(*multiGrantTuplesCache)) + assert.ElementsMatch(t, *multiGrantTuplesCache, expMultiGrantTuples) + }) + + t.Run("exploded-grants", func(t *testing.T) { + // We expect to see: + // + // * No grants from noOrg/noProj + // * Grants from direct orgs/projs: + // * directGrantOrg1/directGrantOrg2 on org and respective projects (6 grants total per org) + // * directGrantProj on respective projects (4 grants total) + expGrantTuples := []perms.GrantTuple{ + // No grants from noOrg/noProj + // Grants from direct org1 to org1/proj1a/proj1b: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant1, + }, + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant2, + }, + // Grants from direct org 1 proj 1a: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Grant: directGrantProj1aRoleGrant, + }, + // Grant from direct org 1 proj 1 b: + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Grant: directGrantProj1bRoleGrant, + }, + + // Grants from direct org2 to org2/proj2a/proj2b: + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + // Grants from direct org 2 proj 2a: + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantProj2aRoleGrant, + }, + // Grant from direct org 2 proj 2 b: + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Grant: directGrantProj2bRoleGrant, + }, + // Child grants from child org1 to proj1a/proj1b: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg1RoleGrant, + }, + // Child grants from child org2 to proj2a/proj2b: + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant1, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant2, + }, + + // Grants from global to every org: + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantGlobalRoleGrant, + }, + + // Grants from global to every org and project: + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Grant: descendantGrantGlobalRoleGrant, + }, + } + + multiGrantTuplesCache := new([]iam.MultiGrantTuple) + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId, iam.WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + assert.ElementsMatch(t, grantTuples, expGrantTuples) + }) + + t.Run("acl-grants", func(t *testing.T) { + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId) + require.NoError(t, err) + grants := make([]perms.Grant, 0, len(grantTuples)) + for _, gt := range grantTuples { + grant, err := perms.Parse(ctx, gt) + require.NoError(t, err) + grants = append(grants, grant) + } + acl := perms.NewACL(grants...) + + t.Run("descendant-grants", func(t *testing.T) { + descendantGrants := acl.DescendantsGrants() + expDescendantGrants := []perms.AclGrant{ + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + } + assert.ElementsMatch(t, descendantGrants, expDescendantGrants) + }) + + t.Run("child-grants", func(t *testing.T) { + childrenGrants := acl.ChildrenScopeGrantMap() + expChildrenGrants := map[string][]perms.AclGrant{ + childGrantOrg1.PublicId: { + { + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.AddMembers: true, action.RemoveMembers: true}, + }, + }, + childGrantOrg2.PublicId: { + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.SetMembers: true}, + }, + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.Delete: true}, + }, + }, + scope.Global.String(): { + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + } + assert.Len(t, childrenGrants, len(expChildrenGrants)) + for k, v := range childrenGrants { + assert.ElementsMatch(t, v, expChildrenGrants[k]) + } + }) + + t.Run("direct-grants", func(t *testing.T) { + directGrants := acl.DirectScopeGrantMap() + expDirectGrants := map[string][]perms.AclGrant{ + directGrantOrg1.PublicId: { + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.Create: true, action.List: true}, + }, + }, + directGrantProj1a.PublicId: { + { + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.AddMembers: true, action.Read: true}, + }, + }, + directGrantProj1b.PublicId: { + { + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantOrg2.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantProj2a.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_1234abcd", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + directGrantProj2b.PublicId: { + { + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Id: "cs_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.Update: true, action.Read: true}, + }, + }, + } + /* + log.Println("org1", directGrantOrg1.PublicId) + log.Println("proj1a", directGrantProj1a.PublicId) + log.Println("proj1b", directGrantProj1b.PublicId) + log.Println("org2", directGrantOrg2.PublicId) + log.Println("proj2a", directGrantProj2a.PublicId) + log.Println("proj2b", directGrantProj2b.PublicId) + */ + assert.Len(t, directGrants, len(expDirectGrants)) + for k, v := range directGrants { + assert.ElementsMatch(t, v, expDirectGrants[k]) + } + }) + }) + }) +} diff --git a/internal/iam/repository_role_grant_test.go b/internal/iam/repository_role_grant_test.go index ba90363692..af0fa7246d 100644 --- a/internal/iam/repository_role_grant_test.go +++ b/internal/iam/repository_role_grant_test.go @@ -589,42 +589,12 @@ func TestGrantsForUser(t *testing.T) { // The first org/project do not have any direct grants to the user. They // contain roles but the user is not a principal. - noGrantOrg1, noGrantProj1 := TestScopes( - t, - repo, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) - noGrantOrg1Role := TestRole(t, conn, noGrantOrg1.PublicId) - TestRoleGrant(t, conn, noGrantOrg1Role.PublicId, "ids=*;type=scope;actions=*") - noGrantProj1Role := TestRole(t, conn, noGrantProj1.PublicId) - TestRoleGrant(t, conn, noGrantProj1Role.PublicId, "ids=*;type=*;actions=*") - noGrantOrg2, noGrantProj2 := TestScopes( - t, - repo, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) - noGrantOrg2Role := TestRole(t, conn, noGrantOrg2.PublicId) - TestRoleGrant(t, conn, noGrantOrg2Role.PublicId, "ids=*;type=scope;actions=*") - noGrantProj2Role := TestRole(t, conn, noGrantProj2.PublicId) - TestRoleGrant(t, conn, noGrantProj2Role.PublicId, "ids=*;type=*;actions=*") + noGrantOrg1, noGrantProj1 := SetupNoGrantScopes(t, conn, repo) + noGrantOrg2, noGrantProj2 := SetupNoGrantScopes(t, conn, repo) // The second org/project set contains direct grants, but without // inheritance. We create two roles in each project. - directGrantOrg1, directGrantProj1a := TestScopes( - t, - repo, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) - directGrantProj1b := TestProject( - t, - repo, - directGrantOrg1.PublicId, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) + directGrantOrg1, directGrantProj1a, directGrantProj1b := SetupDirectGrantScopes(t, conn, repo) directGrantOrg1Role := TestRole(t, conn, directGrantOrg1.PublicId) TestUserRole(t, conn, directGrantOrg1Role.PublicId, user.PublicId) directGrantOrg1RoleGrant1 := "ids=*;type=*;actions=*" @@ -641,19 +611,7 @@ func TestGrantsForUser(t *testing.T) { directGrantProj1bRoleGrant := "ids=*;type=session;actions=list,read" TestRoleGrant(t, conn, directGrantProj1bRole.PublicId, directGrantProj1bRoleGrant) - directGrantOrg2, directGrantProj2a := TestScopes( - t, - repo, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) - directGrantProj2b := TestProject( - t, - repo, - directGrantOrg2.PublicId, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) + directGrantOrg2, directGrantProj2a, directGrantProj2b := SetupDirectGrantScopes(t, conn, repo) directGrantOrg2Role := TestRole(t, conn, directGrantOrg2.PublicId, WithGrantScopeIds([]string{ globals.GrantScopeThis, @@ -676,12 +634,7 @@ func TestGrantsForUser(t *testing.T) { // For the third set we create a couple of orgs/projects and then use // globals.GrantScopeChildren. - childGrantOrg1, childGrantOrg1Proj := TestScopes( - t, - repo, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) + childGrantOrg1, childGrantOrg1Proj := SetupChildGrantScopes(t, conn, repo) childGrantOrg1Role := TestRole(t, conn, childGrantOrg1.PublicId, WithGrantScopeIds([]string{ globals.GrantScopeChildren, @@ -689,12 +642,8 @@ func TestGrantsForUser(t *testing.T) { TestUserRole(t, conn, childGrantOrg1Role.PublicId, user.PublicId) childGrantOrg1RoleGrant := "ids=*;type=host-set;actions=add-hosts,remove-hosts" TestRoleGrant(t, conn, childGrantOrg1Role.PublicId, childGrantOrg1RoleGrant) - childGrantOrg2, childGrantOrg2Proj := TestScopes( - t, - repo, - WithSkipAdminRoleCreation(true), - WithSkipDefaultRoleCreation(true), - ) + + childGrantOrg2, childGrantOrg2Proj := SetupChildGrantScopes(t, conn, repo) childGrantOrg2Role := TestRole(t, conn, childGrantOrg2.PublicId, WithGrantScopeIds([]string{ globals.GrantScopeChildren, @@ -725,7 +674,7 @@ func TestGrantsForUser(t *testing.T) { t.Run("db-grants", func(t *testing.T) { // Here we should see exactly what the DB has returned, before we do some // local exploding of grants and grant scopes - expMultiGrantTuples := []multiGrantTuple{ + expMultiGrantTuples := []MultiGrantTuple{ // No grants from noOrg/noProj // Direct org1/2: { @@ -801,11 +750,11 @@ func TestGrantsForUser(t *testing.T) { }, } for i, tuple := range expMultiGrantTuples { - tuple.testStableSort() + tuple.TestStableSort() expMultiGrantTuples[i] = tuple } - multiGrantTuplesCache := new([]multiGrantTuple) - _, err := repo.GrantsForUser(ctx, user.PublicId, withTestCacheMultiGrantTuples(multiGrantTuplesCache)) + multiGrantTuplesCache := new([]MultiGrantTuple) + _, err := repo.GrantsForUser(ctx, user.PublicId, WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) require.NoError(t, err) // log.Println("multiGrantTuplesCache", pretty.Sprint(*multiGrantTuplesCache)) @@ -939,8 +888,8 @@ func TestGrantsForUser(t *testing.T) { }, } - multiGrantTuplesCache := new([]multiGrantTuple) - grantTuples, err := repo.GrantsForUser(ctx, user.PublicId, withTestCacheMultiGrantTuples(multiGrantTuplesCache)) + multiGrantTuplesCache := new([]MultiGrantTuple) + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId, WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) require.NoError(t, err) assert.ElementsMatch(t, grantTuples, expGrantTuples) }) @@ -1536,3 +1485,961 @@ func TestGrantsForUser(t *testing.T) { } }) } + +func TestGrantsForUser_Group(t *testing.T) { + ctx := context.Background() + + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + + repo := TestRepo(t, conn, wrap) + user := TestUser(t, repo, "global") + group := TestGroup(t, conn, "global") + + TestGroupMember(t, conn, group.PublicId, user.PublicId) + + // Create a series of scopes with roles in each. We'll create two of each + // kind to ensure we're not just picking up the first role in each. + + // The first org/project do not have any direct grants to the user. They + // contain roles but the user is not a principal. + noGrantOrg1, noGrantProj1 := SetupNoGrantScopes(t, conn, repo) + noGrantOrg2, noGrantProj2 := SetupNoGrantScopes(t, conn, repo) + + // The second org/project set contains direct grants, but without + // inheritance. We create two roles in each project. + directGrantOrg1, directGrantProj1a, directGrantProj1b := SetupDirectGrantScopes(t, conn, repo) + directGrantOrg1Role := TestRole(t, conn, directGrantOrg1.PublicId) + TestGroupRole(t, conn, directGrantOrg1Role.PublicId, group.PublicId) + directGrantOrg1RoleGrant1 := "ids=*;type=*;actions=*" + TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant1) + directGrantOrg1RoleGrant2 := "ids=*;type=role;actions=list,read" + TestRoleGrant(t, conn, directGrantOrg1Role.PublicId, directGrantOrg1RoleGrant2) + + directGrantProj1aRole := TestRole(t, conn, directGrantProj1a.PublicId) + TestGroupRole(t, conn, directGrantProj1aRole.PublicId, group.PublicId) + directGrantProj1aRoleGrant := "ids=*;type=target;actions=authorize-session,read" + TestRoleGrant(t, conn, directGrantProj1aRole.PublicId, directGrantProj1aRoleGrant) + directGrantProj1bRole := TestRole(t, conn, directGrantProj1b.PublicId) + TestGroupRole(t, conn, directGrantProj1bRole.PublicId, group.PublicId) + directGrantProj1bRoleGrant := "ids=*;type=session;actions=list,read" + TestRoleGrant(t, conn, directGrantProj1bRole.PublicId, directGrantProj1bRoleGrant) + + directGrantOrg2, directGrantProj2a, directGrantProj2b := SetupDirectGrantScopes(t, conn, repo) + directGrantOrg2Role := TestRole(t, conn, directGrantOrg2.PublicId, + WithGrantScopeIds([]string{ + globals.GrantScopeThis, + directGrantProj2a.PublicId, + })) + TestGroupRole(t, conn, directGrantOrg2Role.PublicId, group.PublicId) + directGrantOrg2RoleGrant1 := "ids=*;type=user;actions=*" + TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant1) + directGrantOrg2RoleGrant2 := "ids=*;type=group;actions=list,read" + TestRoleGrant(t, conn, directGrantOrg2Role.PublicId, directGrantOrg2RoleGrant2) + + directGrantProj2aRole := TestRole(t, conn, directGrantProj2a.PublicId) + TestGroupRole(t, conn, directGrantProj2aRole.PublicId, group.PublicId) + directGrantProj2aRoleGrant := "ids=hcst_abcd1234,hcst_1234abcd;actions=*" + TestRoleGrant(t, conn, directGrantProj2aRole.PublicId, directGrantProj2aRoleGrant) + directGrantProj2bRole := TestRole(t, conn, directGrantProj2b.PublicId) + TestGroupRole(t, conn, directGrantProj2bRole.PublicId, group.PublicId) + directGrantProj2bRoleGrant := "ids=cs_abcd1234;actions=read,update" + TestRoleGrant(t, conn, directGrantProj2bRole.PublicId, directGrantProj2bRoleGrant) + + // For the third set we create a couple of orgs/projects and then use + // globals.GrantScopeChildren. + childGrantOrg1, childGrantOrg1Proj := SetupChildGrantScopes(t, conn, repo) + childGrantOrg1Role := TestRole(t, conn, childGrantOrg1.PublicId, + WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + TestGroupRole(t, conn, childGrantOrg1Role.PublicId, group.PublicId) + childGrantOrg1RoleGrant := "ids=*;type=host-set;actions=add-hosts,remove-hosts" + TestRoleGrant(t, conn, childGrantOrg1Role.PublicId, childGrantOrg1RoleGrant) + + childGrantOrg2, childGrantOrg2Proj := SetupChildGrantScopes(t, conn, repo) + childGrantOrg2Role := TestRole(t, conn, childGrantOrg2.PublicId, + WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + TestGroupRole(t, conn, childGrantOrg2Role.PublicId, group.PublicId) + childGrantOrg2RoleGrant1 := "ids=*;type=session;actions=cancel:self" + TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant1) + childGrantOrg2RoleGrant2 := "ids=*;type=session;actions=read:self" + TestRoleGrant(t, conn, childGrantOrg2Role.PublicId, childGrantOrg2RoleGrant2) + + // Finally, let's create some roles at global scope with children and + // descendants grants + childGrantGlobalRole := TestRole(t, conn, scope.Global.String(), + WithGrantScopeIds([]string{ + globals.GrantScopeChildren, + })) + TestUserRole(t, conn, childGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + childGrantGlobalRoleGrant := "ids=*;type=account;actions=*" + TestRoleGrant(t, conn, childGrantGlobalRole.PublicId, childGrantGlobalRoleGrant) + descendantGrantGlobalRole := TestRole(t, conn, scope.Global.String(), + WithGrantScopeIds([]string{ + globals.GrantScopeDescendants, + })) + TestUserRole(t, conn, descendantGrantGlobalRole.PublicId, globals.AnyAuthenticatedUserId) + descendantGrantGlobalRoleGrant := "ids=*;type=credential;actions=*" + TestRoleGrant(t, conn, descendantGrantGlobalRole.PublicId, descendantGrantGlobalRoleGrant) + + t.Run("db-grants", func(t *testing.T) { + // Here we should see exactly what the DB has returned, before we do some + // local exploding of grants and grant scopes + expMultiGrantTuples := []MultiGrantTuple{ + // No grants from noOrg/noProj + // Direct org1/2: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeThis, + Grants: strings.Join([]string{directGrantOrg1RoleGrant1, directGrantOrg1RoleGrant2}, "^"), + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: strings.Join([]string{globals.GrantScopeThis, directGrantProj2a.PublicId}, "^"), + Grants: strings.Join([]string{directGrantOrg2RoleGrant1, directGrantOrg2RoleGrant2}, "^"), + }, + // Proj orgs 1/2: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1aRoleGrant, + }, + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj1bRoleGrant, + }, + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2aRoleGrant, + }, + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeIds: globals.GrantScopeThis, + Grants: directGrantProj2bRoleGrant, + }, + // Child grants from orgs 1/2: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantOrg1RoleGrant, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: strings.Join([]string{childGrantOrg2RoleGrant1, childGrantOrg2RoleGrant2}, "^"), + }, + // Children of global and descendants of global + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeDescendants, + Grants: descendantGrantGlobalRoleGrant, + }, + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeIds: globals.GrantScopeChildren, + Grants: childGrantGlobalRoleGrant, + }, + } + for i, tuple := range expMultiGrantTuples { + tuple.TestStableSort() + expMultiGrantTuples[i] = tuple + } + multiGrantTuplesCache := new([]MultiGrantTuple) + _, err := repo.GrantsForUser(ctx, user.PublicId, WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + + // log.Println("multiGrantTuplesCache", pretty.Sprint(*multiGrantTuplesCache)) + assert.ElementsMatch(t, *multiGrantTuplesCache, expMultiGrantTuples) + }) + + t.Run("exploded-grants", func(t *testing.T) { + // We expect to see: + // + // * No grants from noOrg/noProj + // * Grants from direct orgs/projs: + // * directGrantOrg1/directGrantOrg2 on org and respective projects (6 grants total per org) + // * directGrantProj on respective projects (4 grants total) + expGrantTuples := []perms.GrantTuple{ + // No grants from noOrg/noProj + // Grants from direct org1 to org1/proj1a/proj1b: + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant1, + }, + { + RoleId: directGrantOrg1Role.PublicId, + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Grant: directGrantOrg1RoleGrant2, + }, + // Grants from direct org 1 proj 1a: + { + RoleId: directGrantProj1aRole.PublicId, + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Grant: directGrantProj1aRoleGrant, + }, + // Grant from direct org 1 proj 1 b: + { + RoleId: directGrantProj1bRole.PublicId, + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Grant: directGrantProj1bRoleGrant, + }, + + // Grants from direct org2 to org2/proj2a/proj2b: + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant1, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + { + RoleId: directGrantOrg2Role.PublicId, + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantOrg2RoleGrant2, + }, + // Grants from direct org 2 proj 2a: + { + RoleId: directGrantProj2aRole.PublicId, + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Grant: directGrantProj2aRoleGrant, + }, + // Grant from direct org 2 proj 2 b: + { + RoleId: directGrantProj2bRole.PublicId, + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Grant: directGrantProj2bRoleGrant, + }, + // Child grants from child org1 to proj1a/proj1b: + { + RoleId: childGrantOrg1Role.PublicId, + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg1RoleGrant, + }, + // Child grants from child org2 to proj2a/proj2b: + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant1, + }, + { + RoleId: childGrantOrg2Role.PublicId, + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantOrg2RoleGrant2, + }, + + // Grants from global to every org: + { + RoleId: childGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Grant: childGrantGlobalRoleGrant, + }, + + // Grants from global to every org and project: + { + RoleId: descendantGrantGlobalRole.PublicId, + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Grant: descendantGrantGlobalRoleGrant, + }, + } + + multiGrantTuplesCache := new([]MultiGrantTuple) + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId, WithTestCacheMultiGrantTuples(multiGrantTuplesCache)) + require.NoError(t, err) + assert.ElementsMatch(t, grantTuples, expGrantTuples) + }) + + t.Run("acl-grants", func(t *testing.T) { + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId) + require.NoError(t, err) + grants := make([]perms.Grant, 0, len(grantTuples)) + for _, gt := range grantTuples { + grant, err := perms.Parse(ctx, gt) + require.NoError(t, err) + grants = append(grants, grant) + } + acl := perms.NewACL(grants...) + + t.Run("descendant-grants", func(t *testing.T) { + descendantGrants := acl.DescendantsGrants() + expDescendantGrants := []perms.AclGrant{ + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeDescendants, + Id: "*", + Type: resource.Credential, + ActionSet: perms.ActionSet{action.All: true}, + }, + } + assert.ElementsMatch(t, descendantGrants, expDescendantGrants) + }) + + t.Run("child-grants", func(t *testing.T) { + childrenGrants := acl.ChildrenScopeGrantMap() + expChildrenGrants := map[string][]perms.AclGrant{ + childGrantOrg1.PublicId: { + { + RoleScopeId: childGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.HostSet, + ActionSet: perms.ActionSet{action.AddHosts: true, action.RemoveHosts: true}, + }, + }, + childGrantOrg2.PublicId: { + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Session, + ActionSet: perms.ActionSet{action.CancelSelf: true}, + }, + { + RoleScopeId: childGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Session, + ActionSet: perms.ActionSet{action.ReadSelf: true}, + }, + }, + scope.Global.String(): { + { + RoleScopeId: scope.Global.String(), + GrantScopeId: globals.GrantScopeChildren, + Id: "*", + Type: resource.Account, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + } + assert.Len(t, childrenGrants, len(expChildrenGrants)) + for k, v := range childrenGrants { + assert.ElementsMatch(t, v, expChildrenGrants[k]) + } + }) + + t.Run("direct-grants", func(t *testing.T) { + directGrants := acl.DirectScopeGrantMap() + expDirectGrants := map[string][]perms.AclGrant{ + directGrantOrg1.PublicId: { + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.All, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg1.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg1.PublicId, + Id: "*", + Type: resource.Role, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantProj1a.PublicId: { + { + RoleScopeId: directGrantProj1a.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1a.PublicId, + Id: "*", + Type: resource.Target, + ActionSet: perms.ActionSet{action.AuthorizeSession: true, action.Read: true}, + }, + }, + directGrantProj1b.PublicId: { + { + RoleScopeId: directGrantProj1b.PublicId, + RoleParentScopeId: directGrantOrg1.PublicId, + GrantScopeId: directGrantProj1b.PublicId, + Id: "*", + Type: resource.Session, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantOrg2.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.User, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantOrg2.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + }, + directGrantProj2a.PublicId: { + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.User, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantOrg2.PublicId, + RoleParentScopeId: scope.Global.String(), + GrantScopeId: directGrantProj2a.PublicId, + Id: "*", + Type: resource.Group, + ActionSet: perms.ActionSet{action.List: true, action.Read: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + { + RoleScopeId: directGrantProj2a.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2a.PublicId, + Id: "hcst_1234abcd", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.All: true}, + }, + }, + directGrantProj2b.PublicId: { + { + RoleScopeId: directGrantProj2b.PublicId, + RoleParentScopeId: directGrantOrg2.PublicId, + GrantScopeId: directGrantProj2b.PublicId, + Id: "cs_abcd1234", + Type: resource.Unknown, + ActionSet: perms.ActionSet{action.Update: true, action.Read: true}, + }, + }, + } + /* + log.Println("org1", directGrantOrg1.PublicId) + log.Println("proj1a", directGrantProj1a.PublicId) + log.Println("proj1b", directGrantProj1b.PublicId) + log.Println("org2", directGrantOrg2.PublicId) + log.Println("proj2a", directGrantProj2a.PublicId) + log.Println("proj2b", directGrantProj2b.PublicId) + */ + assert.Len(t, directGrants, len(expDirectGrants)) + for k, v := range directGrants { + assert.ElementsMatch(t, v, expDirectGrants[k]) + } + }) + }) + t.Run("real-world", func(t *testing.T) { + // These tests cases crib from the initial setup of the grants, and + // include a number of cases to ensure the ones that should work do and + // various that should not do not + type testCase struct { + name string + res perms.Resource + act action.Type + shouldWork bool + } + testCases := []testCase{} + + // These test cases should fail because the grants are in roles where + // the user is not a principal + { + testCases = append(testCases, testCase{ + name: "nogrant-a", + res: perms.Resource{ + ScopeId: noGrantOrg1.PublicId, + Id: "u_abcd1234", + Type: resource.Scope, + ParentScopeId: scope.Global.String(), + }, + act: action.Read, + }, testCase{ + name: "nogrant-b", + res: perms.Resource{ + ScopeId: noGrantProj1.PublicId, + Id: "u_abcd1234", + Type: resource.User, + ParentScopeId: noGrantOrg1.String(), + }, + act: action.Read, + }, testCase{ + name: "nogrant-c", + res: perms.Resource{ + ScopeId: noGrantOrg2.PublicId, + Id: "u_abcd1234", + Type: resource.Scope, + ParentScopeId: scope.Global.String(), + }, + act: action.Read, + }, testCase{ + name: "nogrant-d", + res: perms.Resource{ + ScopeId: noGrantProj2.PublicId, + Id: "u_abcd1234", + Type: resource.User, + ParentScopeId: noGrantOrg2.String(), + }, + act: action.Read, + }, + ) + } + // These test cases are for org1 and its projects where the grants are + // direct, not via children/descendants. They test some actions that + // should work and some that shouldn't. + { + testCases = append(testCases, testCase{ + name: "direct-a", + res: perms.Resource{ + ScopeId: directGrantOrg1.PublicId, + Id: "u_abcd1234", + Type: resource.User, + ParentScopeId: scope.Global.String(), + }, + act: action.Read, + shouldWork: true, + }, testCase{ + name: "direct-b", + res: perms.Resource{ + ScopeId: directGrantOrg1.PublicId, + Id: "r_abcd1234", + Type: resource.Role, + ParentScopeId: scope.Global.String(), + }, + act: action.Read, + shouldWork: true, + }, testCase{ + name: "direct-c", + res: perms.Resource{ + ScopeId: directGrantProj1a.PublicId, + Id: "ttcp_abcd1234", + Type: resource.Target, + ParentScopeId: directGrantOrg1.PublicId, + }, + act: action.AuthorizeSession, + shouldWork: true, + }, testCase{ + name: "direct-d", + res: perms.Resource{ + ScopeId: directGrantProj1a.PublicId, + Id: "s_abcd1234", + Type: resource.Session, + ParentScopeId: directGrantOrg1.PublicId, + }, + act: action.Read, + }, testCase{ + name: "direct-e", + res: perms.Resource{ + ScopeId: directGrantProj1b.PublicId, + Id: "ttcp_abcd1234", + Type: resource.Target, + ParentScopeId: directGrantOrg1.PublicId, + }, + act: action.AuthorizeSession, + }, testCase{ + name: "direct-f", + res: perms.Resource{ + ScopeId: directGrantProj1b.PublicId, + Id: "s_abcd1234", + Type: resource.Session, + ParentScopeId: directGrantOrg1.PublicId, + }, + act: action.Read, + shouldWork: true, + }, + ) + } + // These test cases are for org2 and its projects where the grants are + // direct, not via children/descendants. They test some actions that + // should work and some that shouldn't. + { + testCases = append(testCases, testCase{ + name: "direct-g", + res: perms.Resource{ + ScopeId: directGrantOrg2.PublicId, + Id: "u_abcd1234", + Type: resource.User, + ParentScopeId: scope.Global.String(), + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "direct-m", + res: perms.Resource{ + ScopeId: directGrantOrg2.PublicId, + Id: "g_abcd1234", + Type: resource.Group, + ParentScopeId: scope.Global.String(), + }, + act: action.Update, + }, testCase{ + name: "direct-h", + res: perms.Resource{ + ScopeId: directGrantOrg2.PublicId, + Id: "acct_abcd1234", + Type: resource.Account, + ParentScopeId: scope.Global.String(), + }, + act: action.Delete, + shouldWork: true, + }, testCase{ + name: "direct-i", + res: perms.Resource{ + ScopeId: directGrantProj2a.PublicId, + Type: resource.Group, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.List, + shouldWork: true, + }, testCase{ + name: "direct-j", + res: perms.Resource{ + ScopeId: directGrantProj2a.PublicId, + Id: "r_abcd1234", + Type: resource.Role, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.Read, + }, testCase{ + name: "direct-n", + res: perms.Resource{ + ScopeId: directGrantProj2a.PublicId, + Id: "u_abcd1234", + Type: resource.User, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.Read, + shouldWork: true, + }, testCase{ + name: "direct-k", + res: perms.Resource{ + ScopeId: directGrantProj2a.PublicId, + Id: "hcst_abcd1234", + Type: resource.HostCatalog, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.Read, + shouldWork: true, + }, testCase{ + name: "direct-l", + res: perms.Resource{ + ScopeId: directGrantProj2b.PublicId, + Id: "cs_abcd1234", + Type: resource.CredentialStore, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.Update, + shouldWork: true, + }, + testCase{ + name: "direct-m", + res: perms.Resource{ + ScopeId: directGrantProj2b.PublicId, + Id: "cl_abcd1234", + Type: resource.CredentialLibrary, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.Update, + }, + ) + } + // These test cases are child grants + { + testCases = append(testCases, testCase{ + name: "children-a", + res: perms.Resource{ + ScopeId: scope.Global.String(), + Id: "a_abcd1234", + Type: resource.Account, + }, + act: action.Update, + }, testCase{ + name: "children-b", + res: perms.Resource{ + ScopeId: noGrantOrg1.PublicId, + Id: "a_abcd1234", + Type: resource.Account, + ParentScopeId: scope.Global.String(), + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "children-c", + res: perms.Resource{ + ScopeId: directGrantOrg1.PublicId, + Id: "a_abcd1234", + Type: resource.Account, + ParentScopeId: scope.Global.String(), + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "children-d", + res: perms.Resource{ + ScopeId: directGrantOrg2.PublicId, + Id: "a_abcd1234", + Type: resource.Account, + ParentScopeId: scope.Global.String(), + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "children-e", + res: perms.Resource{ + ScopeId: childGrantOrg2.PublicId, + Id: "s_abcd1234", + Type: resource.Session, + ParentScopeId: scope.Global.String(), + }, + act: action.CancelSelf, + }, testCase{ + name: "children-f", + res: perms.Resource{ + ScopeId: childGrantOrg1Proj.PublicId, + Id: "s_abcd1234", + Type: resource.Session, + ParentScopeId: childGrantOrg1.PublicId, + }, + act: action.CancelSelf, + }, testCase{ + name: "children-g", + res: perms.Resource{ + ScopeId: childGrantOrg2Proj.PublicId, + Id: "s_abcd1234", + Type: resource.Session, + ParentScopeId: childGrantOrg2.PublicId, + }, + act: action.CancelSelf, + shouldWork: true, + }, testCase{ + name: "children-h", + res: perms.Resource{ + ScopeId: childGrantOrg2Proj.PublicId, + Id: "s_abcd1234", + Type: resource.Session, + ParentScopeId: childGrantOrg2.PublicId, + }, + act: action.CancelSelf, + shouldWork: true, + }, testCase{ + name: "children-i", + res: perms.Resource{ + ScopeId: childGrantOrg1.PublicId, + Id: "hsst_abcd1234", + Type: resource.HostSet, + ParentScopeId: scope.Global.String(), + }, + act: action.AddHosts, + }, testCase{ + name: "children-j", + res: perms.Resource{ + ScopeId: childGrantOrg1Proj.PublicId, + Id: "hsst_abcd1234", + Type: resource.HostSet, + ParentScopeId: childGrantOrg1.PublicId, + }, + act: action.AddHosts, + shouldWork: true, + }, testCase{ + name: "children-k", + res: perms.Resource{ + ScopeId: childGrantOrg2Proj.PublicId, + Id: "hsst_abcd1234", + Type: resource.HostSet, + ParentScopeId: childGrantOrg2.PublicId, + }, + act: action.AddHosts, + }, + ) + } + // These test cases are global descendants grants + { + testCases = append(testCases, testCase{ + name: "descendants-a", + res: perms.Resource{ + ScopeId: scope.Global.String(), + Id: "cs_abcd1234", + Type: resource.Credential, + }, + act: action.Update, + }, testCase{ + name: "descendants-b", + res: perms.Resource{ + ScopeId: noGrantProj1.PublicId, + Id: "cs_abcd1234", + Type: resource.Credential, + ParentScopeId: noGrantOrg1.PublicId, + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "descendants-c", + res: perms.Resource{ + ScopeId: directGrantOrg2.PublicId, + Id: "cs_abcd1234", + Type: resource.Credential, + ParentScopeId: scope.Global.String(), + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "descendants-d", + res: perms.Resource{ + ScopeId: directGrantProj1a.PublicId, + Id: "cs_abcd1234", + Type: resource.Credential, + ParentScopeId: directGrantOrg1.PublicId, + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "descendants-e", + res: perms.Resource{ + ScopeId: directGrantProj1a.PublicId, + Id: "cs_abcd1234", + Type: resource.Credential, + ParentScopeId: directGrantOrg1.PublicId, + }, + act: action.Update, + shouldWork: true, + }, testCase{ + name: "descendants-f", + res: perms.Resource{ + ScopeId: directGrantProj2b.PublicId, + Id: "cs_abcd1234", + Type: resource.Credential, + ParentScopeId: directGrantOrg2.PublicId, + }, + act: action.Update, + shouldWork: true, + }, + ) + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + grantTuples, err := repo.GrantsForUser(ctx, user.PublicId) + require.NoError(t, err) + grants := make([]perms.Grant, 0, len(grantTuples)) + for _, gt := range grantTuples { + grant, err := perms.Parse(ctx, gt) + require.NoError(t, err) + grants = append(grants, grant) + } + acl := perms.NewACL(grants...) + assert.True(t, acl.Allowed(tc.res, tc.act, "u_abc123").Authorized == tc.shouldWork) + }) + } + }) +} + +func SetupNoGrantScopes(t *testing.T, conn *db.DB, repo *Repository) (noGrantOrg, noGrantProj *Scope) { + t.Helper() + noGrantOrg, noGrantProj = TestScopes( + t, + repo, + WithSkipAdminRoleCreation(true), + WithSkipDefaultRoleCreation(true), + ) + noGrantOrg1Role := TestRole(t, conn, noGrantOrg.PublicId) + TestRoleGrant(t, conn, noGrantOrg1Role.PublicId, "ids=*;type=scope;actions=*") + noGrantProj1Role := TestRole(t, conn, noGrantProj.PublicId) + TestRoleGrant(t, conn, noGrantProj1Role.PublicId, "ids=*;type=*;actions=*") + return +} + +func SetupDirectGrantScopes(t *testing.T, conn *db.DB, repo *Repository) (directGrantOrg, directGrantProjA, directGrantProjB *Scope) { + t.Helper() + directGrantOrg, directGrantProjA = TestScopes( + t, + repo, + WithSkipAdminRoleCreation(true), + WithSkipDefaultRoleCreation(true), + ) + directGrantProjB = TestProject( + t, + repo, + directGrantOrg.PublicId, + WithSkipAdminRoleCreation(true), + WithSkipDefaultRoleCreation(true), + ) + return +} + +func SetupChildGrantScopes(t *testing.T, conn *db.DB, repo *Repository) (childGrantOrg, childGrantOrgProj *Scope) { + t.Helper() + childGrantOrg, childGrantOrgProj = TestScopes( + t, + repo, + WithSkipAdminRoleCreation(true), + WithSkipDefaultRoleCreation(true), + ) + return +}