2020-06-10 17:58:52 -04:00
package perms
import (
"encoding/json"
"fmt"
"sort"
"strings"
2021-01-13 15:50:07 -08:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
2020-06-10 17:58:52 -04:00
)
2021-06-07 09:19:21 -05:00
// GrantTuple is simply a struct that can be reference from other code to return
2020-07-14 14:32:47 -07:00
// a set of scopes and grants to parse
2021-06-07 09:19:21 -05:00
type GrantTuple struct {
RoleId string
2020-07-14 14:32:47 -07:00
ScopeId string
Grant string
}
2020-06-10 17:58:52 -04:00
// Scope provides an in-memory representation of iam.Scope without the
// underlying storage references or capabilities.
type Scope struct {
// Id is the public id of the iam.Scope
Id string
// Type is the scope's type (org or project)
2020-06-26 11:00:14 -04:00
Type scope . Type
2020-06-10 17:58:52 -04:00
}
// Grant is a Go representation of a parsed grant
type Grant struct {
// The scope ID, which will be a project ID or an org ID
scope Scope
// The ID in the grant, if provided.
id string
// The type, if provided
2020-06-26 11:00:14 -04:00
typ resource . Type
2020-06-10 17:58:52 -04:00
// The set of actions being granted
2020-06-26 11:00:14 -04:00
actions map [ action . Type ] bool
2020-06-10 17:58:52 -04:00
2021-05-04 19:57:35 -05:00
// The set of output fields granted
OutputFields OutputFieldsMap
2020-06-10 17:58:52 -04:00
// This is used as a temporary staging area before validating permissions to
// allow the same validation code across grant string formats
actionsBeingParsed [] string
}
2020-07-16 22:18:27 -04:00
func ( g Grant ) Id () string {
return g . id
}
func ( g Grant ) Type () resource . Type {
return g . typ
}
func ( g Grant ) Actions () ( typs [] action . Type , strs [] string ) {
typs = make ([] action . Type , 0 , len ( g . actions ))
strs = make ([] string , 0 , len ( g . actions ))
for k , v := range g . actions {
// Nothing should be in there if not true, but doesn't hurt to validate
if ! v {
continue
}
typs = append ( typs , k )
strs = append ( strs , k . String ())
}
return
}
2020-06-10 17:58:52 -04:00
func ( g Grant ) clone () * Grant {
ret := & Grant {
2020-06-23 11:28:44 -04:00
scope : g . scope ,
id : g . id ,
typ : g . typ ,
2020-06-10 17:58:52 -04:00
}
if g . actionsBeingParsed != nil {
ret . actionsBeingParsed = append ( ret . actionsBeingParsed , g . actionsBeingParsed ... )
}
if g . actions != nil {
2020-06-26 11:00:14 -04:00
ret . actions = make ( map [ action . Type ] bool , len ( g . actions ))
2020-06-10 17:58:52 -04:00
for action := range g . actions {
ret . actions [ action ] = true
}
}
2021-05-04 19:57:35 -05:00
if g . OutputFields != nil {
ret . OutputFields = make ( OutputFieldsMap , len ( g . OutputFields ))
for k , v := range g . OutputFields {
ret . OutputFields [ k ] = v
}
}
2020-06-10 17:58:52 -04:00
return ret
}
// CanonicalString returns the canonical representation of the grant
func ( g Grant ) CanonicalString () string {
var builder [] string
if g . id != "" {
builder = append ( builder , fmt . Sprintf ( "id=%s" , g . id ))
}
2020-06-26 11:00:14 -04:00
if g . typ != resource . Unknown {
builder = append ( builder , fmt . Sprintf ( "type=%s" , g . typ . String ()))
2020-06-10 17:58:52 -04:00
}
if len ( g . actions ) > 0 {
actions := make ([] string , 0 , len ( g . actions ))
for action := range g . actions {
actions = append ( actions , action . String ())
}
sort . Strings ( actions )
builder = append ( builder , fmt . Sprintf ( "actions=%s" , strings . Join ( actions , "," )))
}
2021-05-04 19:57:35 -05:00
if len ( g . OutputFields ) > 0 {
builder = append ( builder , fmt . Sprintf ( "output_fields=%s" , strings . Join ( g . OutputFields . Fields (), "," )))
}
2020-06-10 17:58:52 -04:00
return strings . Join ( builder , ";" )
}
// MarshalJSON provides a custom marshaller for grants
func ( g Grant ) MarshalJSON () ([] byte , error ) {
2021-01-13 15:50:07 -08:00
const op = "perms.(Grant).MarshalJSON"
2020-06-10 17:58:52 -04:00
res := make ( map [ string ] interface {}, 4 )
if g . id != "" {
res [ "id" ] = g . id
}
2020-06-26 11:00:14 -04:00
if g . typ != resource . Unknown {
res [ "type" ] = g . typ . String ()
2020-06-10 17:58:52 -04:00
}
if len ( g . actions ) > 0 {
actions := make ([] string , 0 , len ( g . actions ))
for action := range g . actions {
actions = append ( actions , action . String ())
}
sort . Strings ( actions )
res [ "actions" ] = actions
}
2021-05-04 19:57:35 -05:00
if len ( g . OutputFields ) > 0 {
res [ "output_fields" ] = g . OutputFields . Fields ()
}
2021-01-13 15:50:07 -08:00
b , err := json . Marshal ( res )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . WrapDeprecated ( err , op , errors . WithCode ( errors . Encode ))
2021-01-13 15:50:07 -08:00
}
return b , nil
2020-06-10 17:58:52 -04:00
}
// This is purposefully unexported since the values being set here are not being
// checked for validity. This should only be called by the main parsing function
// when JSON is detected.
func ( g * Grant ) unmarshalJSON ( data [] byte ) error {
2021-01-13 15:50:07 -08:00
const op = "perms.(Grant).unmarshalJSON"
2020-06-10 17:58:52 -04:00
raw := make ( map [ string ] interface {}, 4 )
if err := json . Unmarshal ( data , & raw ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . WrapDeprecated ( err , op , errors . WithCode ( errors . Decode ))
2020-06-10 17:58:52 -04:00
}
if rawId , ok := raw [ "id" ]; ok {
id , ok := rawId .( string )
if ! ok {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unable to interpret %q as string" , "id" ))
2020-06-10 17:58:52 -04:00
}
2020-12-18 13:25:28 -05:00
g . id = id
2020-06-10 17:58:52 -04:00
}
if rawType , ok := raw [ "type" ]; ok {
typ , ok := rawType .( string )
if ! ok {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unable to interpret %q as string" , "type" ))
2020-06-10 17:58:52 -04:00
}
2020-07-14 14:32:47 -07:00
g . typ = resource . Map [ typ ]
if g . typ == resource . Unknown {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unknown type specifier %q" , typ ))
2020-07-14 14:32:47 -07:00
}
2020-06-10 17:58:52 -04:00
}
if rawActions , ok := raw [ "actions" ]; ok {
interfaceActions , ok := rawActions .([] interface {})
if ! ok {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unable to interpret %q as array" , "actions" ))
2020-06-10 17:58:52 -04:00
}
if len ( interfaceActions ) > 0 {
g . actionsBeingParsed = make ([] string , 0 , len ( interfaceActions ))
for _ , v := range interfaceActions {
actionStr , ok := v .( string )
switch {
case ! ok :
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unable to interpret %v in actions array as string" , v ))
2020-06-10 17:58:52 -04:00
case actionStr == "" :
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "empty action found" )
2020-06-10 17:58:52 -04:00
default :
g . actionsBeingParsed = append ( g . actionsBeingParsed , strings . ToLower ( actionStr ))
}
}
}
}
2021-05-04 19:57:35 -05:00
if rawOutputFields , ok := raw [ "output_fields" ]; ok {
interfaceOutputFields , ok := rawOutputFields .([] interface {})
if ! ok {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unable to interpret %q as array" , "output_fields" ))
2021-05-04 19:57:35 -05:00
}
// We do the make here because we detect later if the field was set but
// no values given
g . OutputFields = make ( OutputFieldsMap , len ( interfaceOutputFields ))
if len ( interfaceOutputFields ) > 0 {
for _ , v := range interfaceOutputFields {
field , ok := v .( string )
switch {
case ! ok :
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unable to interpret %v in output_fields array as string" , v ))
2021-05-04 19:57:35 -05:00
default :
g . OutputFields [ field ] = true
}
}
}
}
2020-06-10 17:58:52 -04:00
return nil
}
func ( g * Grant ) unmarshalText ( grantString string ) error {
2021-01-13 15:50:07 -08:00
const op = "perms.(Grant).unmarshalText"
2020-06-10 17:58:52 -04:00
segments := strings . Split ( grantString , ";" )
for _ , segment := range segments {
kv := strings . Split ( segment , "=" )
// Ensure we don't accept "foo=bar=baz", "=foo", or "foo="
switch {
case len ( kv ) != 2 :
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "segment %q not formatted correctly, wrong number of equal signs" , segment ))
2020-06-10 17:58:52 -04:00
case len ( kv [ 0 ]) == 0 :
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "segment %q not formatted correctly, missing key" , segment ))
2020-06-10 17:58:52 -04:00
case len ( kv [ 1 ]) == 0 :
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "segment %q not formatted correctly, missing value" , segment ))
2020-06-10 17:58:52 -04:00
}
switch kv [ 0 ] {
case "id" :
2020-12-18 13:25:28 -05:00
g . id = kv [ 1 ]
2020-06-10 17:58:52 -04:00
case "type" :
2020-06-26 11:00:14 -04:00
typeString := strings . ToLower ( kv [ 1 ])
2020-07-14 14:32:47 -07:00
g . typ = resource . Map [ typeString ]
2020-06-26 11:00:14 -04:00
if g . typ == resource . Unknown {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unknown type specifier %q" , typeString ))
2020-06-26 11:00:14 -04:00
}
2020-06-10 17:58:52 -04:00
case "actions" :
actions := strings . Split ( kv [ 1 ], "," )
if len ( actions ) > 0 {
g . actionsBeingParsed = make ([] string , 0 , len ( actions ))
for _ , action := range actions {
if action == "" {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "empty action found" )
2020-06-10 17:58:52 -04:00
}
g . actionsBeingParsed = append ( g . actionsBeingParsed , strings . ToLower ( action ))
}
}
2021-05-04 19:57:35 -05:00
case "output_fields" :
g . OutputFields = g . OutputFields . AddFields ( strings . Split ( kv [ 1 ], "," ))
2020-06-10 17:58:52 -04:00
}
}
return nil
}
// Parse parses a grant string. Note that this does not do checking
// of the validity of IDs and such; that's left for other parts of the system.
// We may not check at all (e.g. let it be an authz-time failure) or could check
// after submission to catch errors.
//
// The scope must be the org and project where this grant originated, not the
// request.
2020-10-01 10:51:54 -04:00
func Parse ( scopeId , grantString string , opt ... Option ) ( Grant , error ) {
2021-01-13 15:50:07 -08:00
const op = "perms.Parse"
2020-06-10 17:58:52 -04:00
if len ( grantString ) == 0 {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "missing grant string" )
2020-06-10 17:58:52 -04:00
}
2020-07-10 17:42:47 -07:00
if scopeId == "" {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "missing scope id" )
2020-06-10 17:58:52 -04:00
}
grant := Grant {
2020-07-10 17:42:47 -07:00
scope : Scope { Id : scopeId },
}
switch {
case scopeId == "global" :
grant . scope . Type = scope . Global
2020-07-11 12:00:49 -04:00
case strings . HasPrefix ( scopeId , scope . Org . Prefix ()):
grant . scope . Type = scope . Org
2020-07-10 17:42:47 -07:00
case strings . HasPrefix ( scopeId , scope . Project . Prefix ()):
grant . scope . Type = scope . Project
default :
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "invalid scope type" )
2020-06-10 17:58:52 -04:00
}
switch {
case grantString [ 0 ] == '{' :
if err := grant . unmarshalJSON ([] byte ( grantString )); err != nil {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . WrapDeprecated ( err , op , errors . WithMsg ( "unable to parse JSON grant string" ))
2020-06-10 17:58:52 -04:00
}
default :
if err := grant . unmarshalText ( grantString ); err != nil {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . WrapDeprecated ( err , op , errors . WithMsg ( "unable to parse grant string" ))
2020-06-10 17:58:52 -04:00
}
}
2020-10-01 10:51:54 -04:00
opts := getOpts ( opt ... )
2020-10-02 16:45:32 -07:00
// Check for templated values ID, and substitute in with the authenticated values
2020-06-10 17:58:52 -04:00
// if so
2020-10-01 10:51:54 -04:00
if grant . id != "" && strings . HasPrefix ( grant . id , "{{" ) {
2020-06-10 17:58:52 -04:00
id := strings . TrimSuffix ( strings . TrimPrefix ( grant . id , "{{" ), "}}" )
2020-12-18 13:25:28 -05:00
id = strings . TrimSpace ( id )
2020-06-10 17:58:52 -04:00
switch id {
case "user.id" :
2020-10-01 10:51:54 -04:00
if opts . withUserId != "" {
grant . id = opts . withUserId
}
case "account.id" :
if opts . withAccountId != "" {
grant . id = opts . withAccountId
}
2020-06-10 17:58:52 -04:00
default :
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unknown template %q in grant %q value" , grant . id , "id" ))
2020-06-10 17:58:52 -04:00
}
}
if err := grant . validateType (); err != nil {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . WrapDeprecated ( err , op )
2020-06-10 17:58:52 -04:00
}
if err := grant . parseAndValidateActions (); err != nil {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . WrapDeprecated ( err , op )
2020-06-10 17:58:52 -04:00
}
2020-10-01 10:51:54 -04:00
if ! opts . withSkipFinalValidation {
2021-02-05 12:39:55 -05:00
// Filter out some forms that don't make sense
// First up, an ID is given, no type, and actions contains "create" or
// "list". Note wildcard for actions is still okay.
if grant . id != "" && grant . typ == resource . Unknown {
if grant . actions [ action . Create ] ||
grant . actions [ action . List ] {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains create or list action in a format that does not allow these" )
2021-02-05 12:39:55 -05:00
}
}
// If no ID is given...
if grant . id == "" {
// Check the type
switch grant . typ {
case resource . Unknown :
// Error -- no ID or type isn't valid (although we should never
// get to this point because original parsing should error)
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains no id or type" )
2021-02-05 12:39:55 -05:00
case resource . All :
// "type=*;actions=..." is not supported -- we reqiure you to
// explicitly set a pin or set the ID to *
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains wildcard type with no id value" )
2021-02-05 12:39:55 -05:00
default :
// Here we have type=something,actions=<something else>. This
// means we're operating on collections. Note that wildcard
// actions are not okay here; that uses the format
// id=*;type=<something>;actions=*
switch len ( grant . actions ) {
case 0 :
2021-09-02 12:54:02 -04:00
// It's okay to have no actions if only output fields are being defined
if len ( grant . OutputFields ) == 0 {
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains no actions or output fields" )
}
2021-02-05 12:39:55 -05:00
case 1 :
if ! grant . actions [ action . Create ] &&
! grant . actions [ action . List ] {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains non-create or non-list action in a format that only allows these" )
2021-02-05 12:39:55 -05:00
}
case 2 :
if ! grant . actions [ action . Create ] || ! grant . actions [ action . List ] {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains non-create or non-list action in a format that only allows these" )
2021-02-05 12:39:55 -05:00
}
default :
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string contains non-create or non-list action in a format that only allows these" )
2021-02-05 12:39:55 -05:00
}
}
}
2021-05-04 19:57:35 -05:00
// Set but empty output fields...
if grant . OutputFields != nil && len ( grant . OutputFields ) == 0 {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string has output_fields set but empty" )
2020-10-01 01:49:20 -04:00
}
2021-05-04 19:57:35 -05:00
// This might be zero if output fields is populated
if len ( grant . actions ) > 0 {
// Create a dummy resource and pass it through Allowed and ensure that
// we get allowed.
acl := NewACL ( grant )
r := Resource {
ScopeId : scopeId ,
Id : grant . id ,
Type : grant . typ ,
}
if ! topLevelType ( grant . typ ) {
r . Pin = grant . id
}
var allowed bool
for k := range grant . actions {
results := acl . Allowed ( r , k )
if results . Authorized {
allowed = true
}
}
if ! allowed {
2021-07-29 18:52:56 -06:00
return Grant {}, errors . NewDeprecated ( errors . InvalidParameter , op , "parsed grant string would not result in any action being authorized" )
2020-10-01 01:49:20 -04:00
}
}
}
2020-06-10 17:58:52 -04:00
return grant , nil
}
func ( g Grant ) validateType () error {
2021-01-13 15:50:07 -08:00
const op = "perms.(Grant).validateType"
2020-06-10 17:58:52 -04:00
switch g . typ {
2020-06-26 11:00:14 -04:00
case resource . Unknown ,
resource . All ,
2020-08-12 00:02:26 -04:00
resource . Scope ,
2020-06-26 11:00:14 -04:00
resource . User ,
2020-08-12 00:02:26 -04:00
resource . Group ,
resource . Role ,
2020-06-26 11:00:14 -04:00
resource . AuthMethod ,
2020-08-12 00:02:26 -04:00
resource . Account ,
2021-04-23 12:45:08 -05:00
resource . AuthToken ,
2020-06-26 11:00:14 -04:00
resource . HostCatalog ,
resource . HostSet ,
resource . Host ,
2020-09-29 20:38:16 -04:00
resource . Target ,
resource . Session :
2020-06-10 17:58:52 -04:00
return nil
}
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unknown type specifier %q" , g . typ ))
2020-06-10 17:58:52 -04:00
}
func ( g * Grant ) parseAndValidateActions () error {
2021-01-13 15:50:07 -08:00
const op = "perms.(Grant).parseAndValidateActions"
2020-06-10 17:58:52 -04:00
if len ( g . actionsBeingParsed ) == 0 {
2021-05-04 19:57:35 -05:00
g . actionsBeingParsed = nil
// If there are no actions it's fine if the grant is just used to
// specify output fields
if len ( g . OutputFields ) > 0 {
return nil
}
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "missing actions" )
2020-06-10 17:58:52 -04:00
}
2020-06-26 11:00:14 -04:00
for _ , a := range g . actionsBeingParsed {
if a == "" {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , "empty action found" )
2020-06-10 17:58:52 -04:00
}
if g . actions == nil {
2020-06-26 11:00:14 -04:00
g . actions = make ( map [ action . Type ] bool , len ( g . actionsBeingParsed ))
2020-06-10 17:58:52 -04:00
}
2020-06-26 11:00:14 -04:00
if am := action . Map [ a ]; am == action . Unknown {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "unknown action %q" , a ))
2020-06-10 17:58:52 -04:00
} else {
2020-06-26 11:00:14 -04:00
g . actions [ am ] = true
2020-06-10 17:58:52 -04:00
}
}
2020-06-26 11:00:14 -04:00
if len ( g . actions ) > 1 && g . actions [ action . All ] {
2021-07-29 18:52:56 -06:00
return errors . NewDeprecated ( errors . InvalidParameter , op , fmt . Sprintf ( "%q cannot be specified with other actions" , action . All . String ()))
2020-06-10 17:58:52 -04:00
}
g . actionsBeingParsed = nil
return nil
}