Files
threshold/internal/iam/repository_role_grant.go
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

496 lines
18 KiB
Go
Raw Normal View History

2020-12-23 11:00:00 -08:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-07-10 15:46:26 -04:00
package iam
import (
"context"
"fmt"
2024-09-17 10:07:43 -04:00
"sort"
"strings"
2020-07-10 15:46:26 -04:00
2022-10-28 15:52:06 -04:00
"github.com/hashicorp/boundary/globals"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/db"
2020-11-05 16:54:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-19 16:47:18 -04:00
"github.com/hashicorp/boundary/internal/kms"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/perms"
2020-07-10 15:46:26 -04:00
)
// AddRoleGrant 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.
2020-12-23 11:00:00 -08:00
func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) ([]*RoleGrant, error) {
const op = "iam.(Repository).AddRoleGrants"
2020-07-10 15:46:26 -04:00
if roleId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
2020-07-10 15:46:26 -04:00
}
if len(grants) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants")
2020-07-10 15:46:26 -04:00
}
if roleVersion == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
2020-07-10 15:46:26 -04:00
role := allocRole()
role.PublicId = roleId
newRoleGrants := make([]*RoleGrant, 0, len(grants))
2020-07-10 15:46:26 -04:00
for _, grant := range grants {
2023-06-06 16:13:44 -04:00
roleGrant, err := NewRoleGrant(ctx, roleId, grant)
2020-07-10 15:46:26 -04:00
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
2020-07-10 15:46:26 -04:00
}
newRoleGrants = append(newRoleGrants, roleGrant)
}
2020-08-19 16:47:18 -04:00
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
2020-08-19 16:47:18 -04:00
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
2020-08-19 16:47:18 -04:00
}
_, err = r.writer.DoTx(
2020-07-10 15:46:26 -04:00
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(ctx, &role)
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
2020-07-10 15:46:26 -04:00
}
// We need to update the role version as that's the aggregate
updatedRole := allocRole()
updatedRole.PublicId = roleId
updatedRole.Version = uint32(roleVersion) + 1
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
2020-07-10 15:46:26 -04:00
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
2020-07-10 15:46:26 -04:00
}
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"))
2020-07-10 15:46:26 -04:00
}
msgs = append(msgs, roleGrantOplogMsgs...)
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
2020-08-19 16:47:18 -04:00
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
2020-07-10 15:46:26 -04:00
"resource-public-id": []string{roleId},
}
2020-08-19 16:47:18 -04:00
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
2020-07-10 15:46:26 -04:00
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
2020-07-10 15:46:26 -04:00
}
return newRoleGrants, nil
2020-07-10 15:46:26 -04:00
}
// 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.
2020-12-23 11:00:00 -08:00
func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) (int, error) {
const op = "iam.(Repository).DeleteRoleGrants"
2020-07-10 15:46:26 -04:00
if roleId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
2020-07-10 15:46:26 -04:00
}
if len(grants) == 0 {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing grants")
2020-07-10 15:46:26 -04:00
}
if roleVersion == 0 {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
2020-07-10 15:46:26 -04:00
role := allocRole()
role.PublicId = roleId
2020-08-19 16:47:18 -04:00
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope to create metadata", roleId)))
2020-08-19 16:47:18 -04:00
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
2020-08-19 16:47:18 -04:00
}
2020-07-10 15:46:26 -04:00
var totalRowsDeleted int
2020-08-19 16:47:18 -04:00
_, err = r.writer.DoTx(
2020-07-10 15:46:26 -04:00
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(ctx, &role)
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
2020-07-10 15:46:26 -04:00
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
updatedRole.Version = uint32(roleVersion) + 1
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
2020-07-10 15:46:26 -04:00
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
2020-07-10 15:46:26 -04:00
}
msgs = append(msgs, &roleOplogMsg)
// Find existing grants
roleGrants := []*RoleGrant{}
2022-10-25 18:29:38 -05:00
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"))
2020-07-10 15:46:26 -04:00
}
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))
2020-07-10 15:46:26 -04:00
for _, grant := range grants {
// Use a fake scope, just want to get out a canonical string
2024-09-17 10:07:43 -04:00
perm, err := perms.Parse(ctx, perms.GrantTuple{RoleScopeId: "o_abcd1234", GrantScopeId: "o_abcd1234", Grant: grant}, perms.WithSkipFinalValidation(true))
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
2020-07-10 15:46:26 -04:00
}
// We don't have what they want to delete, so ignore it
if !found[perm.CanonicalString()] {
continue
}
2023-06-06 16:13:44 -04:00
roleGrant, err := NewRoleGrant(ctx, roleId, grant)
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
2020-07-10 15:46:26 -04:00
}
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"))
2020-07-10 15:46:26 -04:00
}
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)))
2020-07-10 15:46:26 -04:00
}
totalRowsDeleted = rowsDeleted
msgs = append(msgs, roleGrantOplogMsgs...)
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
2020-08-19 16:47:18 -04:00
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
2020-07-10 15:46:26 -04:00
"resource-public-id": []string{roleId},
}
2020-08-19 16:47:18 -04:00
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
2020-07-10 15:46:26 -04:00
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
2020-07-10 15:46:26 -04:00
}
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.
2020-12-23 11:00:00 -08:00
func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, _ ...Option) ([]*RoleGrant, int, error) {
const op = "iam.(Repository).SetRoleGrants"
2020-07-10 15:46:26 -04:00
if roleId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
2020-07-10 15:46:26 -04:00
}
if roleVersion == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
2020-07-10 15:46:26 -04:00
// 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")
2020-07-10 15:46:26 -04:00
}
role := allocRole()
role.PublicId = roleId
2020-08-26 12:31:18 -04:00
// TODO(mgaffney) 08/2020: Use SQL to calculate changes.
2020-07-10 15:46:26 -04:00
// 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{}
2022-10-25 18:29:38 -05:00
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"))
2020-07-10 15:46:26 -04:00
}
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))
2020-07-10 15:46:26 -04:00
for _, grant := range grants {
// Use a fake scope, just want to get out a canonical string
2024-09-17 10:07:43 -04:00
perm, err := perms.Parse(ctx, perms.GrantTuple{RoleScopeId: "o_abcd1234", GrantScopeId: "o_abcd1234", Grant: grant}, perms.WithSkipFinalValidation(true))
2020-07-10 15:46:26 -04:00
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing grant string"))
2020-07-10 15:46:26 -04:00
}
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
2023-06-06 16:13:44 -04:00
rg, err = NewRoleGrant(ctx, roleId, grant)
2020-07-10 15:46:26 -04:00
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
2020-07-10 15:46:26 -04:00
}
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 {
2020-08-19 16:47:18 -04:00
return currentRoleGrants, db.NoRowsAffected, nil
}
scope, err := role.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get role %s scope", roleId)))
2020-08-19 16:47:18 -04:00
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
2020-07-10 15:46:26 -04:00
}
var totalRowsDeleted int
2020-08-19 16:47:18 -04:00
_, err = r.writer.DoTx(
2020-07-10 15:46:26 -04:00
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 2)
roleTicket, err := w.GetTicket(ctx, &role)
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
2020-07-10 15:46:26 -04:00
}
updatedRole := allocRole()
updatedRole.PublicId = roleId
updatedRole.Version = roleVersion + 1
2020-07-10 15:46:26 -04:00
var roleOplogMsg oplog.Message
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(&roleVersion))
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update role version"))
2020-07-10 15:46:26 -04:00
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated role and %d rows updated", rowsUpdated))
2020-07-10 15:46:26 -04:00
}
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"))
2020-07-10 15:46:26 -04:00
}
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"))
2020-07-10 15:46:26 -04:00
}
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)))
2020-07-10 15:46:26 -04:00
}
totalRowsDeleted = rowsDeleted
msgs = append(msgs, roleGrantOplogMsgs...)
}
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String(), oplog.OpType_OP_TYPE_CREATE.String()},
2020-08-19 16:47:18 -04:00
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
2020-07-10 15:46:26 -04:00
"resource-public-id": []string{roleId},
}
2020-08-19 16:47:18 -04:00
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
2020-07-10 15:46:26 -04:00
}
currentRoleGrants, err = r.ListRoleGrants(ctx, roleId, WithReaderWriter(reader, w))
2020-07-10 15:46:26 -04:00
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current role grants after set"))
2020-07-10 15:46:26 -04:00
}
return nil
},
)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
2020-07-10 15:46:26 -04:00
}
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) {
2020-12-23 11:00:00 -08:00
const op = "iam.(Repository).ListRoleGrants"
2020-07-10 15:46:26 -04:00
if roleId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
2020-07-10 15:46:26 -04:00
}
var roleGrants []*RoleGrant
2022-10-25 18:29:38 -05:00
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"))
2020-07-10 15:46:26 -04:00
}
return roleGrants, nil
}
2024-01-23 14:33:09 -05:00
// ListRoleGrantScopes returns the grant scopes for the roleId and supports the WithLimit
// option.
2024-06-25 13:05:52 -04:00
func (r *Repository) ListRoleGrantScopes(ctx context.Context, roleIds []string, opt ...Option) ([]*RoleGrantScope, error) {
2024-01-23 14:33:09 -05:00
const op = "iam.(Repository).ListRoleGrantScopes"
2024-06-25 13:05:52 -04:00
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)
2024-01-23 14:33:09 -05:00
}
var roleGrantScopes []*RoleGrantScope
2024-06-25 13:05:52 -04:00
if err := r.list(ctx, &roleGrantScopes, fmt.Sprintf("role_id in (%s)", query), args, opt...); err != nil {
2024-01-23 14:33:09 -05:00
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup role grant scopes"))
}
return roleGrantScopes, nil
}
2024-09-17 10:07:43 -04:00
type multiGrantTuple struct {
RoleId string
RoleScopeId string
RoleParentScopeId string
GrantScopeIds string
Grants string
}
func (r *Repository) GrantsForUser(ctx context.Context, userId string, opt ...Option) (perms.GrantTuples, error) {
2020-12-23 11:00:00 -08:00
const op = "iam.(Repository).GrantsForUser"
if userId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing user id")
}
2024-09-17 10:07:43 -04:00
opts := getOpts(opt...)
const (
anonUser = `where public_id in (?)`
authUser = `where public_id in ('u_anon', 'u_auth', ?)`
)
var query string
switch userId {
2022-10-28 15:52:06 -04:00
case globals.AnonymousUserId:
2024-01-23 14:33:09 -05:00
query = fmt.Sprintf(grantsForUserQuery, anonUser)
default:
2024-01-23 14:33:09 -05:00
query = fmt.Sprintf(grantsForUserQuery, authUser)
}
2024-09-17 10:07:43 -04:00
var grants []multiGrantTuple
2022-10-25 18:29:38 -05:00
rows, err := r.reader.Query(ctx, query, []any{userId})
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
defer rows.Close()
for rows.Next() {
2024-01-23 14:33:09 -05:00
if err := r.reader.ScanRows(ctx, rows, &grants); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
2024-01-22 14:03:40 -05:00
if err := rows.Err(); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
2024-09-17 10:07:43 -04:00
ret := make(perms.GrantTuples, 0, len(grants)*3)
for _, grant := range grants {
for _, grantScopeId := range strings.Split(grant.GrantScopeIds, "^") {
for _, canonicalGrant := range strings.Split(grant.Grants, "^") {
gt := perms.GrantTuple{
RoleId: grant.RoleId,
RoleScopeId: grant.RoleScopeId,
RoleParentScopeId: grant.RoleParentScopeId,
GrantScopeId: grantScopeId,
Grant: canonicalGrant,
}
if gt.GrantScopeId == globals.GrantScopeThis || gt.GrantScopeId == "" {
gt.GrantScopeId = grant.RoleScopeId
}
ret = append(ret, gt)
}
}
}
if opts.withTestCacheMultiGrantTuples != nil {
for i, grant := range grants {
grant.testStableSort()
grants[i] = grant
}
*opts.withTestCacheMultiGrantTuples = grants
}
return ret, nil
}
func (m *multiGrantTuple) testStableSort() {
grantScopeIds := strings.Split(m.GrantScopeIds, "^")
sort.Strings(grantScopeIds)
m.GrantScopeIds = strings.Join(grantScopeIds, "^")
gts := strings.Split(m.Grants, "^")
sort.Strings(gts)
m.Grants = strings.Join(gts, "^")
}