Files
threshold/internal/iam/role_grant.go
T
Todd KnightandJeff Mitchell 847ca1cc78 Grant management added to Roles API and SDK (#185)
* Adding grants related methods to Roles.

* Adding grants back after the merge of Principals.

* Adding tests for Grant related custom methods.

* Improving api handler methods.

* Fixing tests and sdk field.

* Adding todos for adding principals and grants to List and Update Role responses.

* Fix up Set* tests and fix SetPrincipals bug.

* Making AddPrincipals and RemovePrincipals similar to the tests for AddGrants and RemoveGrants.

* Add JSON grants to output (#187)

I also changed canonical_grants to grants_canonical and used
grants_json. I believe that when members are simply different formats of
other members this is the preferred way to have it in the API. Let me
know if you disagree and we can dig in more.

* Regenerate the API role resource.

Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
2020-07-10 17:42:47 -07:00

110 lines
3.3 KiB
Go

package iam
import (
"context"
"fmt"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/iam/store"
"github.com/hashicorp/watchtower/internal/perms"
"github.com/hashicorp/watchtower/internal/types/resource"
"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: Clonable and db.VetForWriter
var _ Clonable = (*RoleGrant)(nil)
var _ db.VetForWriter = (*RoleGrant)(nil)
// NewRoleGrant creates a new in memory role grant
func NewRoleGrant(roleId string, grant string, opt ...Option) (*RoleGrant, error) {
if roleId == "" {
return nil, fmt.Errorf("new role grant: role id is not set: %w", db.ErrNilParameter)
}
if grant == "" {
return nil, fmt.Errorf("new role grant: grant is empty: %w", db.ErrNilParameter)
}
// 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("o_abcd1234", "", grant)
if err != nil {
return nil, fmt.Errorf("new role grant: error parsing grant string: %w", err)
}
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() interface{} {
cp := proto.Clone(g.RoleGrant)
return &RoleGrant{
RoleGrant: cp.(*store.RoleGrant),
}
}
// VetForWrite implements db.VetForWrite() interface
func (g *RoleGrant) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
if g.RawGrant == "" {
return fmt.Errorf("vet role grant for writing: grant is empty: %w", db.ErrNilParameter)
}
// 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("o_abcd1234", "", g.RawGrant)
if err != nil {
return fmt.Errorf("vet role grant for writing: error parsing grant string: %w", err)
}
canonical := perm.CanonicalString()
if g.CanonicalGrant != "" && g.CanonicalGrant != canonical {
return fmt.Errorf("vet role grant for writing: existing canonical grant and derived one do not match: %w", err)
}
g.CanonicalGrant = canonical
return nil
}
// ResourceType returns the type of the RoleGrant
func (*RoleGrant) ResourceType() resource.Type { return resource.RoleGrant }
// 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
func (g *RoleGrant) SetTableName(n string) {
switch n {
case "":
g.tableName = defaultRoleGrantTable
default:
g.tableName = n
}
}