Skip to content

Commit d8ff03b

Browse files
elimtdkanney
andauthored
feat: grantsForUser for Global Resources (#5612)
* feat: grantsForUser for Global Resources add query to fetch grants for a user for resources that are only globally scoped * Update query based on change to bifurcate individual table * Create subtests for different resources * Return grant.grant_scope instead of the request scope * Remove 'individual' subquery & unused reqScope parameter * Use sql.Named for better readability * Fix op function name * Remove individual grant scope logic from global resource repo function No need to handle individual grant scopes since global resources can only be queried via 'this' grant scope at the global scope. * Fix row scan order * Remove data gen function * Adjust query formatting Remove canonical_grant filter from query. `iam_grant.canonical_grant` is a primary key, so it can't be null anyway -- no need to filter out null canonical grants * Use the consts for u_auth and u_anon * Specify "empty" instead of "NULL" in struct field comment * Build query args with `pq.Array` instead of `fmt.Sprintf` * Fix TestGrantsForUserGlobalResources No longer using a hard-coded value for roleVersion * Refactor grantsForUserGlobalResources tests into testcases * go mod tidy * Update query comment for correctness --------- Co-authored-by: dkanney <[email protected]> Co-authored-by: dkanney <[email protected]>
1 parent 8336e48 commit d8ff03b

File tree

4 files changed

+368
-1
lines changed

4 files changed

+368
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ require (
6666
github.com/kelseyhightower/envconfig v1.4.0
6767
github.com/kr/pretty v0.3.1
6868
github.com/kr/text v0.2.0
69+
github.com/lib/pq v1.10.9
6970
github.com/mattn/go-colorable v0.1.13
7071
github.com/miekg/dns v1.1.58
7172
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
@@ -169,7 +170,6 @@ require (
169170
github.com/jinzhu/now v1.1.5 // indirect
170171
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
171172
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
172-
github.com/lib/pq v1.10.9 // indirect
173173
github.com/mattn/go-isatty v0.0.20 // indirect
174174
github.com/mattn/go-sqlite3 v1.14.22 // indirect
175175
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect

internal/iam/query.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,109 @@ const (
206206
on grants.role_id = roles.role_id;
207207
`
208208

209+
grantsForUserGlobalResourcesQuery = `
210+
with
211+
users (id) as (
212+
select public_id
213+
from iam_user
214+
where public_id = any(@user_ids)
215+
),
216+
user_groups (id) as (
217+
select group_id
218+
from iam_group_member_user
219+
where member_id in (select id
220+
from users)
221+
),
222+
user_accounts (id) as (
223+
select public_id
224+
from auth_account
225+
where iam_user_id in (select id
226+
from users)
227+
),
228+
user_oidc_managed_groups (id) as (
229+
select managed_group_id
230+
from auth_oidc_managed_group_member_account
231+
where member_id in (select id
232+
from user_accounts)
233+
),
234+
user_ldap_managed_groups (id) as (
235+
select managed_group_id
236+
from auth_ldap_managed_group_member_account
237+
where member_id in (select id
238+
from user_accounts)
239+
),
240+
managed_group_roles (role_id) as (
241+
select distinct role_id
242+
from iam_managed_group_role
243+
where principal_id in (select id
244+
from user_oidc_managed_groups)
245+
or principal_id in (select id
246+
from user_ldap_managed_groups)
247+
),
248+
group_roles (role_id) as (
249+
select role_id
250+
from iam_group_role
251+
where principal_id in (select id
252+
from user_groups)
253+
),
254+
user_roles (role_id) as (
255+
select role_id
256+
from iam_user_role
257+
where principal_id in (select id
258+
from users)
259+
),
260+
user_group_roles (role_id) as (
261+
select role_id
262+
from group_roles
263+
union
264+
select role_id
265+
from user_roles
266+
union
267+
select role_id
268+
from managed_group_roles
269+
),
270+
roles_with_grants (role_id, canonical_grant) as (
271+
select iam_role_grant.role_id,
272+
iam_role_grant.canonical_grant
273+
from iam_role_grant
274+
join iam_role
275+
on iam_role.public_id = iam_role_grant.role_id
276+
join iam_grant
277+
on iam_grant.canonical_grant = iam_role_grant.canonical_grant
278+
where iam_role.public_id in (select role_id
279+
from user_group_roles)
280+
and iam_grant.resource = any(@resources)
281+
),
282+
global_roles as (
283+
select iam_role_global.public_id as role_id,
284+
iam_role_global.scope_id as role_scope_id,
285+
iam_scope.type as role_type,
286+
'global' as role_parent_scope_id, -- manually set to global because we are only looking at global roles and the parent scope is always global
287+
iam_role_global.grant_scope as grant_scope,
288+
iam_role_global.grant_this_role_scope as grant_this_role_scope,
289+
roles_with_grants.canonical_grant as canonical_grant
290+
from iam_role_global
291+
join roles_with_grants
292+
on roles_with_grants.role_id = iam_role_global.public_id
293+
join iam_scope
294+
on iam_scope.public_id = iam_role_global.scope_id
295+
)
296+
select role_id,
297+
role_scope_id,
298+
role_parent_scope_id,
299+
grant_scope,
300+
grant_this_role_scope,
301+
null as individual_grant_scopes, -- individual grant scopes do not apply to resources in the global scope
302+
array_agg(distinct(canonical_grant)) as canonical_grants
303+
from global_roles
304+
where global_roles.grant_this_role_scope = true
305+
group by role_id,
306+
role_scope_id,
307+
role_parent_scope_id,
308+
grant_scope,
309+
grant_this_role_scope;
310+
`
311+
209312
estimateCountRoles = `
210313
select reltuples::bigint as estimate from pg_class where oid in ('iam_role'::regclass)
211314
`

internal/iam/repository_role_grant.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package iam
55

66
import (
77
"context"
8+
"database/sql"
89
"fmt"
910
"sort"
1011
"strings"
@@ -15,7 +16,9 @@ import (
1516
"github.com/hashicorp/boundary/internal/kms"
1617
"github.com/hashicorp/boundary/internal/oplog"
1718
"github.com/hashicorp/boundary/internal/perms"
19+
"github.com/hashicorp/boundary/internal/types/resource"
1820
"github.com/hashicorp/boundary/internal/types/scope"
21+
"github.com/lib/pq"
1922
)
2023

2124
// AddRoleGrants will add role grants associated with the role ID in the
@@ -437,6 +440,29 @@ type MultiGrantTuple struct {
437440
Grants string
438441
}
439442

443+
type grantsForUserResults struct {
444+
// roleId is the public ID of the role.
445+
roleId string
446+
// roleScopeId is the scope ID of the role.
447+
roleScopeId string
448+
// roleParentScopeId is the parent scope ID of the role.
449+
roleParentScopeId string
450+
// grantScope is the grant scope of the role.
451+
// The valid values are: "individual", "children" and "descendants".
452+
grantScope string
453+
// grantThisRoleScope is a boolean that indicates if the role has a grant
454+
// for itself aka "this" or "individual" scope.
455+
grantThisRoleScope bool
456+
// individualGrantScopes represents the individual grant scopes for the role.
457+
// This is a slice of strings that may be empty if the role does
458+
// not have individual grants.
459+
individualGrantScopes []string
460+
// canonicalGrants represents the canonical grants for the role.
461+
// This is a slice of strings that may be empty if the role does
462+
// not have canonical grants associated with it.
463+
canonicalGrants []string
464+
}
465+
440466
func (r *Repository) GrantsForUser(ctx context.Context, userId string, opt ...Option) (perms.GrantTuples, error) {
441467
const op = "iam.(Repository).GrantsForUser"
442468
if userId == "" {
@@ -511,3 +537,76 @@ func (m *MultiGrantTuple) TestStableSort() {
511537
sort.Strings(gts)
512538
m.Grants = strings.Join(gts, "^")
513539
}
540+
541+
// grantsForUserGlobalResources returns the grants for the user for resources that can
542+
// only be globally scoped.
543+
func (r *Repository) grantsForUserGlobalResources(
544+
ctx context.Context,
545+
userId string,
546+
res resource.Type,
547+
opt ...Option,
548+
) (perms.GrantTuples, error) {
549+
const op = "iam.(Repository).grantsForUserGlobalResources"
550+
if userId == "" {
551+
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing user id")
552+
}
553+
554+
var (
555+
args []any
556+
userIds []string
557+
resources []string
558+
)
559+
switch userId {
560+
case globals.AnonymousUserId:
561+
userIds = []string{globals.AnonymousUserId}
562+
default:
563+
userIds = []string{globals.AnonymousUserId, globals.AnyAuthenticatedUserId, userId}
564+
}
565+
resources = []string{res.String(), "unknown", "*"}
566+
567+
args = append(args,
568+
sql.Named("user_ids", pq.Array(userIds)),
569+
sql.Named("resources", pq.Array(resources)),
570+
)
571+
572+
var grants []grantsForUserResults
573+
rows, err := r.reader.Query(ctx, grantsForUserGlobalResourcesQuery, args)
574+
if err != nil {
575+
return nil, errors.Wrap(ctx, err, op)
576+
}
577+
defer rows.Close()
578+
for rows.Next() {
579+
var g grantsForUserResults
580+
if err := rows.Scan(
581+
&g.roleId,
582+
&g.roleScopeId,
583+
&g.roleParentScopeId,
584+
&g.grantScope,
585+
&g.grantThisRoleScope,
586+
pq.Array(&g.individualGrantScopes),
587+
pq.Array(&g.canonicalGrants),
588+
); err != nil {
589+
return nil, errors.Wrap(ctx, err, op)
590+
}
591+
grants = append(grants, g)
592+
}
593+
if err := rows.Err(); err != nil {
594+
return nil, errors.Wrap(ctx, err, op)
595+
}
596+
597+
ret := make(perms.GrantTuples, 0)
598+
for _, grant := range grants {
599+
for _, canonicalGrant := range grant.canonicalGrants {
600+
gt := perms.GrantTuple{
601+
RoleId: grant.roleId,
602+
RoleScopeId: grant.roleScopeId,
603+
RoleParentScopeId: grant.roleParentScopeId,
604+
GrantScopeId: grant.roleScopeId, // use "global" for all global grants
605+
Grant: canonicalGrant,
606+
}
607+
ret = append(ret, gt)
608+
}
609+
}
610+
611+
return ret, nil
612+
}

0 commit comments

Comments
 (0)