Support multiple IDs in grants (#3263)

This allows specifying multiple IDs in grants via an `ids` field, which
will eventually become the normal way to specify IDs. Internally, each
grant with multiple IDs turns into multiple ACL grants each with a
single ID; to make this distinction clearer and allow for better
separation of concerns an AclGrant type has been introduced, so really
multiple IDs in a Grant turn into multiple AclGrants.

The nice thing about doing it this way is that it becomes purely a
parsing concern; no actual validation logic has changed.

For backwards compatibility with grants already stored in roles, we do
not error out if `id` is specified. However, eventually we will simply
not support grants with `id` coming via the API; they will fail API
validation, such that new grants will require the use of the updated
`ids` field.

A follow-on PR will plumb this through the service handlers/API/CLI, but
I decided to do it in separate chunks to make reviewing easier, given
that they are easily separable into the internal and external aspects of
the change.

* Add CLI/API checks around using ID in grants (#3264)

* Plumb through some contexts to remove a lot of deprecated errors (#3268)
This commit is contained in:
Jeff Mitchell committed 2023-06-14 21:55:24 -04:00
1 parent 8acc73dc6e
commit feb3aea38f
25 files changed
+1043 -381

No files matched your search

+13
View File
@@ -4,6 +4,19 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
## Next
### New and Improved
* roles: In grants, the `id` field has been changed to `ids` (but `id` will
still be accepted for now, up until 0.15.0). In the `ids` field, multiple IDs
can now be specified in a grant, either via commas (text format) or array
(JSON format). ([PR](https://github.com/hashicorp/boundary/pull/3263)).
### Deprecations/Changes
* Grants can now accept more than one ID per grant string (or entry in JSON) via
the `ids` parameter. In 0.15.0 the ability to add new grants via the `id`
parameter will be removed.
## 0.13 (2023/06/13)
### New and Improved
+1
View File
@@ -6,6 +6,7 @@ package roles
type GrantJson struct {
Id string `json:"id,omitempty"`
Ids []string `json:"ids,omitempty"`
Type string `json:"type,omitempty"`
Actions []string `json:"actions,omitempty"`
}
+1
View File
@@ -5,6 +5,7 @@ package globals
const (
IdField = "id"
IdsField = "ids"
VersionField = "version"
NameField = "name"
DescriptionField = "description"
+14
View File
@@ -163,6 +163,14 @@ var prefixToResourceType = map[string]resource.Type{
SessionRecordingPrefix: resource.SessionRecording,
}
var resourceTypeToPrefixes map[resource.Type][]string = func() map[resource.Type][]string {
ret := make(map[resource.Type][]string)
for k, v := range prefixToResourceType {
ret[v] = append(ret[v], k)
}
return ret
}()
// ResourceTypeFromPrefix takes in a resource ID (or a prefix) and returns the
// corresponding resource typ
func ResourceTypeFromPrefix(in string) resource.Type {
@@ -170,3 +178,9 @@ func ResourceTypeFromPrefix(in string) resource.Type {
in, _, _ = strings.Cut(in, "_")
return prefixToResourceType[in]
}
// ResourcePrefixesFromType returns the known prefixes for a given type; if a
// type is not known the return value will be nil
func ResourcePrefixesFromType(in resource.Type) []string {
return resourceTypeToPrefixes[in]
}
+11 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/version"
"github.com/mitchellh/go-wordwrap"
)
@@ -218,11 +219,20 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]roles.Opti
if len(c.flagGrants) > 0 {
for _, grant := range c.flagGrants {
_, err := perms.Parse(scope.Global.String(), grant)
parsed, err := perms.Parse(c.Context, scope.Global.String(), grant)
if err != nil {
c.UI.Error(fmt.Errorf("Grant %q could not be parsed successfully: %w", grant, err).Error())
return false
}
switch {
case parsed.Id() == "":
// Nothing
case version.SupportsFeature(version.Binary, version.SupportIdInGrants):
c.UI.Warn(fmt.Sprintf("Grant %q uses the %q field, which is deprecated and will not be allowed in version 0.15.0+. Please use %q instead.", grant, "id", "ids"))
default:
c.UI.Error(fmt.Sprintf("Grant %q uses the %q field which is no longer supported. Please use %q instead.", grant, "id", "ids"))
return false
}
}
}
+8 -2
View File
@@ -346,7 +346,10 @@ func DevWorker(opt ...Option) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
}
opts := getOpts(opt...)
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options: %w", err)
}
parsed.Eventing.AuditEnabled = opts.withAuditEventsEnabled
parsed.Eventing.ObservationsEnabled = opts.withObservationsEnabled
parsed.Eventing.SysEventsEnabled = opts.withSysEventsEnabled
@@ -389,7 +392,10 @@ func DevController(opt ...Option) (*Config, error) {
parsed.DevWorkerAuthKey = workerAuthKey
parsed.DevBsrKey = bsrKey
parsed.DevRecoveryKey = recoveryKey
opts := getOpts(opt...)
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options: %w", err)
}
parsed.Eventing.AuditEnabled = opts.withAuditEventsEnabled
parsed.Eventing.ObservationsEnabled = opts.withObservationsEnabled
parsed.Eventing.SysEventsEnabled = opts.withSysEventsEnabled
+49 -23
View File
@@ -5,19 +5,30 @@ package config
import (
"os"
"testing"
"github.com/hashicorp/go-secure-stdlib/parseutil"
)
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
func getOpts(opt ...Option) (options, error) {
opts, err := getDefaultOptions()
if err != nil {
return opts, err
}
return opts
for _, o := range opt {
if o == nil {
continue
}
err = o(&opts)
if err != nil {
return opts, err
}
}
return opts, nil
}
// Option - how Options are passed as arguments
type Option func(*options)
type Option func(*options) error
// options = how options are represented
type options struct {
@@ -27,50 +38,65 @@ type options struct {
testWithErrorEventsEnabled bool
}
func getDefaultOptions() options {
func getDefaultOptions() (options, error) {
opts := options{}
if os.Getenv("BOUNDARY_ENABLE_TEST_SYS_EVENTS") != "" {
opts.withSysEventsEnabled = true
}
if os.Getenv("BOUNDARY_ENABLE_TEST_AUDIT_EVENTS") != "" {
opts.withAuditEventsEnabled = true
}
if os.Getenv("BOUNDARY_ENABLE_TEST_OBSERVATIONS") != "" {
opts.withObservationsEnabled = true
}
if os.Getenv("BOUNDARY_ENABLE_TEST_ERROR_EVENTS") != "" {
opts.testWithErrorEventsEnabled = true
sysEvents, err := parseutil.ParseBool(os.Getenv("BOUNDARY_ENABLE_TEST_SYS_EVENTS"))
if err != nil {
return opts, err
}
opts.withSysEventsEnabled = sysEvents
return opts
auditEvents, err := parseutil.ParseBool(os.Getenv("BOUNDARY_ENABLE_TEST_AUDIT_EVENTS"))
if err != nil {
return opts, err
}
opts.withAuditEventsEnabled = auditEvents
obs, err := parseutil.ParseBool(os.Getenv("BOUNDARY_ENABLE_TEST_OBSERVATIONS"))
if err != nil {
return opts, err
}
opts.withObservationsEnabled = obs
errEvents, err := parseutil.ParseBool(os.Getenv("BOUNDARY_ENABLE_TEST_ERROR_EVENTS"))
if err != nil {
return opts, err
}
opts.testWithErrorEventsEnabled = errEvents
return opts, nil
}
// WithSysEventsEnabled provides an option for enabling system events
func WithSysEventsEnabled(enable bool) Option {
return func(o *options) {
return func(o *options) error {
o.withSysEventsEnabled = enable
return nil
}
}
// WithAuditEventsEnabled provides an option for enabling audit events
func WithAuditEventsEnabled(enable bool) Option {
return func(o *options) {
return func(o *options) error {
o.withAuditEventsEnabled = enable
return nil
}
}
// WithObservationsEnabled provides an option for enabling observation events
func WithObservationsEnabled(enable bool) Option {
return func(o *options) {
return func(o *options) error {
o.withObservationsEnabled = enable
return nil
}
}
// TestWithErrorEventsEnabled provides an option for enabling error events
// during tests.
func TestWithErrorEventsEnabled(_ testing.TB, enable bool) Option {
return func(o *options) {
return func(o *options) error {
o.testWithErrorEventsEnabled = enable
return nil
}
}
+1
View File
@@ -656,6 +656,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (
permsOpts = append(permsOpts, perms.WithAccountId(*userData.Account.Id))
}
parsed, err := perms.Parse(
ctx,
pair.ScopeId,
pair.Grant,
permsOpts...)
@@ -22,6 +22,7 @@ import (
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/roles"
"github.com/hashicorp/boundary/version"
"github.com/hashicorp/go-secure-stdlib/strutil"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/wrapperspb"
@@ -401,7 +402,7 @@ func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePr
func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsRequest) (*pbs.AddRoleGrantsResponse, error) {
const op = "roles.(Service).AddRoleGrants"
if err := validateAddRoleGrantsRequest(req); err != nil {
if err := validateAddRoleGrantsRequest(ctx, req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.AddGrants)
@@ -439,7 +440,7 @@ func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsReques
func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsRequest) (*pbs.SetRoleGrantsResponse, error) {
const op = "roles.(Service).SetRoleGrants"
if err := validateSetRoleGrantsRequest(req); err != nil {
if err := validateSetRoleGrantsRequest(ctx, req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.SetGrants)
@@ -477,7 +478,7 @@ func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsReques
func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrantsRequest) (*pbs.RemoveRoleGrantsResponse, error) {
const op = "roles.(Service).RemoveRoleGrants"
if err := validateRemoveRoleGrantsRequest(req); err != nil {
if err := validateRemoveRoleGrantsRequest(ctx, req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.RemoveGrants)
@@ -853,7 +854,7 @@ func toProto(ctx context.Context, in *iam.Role, principals []*iam.PrincipalRole,
}
if outputFields.Has(globals.GrantsField) {
for _, g := range grants {
parsed, err := perms.Parse(in.GetGrantScopeId(), g.GetRawGrant())
parsed, err := perms.Parse(ctx, in.GetGrantScopeId(), g.GetRawGrant())
if err != nil {
// This should never happen as we validate on the way in, but let's
// return what we can since we are still returning the raw grant
@@ -869,6 +870,7 @@ func toProto(ctx context.Context, in *iam.Role, principals []*iam.PrincipalRole,
Canonical: g.GetCanonicalGrant(),
Json: &pb.GrantJson{
Id: parsed.Id(),
Ids: parsed.Ids(),
Type: parsed.Type().String(),
Actions: actions,
},
@@ -1039,7 +1041,7 @@ func validateRemoveRolePrincipalsRequest(req *pbs.RemoveRolePrincipalsRequest) e
return nil
}
func validateAddRoleGrantsRequest(req *pbs.AddRoleGrantsRequest) error {
func validateAddRoleGrantsRequest(ctx context.Context, req *pbs.AddRoleGrantsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), globals.RolePrefix) {
badFields["id"] = "Incorrectly formatted identifier."
@@ -1055,7 +1057,7 @@ func validateAddRoleGrantsRequest(req *pbs.AddRoleGrantsRequest) error {
badFields["grant_strings"] = "Grant strings must not be empty."
break
}
grant, err := perms.Parse("p_anything", v)
grant, err := perms.Parse(ctx, "p_anything", v)
if err != nil {
badFields["grant_strings"] = fmt.Sprintf("Improperly formatted grant %q.", v)
break
@@ -1066,6 +1068,14 @@ func validateAddRoleGrantsRequest(req *pbs.AddRoleGrantsRequest) error {
badFields["grant_strings"] = fmt.Sprintf("Action %q has been deprecated and is not allowed to be set in grants. Use %q instead.", actStr, depAct.String())
}
}
switch {
case grant.Id() == "":
// Nothing
case version.SupportsFeature(version.Binary, version.SupportIdInGrants):
// This will warn on the CLI
default:
badFields["grant_strings"] = fmt.Sprintf("Grant %q uses the %q field which is no longer supported. Please use %q instead.", v, "id", "ids")
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
@@ -1073,7 +1083,7 @@ func validateAddRoleGrantsRequest(req *pbs.AddRoleGrantsRequest) error {
return nil
}
func validateSetRoleGrantsRequest(req *pbs.SetRoleGrantsRequest) error {
func validateSetRoleGrantsRequest(ctx context.Context, req *pbs.SetRoleGrantsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), globals.RolePrefix) {
badFields["id"] = "Incorrectly formatted identifier."
@@ -1086,7 +1096,7 @@ func validateSetRoleGrantsRequest(req *pbs.SetRoleGrantsRequest) error {
badFields["grant_strings"] = "Grant strings must not be empty."
break
}
grant, err := perms.Parse("p_anything", v)
grant, err := perms.Parse(ctx, "p_anything", v)
if err != nil {
badFields["grant_strings"] = fmt.Sprintf("Improperly formatted grant %q.", v)
break
@@ -1097,6 +1107,14 @@ func validateSetRoleGrantsRequest(req *pbs.SetRoleGrantsRequest) error {
badFields["grant_strings"] = fmt.Sprintf("Action %q has been deprecated and is not allowed to be set in grants. Use %q instead.", actStr, depAct.String())
}
}
switch {
case grant.Id() == "":
// Nothing
case version.SupportsFeature(version.Binary, version.SupportIdInGrants):
// This will warn on the CLI
default:
badFields["grant_strings"] = fmt.Sprintf("Grant %q uses the %q field which is no longer supported. Please use %q instead.", v, "id", "ids")
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
@@ -1104,7 +1122,7 @@ func validateSetRoleGrantsRequest(req *pbs.SetRoleGrantsRequest) error {
return nil
}
func validateRemoveRoleGrantsRequest(req *pbs.RemoveRoleGrantsRequest) error {
func validateRemoveRoleGrantsRequest(ctx context.Context, req *pbs.RemoveRoleGrantsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), globals.RolePrefix) {
badFields["id"] = "Incorrectly formatted identifier."
@@ -1120,7 +1138,7 @@ func validateRemoveRoleGrantsRequest(req *pbs.RemoveRoleGrantsRequest) error {
badFields["grant_strings"] = "Grant strings must not be empty."
break
}
if _, err := perms.Parse("p_anything", v); err != nil {
if _, err := perms.Parse(ctx, "p_anything", v); err != nil {
badFields["grant_strings"] = fmt.Sprintf("Improperly formatted grant %q.", v)
break
}
@@ -24,6 +24,7 @@ import (
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/roles"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/hashicorp/boundary/version"
"github.com/kr/pretty"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc/codes"
@@ -571,7 +572,7 @@ func TestCreate(t *testing.T) {
func TestUpdate(t *testing.T) {
grantString := "id=*;type=*;actions=*"
g, err := perms.Parse("global", grantString)
g, err := perms.Parse(context.Background(), "global", grantString)
require.NoError(t, err)
_, actions := g.Actions()
grant := &pb.Grant{
@@ -579,6 +580,7 @@ func TestUpdate(t *testing.T) {
Canonical: g.CanonicalString(),
Json: &pb.GrantJson{
Id: g.Id(),
Ids: g.Ids(),
Type: g.Type().String(),
Actions: actions,
},
@@ -1632,7 +1634,7 @@ func checkEqualGrants(t *testing.T, expected []string, got *pb.Role) {
require.Equal(len(expected), len(got.GrantStrings))
require.Equal(len(expected), len(got.Grants))
for i, v := range expected {
parsed, err := perms.Parse("o_abc123", v)
parsed, err := perms.Parse(context.Background(), "o_abc123", v)
require.NoError(err)
assert.Equal(expected[i], got.GrantStrings[i])
assert.Equal(expected[i], got.Grants[i].GetRaw())
@@ -1640,6 +1642,7 @@ func checkEqualGrants(t *testing.T, expected []string, got *pb.Role) {
j := got.Grants[i].GetJson()
require.NotNil(j)
assert.Equal(parsed.Id(), j.GetId())
assert.Equal(parsed.Ids(), j.GetIds())
assert.Equal(parsed.Type().String(), j.GetType())
_, acts := parsed.Actions()
assert.Equal(acts, j.GetActions())
@@ -1666,34 +1669,43 @@ func TestAddGrants(t *testing.T) {
}{
{
name: "Add grant on empty role",
add: []string{"id=*;type=*;actions=delete"},
result: []string{"id=*;type=*;actions=delete"},
add: []string{"ids=*;type=*;actions=delete"},
result: []string{"ids=*;type=*;actions=delete"},
},
{
name: "Add grant on role with grant",
existing: []string{"id=u_foo;actions=read"},
add: []string{"id=*;type=*;actions=delete"},
result: []string{"id=u_foo;actions=read", "id=*;type=*;actions=delete"},
add: []string{"ids=*;type=*;actions=delete"},
result: []string{"id=u_foo;actions=read", "ids=*;type=*;actions=delete"},
},
{
name: "Add duplicate grant on role with grant",
existing: []string{"id=u_fooaA1;actions=read"},
add: []string{"id=*;type=*;actions=delete", "id=*;type=*;actions=delete"},
result: []string{"id=u_fooaA1;actions=read", "id=*;type=*;actions=delete"},
add: []string{"ids=*;type=*;actions=delete", "ids=*;type=*;actions=delete"},
result: []string{"id=u_fooaA1;actions=read", "ids=*;type=*;actions=delete"},
},
{
name: "Add grant matching existing grant",
existing: []string{"id=u_foo;actions=read", "id=*;type=*;actions=delete"},
add: []string{"id=*;type=*;actions=delete"},
existing: []string{"ids=u_foo;actions=read", "ids=*;type=*;actions=delete"},
add: []string{"ids=*;type=*;actions=delete"},
wantErr: true,
},
{
name: "Check deprecation",
name: "Check add-host-sets deprecation",
existing: []string{"id=u_foo;actions=read", "id=*;type=*;actions=delete"},
add: []string{"id=*;type=target;actions=add-host-sets"},
add: []string{"ids=*;type=target;actions=add-host-sets"},
wantErr: true,
wantErrContains: "Use \\\"add-host-sources\\\" instead",
},
{
name: "Check id field deprecation",
existing: []string{"id=u_fooaA1;actions=read"},
add: []string{"id=*;type=*;actions=delete"},
result: []string{"id=u_fooaA1;actions=read", "id=*;type=*;actions=delete"},
wantErr: func() bool {
return !version.SupportsFeature(version.Binary, version.SupportIdInGrants)
}(),
},
}
for _, tc := range addCases {
@@ -1739,7 +1751,7 @@ func TestAddGrants(t *testing.T) {
name: "Bad Version",
req: &pbs.AddRoleGrantsRequest{
Id: role.GetPublicId(),
GrantStrings: []string{"id=*;type=*;actions=create"},
GrantStrings: []string{"ids=*;type=*;actions=create"},
Version: role.GetVersion() + 2,
},
err: handlers.ApiErrorWithCode(codes.Internal),
@@ -1748,7 +1760,7 @@ func TestAddGrants(t *testing.T) {
name: "Bad Role Id",
req: &pbs.AddRoleGrantsRequest{
Id: "bad id",
GrantStrings: []string{"id=*;type=*;actions=create"},
GrantStrings: []string{"ids=*;type=*;actions=create"},
Version: role.GetVersion(),
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
@@ -1757,7 +1769,7 @@ func TestAddGrants(t *testing.T) {
name: "Unparseable Grant",
req: &pbs.AddRoleGrantsRequest{
Id: role.GetPublicId(),
GrantStrings: []string{"id=*;type=*;actions=create", "unparseable"},
GrantStrings: []string{"ids=*;type=*;actions=create", "unparseable"},
Version: role.GetVersion(),
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
@@ -1766,7 +1778,7 @@ func TestAddGrants(t *testing.T) {
name: "Empty Grant",
req: &pbs.AddRoleGrantsRequest{
Id: role.GetPublicId(),
GrantStrings: []string{"id=*;type=*;actions=create", ""},
GrantStrings: []string{"ids=*;type=*;actions=create", ""},
Version: role.GetVersion(),
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
@@ -1805,26 +1817,26 @@ func TestSetGrants(t *testing.T) {
}{
{
name: "Set grant on empty role",
set: []string{"id=*;type=*;actions=delete"},
result: []string{"id=*;type=*;actions=delete"},
set: []string{"ids=*;type=*;actions=delete"},
result: []string{"ids=*;type=*;actions=delete"},
},
{
name: "Set grant on role with grant",
existing: []string{"id=u_foo;actions=read"},
set: []string{"id=*;type=*;actions=delete"},
result: []string{"id=*;type=*;actions=delete"},
set: []string{"ids=*;type=*;actions=delete"},
result: []string{"ids=*;type=*;actions=delete"},
},
{
name: "Set grant matching existing grant",
existing: []string{"id=u_foo;actions=read", "id=*;type=*;actions=delete"},
set: []string{"id=*;type=*;actions=delete"},
result: []string{"id=*;type=*;actions=delete"},
set: []string{"ids=*;type=*;actions=delete"},
result: []string{"ids=*;type=*;actions=delete"},
},
{
name: "Set duplicate grant matching existing grant",
existing: []string{"id=u_foo;actions=read", "id=*;type=*;actions=delete"},
set: []string{"id=*;type=*;actions=delete", "id=*;type=*;actions=delete"},
result: []string{"id=*;type=*;actions=delete"},
set: []string{"ids=*;type=*;actions=delete", "ids=*;type=*;actions=delete"},
result: []string{"ids=*;type=*;actions=delete"},
},
{
name: "Set empty on role",
@@ -1833,12 +1845,21 @@ func TestSetGrants(t *testing.T) {
result: nil,
},
{
name: "Check deprecation",
name: "Check add-host-sets deprecation",
existing: []string{"id=u_foo;actions=read", "id=*;type=*;actions=delete"},
set: []string{"id=*;type=target;actions=add-host-sets"},
set: []string{"ids=*;type=target;actions=add-host-sets"},
wantErr: true,
wantErrContains: "Use \\\"add-host-sources\\\" instead",
},
{
name: "Check id field deprecation",
existing: []string{"id=u_fooaA1;actions=read"},
set: []string{"id=*;type=*;actions=delete"},
result: []string{"id=*;type=*;actions=delete"},
wantErr: func() bool {
return !version.SupportsFeature(version.Binary, version.SupportIdInGrants)
}(),
},
}
for _, tc := range setCases {
@@ -1885,7 +1906,7 @@ func TestSetGrants(t *testing.T) {
name: "Bad Version",
req: &pbs.SetRoleGrantsRequest{
Id: role.GetPublicId(),
GrantStrings: []string{"id=*;type=*;actions=create"},
GrantStrings: []string{"ids=*;type=*;actions=create"},
Version: role.GetVersion() + 2,
},
err: handlers.ApiErrorWithCode(codes.Internal),
@@ -1894,7 +1915,7 @@ func TestSetGrants(t *testing.T) {
name: "Bad Role Id",
req: &pbs.SetRoleGrantsRequest{
Id: "bad id",
GrantStrings: []string{"id=*;type=*;actions=create"},
GrantStrings: []string{"ids=*;type=*;actions=create"},
Version: role.GetVersion(),
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
@@ -1903,7 +1924,7 @@ func TestSetGrants(t *testing.T) {
name: "Unparsable grant",
req: &pbs.SetRoleGrantsRequest{
Id: role.GetPublicId(),
GrantStrings: []string{"id=*;type=*;actions=create", "unparseable"},
GrantStrings: []string{"ids=*;type=*;actions=create", "unparseable"},
Version: role.GetVersion(),
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
+9 -1
View File
@@ -5248,7 +5248,15 @@
"properties": {
"id": {
"type": "string",
"description": "Output only. The ID, if set.",
"description": "Output only. The ID, if set.\nDeprecated: use \"ids\" instead.",
"readOnly": true
},
"ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Output only. The IDs, if set.",
"readOnly": true
},
"type": {
+5 -5
View File
@@ -34,7 +34,7 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi
newRoleGrants := make([]any, 0, len(grants))
for _, grant := range grants {
roleGrant, err := NewRoleGrant(roleId, grant)
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"))
}
@@ -169,7 +169,7 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
deleteRoleGrants := make([]any, 0, len(grants))
for _, grant := range grants {
// Use a fake scope, just want to get out a canonical string
perm, err := perms.Parse("o_abcd1234", grant, perms.WithSkipFinalValidation(true))
perm, err := perms.Parse(ctx, "o_abcd1234", grant, perms.WithSkipFinalValidation(true))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
}
@@ -178,7 +178,7 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe
continue
}
roleGrant, err := NewRoleGrant(roleId, grant)
roleGrant, err := NewRoleGrant(ctx, roleId, grant)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
@@ -261,7 +261,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
deleteRoleGrants := make([]any, 0, len(grants))
for _, grant := range grants {
// Use a fake scope, just want to get out a canonical string
perm, err := perms.Parse("o_abcd1234", grant, perms.WithSkipFinalValidation(true))
perm, err := perms.Parse(ctx, "o_abcd1234", grant, perms.WithSkipFinalValidation(true))
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing grant string"))
}
@@ -277,7 +277,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi
}
// Not found, so add
rg, err = NewRoleGrant(roleId, grant)
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"))
}
+8 -6
View File
@@ -223,6 +223,7 @@ func TestRepository_ListRoleGrants(t *testing.T) {
func TestRepository_DeleteRoleGrants(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
@@ -340,7 +341,7 @@ func TestRepository_DeleteRoleGrants(t *testing.T) {
grants := make([]*RoleGrant, 0, tt.args.createCnt)
grantStrings := make([]string, 0, tt.args.createCnt)
for i := 0; i < tt.args.createCnt; i++ {
g, err := NewRoleGrant(tt.args.role.PublicId, fmt.Sprintf("actions=*;id=s_%d", i), tt.args.opt...)
g, err := NewRoleGrant(ctx, tt.args.role.PublicId, fmt.Sprintf("actions=*;id=s_%d", i), tt.args.opt...)
require.NoError(err)
grantStrings = append(grantStrings, g.RawGrant)
grants = append(grants, g)
@@ -405,6 +406,7 @@ func TestRepository_DeleteRoleGrants(t *testing.T) {
func TestRepository_SetRoleGrants_Randomize(t *testing.T) {
t.Parallel()
ctx := context.Background()
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
@@ -421,7 +423,7 @@ func TestRepository_SetRoleGrants_Randomize(t *testing.T) {
totalCnt := 30
grants := make([]*roleGrantWrapper, 0, totalCnt)
for i := 0; i < totalCnt; i++ {
g, err := NewRoleGrant(role.PublicId, fmt.Sprintf("id=s_%d;actions=*", i))
g, err := NewRoleGrant(ctx, role.PublicId, fmt.Sprintf("id=s_%d;actions=*", i))
require.NoError(err)
grants = append(grants, &roleGrantWrapper{
grantString: g.RawGrant,
@@ -446,16 +448,16 @@ func TestRepository_SetRoleGrants_Randomize(t *testing.T) {
// First time, run a couple of error conditions
if i == 1 {
_, _, err := repo.SetRoleGrants(context.Background(), "", 1, []string{})
_, _, err := repo.SetRoleGrants(ctx, "", 1, []string{})
require.Error(err)
_, _, err = repo.SetRoleGrants(context.Background(), role.PublicId, 1, nil)
_, _, err = repo.SetRoleGrants(ctx, role.PublicId, 1, nil)
require.Error(err)
}
_, _, err := repo.SetRoleGrants(context.Background(), role.PublicId, uint32(i), grantsToSet)
_, _, err := repo.SetRoleGrants(ctx, role.PublicId, uint32(i), grantsToSet)
require.NoError(err)
roleGrants, err := repo.ListRoleGrants(context.Background(), role.PublicId)
roleGrants, err := repo.ListRoleGrants(ctx, role.PublicId)
require.NoError(err)
require.Equal(len(grantsToSet), len(roleGrants))
for _, rg := range roleGrants {
+7 -7
View File
@@ -223,7 +223,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
msgs = append(msgs, &roleOplogMsg)
roleGrant, err := NewRoleGrant(adminRolePublicId, "id=*;type=*;actions=*")
roleGrant, err := NewRoleGrant(ctx, adminRolePublicId, "id=*;type=*;actions=*")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
@@ -291,38 +291,38 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
switch s.Type {
case scope.Project.String():
roleGrant, err := NewRoleGrant(defaultRolePublicId, "id=*;type=session;actions=list,read:self,cancel:self")
roleGrant, err := NewRoleGrant(ctx, defaultRolePublicId, "id=*;type=session;actions=list,read:self,cancel:self")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrant, err = NewRoleGrant(defaultRolePublicId, "type=target;actions=list")
roleGrant, err = NewRoleGrant(ctx, defaultRolePublicId, "type=target;actions=list")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
default:
roleGrant, err := NewRoleGrant(defaultRolePublicId, "id=*;type=scope;actions=list,no-op")
roleGrant, err := NewRoleGrant(ctx, defaultRolePublicId, "id=*;type=scope;actions=list,no-op")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id=*;type=auth-method;actions=authenticate,list")
roleGrant, err = NewRoleGrant(ctx, defaultRolePublicId, "id=*;type=auth-method;actions=authenticate,list")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id={{.Account.Id}};actions=read,change-password")
roleGrant, err = NewRoleGrant(ctx, defaultRolePublicId, "id={{.Account.Id}};actions=read,change-password")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
grants = append(grants, roleGrant)
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id=*;type=auth-token;actions=list,read:self,delete:self")
roleGrant, err = NewRoleGrant(ctx, defaultRolePublicId, "id=*;type=auth-token;actions=list,read:self,delete:self")
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory role grant"))
}
+6 -6
View File
@@ -28,21 +28,21 @@ var (
)
// NewRoleGrant creates a new in memory role grant
func NewRoleGrant(roleId string, grant string, _ ...Option) (*RoleGrant, error) {
func NewRoleGrant(ctx context.Context, roleId string, grant string, _ ...Option) (*RoleGrant, error) {
const op = "iam.NewRoleGrant"
if roleId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing role id")
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing role id")
}
if grant == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing 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("o_abcd1234", grant)
perm, err := perms.Parse(ctx, "o_abcd1234", grant)
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("parsing grant string"))
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
}
rg := &RoleGrant{
RoleGrant: &store.RoleGrant{
@@ -80,7 +80,7 @@ func (g *RoleGrant) VetForWrite(ctx context.Context, _ db.Reader, _ db.OpType, _
// 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)
perm, err := perms.Parse(ctx, "o_abcd1234", g.RawGrant)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("parsing grant string"))
}
+9 -6
View File
@@ -17,6 +17,7 @@ import (
func TestRoleGrant_Create(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
@@ -90,7 +91,7 @@ func TestRoleGrant_Create(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() any { a := allocRoleGrant(); return &a }(), "1=1")
got, err := NewRoleGrant(tt.args.roleId, tt.args.grant, tt.args.opt...)
got, err := NewRoleGrant(ctx, tt.args.roleId, tt.args.grant, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
@@ -138,6 +139,7 @@ func TestRoleGrant_Update(t *testing.T) {
func TestRoleGrant_Delete(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
@@ -196,10 +198,10 @@ func TestRoleGrant_Delete(t *testing.T) {
assert, require := assert.New(t), require.New(t)
r := allocRoleGrant()
db.TestDeleteWhere(t, conn, &r, "1=1")
rg, err := NewRoleGrant(projRole.PublicId, "id=u_bcde;actions=read,update")
rg, err := NewRoleGrant(ctx, projRole.PublicId, "id=u_bcde;actions=read,update")
require.NoError(err)
require.NoError(rw.Create(context.Background(), rg))
rg, err = NewRoleGrant(projRole.PublicId, "id=u_cdef;actions=read,update")
rg, err = NewRoleGrant(ctx, projRole.PublicId, "id=u_cdef;actions=read,update")
require.NoError(err)
require.NoError(rw.Create(context.Background(), rg))
@@ -224,6 +226,7 @@ func TestRoleGrant_Delete(t *testing.T) {
func TestRoleGrant_Clone(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
@@ -232,7 +235,7 @@ func TestRoleGrant_Clone(t *testing.T) {
s := testOrg(t, repo, "", "")
role := TestRole(t, conn, s.PublicId)
g, err := NewRoleGrant(role.PublicId, "id=*;type=*;actions=*")
g, err := NewRoleGrant(ctx, role.PublicId, "id=*;type=*;actions=*")
assert.NoError(err)
assert.NotNil(g)
assert.Equal(g.RoleId, role.PublicId)
@@ -246,13 +249,13 @@ func TestRoleGrant_Clone(t *testing.T) {
s := testOrg(t, repo, "", "")
role := TestRole(t, conn, s.PublicId)
g, err := NewRoleGrant(role.PublicId, "id=*;type=*;actions=*")
g, err := NewRoleGrant(ctx, role.PublicId, "id=*;type=*;actions=*")
assert.NoError(err)
require.NotNil(g)
assert.Equal(g.RoleId, role.PublicId)
assert.Equal(g.RawGrant, "id=*;type=*;actions=*")
g2, err := NewRoleGrant(role.PublicId, "id=u_foo;actions=read")
g2, err := NewRoleGrant(ctx, role.PublicId, "id=u_foo;actions=read")
assert.NoError(err)
require.NotNil(g2)
assert.Equal(g2.RoleId, role.PublicId)
+1 -1
View File
@@ -200,7 +200,7 @@ func TestRoleGrant(t testing.TB, conn *db.DB, roleId, grant string, opt ...Optio
require := require.New(t)
rw := db.New(conn)
g, err := NewRoleGrant(roleId, grant, opt...)
g, err := NewRoleGrant(context.Background(), roleId, grant, opt...)
require.NoError(err)
err = rw.Create(context.Background(), g)
require.NoError(err)
+49 -5
View File
@@ -12,10 +12,35 @@ import (
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
)
// AclGrant is used to decouple API-based grants from those we utilize for ACLs.
// Notably it uses a single ID per grant instead of multiple IDs.
type AclGrant struct {
// The scope ID, which will be a project ID or an org ID
scope Scope
// The ID to use
id string
// The type, if provided
typ resource.Type
// The set of actions being granted
actions actionSet
// The set of output fields granted
OutputFields *OutputFields
}
// Actions returns the actions as a slice from the internal map, along with the
// string representations of those actions.
func (a AclGrant) Actions() ([]action.Type, []string) {
return a.actions.Actions()
}
// ACL provides an entry point into the permissions engine for determining if an
// action is allowed on a resource based on a principal's (user or group) grants.
type ACL struct {
scopeMap map[string][]Grant
scopeMap map[string][]AclGrant
}
// ACLResults provides a type for the permission's engine results so that we can
@@ -27,7 +52,7 @@ type ACLResults struct {
OutputFields *OutputFields
// This is included but unexported for testing/debugging
scopeMap map[string][]Grant
scopeMap map[string][]AclGrant
}
// Permission provides information about the specific
@@ -65,19 +90,38 @@ type Resource struct {
Pin string `json:"pin,omitempty"`
}
// NewACL creates an ACL from the grants provided.
// NewACL creates an ACL from the grants provided. Note that this converts the
// API-based Grants to AclGrants.
func NewACL(grants ...Grant) ACL {
ret := ACL{
scopeMap: make(map[string][]Grant, len(grants)),
scopeMap: make(map[string][]AclGrant, len(grants)),
}
for _, grant := range grants {
ret.scopeMap[grant.scope.Id] = append(ret.scopeMap[grant.scope.Id], grant)
switch {
case len(grant.ids) > 0:
for _, id := range grant.ids {
ret.scopeMap[grant.scope.Id] = append(ret.scopeMap[grant.scope.Id], aclGrantFromGrant(grant, id))
}
default:
// This handles the no-ID case as well as the deprecated single-ID case
ret.scopeMap[grant.scope.Id] = append(ret.scopeMap[grant.scope.Id], aclGrantFromGrant(grant, grant.id))
}
}
return ret
}
func aclGrantFromGrant(grant Grant, id string) AclGrant {
return AclGrant{
scope: grant.scope,
id: id,
typ: grant.typ,
actions: grant.actions,
OutputFields: grant.OutputFields,
}
}
// Allowed determines if the grants for an ACL allow an action for a resource.
func (a ACL) Allowed(r Resource, aType action.Type, userId string, opt ...Option) (results ACLResults) {
opts := getOpts(opt...)
+76 -18
View File
@@ -4,6 +4,7 @@
package perms
import (
"context"
"encoding/json"
"fmt"
"testing"
@@ -25,6 +26,8 @@ type scopeGrant struct {
func Test_ACLAllowed(t *testing.T) {
t.Parallel()
ctx := context.Background()
type actionAuthorized struct {
action action.Type
authorized bool
@@ -44,8 +47,8 @@ func Test_ACLAllowed(t *testing.T) {
{
scope: "o_a",
grants: []string{
"id=ampw_bar;actions=read,update",
"id=ampw_baz;actions=read:self,update",
"ids=ampw_bar,ampw_baz;actions=read,update",
"id=ampw_bop;actions=read:self,update",
"type=host-catalog;actions=create",
"type=target;actions=list",
"id=*;type=host-set;actions=list,create",
@@ -55,7 +58,7 @@ func Test_ACLAllowed(t *testing.T) {
scope: "o_b",
grants: []string{
"id=*;type=host-set;actions=list,create",
"id=hcst_mypin;type=host;actions=*;output_fields=name,description",
"ids=hcst_mypin;type=host;actions=*;output_fields=name,description",
"id=*;type=*;actions=authenticate",
"id=*;type=*;output_fields=id",
},
@@ -118,7 +121,7 @@ func Test_ACLAllowed(t *testing.T) {
},
},
{
name: "matching scope and id and matching action",
name: "matching scope and id and matching action first id",
resource: Resource{ScopeId: "o_a", Id: "ampw_bar"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
@@ -127,6 +130,16 @@ func Test_ACLAllowed(t *testing.T) {
{action: action.Delete},
},
},
{
name: "matching scope and id and matching action second id",
resource: Resource{ScopeId: "o_a", Id: "ampw_baz"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
{action: action.Read, authorized: true},
{action: action.Update, authorized: true},
{action: action.Delete},
},
},
{
name: "matching scope and type and all action with valid pin",
resource: Resource{ScopeId: "o_b", Pin: "hcst_mypin", Type: resource.Host},
@@ -187,7 +200,7 @@ func Test_ACLAllowed(t *testing.T) {
},
},
{
name: "matching scope, type, action, random id and bad pin",
name: "matching scope, type, action, random id and bad pin first id",
resource: Resource{ScopeId: "o_a", Id: "anything", Type: resource.HostCatalog, Pin: "ampw_bar"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
@@ -196,6 +209,16 @@ func Test_ACLAllowed(t *testing.T) {
{action: action.Read},
},
},
{
name: "matching scope, type, action, random id and bad pin second id",
resource: Resource{ScopeId: "o_a", Id: "anything", Type: resource.HostCatalog, Pin: "ampw_baz"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
{action: action.Update},
{action: action.Delete},
{action: action.Read},
},
},
{
name: "wrong scope and matching type",
resource: Resource{ScopeId: "o_bad", Type: resource.HostSet},
@@ -306,7 +329,7 @@ func Test_ACLAllowed(t *testing.T) {
},
},
{
name: "read self with top level read",
name: "read self with top level read first id",
resource: Resource{ScopeId: "o_a", Id: "ampw_bar"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
@@ -315,9 +338,18 @@ func Test_ACLAllowed(t *testing.T) {
},
},
{
name: "read self only",
name: "read self with top level read second id",
resource: Resource{ScopeId: "o_a", Id: "ampw_baz"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
{action: action.Read, authorized: true},
{action: action.ReadSelf, authorized: true},
},
},
{
name: "read self only",
resource: Resource{ScopeId: "o_a", Id: "ampw_bop"},
scopeGrants: commonGrants,
actionsAuthorized: []actionAuthorized{
{action: action.Read},
{action: action.ReadSelf, authorized: true},
@@ -360,7 +392,7 @@ func Test_ACLAllowed(t *testing.T) {
var grants []Grant
for _, sg := range test.scopeGrants {
for _, g := range sg.grants {
grant, err := Parse(sg.scope, g, WithAccountId(test.accountId), WithUserId(test.userId))
grant, err := Parse(ctx, sg.scope, g, WithAccountId(test.accountId), WithUserId(test.userId))
require.NoError(t, err)
grants = append(grants, grant)
}
@@ -381,6 +413,10 @@ func Test_ACLAllowed(t *testing.T) {
}
func TestACL_ListPermissions(t *testing.T) {
t.Parallel()
ctx := context.Background()
tests := []struct {
name string
userId string
@@ -498,8 +534,7 @@ func TestACL_ListPermissions(t *testing.T) {
scope: "o_1",
grants: []string{
"id=s_1;type=session;actions=list,read",
"id=s_2;type=session;actions=list,read",
"id=s_3;type=session;actions=list,read",
"ids=s_2,s_3;type=session;actions=list,read",
},
},
},
@@ -581,8 +616,7 @@ func TestACL_ListPermissions(t *testing.T) {
scope: "o_1",
grants: []string{
"id=s_1;type=session;actions=list,no-op",
"id=s_2;type=session;actions=list,no-op",
"id=s_3;type=session;actions=list,no-op",
"ids=s_2,s_3;type=session;actions=list,no-op",
},
},
},
@@ -698,8 +732,7 @@ func TestACL_ListPermissions(t *testing.T) {
scope: "o_1",
grants: []string{
"id=s_1;type=session;actions=list,read",
"id=s_2;type=session;actions=list,read",
"id=s_3;type=session;actions=list,read",
"ids=s_2,s_3;type=session;actions=list,read",
},
},
{
@@ -815,7 +848,7 @@ func TestACL_ListPermissions(t *testing.T) {
var grants []Grant
for _, sg := range tt.aclGrants {
for _, g := range sg.grants {
grant, err := Parse(sg.scope, g, WithSkipFinalValidation(tt.skipGrantValidationChecking))
grant, err := Parse(ctx, sg.scope, g, WithSkipFinalValidation(tt.skipGrantValidationChecking))
require.NoError(t, err)
grants = append(grants, grant)
}
@@ -847,6 +880,8 @@ func TestJsonMarshal(t *testing.T) {
func Test_AnonRestrictions(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
grant string
@@ -858,16 +893,30 @@ func Test_AnonRestrictions(t *testing.T) {
name: "id-specific",
grant: "id=foobar;actions=%s",
},
{
name: "ids-specific",
grant: "ids=foobar;actions=%s",
},
{
name: "wildcard-id",
grant: "id=*;type=%s;actions=%s",
templatedType: true,
shouldHaveSuccess: true,
},
{
name: "wildcard-ids",
grant: "ids=*;type=%s;actions=%s",
templatedType: true,
shouldHaveSuccess: true,
},
{
name: "wildcard-id-and-type",
grant: "id=*;type=*;actions=%s",
},
{
name: "wildcard-ids-and-type",
grant: "ids=*;type=*;actions=%s",
},
{
name: "no-id",
grant: "type=%s;actions=%s",
@@ -883,10 +932,19 @@ func Test_AnonRestrictions(t *testing.T) {
if i == resource.Controller || i == resource.Worker {
continue
}
for j := action.Type(1); j <= action.ReadCertificateAuthority; j++ {
for j := action.Type(1); j <= action.Download; j++ {
id := "foobar"
prefixes := globals.ResourcePrefixesFromType(resource.Type(i))
if len(prefixes) > 0 {
id = fmt.Sprintf("%s_%s", prefixes[0], id)
// If it's global scope, correct it
if id == "global_foobar" {
id = "global"
}
}
res := Resource{
ScopeId: scope.Global.String(),
Id: "foobar",
Id: id,
Type: resource.Type(i),
}
grant := test.grant
@@ -896,7 +954,7 @@ func Test_AnonRestrictions(t *testing.T) {
grant = fmt.Sprintf(grant, action.Type(j).String())
}
parsedGrant, err := Parse(scope.Global.String(), grant, WithSkipFinalValidation(true))
parsedGrant, err := Parse(ctx, scope.Global.String(), grant, WithSkipFinalValidation(true))
require.NoError(err)
acl := NewACL(parsedGrant)
+272 -156
View File
@@ -4,6 +4,7 @@
package perms
import (
"context"
"encoding/json"
"fmt"
"sort"
@@ -15,8 +16,27 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"golang.org/x/exp/slices"
)
type actionSet map[action.Type]bool
// Actions is a helper that goes through the map and returns both the actual
// types of actions as a slice and the equivalent strings
func (a actionSet) Actions() (typs []action.Type, strs []string) {
typs = make([]action.Type, 0, len(a))
strs = make([]string, 0, len(a))
for k, v := range a {
// 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
}
// GrantTuple is simply a struct that can be reference from other code to return
// a set of scopes and grants to parse
type GrantTuple struct {
@@ -37,17 +57,20 @@ type Scope struct {
// 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
// The scope, containing the ID and type
scope Scope
// The ID in the grant, if provided.
// The ID of the grant, if provided. Deprecated in favor of ids.
id string
// The IDs in the grant, if provided
ids []string
// The type, if provided
typ resource.Type
// The set of actions being granted
actions map[action.Type]bool
actions actionSet
// The set of output fields granted
OutputFields *OutputFields
@@ -57,26 +80,25 @@ type Grant struct {
actionsBeingParsed []string
}
// Id returns the ID the grant refers to, if any
func (g Grant) Id() string {
return g.id
}
// Ids returns the IDs the grant refers to, if any
func (g Grant) Ids() []string {
return g.ids
}
// Type returns the type the grant refers to, or Unknown
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
// Actions returns the actions as a slice from the internal map, along with the
// string representations of those actions.
func (g Grant) Actions() ([]action.Type, []string) {
return g.actions.Actions()
}
// hasActionOrSubaction checks whether a grant's action set contains the given
@@ -97,8 +119,13 @@ func (g Grant) clone() *Grant {
ret := &Grant{
scope: g.scope,
id: g.id,
ids: g.ids,
typ: g.typ,
}
if g.ids != nil {
ret.ids = make([]string, len(g.ids))
copy(ret.ids, g.ids)
}
if g.actionsBeingParsed != nil {
ret.actionsBeingParsed = append(ret.actionsBeingParsed, g.actionsBeingParsed...)
}
@@ -126,6 +153,10 @@ func (g Grant) CanonicalString() string {
builder = append(builder, fmt.Sprintf("id=%s", g.id))
}
if len(g.ids) > 0 {
builder = append(builder, fmt.Sprintf("ids=%s", strings.Join(g.ids, ",")))
}
if g.typ != resource.Unknown {
builder = append(builder, fmt.Sprintf("type=%s", g.typ.String()))
}
@@ -153,6 +184,9 @@ func (g Grant) MarshalJSON() ([]byte, error) {
if g.id != "" {
res["id"] = g.id
}
if len(g.ids) > 0 {
res["ids"] = g.ids
}
if g.typ != resource.Unknown {
res["type"] = g.typ.String()
}
@@ -190,6 +224,20 @@ func (g *Grant) unmarshalJSON(data []byte) error {
}
g.id = id
}
if rawIds, ok := raw["ids"]; ok {
ids, ok := rawIds.([]any)
if !ok {
return errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("unable to interpret %q as array", "ids"))
}
g.ids = make([]string, len(ids))
for i, id := range ids {
idStr, ok := id.(string)
if !ok {
return errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("unable to interpret %q element %q as string", "ids", id))
}
g.ids[i] = idStr
}
}
if rawType, ok := raw["type"]; ok {
typ, ok := rawType.(string)
if !ok {
@@ -268,6 +316,9 @@ func (g *Grant) unmarshalText(grantString string) error {
case "id":
g.id = kv[1]
case "ids":
g.ids = strings.Split(kv[1], ",")
case "type":
typeString := strings.ToLower(kv[1])
g.typ = resource.Map[typeString]
@@ -307,13 +358,13 @@ func (g *Grant) unmarshalText(grantString string) error {
//
// The scope must be the org and project where this grant originated, not the
// request.
func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
func Parse(ctx context.Context, scopeId, grantString string, opt ...Option) (Grant, error) {
const op = "perms.Parse"
if len(grantString) == 0 {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing grant string")
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, "missing grant string")
}
if scopeId == "" {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing scope id")
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
grantString = strings.ToValidUTF8(grantString, string(unicode.ReplacementChar))
@@ -328,169 +379,234 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
case strings.HasPrefix(scopeId, scope.Project.Prefix()):
grant.scope.Type = scope.Project
default:
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, "invalid scope type")
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, "invalid scope type")
}
switch {
case grantString[0] == '{':
if err := grant.unmarshalJSON([]byte(grantString)); err != nil {
return Grant{}, errors.WrapDeprecated(err, op, errors.WithMsg("unable to parse JSON grant string"))
return Grant{}, errors.Wrap(ctx, err, op, errors.WithMsg("unable to parse JSON grant string"))
}
default:
if err := grant.unmarshalText(grantString); err != nil {
return Grant{}, errors.WrapDeprecated(err, op, errors.WithMsg("unable to parse grant string"))
return Grant{}, errors.Wrap(ctx, err, op, errors.WithMsg("unable to parse grant string"))
}
}
if grant.id != "" && len(grant.ids) > 0 {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("input grant string %q contains both %q and %q fields", grantString, "id", "ids"))
}
if len(grant.ids) > 1 && slices.Contains(grant.ids, "*") {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("input grant string %q contains both wildcard and non-wildcard values in %q field", grantString, "ids"))
}
opts := getOpts(opt...)
// Check for templated values ID, and substitute in with the authenticated
// values if so. If we are using a dummy user or account ID, store the
// original ID and return it at the end; this is usually the case when
// validating grant formats.
var origId string
if grant.id != "" && strings.HasPrefix(grant.id, "{{") {
id := strings.TrimSuffix(strings.TrimPrefix(grant.id, "{{"), "}}")
id = strings.TrimSpace(id)
switch id {
case "user.id", ".User.Id":
if opts.withUserId != "" {
grant.id = strings.ToValidUTF8(opts.withUserId, string(unicode.ReplacementChar))
var grantIds []string
var deprecatedId bool
switch {
case grant.id != "":
grantIds = []string{grant.id}
deprecatedId = true
case len(grant.ids) > 0:
grantIds = grant.ids
// Ensure we aren't seeing mixed types. We will have already filtered
// out the wildcard case above.
if len(grant.ids) > 1 {
var seenType resource.Type
for i, id := range grantIds {
if i == 0 {
seenType = globals.ResourceTypeFromPrefix(id)
continue
}
if seenType != globals.ResourceTypeFromPrefix(id) {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("input grant string %q contains ids of differently-typed resources", grantString))
}
}
// Otherwise, substitute in a dummy value
origId = grant.id
grant.id = "u_dummy"
case "account.id", ".Account.Id":
if opts.withAccountId != "" {
grant.id = strings.ToValidUTF8(opts.withAccountId, string(unicode.ReplacementChar))
}
origId = grant.id
grant.id = "acctoidc_dummy"
default:
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("unknown template %q in grant %q value", grant.id, "id"))
}
}
if err := grant.validateType(); err != nil {
return Grant{}, errors.WrapDeprecated(err, op)
// It's possible that there is no id in a grant. In that case we still need
// to validate it and build up the parsed Grant. We insert an empty ID value
// in this case; the code below will check that it's non-empty before
// running any ID-specific logic on it.
if len(grantIds) == 0 {
grantIds = []string{""}
}
if err := grant.parseAndValidateActions(); err != nil {
return Grant{}, errors.WrapDeprecated(err, op)
}
if !opts.withSkipFinalValidation {
switch {
case grant.id == "*":
// Matches
// id=*;type=sometype;actions=foo,bar
// or
// id=*;type=*;actions=foo,bar
// This can be a non-unknown type or wildcard
if grant.typ == resource.Unknown {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard id and no specified type", grant.CanonicalString()))
}
case grant.id != "":
// Non-wildcard but specified ID. This can match
// id=foo_bar;actions=foo,bar
// or
// id=foo_bar;type=sometype;actions=foo,bar
// or
// id=foo_bar;type=*;actions=foo,bar
// but notably the specified types have to actually make sense: in
// the second example the type corresponding to the ID must have the
// specified type as a child type; in the third the ID must be a
// type that has child types.
idType := globals.ResourceTypeFromPrefix(grant.id)
if idType == resource.Unknown {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains an id %q of an unknown resource type", grant.CanonicalString(), grant.id))
}
switch grant.typ {
case resource.Unknown:
// This is fine as-is but we do not support collection actions
// without a type (either directly specified or wildcard) so
// check that
if grant.actions[action.Create] ||
grant.actions[action.List] {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains create or list action in a format that does not allow these", grant.CanonicalString()))
}
case resource.All:
// Verify that the ID is a type that has child types
if !resource.HasChildTypes(idType) {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains an id that does not support child types", grant.CanonicalString()))
}
default:
// Specified resource type, verify it's a child
if resource.Parent(grant.typ) != idType {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains type %s that is not a child type of the type (%s) of the specified id", grant.CanonicalString(), grant.typ.String(), idType.String()))
}
}
default: // no specified id
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)
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains no id or type", grant.CanonicalString()))
case resource.All:
// "type=*;actions=..." is not supported -- we require you to
// explicitly set a pin or set the ID to *
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard type with no id value", grant.CanonicalString()))
default:
// Here we have type=something,actions=<something else>. This
// means we're operating on collections and support only create
// or list. Note that wildcard actions are not okay here; that
// uses the format id=*;type=<something>;actions=*
switch len(grant.actions) {
case 0:
// It's okay to have no actions if only output fields are being defined
if _, hasSetFields := grant.OutputFields.Fields(); !hasSetFields {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains no actions or output fields", grant.CanonicalString()))
for i, currId := range grantIds {
// Check for templated values ID, and substitute in with the authenticated
// values if so. If we are using a dummy user or account ID, store the
// original ID and return it at the end; this is usually the case when
// validating grant formats.
var origId string
if currId != "" {
if strings.HasPrefix(currId, "{{") {
id := strings.TrimSuffix(strings.TrimPrefix(currId, "{{"), "}}")
id = strings.TrimSpace(id)
switch id {
case "user.id", ".User.Id":
if opts.withUserId != "" {
grantIds[i] = strings.ToValidUTF8(opts.withUserId, string(unicode.ReplacementChar))
} else {
// Otherwise, substitute in a dummy value
origId = currId
grantIds[i] = "u_dummy"
}
case 1:
if !grant.hasActionOrSubaction(action.Create) &&
!grant.hasActionOrSubaction(action.List) {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains non-create or non-list action in a format that only allows these", grant.CanonicalString()))
}
case 2:
if !grant.hasActionOrSubaction(action.Create) || !grant.hasActionOrSubaction(action.List) {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains non-create or non-list action in a format that only allows these", grant.CanonicalString()))
case "account.id", ".Account.Id":
if opts.withAccountId != "" {
grantIds[i] = strings.ToValidUTF8(opts.withAccountId, string(unicode.ReplacementChar))
} else {
origId = currId
grantIds[i] = "acctoidc_dummy"
}
default:
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains non-create or non-list action in a format that only allows these", grant.CanonicalString()))
fieldName := "ids"
if deprecatedId {
fieldName = "id"
}
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown template %q in grant %q value", currId, fieldName))
}
}
}
// 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,
// We don't need to do these twice as they don't depend on IDs; they
// also clear state such as actionsBeingParsed
if i == 0 {
if err := grant.validateType(ctx); err != nil {
return Grant{}, errors.Wrap(ctx, err, op)
}
if !resource.TopLevelType(grant.typ) {
r.Pin = grant.id
if err := grant.parseAndValidateActions(ctx); err != nil {
return Grant{}, errors.Wrap(ctx, err, op)
}
var allowed bool
for k := range grant.actions {
results := acl.Allowed(r, k, globals.AnonymousUserId, WithSkipAnonymousUserRestrictions(true))
if results.Authorized {
allowed = true
break
}
if !opts.withSkipFinalValidation {
switch {
case grantIds[i] == "*":
// Matches
// id=*;type=sometype;actions=foo,bar
// or
// id=*;type=*;actions=foo,bar
// This can be a non-unknown type or wildcard
if grant.typ == resource.Unknown {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard id and no specified type", grant.CanonicalString()))
}
case grantIds[i] != "":
// Non-wildcard but specified ID. This can match
// id=foo_bar;actions=foo,bar
// or
// id=foo_bar;type=sometype;actions=foo,bar
// or
// id=foo_bar;type=*;actions=foo,bar
// but notably the specified types have to actually make sense: in
// the second example the type corresponding to the ID must have the
// specified type as a child type; in the third the ID must be a
// type that has child types.
idType := globals.ResourceTypeFromPrefix(grantIds[i])
if idType == resource.Unknown {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains an id %q of an unknown resource type", grant.CanonicalString(), grantIds[i]))
}
switch grant.typ {
case resource.Unknown:
// This is fine as-is but we do not support collection actions
// without a type (either directly specified or wildcard) so
// check that
if grant.actions[action.Create] ||
grant.actions[action.List] {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains create or list action in a format that does not allow these", grant.CanonicalString()))
}
case resource.All:
// Verify that the ID is a type that has child types
if !resource.HasChildTypes(idType) {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains an id that does not support child types", grant.CanonicalString()))
}
default:
// Specified resource type, verify it's a child
if resource.Parent(grant.typ) != idType {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains type %s that is not a child type of the type (%s) of the specified id", grant.CanonicalString(), grant.typ.String(), idType.String()))
}
}
default: // no specified id
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)
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains no id or type", grant.CanonicalString()))
case resource.All:
// "type=*;actions=..." is not supported -- we require you to
// explicitly set a pin or set the ID to *
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard type with no id value", grant.CanonicalString()))
default:
// Here we have type=something,actions=<something else>. This
// means we're operating on collections and support only create
// or list. Note that wildcard actions are not okay here; that
// uses the format id=*;type=<something>;actions=*
switch len(grant.actions) {
case 0:
// It's okay to have no actions if only output fields are being defined
if _, hasSetFields := grant.OutputFields.Fields(); !hasSetFields {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains no actions or output fields", grant.CanonicalString()))
}
case 1:
if !grant.hasActionOrSubaction(action.Create) &&
!grant.hasActionOrSubaction(action.List) {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains non-create or non-list action in a format that only allows these", grant.CanonicalString()))
}
case 2:
if !grant.hasActionOrSubaction(action.Create) || !grant.hasActionOrSubaction(action.List) {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains non-create or non-list action in a format that only allows these", grant.CanonicalString()))
}
default:
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains non-create or non-list action in a format that only allows these", grant.CanonicalString()))
}
}
}
if !allowed {
return Grant{}, errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q would not result in any action being authorized", grant.CanonicalString()))
// 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. We need to use the templated
// grant, if any, so we send in a clone with an updated ID.
grantForValidation := grant.clone()
grantForValidation.id = grantIds[i]
acl := NewACL(*grantForValidation)
r := Resource{
ScopeId: scopeId,
Id: grantIds[i],
Type: grant.typ,
}
if !resource.TopLevelType(grant.typ) {
r.Pin = grantIds[i]
}
var allowed bool
for k := range grant.actions {
results := acl.Allowed(r, k, globals.AnonymousUserId, WithSkipAnonymousUserRestrictions(true))
if results.Authorized {
allowed = true
break
}
}
if !allowed {
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q would not result in any action being authorized", grant.CanonicalString()))
}
}
}
// If we substituted in a dummy value, replace with the original now
if origId != "" {
grantIds[i] = origId
}
}
// If we substituted in a dummy value, replace with the original now
if origId != "" {
grant.id = origId
// See if we need to move grantIds back for the deprecated case. grantIds
// will always be at least size 1 since we add the empty string if no IDs
// were provided, so we can check to see if that was the case first.
switch {
case grantIds[0] == "":
// Nothing to do
case deprecatedId:
grant.id = grantIds[0]
default:
grant.ids = grantIds
}
return grant, nil
@@ -500,16 +616,16 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
// types. It does not explicitly check the resource string itself; that's the
// job of the parsing functions to look up the string from the Map and ensure
// it's not unknown.
func (g Grant) validateType() error {
func (g Grant) validateType(ctx context.Context) error {
const op = "perms.(Grant).validateType"
switch g.typ {
case resource.Controller:
return errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("unknown type specifier %q", g.typ))
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown type specifier %q", g.typ))
}
return nil
}
func (g *Grant) parseAndValidateActions() error {
func (g *Grant) parseAndValidateActions(ctx context.Context) error {
const op = "perms.(Grant).parseAndValidateActions"
if len(g.actionsBeingParsed) == 0 {
g.actionsBeingParsed = nil
@@ -518,25 +634,25 @@ func (g *Grant) parseAndValidateActions() error {
if _, hasSetFields := g.OutputFields.Fields(); hasSetFields {
return nil
}
return errors.NewDeprecated(errors.InvalidParameter, op, "missing actions")
return errors.New(ctx, errors.InvalidParameter, op, "missing actions")
}
for _, a := range g.actionsBeingParsed {
if a == "" {
return errors.NewDeprecated(errors.InvalidParameter, op, "empty action found")
return errors.New(ctx, errors.InvalidParameter, op, "empty action found")
}
if g.actions == nil {
g.actions = make(map[action.Type]bool, len(g.actionsBeingParsed))
}
if am := action.Map[a]; am == action.Unknown {
return errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("unknown action %q", a))
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown action %q", a))
} else {
g.actions[am] = true
}
}
if len(g.actions) > 1 && g.actions[action.All] {
return errors.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("%q cannot be specified with other actions", action.All.String()))
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%q cannot be specified with other actions", action.All.String()))
}
g.actionsBeingParsed = nil
+324 -32
View File
@@ -4,6 +4,7 @@
package perms
import (
"context"
"fmt"
"testing"
@@ -18,6 +19,8 @@ import (
func Test_ActionParsingValidation(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
input Grant
@@ -100,7 +103,7 @@ func Test_ActionParsingValidation(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.input.parseAndValidateActions()
err := test.input.parseAndValidateActions(ctx)
if test.errResult == "" {
require.NoError(t, err)
assert.Equal(t, test.result, test.input)
@@ -114,13 +117,14 @@ func Test_ActionParsingValidation(t *testing.T) {
func Test_ValidateType(t *testing.T) {
t.Parallel()
ctx := context.Background()
var g Grant
for i := resource.Unknown; i <= resource.Credential; i++ {
for i := resource.Unknown; i <= resource.StorageBucket; i++ {
g.typ = i
if i == resource.Controller {
assert.Error(t, g.validateType())
assert.Error(t, g.validateType(ctx))
} else {
assert.NoError(t, g.validateType())
assert.NoError(t, g.validateType(ctx))
}
}
}
@@ -159,7 +163,31 @@ func Test_MarshalingAndCloning(t *testing.T) {
canonicalString: `id=baz;type=group`,
},
{
name: "output fields",
name: "type and ids",
input: Grant{
ids: []string{"baz", "bop"},
scope: Scope{
Type: scope.Project,
},
typ: resource.Group,
},
jsonOutput: `{"ids":["baz","bop"],"type":"group"}`,
canonicalString: `ids=baz,bop;type=group`,
},
{
name: "type and ids single id",
input: Grant{
ids: []string{"baz"},
scope: Scope{
Type: scope.Project,
},
typ: resource.Group,
},
jsonOutput: `{"ids":["baz"],"type":"group"}`,
canonicalString: `ids=baz;type=group`,
},
{
name: "output fields id",
input: Grant{
id: "baz",
scope: Scope{
@@ -178,7 +206,26 @@ func Test_MarshalingAndCloning(t *testing.T) {
canonicalString: `id=baz;type=group;output_fields=id,name,version`,
},
{
name: "everything",
name: "output fields ids",
input: Grant{
ids: []string{"baz", "bop"},
scope: Scope{
Type: scope.Project,
},
typ: resource.Group,
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"id": true,
},
},
},
jsonOutput: `{"ids":["baz","bop"],"output_fields":["id","name","version"],"type":"group"}`,
canonicalString: `ids=baz,bop;type=group;output_fields=id,name,version`,
},
{
name: "everything id",
input: Grant{
id: "baz",
scope: Scope{
@@ -201,6 +248,30 @@ func Test_MarshalingAndCloning(t *testing.T) {
jsonOutput: `{"actions":["create","read"],"id":"baz","output_fields":["id","name","version"],"type":"group"}`,
canonicalString: `id=baz;type=group;actions=create,read;output_fields=id,name,version`,
},
{
name: "everything ids",
input: Grant{
ids: []string{"baz", "bop"},
scope: Scope{
Type: scope.Project,
},
typ: resource.Group,
actions: map[action.Type]bool{
action.Create: true,
action.Read: true,
},
actionsBeingParsed: []string{"create", "read"},
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"ids": true,
},
},
},
jsonOutput: `{"actions":["create","read"],"ids":["baz","bop"],"output_fields":["ids","name","version"],"type":"group"}`,
canonicalString: `ids=baz,bop;type=group;actions=create,read;output_fields=ids,name,version`,
},
}
for _, test := range tests {
@@ -240,12 +311,19 @@ func Test_Unmarshaling(t *testing.T) {
jsonErr: "perms.(Grant).unmarshalJSON: error occurred during decode, encoding issue: error #303: invalid character 'w' looking for beginning of value",
},
{
name: "bad segment",
name: "bad segment id",
jsonInput: `{"id":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "id" as string: parameter violation: error #100`,
textInput: `id=`,
textErr: `perms.(Grant).unmarshalText: segment "id=" not formatted correctly, missing value: parameter violation: error #100`,
},
{
name: "bad segment ids",
jsonInput: `{"ids":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "ids" as array: parameter violation: error #100`,
textInput: `ids=`,
textErr: `perms.(Grant).unmarshalText: segment "ids=" not formatted correctly, missing value: parameter violation: error #100`,
},
{
name: "good id",
expected: Grant{
@@ -254,6 +332,14 @@ func Test_Unmarshaling(t *testing.T) {
jsonInput: `{"id":"foobar"}`,
textInput: `id=foobar`,
},
{
name: "good ids",
expected: Grant{
ids: []string{"foobar"},
},
jsonInput: `{"ids":["foobar"]}`,
textInput: `ids=foobar`,
},
{
name: "bad id",
jsonInput: `{"id":true}`,
@@ -261,6 +347,13 @@ func Test_Unmarshaling(t *testing.T) {
textInput: `=id`,
textErr: `perms.(Grant).unmarshalText: segment "=id" not formatted correctly, missing key: parameter violation: error #100`,
},
{
name: "bad ids",
jsonInput: `{"ids":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "ids" as array: parameter violation: error #100`,
textInput: `=ids`,
textErr: `perms.(Grant).unmarshalText: segment "=ids" not formatted correctly, missing key: parameter violation: error #100`,
},
{
name: "good type",
expected: Grant{
@@ -277,7 +370,7 @@ func Test_Unmarshaling(t *testing.T) {
textErr: `perms.(Grant).unmarshalText: segment "type=host-catalog=id" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "good output fields",
name: "good output fields id",
expected: Grant{
OutputFields: &OutputFields{
fields: map[string]bool{
@@ -291,12 +384,33 @@ func Test_Unmarshaling(t *testing.T) {
textInput: `output_fields=id,version,name`,
},
{
name: "bad output fields",
name: "good output fields ids",
expected: Grant{
OutputFields: &OutputFields{
fields: map[string]bool{
"name": true,
"version": true,
"ids": true,
},
},
},
jsonInput: `{"output_fields":["ids","name","version"]}`,
textInput: `output_fields=ids,version,name`,
},
{
name: "bad output fields id",
jsonInput: `{"output_fields":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "output_fields" as array: parameter violation: error #100`,
textInput: `output_fields=id=version,name`,
textErr: `perms.(Grant).unmarshalText: segment "output_fields=id=version,name" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "bad output fields ids",
jsonInput: `{"output_fields":true}`,
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "output_fields" as array: parameter violation: error #100`,
textInput: `output_fields=ids=version,name`,
textErr: `perms.(Grant).unmarshalText: segment "output_fields=ids=version,name" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
},
{
name: "good actions",
expected: Grant{
@@ -359,6 +473,8 @@ func Test_Unmarshaling(t *testing.T) {
func Test_Parse(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
input string
@@ -386,28 +502,38 @@ func Test_Parse(t *testing.T) {
},
{
name: "bad type",
input: "id=foobar;type=barfoo;actions=read",
input: "ids=s_foobar;type=barfoo;actions=read",
err: `perms.Parse: unable to parse grant string: perms.(Grant).unmarshalText: unknown type specifier "barfoo": parameter violation: error #100`,
},
{
name: "bad actions",
input: "id=foobar;type=host-catalog;actions=createread",
input: "ids=hcst_foobar;type=host-catalog;actions=createread",
err: `perms.Parse: perms.(Grant).parseAndValidateActions: unknown action "createread": parameter violation: error #100`,
},
{
name: "bad id type",
input: "id=foobar;actions=create",
err: `perms.Parse: parsed grant string "id=foobar;actions=create" contains an id "foobar" of an unknown resource type: parameter violation: error #100`,
input: "id=foobar;actions=read",
err: `perms.Parse: parsed grant string "id=foobar;actions=read" contains an id "foobar" of an unknown resource type: parameter violation: error #100`,
},
{
name: "bad create action for id",
input: "id=u_foobar;actions=create",
err: `perms.Parse: parsed grant string "id=u_foobar;actions=create" contains create or list action in a format that does not allow these: parameter violation: error #100`,
name: "bad ids type first position",
input: "ids=foobar,hcst_foobar;actions=read",
err: `perms.Parse: input grant string "ids=foobar,hcst_foobar;actions=read" contains ids of differently-typed resources: parameter violation: error #100`,
},
{
name: "bad create action for id with other perms",
input: "id=u_foobar;actions=read,create",
err: `perms.Parse: parsed grant string "id=u_foobar;actions=create,read" contains create or list action in a format that does not allow these: parameter violation: error #100`,
name: "bad ids type second position",
input: "ids=hcst_foobar,foobar;actions=read",
err: `perms.Parse: input grant string "ids=hcst_foobar,foobar;actions=read" contains ids of differently-typed resources: parameter violation: error #100`,
},
{
name: "bad create action for ids",
input: "ids=u_foobar;actions=create",
err: `perms.Parse: parsed grant string "ids=u_foobar;actions=create" contains create or list action in a format that does not allow these: parameter violation: error #100`,
},
{
name: "bad create action for ids with other perms",
input: "ids=u_foobar;actions=read,create",
err: `perms.Parse: parsed grant string "ids=u_foobar;actions=create,read" contains create or list action in a format that does not allow these: parameter violation: error #100`,
},
{
name: "bad list action for id",
@@ -415,7 +541,7 @@ func Test_Parse(t *testing.T) {
err: `perms.Parse: parsed grant string "id=u_foobar;actions=list" contains create or list action in a format that does not allow these: parameter violation: error #100`,
},
{
name: "bad list action for id with other perms",
name: "bad list action for type with other perms",
input: "type=host-catalog;actions=list,read",
err: `perms.Parse: parsed grant string "type=host-catalog;actions=list,read" contains non-create or non-list action in a format that only allows these: parameter violation: error #100`,
},
@@ -424,18 +550,38 @@ func Test_Parse(t *testing.T) {
input: "id=*;actions=read",
err: `perms.Parse: parsed grant string "id=*;actions=read" contains wildcard id and no specified type: parameter violation: error #100`,
},
{
name: "wildcard ids and actions without collection",
input: "ids=*;actions=read",
err: `perms.Parse: parsed grant string "ids=*;actions=read" contains wildcard id and no specified type: parameter violation: error #100`,
},
{
name: "wildcard id and actions with list",
input: "id=*;actions=read,list",
err: `perms.Parse: parsed grant string "id=*;actions=list,read" contains wildcard id and no specified type: parameter violation: error #100`,
},
{
name: "wildcard type with no id",
name: "wildcard ids and actions with list",
input: "ids=*;actions=read,list",
err: `perms.Parse: parsed grant string "ids=*;actions=list,read" contains wildcard id and no specified type: parameter violation: error #100`,
},
{
name: "wildcard type with no ids",
input: "type=*;actions=read,list",
err: `perms.Parse: parsed grant string "type=*;actions=list,read" contains wildcard type with no id value: parameter violation: error #100`,
},
{
name: "empty id and type",
name: "mixed wildcard and non wildcard ids first position",
input: "ids=*,u_foobar;actions=read,list",
err: `perms.Parse: input grant string "ids=*,u_foobar;actions=read,list" contains both wildcard and non-wildcard values in "ids" field: parameter violation: error #100`,
},
{
name: "mixed wildcard and non wildcard ids second position",
input: "ids=u_foobar,*;actions=read,list",
err: `perms.Parse: input grant string "ids=u_foobar,*;actions=read,list" contains both wildcard and non-wildcard values in "ids" field: parameter violation: error #100`,
},
{
name: "empty ids and type",
input: "actions=create",
err: `perms.Parse: parsed grant string "actions=create" contains no id or type: parameter violation: error #100`,
},
@@ -445,10 +591,30 @@ func Test_Parse(t *testing.T) {
err: `perms.Parse: parsed grant string "id=ttcp_1234567890;type=*;actions=create" contains an id that does not support child types: parameter violation: error #100`,
},
{
name: "specified resource type non child",
name: "wildcard type non child ids first position",
input: "ids=ttcp_1234567890,ttcp_1234567890;type=*;actions=create",
err: `perms.Parse: parsed grant string "ids=ttcp_1234567890,ttcp_1234567890;type=*;actions=create" contains an id that does not support child types: parameter violation: error #100`,
},
{
name: "wildcard type non child ids second position",
input: "ids=ttcp_1234567890,ttcp_1234567890;type=*;actions=create",
err: `perms.Parse: parsed grant string "ids=ttcp_1234567890,ttcp_1234567890;type=*;actions=create" contains an id that does not support child types: parameter violation: error #100`,
},
{
name: "specified resource type non child id",
input: "id=hcst_1234567890;type=account;actions=read",
err: `perms.Parse: parsed grant string "id=hcst_1234567890;type=account;actions=read" contains type account that is not a child type of the type (host-catalog) of the specified id: parameter violation: error #100`,
},
{
name: "specified resource type non child ids first position",
input: "ids=hcst_1234567890,hcst_1234567890;type=account;actions=read",
err: `perms.Parse: parsed grant string "ids=hcst_1234567890,hcst_1234567890;type=account;actions=read" contains type account that is not a child type of the type (host-catalog) of the specified id: parameter violation: error #100`,
},
{
name: "specified resource type non child ids second position",
input: "ids=hcst_1234567890,hcst_1234567890;type=account;actions=read",
err: `perms.Parse: parsed grant string "ids=hcst_1234567890,hcst_1234567890;type=account;actions=read" contains type account that is not a child type of the type (host-catalog) of the specified id: parameter violation: error #100`,
},
{
name: "no id with one bad action",
input: "type=host-set;actions=read",
@@ -518,6 +684,22 @@ func Test_Parse(t *testing.T) {
},
},
},
{
name: "wildcard ids and type and actions with list",
input: "ids=*;type=*;actions=read,list",
expected: Grant{
scope: Scope{
Id: "o_scope",
Type: scope.Org,
},
ids: []string{"*"},
typ: resource.All,
actions: map[action.Type]bool{
action.Read: true,
action.List: true,
},
},
},
{
name: "good json type",
input: `{"type":"host-catalog","actions":["create"]}`,
@@ -548,7 +730,22 @@ func Test_Parse(t *testing.T) {
},
},
{
name: "good json output fields",
name: "good json ids",
input: `{"ids":["hcst_foobar", "hcst_foobaz"],"actions":["read"]}`,
expected: Grant{
scope: Scope{
Id: "o_scope",
Type: scope.Org,
},
ids: []string{"hcst_foobar", "hcst_foobaz"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "good json output fields id",
input: `{"id":"u_foobar","actions":["read"],"output_fields":["version","id","name"]}`,
expected: Grant{
scope: Scope{
@@ -569,6 +766,28 @@ func Test_Parse(t *testing.T) {
},
},
},
{
name: "good json output fields ids",
input: `{"ids":["u_foobar"],"actions":["read"],"output_fields":["version","ids","name"]}`,
expected: Grant{
scope: Scope{
Id: "o_scope",
Type: scope.Org,
},
ids: []string{"u_foobar"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"ids": true,
"name": true,
},
},
},
},
{
name: "good json output fields no action",
input: `{"id":"u_foobar","output_fields":["version","id","name"]}`,
@@ -618,7 +837,22 @@ func Test_Parse(t *testing.T) {
},
},
{
name: "good output fields",
name: "good text ids",
input: `ids=hcst_foobar,hcst_foobaz;actions=read`,
expected: Grant{
scope: Scope{
Id: "o_scope",
Type: scope.Org,
},
ids: []string{"hcst_foobar", "hcst_foobaz"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
},
},
{
name: "good output fields id",
input: `id=u_foobar;actions=read;output_fields=version,id,name`,
expected: Grant{
scope: Scope{
@@ -639,6 +873,28 @@ func Test_Parse(t *testing.T) {
},
},
},
{
name: "good output fields ids",
input: `ids=hcst_foobar,hcst_foobaz;actions=read;output_fields=version,ids,name`,
expected: Grant{
scope: Scope{
Id: "o_scope",
Type: scope.Org,
},
ids: []string{"hcst_foobar", "hcst_foobaz"},
typ: resource.Unknown,
actions: map[action.Type]bool{
action.Read: true,
},
OutputFields: &OutputFields{
fields: map[string]bool{
"version": true,
"ids": true,
"name": true,
},
},
},
},
{
name: "default project scope",
input: `id=hcst_foobar;actions=read`,
@@ -693,6 +949,12 @@ func Test_Parse(t *testing.T) {
userId: "u_abcd1234",
err: `perms.Parse: unknown template "{{superman}}" in grant "id" value: parameter violation: error #100`,
},
{
name: "bad user ids template",
input: `ids={{superman}};actions=create,read`,
userId: "u_abcd1234",
err: `perms.Parse: unknown template "{{superman}}" in grant "ids" value: parameter violation: error #100`,
},
{
name: "good user id template",
input: `id={{ user.id}};actions=read,update`,
@@ -715,6 +977,12 @@ func Test_Parse(t *testing.T) {
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPreviousPrefix),
err: `perms.Parse: unknown template "{{superman}}" in grant "id" value: parameter violation: error #100`,
},
{
name: "bad old account ids template",
input: `ids={{superman}};actions=read`,
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPreviousPrefix),
err: `perms.Parse: unknown template "{{superman}}" in grant "ids" value: parameter violation: error #100`,
},
{
name: "bad new account id template",
input: `id={{superman}};actions=read`,
@@ -753,13 +1021,30 @@ func Test_Parse(t *testing.T) {
},
},
},
{
name: "good ids template",
input: `ids={{ user.id}},{{ account.id}};actions=read,update`,
userId: "u_abcd1234",
accountId: fmt.Sprintf("%s_1234567890", globals.PasswordAccountPrefix),
expected: Grant{
scope: Scope{
Id: "o_scope",
Type: scope.Org,
},
ids: []string{"u_abcd1234", "acctpw_1234567890"},
actions: map[action.Type]bool{
action.Update: true,
action.Read: true,
},
},
},
}
_, err := Parse("", "")
_, err := Parse(ctx, "", "")
require.Error(t, err)
assert.Equal(t, "perms.Parse: missing grant string: parameter violation: error #100", err.Error())
_, err = Parse("", "{}")
_, err = Parse(ctx, "", "{}")
require.Error(t, err)
assert.Equal(t, "perms.Parse: missing scope id: parameter violation: error #100", err.Error())
@@ -771,7 +1056,7 @@ func Test_Parse(t *testing.T) {
if test.scopeOverride != "" {
scope = test.scopeOverride
}
grant, err := Parse(scope, test.input, WithUserId(test.userId), WithAccountId(test.accountId))
grant, err := Parse(ctx, scope, test.input, WithUserId(test.userId), WithAccountId(test.accountId))
if test.err != "" {
require.Error(err)
assert.Equal(test.err, err.Error())
@@ -833,20 +1118,27 @@ func TestHasActionOrSubaction(t *testing.T) {
}
func FuzzParse(f *testing.F) {
ctx := context.Background()
f.Add("type=host-catalog;actions=create")
f.Add("type=*;actions=*")
f.Add("id=*;type=*;actions=*")
f.Add("ids=*;type=*;actions=*")
f.Add("id=*;type=*;actions=read,list")
f.Add("ids=*;type=*;actions=read,list")
f.Add("id=foobar;actions=read;output_fields=version,id,name")
f.Add("ids=foobar,foobaz;actions=read;output_fields=version,id,name")
f.Add("id={{account.id}};actions=update,read")
f.Add(`{id:"foobar","type":"host-catalog","actions":["create"]}`)
f.Add("ids={{account.id}},{{user.id}};actions=update,read")
f.Add(`{"id":"foobar","type":"host-catalog","actions":["create"]}`)
f.Add(`{"ids":["foobar"],"type":"host-catalog","actions":["create"]}`)
f.Fuzz(func(t *testing.T, grant string) {
g, err := Parse("global", grant, WithSkipFinalValidation(true))
g, err := Parse(ctx, "global", grant, WithSkipFinalValidation(true))
if err != nil {
return
}
g2, err := Parse("global", g.CanonicalString(), WithSkipFinalValidation(true))
g2, err := Parse(ctx, "global", g.CanonicalString(), WithSkipFinalValidation(true))
if err != nil {
t.Fatal("Failed to parse canonical string:", err)
}
@@ -857,7 +1149,7 @@ func FuzzParse(f *testing.F) {
if err != nil {
t.Error("Failed to marshal JSON:", err)
}
g3, err := Parse("global", string(jsonBytes), WithSkipFinalValidation(true))
g3, err := Parse(ctx, "global", string(jsonBytes), WithSkipFinalValidation(true))
if err != nil {
t.Fatal("Failed to parse json string:", err)
}
+4 -1
View File
@@ -4,6 +4,7 @@
package perms
import (
"context"
"testing"
"github.com/hashicorp/boundary/globals"
@@ -135,6 +136,8 @@ func Test_OutputFields(t *testing.T) {
func Test_ACLOutputFields(t *testing.T) {
t.Parallel()
ctx := context.Background()
type input struct {
name string
grants []string
@@ -276,7 +279,7 @@ func Test_ACLOutputFields(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var grants []Grant
for _, g := range test.grants {
grant, err := Parse("o_myorg", g)
grant, err := Parse(ctx, "o_myorg", g)
require.NoError(t, err)
grants = append(grants, grant)
}
@@ -25,7 +25,11 @@ message Principal {
message GrantJson {
// Output only. The ID, if set.
string id = 1; // @gotags: `class:"public"`
// Deprecated: use "ids" instead.
string id = 1 [deprecated = true]; // @gotags: `class:"public"`
// Output only. The IDs, if set.
repeated string ids = 4; // @gotags: `class:"public"`
// Output only. The type, if set.
string type = 2; // @gotags: `class:"public"`
@@ -99,7 +99,12 @@ type GrantJson struct {
unknownFields protoimpl.UnknownFields
// Output only. The ID, if set.
// Deprecated: use "ids" instead.
//
// Deprecated: Marked as deprecated in controller/api/resources/roles/v1/role.proto.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
// Output only. The IDs, if set.
Ids []string `protobuf:"bytes,4,rep,name=ids,proto3" json:"ids,omitempty" class:"public"` // @gotags: `class:"public"`
// Output only. The type, if set.
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty" class:"public"` // @gotags: `class:"public"`
// Output only. The actions.
@@ -138,6 +143,7 @@ func (*GrantJson) Descriptor() ([]byte, []int) {
return file_controller_api_resources_roles_v1_role_proto_rawDescGZIP(), []int{1}
}
// Deprecated: Marked as deprecated in controller/api/resources/roles/v1/role.proto.
func (x *GrantJson) GetId() string {
if x != nil {
return x.Id
@@ -145,6 +151,13 @@ func (x *GrantJson) GetId() string {
return ""
}
func (x *GrantJson) GetIds() []string {
if x != nil {
return x.Ids
}
return nil
}
func (x *GrantJson) GetType() string {
if x != nil {
return x.Type
@@ -414,77 +427,78 @@ var file_controller_api_resources_roles_v1_role_proto_rawDesc = []byte{
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x49, 0x0a, 0x09, 0x47,
0x72, 0x61, 0x6e, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x79, 0x0a, 0x05, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x12,
0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x61,
0x77, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x12,
0x40, 0x0a, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x04, 0x6a, 0x73, 0x6f,
0x6e, 0x22, 0xb9, 0x06, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63,
0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63,
0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18,
0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29,
0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a,
0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64,
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x6c, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29,
0x1e, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69,
0x64, 0x12, 0x0c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x52,
0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12,
0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73,
0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61,
0x6c, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70,
0x61, 0x6c, 0x73, 0x18, 0x6e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72,
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70,
0x61, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x73, 0x18, 0x78, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e,
0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x06, 0x67, 0x72, 0x61,
0x6e, 0x74, 0x73, 0x18, 0x82, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x72, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x12,
0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x4c, 0x5a,
0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68,
0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73,
0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f,
0x72, 0x6f, 0x6c, 0x65, 0x73, 0x3b, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x5f, 0x0a, 0x09, 0x47,
0x72, 0x61, 0x6e, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03,
0x69, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x12,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79,
0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x79, 0x0a, 0x05,
0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x6f, 0x6e,
0x69, 0x63, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x6f,
0x6e, 0x69, 0x63, 0x61, 0x6c, 0x12, 0x40, 0x0a, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72,
0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x4a, 0x73, 0x6f,
0x6e, 0x52, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0xb9, 0x06, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05,
0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70,
0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0,
0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda,
0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a,
0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a,
0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x6c, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74,
0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0,
0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73,
0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x0c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x53, 0x63,
0x6f, 0x70, 0x65, 0x49, 0x64, 0x52, 0x0e, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f,
0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70,
0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72,
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0a, 0x70,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x6e, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x52, 0x0a, 0x70,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61,
0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x78, 0x20, 0x03, 0x28, 0x09,
0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12,
0x41, 0x0a, 0x06, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x82, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x67, 0x72, 0x61, 0x6e,
0x74, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x3b, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
+7
View File
@@ -21,6 +21,7 @@ const (
UseTargetIdForHostId
RequireVersionInWorkerInfo
SshSessionRecording
SupportIdInGrants
)
var featureMap map[Feature]MetadataConstraint
@@ -72,6 +73,12 @@ func init() {
featureMap[SshSessionRecording] = MetadataConstraint{
Constraints: mustNewConstraints(">= 0.13.0"),
}
// Warn until 0.15 about using the now-deprecated id field in grants; after
// that disallow it
featureMap[SupportIdInGrants] = MetadataConstraint{
Constraints: mustNewConstraints("< 0.15.0"),
}
}
func mustNewConstraints(v string) gvers.Constraints {