From 4e0c2d79ac886e8d8804bcba12aaf2f6ff84a722 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 25 Jun 2024 13:05:52 -0400 Subject: [PATCH] Add grant scope IDs to role list (#4893) This adds grant_scope_ids in the output of role listing. The lookup for grant scope IDs was changed to allow taking in a set of roles instead of just a single role, and the results are collated afterwards. --------- Co-authored-by: Johan Brandhorst-Satzkorn --- CHANGELOG.md | 2 ++ .../controller/handlers/roles/role_service.go | 2 +- .../handlers/roles/role_service_test.go | 9 +++++- internal/iam/repository_role.go | 28 +++++++++++++++---- internal/iam/repository_role_grant.go | 19 ++++++++++--- internal/iam/role.go | 9 ++++-- internal/iam/service_list_ext_test.go | 2 +- internal/iam/testing.go | 1 + 8 files changed, 58 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9458c9b2f..03c330cdbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ assumed to be the incoming worker key, which caused the wrong key information to * Allow descriptions to contain newlines and other whitespace ([PR](https://github.com/hashicorp/boundary/pull/2599)) +* Listed roles contain grant scope ID information + ([PR](https://github.com/hashicorp/boundary/pull/4893)) ### Deprecations/Changes diff --git a/internal/daemon/controller/handlers/roles/role_service.go b/internal/daemon/controller/handlers/roles/role_service.go index c99d004d9e..091777296b 100644 --- a/internal/daemon/controller/handlers/roles/role_service.go +++ b/internal/daemon/controller/handlers/roles/role_service.go @@ -209,7 +209,7 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs if !ok { continue } - item, err := toProto(ctx, item, nil, nil, nil, outputOpts...) + item, err := toProto(ctx, item, nil, nil, item.GrantScopes, outputOpts...) if err != nil { return nil, errors.Wrap(ctx, err, op) } diff --git a/internal/daemon/controller/handlers/roles/role_service_test.go b/internal/daemon/controller/handlers/roles/role_service_test.go index 8a3e4258ca..05524d693f 100644 --- a/internal/daemon/controller/handlers/roles/role_service_test.go +++ b/internal/daemon/controller/handlers/roles/role_service_test.go @@ -220,6 +220,7 @@ func TestList(t *testing.T) { var totalRoles []*pb.Role for i := 0; i < 10; i++ { or := iam.TestRole(t, conn, oWithRoles.GetPublicId()) + _ = iam.TestRoleGrantScope(t, conn, or.GetPublicId(), globals.GrantScopeChildren) wantOrgRoles = append(wantOrgRoles, &pb.Role{ Id: or.GetPublicId(), ScopeId: or.GetScopeId(), @@ -228,6 +229,7 @@ func TestList(t *testing.T) { UpdatedTime: or.GetUpdateTime().GetTimestamp(), Version: or.GetVersion(), AuthorizedActions: testAuthorizedActions, + GrantScopeIds: []string{"this", "children"}, }) totalRoles = append(totalRoles, wantOrgRoles[i]) pr := iam.TestRole(t, conn, pWithRoles.GetPublicId()) @@ -239,6 +241,7 @@ func TestList(t *testing.T) { UpdatedTime: pr.GetUpdateTime().GetTimestamp(), Version: pr.GetVersion(), AuthorizedActions: testAuthorizedActions, + GrantScopeIds: []string{"this"}, }) totalRoles = append(totalRoles, wantProjRoles[i]) } @@ -422,7 +425,7 @@ func TestList(t *testing.T) { } func roleToProto(r *iam.Role, scope *scopes.ScopeInfo, authorizedActions []string) *pb.Role { - return &pb.Role{ + ret := &pb.Role{ Id: r.GetPublicId(), ScopeId: r.GetScopeId(), Scope: scope, @@ -431,6 +434,10 @@ func roleToProto(r *iam.Role, scope *scopes.ScopeInfo, authorizedActions []strin Version: r.GetVersion(), AuthorizedActions: testAuthorizedActions, } + for _, r := range r.GrantScopes { + ret.GrantScopeIds = append(ret.GrantScopeIds, r.ScopeIdOrSpecial) + } + return ret } func TestListPagination(t *testing.T) { diff --git a/internal/iam/repository_role.go b/internal/iam/repository_role.go index abc7633b68..9503303b10 100644 --- a/internal/iam/repository_role.go +++ b/internal/iam/repository_role.go @@ -185,7 +185,7 @@ func (r *Repository) LookupRole(ctx context.Context, withPublicId string, opt .. if err != nil { return errors.Wrap(ctx, err, op) } - rgs, err = repo.ListRoleGrantScopes(ctx, withPublicId) + rgs, err = repo.ListRoleGrantScopes(ctx, []string{withPublicId}) if err != nil { return errors.Wrap(ctx, err, op) } @@ -311,20 +311,38 @@ func (r *Repository) queryRoles(ctx context.Context, whereClause string, args [] const op = "iam.(Repository).queryRoles" var transactionTimestamp time.Time - var ret []*Role + var retRoles []*Role + var retRoleGrantScopes []*RoleGrantScope if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(rd db.Reader, w db.Writer) error { var inRet []*Role if err := rd.SearchWhere(ctx, &inRet, whereClause, args, opt...); err != nil { - return errors.Wrap(ctx, err, op) + return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query roles")) } - ret = inRet + retRoles = inRet var err error + if len(retRoles) > 0 { + roleIds := make([]string, 0, len(retRoles)) + for _, retRole := range retRoles { + roleIds = append(roleIds, retRole.PublicId) + } + retRoleGrantScopes, err = r.ListRoleGrantScopes(ctx, roleIds) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query role grant scopes")) + } + } transactionTimestamp, err = rd.Now(ctx) return err }); err != nil { return nil, time.Time{}, err } - return ret, transactionTimestamp, nil + roleGrantScopesMap := make(map[string][]*RoleGrantScope) + for _, rgs := range retRoleGrantScopes { + roleGrantScopesMap[rgs.RoleId] = append(roleGrantScopesMap[rgs.RoleId], rgs) + } + for _, role := range retRoles { + role.GrantScopes = roleGrantScopesMap[role.PublicId] + } + return retRoles, transactionTimestamp, nil } // listRoleDeletedIds lists the public IDs of any roles deleted since the timestamp provided. diff --git a/internal/iam/repository_role_grant.go b/internal/iam/repository_role_grant.go index b0d2a89a2a..8e37e63f9f 100644 --- a/internal/iam/repository_role_grant.go +++ b/internal/iam/repository_role_grant.go @@ -391,13 +391,24 @@ func (r *Repository) ListRoleGrants(ctx context.Context, roleId string, opt ...O // ListRoleGrantScopes returns the grant scopes for the roleId and supports the WithLimit // option. -func (r *Repository) ListRoleGrantScopes(ctx context.Context, roleId string, opt ...Option) ([]*RoleGrantScope, error) { +func (r *Repository) ListRoleGrantScopes(ctx context.Context, roleIds []string, opt ...Option) ([]*RoleGrantScope, error) { const op = "iam.(Repository).ListRoleGrantScopes" - if roleId == "" { - return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id") + if len(roleIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role ids") + } + query := "?" + var args []any + for i, roleId := range roleIds { + if roleId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role ids") + } + if i > 0 { + query = query + ", ?" + } + args = append(args, roleId) } var roleGrantScopes []*RoleGrantScope - if err := r.list(ctx, &roleGrantScopes, "role_id = ?", []any{roleId}, opt...); err != nil { + if err := r.list(ctx, &roleGrantScopes, fmt.Sprintf("role_id in (%s)", query), args, opt...); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup role grant scopes")) } return roleGrantScopes, nil diff --git a/internal/iam/role.go b/internal/iam/role.go index 1117e47853..ed508eb207 100644 --- a/internal/iam/role.go +++ b/internal/iam/role.go @@ -23,7 +23,8 @@ const ( // Roles are granted permissions and assignable to Users and Groups. type Role struct { *store.Role - tableName string `gorm:"-"` + GrantScopes []*RoleGrantScope `gorm:"-"` + tableName string `gorm:"-"` } // ensure that Role implements the interfaces of: Resource, Cloneable, and db.VetForWriter. @@ -60,9 +61,13 @@ func allocRole() Role { // Clone creates a clone of the Role. func (role *Role) Clone() any { cp := proto.Clone(role.Role) - return &Role{ + ret := &Role{ Role: cp.(*store.Role), } + for _, grantScope := range role.GrantScopes { + ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope)) + } + return ret } // VetForWrite implements db.VetForWrite() interface. diff --git a/internal/iam/service_list_ext_test.go b/internal/iam/service_list_ext_test.go index 4f6b0fd4f4..95b024091a 100644 --- a/internal/iam/service_list_ext_test.go +++ b/internal/iam/service_list_ext_test.go @@ -57,7 +57,7 @@ func TestService_ListRoles(t *testing.T) { _, err = sqlDB.ExecContext(ctx, "analyze") require.NoError(t, err) - cmpIgnoreUnexportedOpts := cmpopts.IgnoreUnexported(iam.Role{}, store.Role{}, timestamp.Timestamp{}, timestamppb.Timestamp{}) + cmpIgnoreUnexportedOpts := cmpopts.IgnoreUnexported(iam.Role{}, store.Role{}, timestamp.Timestamp{}, timestamppb.Timestamp{}, iam.RoleGrantScope{}, store.RoleGrantScope{}) t.Run("List validation", func(t *testing.T) { t.Parallel() diff --git a/internal/iam/testing.go b/internal/iam/testing.go index 5f8c94160b..6af5836df0 100644 --- a/internal/iam/testing.go +++ b/internal/iam/testing.go @@ -216,6 +216,7 @@ func TestRole(t testing.TB, conn *db.DB, scopeId string, opt ...Option) *Role { gs, err := NewRoleGrantScope(ctx, id, gsi) require.NoError(err) require.NoError(rw.Create(ctx, gs)) + role.GrantScopes = append(role.GrantScopes, gs) } require.Equal(opts.withDescription, role.Description) require.Equal(opts.withName, role.Name)