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:
1 parent
8acc73dc6e
commit
feb3aea38f
25 files changed
+951
-289
No files matched your search
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -5,6 +5,7 @@ package globals
|
||||
|
||||
const (
|
||||
IdField = "id"
|
||||
IdsField = "ids"
|
||||
VersionField = "version"
|
||||
NameField = "name"
|
||||
DescriptionField = "description"
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
+180
-64
@@ -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,70 +379,119 @@ 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...)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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{""}
|
||||
}
|
||||
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 grant.id != "" && strings.HasPrefix(grant.id, "{{") {
|
||||
id := strings.TrimSuffix(strings.TrimPrefix(grant.id, "{{"), "}}")
|
||||
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 != "" {
|
||||
grant.id = strings.ToValidUTF8(opts.withUserId, string(unicode.ReplacementChar))
|
||||
}
|
||||
grantIds[i] = strings.ToValidUTF8(opts.withUserId, string(unicode.ReplacementChar))
|
||||
} else {
|
||||
// Otherwise, substitute in a dummy value
|
||||
origId = grant.id
|
||||
grant.id = "u_dummy"
|
||||
origId = currId
|
||||
grantIds[i] = "u_dummy"
|
||||
}
|
||||
case "account.id", ".Account.Id":
|
||||
if opts.withAccountId != "" {
|
||||
grant.id = strings.ToValidUTF8(opts.withAccountId, string(unicode.ReplacementChar))
|
||||
grantIds[i] = strings.ToValidUTF8(opts.withAccountId, string(unicode.ReplacementChar))
|
||||
} else {
|
||||
origId = currId
|
||||
grantIds[i] = "acctoidc_dummy"
|
||||
}
|
||||
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"))
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := grant.validateType(); err != nil {
|
||||
return Grant{}, errors.WrapDeprecated(err, op)
|
||||
// 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 err := grant.parseAndValidateActions(ctx); err != nil {
|
||||
return Grant{}, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
if err := grant.parseAndValidateActions(); err != nil {
|
||||
return Grant{}, errors.WrapDeprecated(err, op)
|
||||
}
|
||||
|
||||
if !opts.withSkipFinalValidation {
|
||||
switch {
|
||||
case grant.id == "*":
|
||||
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.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard id and no specified type", grant.CanonicalString()))
|
||||
return Grant{}, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard id and no specified type", grant.CanonicalString()))
|
||||
}
|
||||
case grant.id != "":
|
||||
case grantIds[i] != "":
|
||||
// Non-wildcard but specified ID. This can match
|
||||
// id=foo_bar;actions=foo,bar
|
||||
// or
|
||||
@@ -402,9 +502,9 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
|
||||
// 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)
|
||||
idType := globals.ResourceTypeFromPrefix(grantIds[i])
|
||||
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))
|
||||
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:
|
||||
@@ -413,17 +513,17 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
|
||||
// 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()))
|
||||
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.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains an id that does not support child types", grant.CanonicalString()))
|
||||
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.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()))
|
||||
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
|
||||
@@ -431,11 +531,11 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
|
||||
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()))
|
||||
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.NewDeprecated(errors.InvalidParameter, op, fmt.Sprintf("parsed grant string %q contains wildcard type with no id value", grant.CanonicalString()))
|
||||
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
|
||||
@@ -445,34 +545,37 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
|
||||
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()))
|
||||
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.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()))
|
||||
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.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()))
|
||||
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.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()))
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
// 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: grant.id,
|
||||
Id: grantIds[i],
|
||||
Type: grant.typ,
|
||||
}
|
||||
if !resource.TopLevelType(grant.typ) {
|
||||
r.Pin = grant.id
|
||||
r.Pin = grantIds[i]
|
||||
}
|
||||
var allowed bool
|
||||
for k := range grant.actions {
|
||||
@@ -483,14 +586,27 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
|
||||
}
|
||||
}
|
||||
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()))
|
||||
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 != "" {
|
||||
grant.id = origId
|
||||
grantIds[i] = 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
@@ -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,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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user