This removes the Cartesian product in the DB for GrantsForUser in favor of returning the actual grant scope information and dealing with it in application code. (cherry picked from commit 913cd536823d73994acc4f4d833208c1cc05f91b)
110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package iam
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/iam/store"
|
|
"github.com/hashicorp/boundary/internal/perms"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
const defaultRoleGrantTable = "iam_role_grant"
|
|
|
|
// RoleGrant defines the grants that are assigned to a role
|
|
type RoleGrant struct {
|
|
*store.RoleGrant
|
|
tableName string `gorm:"-"`
|
|
}
|
|
|
|
// ensure that RoleGrant implements the interfaces of: Cloneable and db.VetForWriter
|
|
var (
|
|
_ Cloneable = (*RoleGrant)(nil)
|
|
_ db.VetForWriter = (*RoleGrant)(nil)
|
|
)
|
|
|
|
// NewRoleGrant creates a new in memory role grant
|
|
func NewRoleGrant(ctx context.Context, roleId string, grant string, _ ...Option) (*RoleGrant, error) {
|
|
const op = "iam.NewRoleGrant"
|
|
if roleId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
|
|
}
|
|
if grant == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grant")
|
|
}
|
|
|
|
// Validate that the grant parses successfully. Note that we fake the scope
|
|
// here to avoid a lookup as the scope is only relevant at actual ACL
|
|
// checking time and we just care that it parses correctly.
|
|
perm, err := perms.Parse(ctx, perms.GrantTuple{RoleScopeId: "o_abcd1234", GrantScopeId: "o_abcd1234", Grant: grant})
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
|
|
}
|
|
rg := &RoleGrant{
|
|
RoleGrant: &store.RoleGrant{
|
|
RoleId: roleId,
|
|
RawGrant: grant,
|
|
CanonicalGrant: perm.CanonicalString(),
|
|
},
|
|
}
|
|
return rg, nil
|
|
}
|
|
|
|
func allocRoleGrant() RoleGrant {
|
|
return RoleGrant{
|
|
RoleGrant: &store.RoleGrant{},
|
|
}
|
|
}
|
|
|
|
// Clone creates a clone of the RoleGrant
|
|
func (g *RoleGrant) Clone() any {
|
|
cp := proto.Clone(g.RoleGrant)
|
|
return &RoleGrant{
|
|
RoleGrant: cp.(*store.RoleGrant),
|
|
}
|
|
}
|
|
|
|
// VetForWrite implements db.VetForWrite() interface
|
|
func (g *RoleGrant) VetForWrite(ctx context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error {
|
|
const op = "iam.(RoleGrant).VetForWrite"
|
|
if g.RawGrant == "" {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "missing grant")
|
|
}
|
|
|
|
// Validate that the grant parses successfully. Note that we fake the scope
|
|
// here to avoid a lookup as the scope is only relevant at actual ACL
|
|
// checking time and we just care that it parses correctly. We may have
|
|
// already done this in NewRoleGrant, but we re-check and set it here
|
|
// anyways because it should still be part of the vetting process.
|
|
perm, err := perms.Parse(ctx, perms.GrantTuple{RoleScopeId: "o_abcd1234", GrantScopeId: "o_abcd1234", Grant: g.RawGrant})
|
|
if err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
|
|
}
|
|
canonical := perm.CanonicalString()
|
|
if g.CanonicalGrant != "" && g.CanonicalGrant != canonical {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("existing canonical grant and derived one do not match"))
|
|
}
|
|
g.CanonicalGrant = canonical
|
|
|
|
return nil
|
|
}
|
|
|
|
// TableName returns the tablename to override the default gorm table name
|
|
func (g *RoleGrant) TableName() string {
|
|
if g.tableName != "" {
|
|
return g.tableName
|
|
}
|
|
return defaultRoleGrantTable
|
|
}
|
|
|
|
// SetTableName sets the tablename and satisfies the ReplayableMessage
|
|
// interface. If the caller attempts to set the name to "" the name will be
|
|
// reset to the default name.
|
|
func (g *RoleGrant) SetTableName(n string) {
|
|
g.tableName = n
|
|
}
|