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

665 lines
24 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"database/sql"
"fmt"
"slices"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/lib/pq"
)
// AddRoleGrants will add role grants associated with the role ID in the
// repository. No options are currently supported. Zero is not a valid value for
// the WithVersion option and will return an error.
func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) ([]*RoleGrant, error) {
const op = "iam.(Repository).AddRoleGrants"
if roleId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
}
if len(grants) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants")
}
if roleVersion == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
newRoleGrants := make([]*RoleGrant, 0, len(grants))
for _, grant := range grants {
roleGrant, err := NewRoleGrant(ctx, roleId, grant)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
newRoleGrants = append(newRoleGrants, roleGrant)
}
scp, err := getRoleScope(ctx, r.reader, roleId)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope id", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scp.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 2)
var updatedRole Resource
switch scp.GetType() {
case scope.Global.String():
g := allocGlobalRole()
g.PublicId = roleId
g.Version = roleVersion + 1
updatedRole = &g
case scope.Org.String():
o := allocOrgRole()
o.PublicId = roleId
o.Version = roleVersion + 1
updatedRole = &o
case scope.Project.String():
p := allocProjectRole()
p.PublicId = roleId
p.Version = roleVersion + 1
updatedRole = &p
default:
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown scope type %s for scope %s", scp.GetType(), scp.GetPublicId()))
}
roleTicket, err := w.GetTicket(ctx, updatedRole)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(newRoleGrants))
if err := w.CreateItems(ctx, newRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grants"))
}
msgs = append(msgs, roleGrantOplogMsgs...)
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
"scope-id": []string{scp.PublicId},
"scope-type": []string{scp.Type},
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return newRoleGrants, nil
}
// DeleteRoleGrants deletes grants (as strings) from a role (roleId). The role's
// current db version must match the roleVersion or an error will be returned.
// Zero is not a valid value for the WithVersion option and will return an
// error.
func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteRoleGrants"
if roleId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
}
if len(grants) == 0 {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing grants")
}
if roleVersion == 0 {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
scp, err := getRoleScope(ctx, r.reader, roleId)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope id", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scp.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var totalRowsDeleted int
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 2)
var updatedRole Resource
switch scp.GetType() {
case scope.Global.String():
g := allocGlobalRole()
g.PublicId = roleId
g.Version = roleVersion + 1
updatedRole = &g
case scope.Org.String():
o := allocOrgRole()
o.PublicId = roleId
o.Version = roleVersion + 1
updatedRole = &o
case scope.Project.String():
p := allocProjectRole()
p.PublicId = roleId
p.Version = roleVersion + 1
updatedRole = &p
default:
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown scope type %s for scope %s", scp.GetType(), scp.GetPublicId()))
}
roleTicket, err := w.GetTicket(ctx, updatedRole)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
// Find existing grants
roleGrants := []*RoleGrant{}
if err := reader.SearchWhere(ctx, &roleGrants, "role_id = ?", []any{roleId}); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to search for grants"))
}
found := map[string]bool{}
for _, rg := range roleGrants {
found[rg.CanonicalGrant] = true
}
// Check incoming grants to see if they exist and if so add to
// delete slice
deleteRoleGrants := make([]*RoleGrant, 0, len(grants))
for _, grant := range grants {
// Use a fake scope, just want to get out a canonical string
perm, err := perms.Parse(ctx, perms.GrantTuple{RoleScopeId: "o_abcd1234", GrantScopeId: "o_abcd1234", Grant: grant}, perms.WithSkipFinalValidation(true))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
}
// We don't have what they want to delete, so ignore it
if !found[perm.CanonicalString()] {
continue
}
roleGrant, err := NewRoleGrant(ctx, roleId, grant)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
deleteRoleGrants = append(deleteRoleGrants, roleGrant)
}
if len(deleteRoleGrants) == 0 {
return nil
}
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(deleteRoleGrants))
rowsDeleted, err := w.DeleteItems(ctx, deleteRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete role grant"))
}
if rowsDeleted != len(deleteRoleGrants) {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("role grants deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrants)))
}
totalRowsDeleted = rowsDeleted
msgs = append(msgs, roleGrantOplogMsgs...)
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
"scope-id": []string{scp.PublicId},
"scope-type": []string{scp.Type},
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
return totalRowsDeleted, nil
}
// SetRoleGrants sets grants on a role (roleId). The role's current db version
// must match the roleVersion or an error will be returned. Zero is not a valid
// value for the WithVersion option and will return an error.
func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) ([]*RoleGrant, int, error) {
const op = "iam.(Repository).SetRoleGrants"
if roleId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
}
if roleVersion == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
// Explicitly set to zero clears, but treat nil as a mistake
if grants == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing grants")
}
// TODO(mgaffney) 08/2020: Use SQL to calculate changes.
// NOTE: Set calculation can safely take place out of the transaction since
// we are using roleVersion to ensure that we end up operating on the same
// set of data from this query to the final set in the transaction function
// Find existing grants
roleGrants := []*RoleGrant{}
if err := r.reader.SearchWhere(ctx, &roleGrants, "role_id = ?", []any{roleId}); err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to search for grants"))
}
found := map[string]*RoleGrant{}
for _, rg := range roleGrants {
found[rg.CanonicalGrant] = rg
}
// Check incoming grants to see if they exist and if so act appropriately
currentRoleGrants := make([]*RoleGrant, 0, len(grants)+len(found))
addRoleGrants := make([]*RoleGrant, 0, len(grants))
deleteRoleGrants := make([]*RoleGrant, 0, len(grants))
for _, grant := range grants {
// Use a fake scope, just want to get out a canonical string
perm, err := perms.Parse(ctx, perms.GrantTuple{RoleScopeId: "o_abcd1234", GrantScopeId: "o_abcd1234", Grant: grant}, perms.WithSkipFinalValidation(true))
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing grant string"))
}
canonicalString := perm.CanonicalString()
rg, ok := found[canonicalString]
if ok {
// If we have an exact match, do nothing, we want to keep
// it, but remove from found
currentRoleGrants = append(currentRoleGrants, rg)
delete(found, canonicalString)
continue
}
// Not found, so add
rg, err = NewRoleGrant(ctx, roleId, grant)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
addRoleGrants = append(addRoleGrants, rg)
currentRoleGrants = append(currentRoleGrants, rg)
}
if len(found) > 0 {
for _, rg := range found {
deleteRoleGrants = append(deleteRoleGrants, rg)
}
}
if len(addRoleGrants) == 0 && len(deleteRoleGrants) == 0 {
return currentRoleGrants, db.NoRowsAffected, nil
}
scp, err := getRoleScope(ctx, r.reader, roleId)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope id", roleId)))
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scp.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var totalRowsDeleted int
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 2)
var updatedRole Resource
switch scp.GetType() {
case scope.Global.String():
g := allocGlobalRole()
g.PublicId = roleId
g.Version = roleVersion + 1
updatedRole = &g
case scope.Org.String():
o := allocOrgRole()
o.PublicId = roleId
o.Version = roleVersion + 1
updatedRole = &o
case scope.Project.String():
p := allocProjectRole()
p.PublicId = roleId
p.Version = roleVersion + 1
updatedRole = &p
default:
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown scope type %s for scope %s", scp.GetType(), scp.GetPublicId()))
}
roleTicket, err := w.GetTicket(ctx, updatedRole)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &roleOplogMsg)
// Write the new ones in
if len(addRoleGrants) > 0 {
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(addRoleGrants))
if err := w.CreateItems(ctx, addRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add grants during set"))
}
msgs = append(msgs, roleGrantOplogMsgs...)
}
// Anything we didn't take out of found needs to be removed
if len(deleteRoleGrants) > 0 {
roleGrantOplogMsgs := make([]*oplog.Message, 0, len(deleteRoleGrants))
rowsDeleted, err := w.DeleteItems(ctx, deleteRoleGrants, db.NewOplogMsgs(&roleGrantOplogMsgs))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete role grant"))
}
if rowsDeleted != len(deleteRoleGrants) {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("role grants deleted %d did not match request for %d", rowsDeleted, len(deleteRoleGrants)))
}
totalRowsDeleted = rowsDeleted
msgs = append(msgs, roleGrantOplogMsgs...)
}
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String(), oplog.OpType_OP_TYPE_CREATE.String()},
"scope-id": []string{scp.PublicId},
"scope-type": []string{scp.Type},
"resource-public-id": []string{roleId},
}
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
currentRoleGrants, err = r.ListRoleGrants(ctx, roleId, WithReaderWriter(reader, w))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current role grants after set"))
}
return nil
},
)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
return currentRoleGrants, totalRowsDeleted, nil
}
// ListRoleGrants returns the grants for the roleId and supports the WithLimit
// option.
func (r *Repository) ListRoleGrants(ctx context.Context, roleId string, opt ...Option) ([]*RoleGrant, error) {
const op = "iam.(Repository).ListRoleGrants"
if roleId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
}
var roleGrants []*RoleGrant
if err := r.list(ctx, &roleGrants, "role_id = ?", []any{roleId}, opt...); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup role grants"))
}
return roleGrants, nil
}
type grantsForUserResults struct {
// roleId is the public ID of the role.
roleId string
// roleScopeId is the scope ID of the role.
roleScopeId string
// roleParentScopeId is the parent scope ID of the role.
roleParentScopeId string
// grantScope is the grant scope of the role.
// The valid values are: "individual", "children" and "descendants".
grantScope string
// grantThisRoleScope is a boolean that indicates if the role has a grant
// for itself aka "this" or "individual" scope.
grantThisRoleScope bool
// individualGrantScopes represents the individual grant scopes for the role.
// This is a slice of strings that may be empty if the role does
// not have individual grants.
individualGrantScopes []string
// canonicalGrants represents the canonical grants for the role.
// This is a slice of strings that may be empty if the role does
// not have canonical grants associated with it.
canonicalGrants []string
}
// GrantsForUser returns perms.GrantTuples associated to a userId scoped down to the requested scope and resource type.
// Use WithRecursive option to indicate that the request is a recursive list request
// Supported options: WithRecursive
func (r *Repository) GrantsForUser(ctx context.Context, userId string, res []resource.Type, reqScopeId string, opt ...Option) (perms.GrantTuples, error) {
const op = "iam.(Repository).GrantsForUser"
if userId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing user id")
}
if res == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing resource type")
}
if slices.Contains(res, resource.Unknown) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "resource type cannot be unknown")
}
if slices.Contains(res, resource.All) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "resource type cannot be all")
}
switch {
case strings.HasPrefix(reqScopeId, globals.GlobalPrefix):
case strings.HasPrefix(reqScopeId, globals.OrgPrefix):
case strings.HasPrefix(reqScopeId, globals.ProjectPrefix):
case reqScopeId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing request scope id")
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, "request scope must be global scope, an org scope, or a project scope")
}
// Determine which query to use based on the resources, request scope, and recursive option
opts := getOpts(opt...)
query, err := r.resolveQuery(ctx, res, reqScopeId, opts.withRecursive)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to resolve query"))
}
// Execute the query to get the user's grants
var (
args []any
userIds []string
resources []string
)
switch userId {
case globals.AnonymousUserId:
userIds = []string{globals.AnonymousUserId}
default:
userIds = []string{globals.AnonymousUserId, globals.AnyAuthenticatedUserId, userId}
}
resources = []string{resource.Unknown.String(), resource.All.String()}
for _, res := range res {
resources = append(resources, res.String())
}
args = append(args,
sql.Named("user_ids", pq.Array(userIds)),
sql.Named("resources", pq.Array(resources)),
sql.Named("request_scope_id", reqScopeId),
)
var grants []grantsForUserResults
rows, err := r.reader.Query(ctx, query, args)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
defer rows.Close()
for rows.Next() {
var g grantsForUserResults
if err := rows.Scan(
&g.roleId,
&g.roleScopeId,
&g.roleParentScopeId,
&g.grantScope,
&g.grantThisRoleScope,
pq.Array(&g.individualGrantScopes),
pq.Array(&g.canonicalGrants),
); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
grants = append(grants, g)
}
if err := rows.Err(); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
ret := make(perms.GrantTuples, 0, len(grants))
for _, grant := range grants {
if grant.grantScope != globals.GrantScopeIndividual {
for _, canonicalGrant := range grant.canonicalGrants {
gt := perms.GrantTuple{
RoleId: grant.roleId,
RoleScopeId: grant.roleScopeId,
RoleParentScopeId: grant.roleParentScopeId,
GrantScopeId: grant.grantScope,
Grant: canonicalGrant,
}
ret = append(ret, gt)
}
}
if grant.grantThisRoleScope {
switch {
case opts.withRecursive:
// Recursive requests can list the entire scope tree at any request scope
fallthrough
case reqScopeId == grant.roleScopeId:
// Non-recursive requests' role scope must match the request scope
for _, canonicalGrant := range grant.canonicalGrants {
gt := perms.GrantTuple{
RoleId: grant.roleId,
RoleScopeId: grant.roleScopeId,
RoleParentScopeId: grant.roleParentScopeId,
GrantScopeId: grant.roleScopeId,
Grant: canonicalGrant,
}
ret = append(ret, gt)
}
}
}
// loop over grants creating tuple with grant_scope = s.ScopeId
for _, individualGrantScope := range grant.individualGrantScopes {
for _, canonicalGrant := range grant.canonicalGrants {
gt := perms.GrantTuple{
RoleId: grant.roleId,
RoleScopeId: grant.roleScopeId,
RoleParentScopeId: grant.roleParentScopeId,
GrantScopeId: individualGrantScope,
Grant: canonicalGrant,
}
ret = append(ret, gt)
}
}
}
return ret, nil
}
func (r *Repository) resolveQuery(
ctx context.Context,
res []resource.Type,
reqScopeId string,
isRecursive bool,
) (string, error) {
const op = "iam.(Repository).resolveQuery"
if res == nil {
return "", errors.New(ctx, errors.InvalidParameter, op, "missing resource type")
}
if slices.Contains(res, resource.Unknown) {
return "", errors.New(ctx, errors.InvalidParameter, op, "resource type cannot be unknown")
}
if slices.Contains(res, resource.All) {
return "", errors.New(ctx, errors.InvalidParameter, op, "resource type cannot be all")
}
if reqScopeId == "" {
return "", errors.New(ctx, errors.InvalidParameter, op, "missing request scope id")
}
// Use the largest set of allowed scopes for the given resources
var resourceAllowedIn []scope.Type
for _, re := range res {
a, err := scope.AllowedIn(ctx, re)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
if len(a) > len(resourceAllowedIn) {
resourceAllowedIn = a
}
}
// Recursive query
if isRecursive {
return grantsForUserRecursiveQuery, nil
}
// Non-recursive queries
switch {
case slices.Equal(resourceAllowedIn, []scope.Type{scope.Global}):
if reqScopeId != globals.GlobalPrefix {
return "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("request scope id must be global for %s resources", res))
}
return grantsForUserGlobalResourcesQuery, nil
case slices.Equal(resourceAllowedIn, []scope.Type{scope.Global, scope.Org}):
switch {
case strings.HasPrefix(reqScopeId, globals.GlobalPrefix):
return grantsForUserGlobalResourcesQuery, nil
case strings.HasPrefix(reqScopeId, globals.OrgPrefix):
return grantsForUserOrgResourcesQuery, nil
default:
return "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("request scope id must be global or org for %s resources", res))
}
case slices.Equal(resourceAllowedIn, []scope.Type{scope.Global, scope.Org, scope.Project}):
switch {
case strings.HasPrefix(reqScopeId, globals.GlobalPrefix):
return grantsForUserGlobalResourcesQuery, nil
case strings.HasPrefix(reqScopeId, globals.OrgPrefix):
return grantsForUserOrgResourcesQuery, nil
case strings.HasPrefix(reqScopeId, globals.ProjectPrefix):
return grantsForUserProjectResourcesQuery, nil
default:
return "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("invalid scope id %s", reqScopeId))
}
case slices.Equal(resourceAllowedIn, []scope.Type{scope.Project}):
if !strings.HasPrefix(reqScopeId, globals.ProjectPrefix) {
return "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("request scope id must be project for %s resources", res))
}
return grantsForUserProjectResourcesQuery, nil
}
return "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("invalid resource type: %v", res))
}