From 531dfeaa25dc77ae437b3fd01c2fdfd273324d3d Mon Sep 17 00:00:00 2001 From: Melissa Page <73hl10n@gmail.com> Date: Mon, 26 Jun 2023 10:56:56 -0700 Subject: [PATCH] Add web changes into main (#5025) Add sensu web changes into main: * [GraphQL] Generate & Implement RBAC types (#4793) * Allow Graphql Middleware Init funcs to add to Context (#4910) * add graphql query validation for max depth on unauthed requests * update graph query depth limit * update graphql-max-depth rule provider * make graphql-depth-limit validator parameterized * update graphql depth test cases * add more test cases and comments to graphql max-depth validator * convert graphql validators test indents to spaces for readability * run graphql depth limit query on authenticated requests * make graphql query depth limit validation unskippable --------- Signed-off-by: James Phillips Signed-off-by: Christian Kruse Signed-off-by: Gustav Danielsson Co-authored-by: James Phillips Co-authored-by: Christian Kruse Co-authored-by: Gustav Danielsson --- CHANGELOG-6.md | 5 + backend/apid/graphql/corev2.go | 110 +++ backend/apid/graphql/corev2_test.go | 178 +++- backend/apid/graphql/node.go | 8 +- backend/apid/graphql/rbac.go | 44 - backend/apid/graphql/schema/corev2.gql.go | 272 +++++++ backend/apid/graphql/schema/corev2.graphql | 67 ++ backend/apid/graphql/schema/rbac.gql.go | 894 --------------------- backend/apid/graphql/schema/rbac.graphql | 88 -- backend/apid/graphql/service.go | 18 +- backend/apid/routers/graphql.go | 1 + graphql/resolvers.go | 3 +- graphql/service.go | 12 +- graphql/validators.go | 137 ++++ graphql/validators_test.go | 174 ++++ 15 files changed, 939 insertions(+), 1072 deletions(-) delete mode 100644 backend/apid/graphql/rbac.go delete mode 100644 backend/apid/graphql/schema/rbac.gql.go delete mode 100644 backend/apid/graphql/schema/rbac.graphql create mode 100644 graphql/validators.go create mode 100644 graphql/validators_test.go diff --git a/CHANGELOG-6.md b/CHANGELOG-6.md index 7760f5a97b..5643e29bff 100644 --- a/CHANGELOG-6.md +++ b/CHANGELOG-6.md @@ -13,6 +13,11 @@ Versioning](http://semver.org/spec/v2.0.0.html). ### Added - Adding a flag at agent level to avoid collecting system.networks property in the agent entity state +### Added +- Adding a flag at agent level to avoid collecting system.networks property in the agent entity state +- Added silences sorting by expiration to GraphQL service +- Added GraphQL validator for query node depth + ## [6.9.1] - 2022-12-01 ### Changed diff --git a/backend/apid/graphql/corev2.go b/backend/apid/graphql/corev2.go index 37fe266915..64d5491ec4 100644 --- a/backend/apid/graphql/corev2.go +++ b/backend/apid/graphql/corev2.go @@ -8,6 +8,12 @@ import ( "github.com/sensu/core/v3/types" ) +// +// CoreV2Pipeline +// + +var _ schema.CoreV2PipelineExtensionOverridesFieldResolvers = (*corev2PipelineImpl)(nil) + type corev2PipelineImpl struct { schema.CoreV2PipelineAliases } @@ -27,3 +33,107 @@ func (*corev2PipelineImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) boo _, ok := s.(*corev2.Pipeline) return ok } + +// +// Implement ClusterRoleFieldResolvers +// + +var _ schema.CoreV2ClusterRoleExtensionOverridesFieldResolvers = (*clusterRoleImpl)(nil) + +type clusterRoleImpl struct { + schema.CoreV2ClusterRoleAliases +} + +// ID implements response to request for 'id' field. +func (*clusterRoleImpl) ID(p graphql.ResolveParams) (string, error) { + return globalid.ClusterRoleTranslator.EncodeToString(p.Context, p.Source), nil +} + +// ToJSON implements response to request for 'toJSON' field. +func (*clusterRoleImpl) ToJSON(p graphql.ResolveParams) (interface{}, error) { + return types.WrapResource(p.Source.(corev2.Resource)), nil +} + +// IsTypeOf is used to determine if a given value is associated with the type +func (*clusterRoleImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { + _, ok := s.(*corev2.ClusterRole) + return ok +} + +// +// Implement ClusterRoleBindingFieldResolvers +// + +var _ schema.CoreV2ClusterRoleBindingExtensionOverridesFieldResolvers = (*clusterRoleBindingImpl)(nil) + +type clusterRoleBindingImpl struct { + schema.CoreV2ClusterRoleBindingAliases +} + +// ID implements response to request for 'id' field. +func (*clusterRoleBindingImpl) ID(p graphql.ResolveParams) (string, error) { + return globalid.ClusterRoleBindingTranslator.EncodeToString(p.Context, p.Source), nil +} + +// ToJSON implements response to request for 'toJSON' field. +func (*clusterRoleBindingImpl) ToJSON(p graphql.ResolveParams) (interface{}, error) { + return types.WrapResource(p.Source.(corev2.Resource)), nil +} + +// IsTypeOf is used to determine if a given value is associated with the type +func (*clusterRoleBindingImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { + _, ok := s.(*corev2.ClusterRoleBinding) + return ok +} + +// +// Implement RoleFieldResolvers +// + +var _ schema.CoreV2RoleExtensionOverridesFieldResolvers = (*roleImpl)(nil) + +type roleImpl struct { + schema.CoreV2RoleAliases +} + +// ID implements response to request for 'id' field. +func (*roleImpl) ID(p graphql.ResolveParams) (string, error) { + return globalid.RoleTranslator.EncodeToString(p.Context, p.Source), nil +} + +// ToJSON implements response to request for 'toJSON' field. +func (*roleImpl) ToJSON(p graphql.ResolveParams) (interface{}, error) { + return types.WrapResource(p.Source.(corev2.Resource)), nil +} + +// IsTypeOf is used to determine if a given value is associated with the type +func (*roleImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { + _, ok := s.(*corev2.Role) + return ok +} + +// +// Implement RoleBindingFieldResolvers +// + +var _ schema.CoreV2RoleBindingExtensionOverridesFieldResolvers = (*roleBindingImpl)(nil) + +type roleBindingImpl struct { + schema.CoreV2RoleBindingAliases +} + +// ID implements response to request for 'id' field. +func (*roleBindingImpl) ID(p graphql.ResolveParams) (string, error) { + return globalid.RoleBindingTranslator.EncodeToString(p.Context, p.Source), nil +} + +// ToJSON implements response to request for 'toJSON' field. +func (*roleBindingImpl) ToJSON(p graphql.ResolveParams) (interface{}, error) { + return types.WrapResource(p.Source.(corev2.Resource)), nil +} + +// IsTypeOf is used to determine if a given value is associated with the type +func (*roleBindingImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { + _, ok := s.(*corev2.RoleBinding) + return ok +} diff --git a/backend/apid/graphql/corev2_test.go b/backend/apid/graphql/corev2_test.go index b297966b53..a8d3442cbe 100644 --- a/backend/apid/graphql/corev2_test.go +++ b/backend/apid/graphql/corev2_test.go @@ -2,6 +2,7 @@ package graphql import ( "context" + "fmt" "reflect" "testing" @@ -10,24 +11,56 @@ import ( "github.com/sensu/core/v3/types" ) -func Test_corev2PipelineImpl_ID(t *testing.T) { +func Test_corev2_ID(t *testing.T) { tests := []struct { - name string - in *corev2.Pipeline + name string + resolver interface { + ID(p graphql.ResolveParams) (string, error) + } + in interface{} want string wantErr bool }{ { - name: "default", - in: corev2.FixturePipeline("test", "default"), - want: "srn:corev2/pipeline:default:test", - wantErr: false, + name: "default", + resolver: &corev2PipelineImpl{}, + in: corev2.FixturePipeline("test", "default"), + want: "srn:corev2/pipeline:default:test", + wantErr: false, + }, + { + name: "role", + resolver: &roleImpl{}, + in: corev2.FixtureRole("test", "default"), + want: "srn:roles:default:test", + wantErr: false, + }, + { + name: "role_binding", + resolver: &roleBindingImpl{}, + in: corev2.FixtureRoleBinding("test", "default"), + want: "srn:rolebindings:default:test", + wantErr: false, + }, + { + name: "cluster_role", + resolver: &clusterRoleImpl{}, + in: corev2.FixtureClusterRole("test"), + want: "srn:clusterroles:test", + wantErr: false, + }, + { + name: "cluster_role_binding", + resolver: &clusterRoleBindingImpl{}, + in: corev2.FixtureClusterRoleBinding("test"), + want: "srn:clusterrolebindings:test", + wantErr: false, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tr := &corev2PipelineImpl{} - got, err := tr.ID(graphql.ResolveParams{Context: context.Background(), Source: tt.in}) + t.Run(fmt.Sprintf("%T/%s", tt.resolver, tt.name), func(t *testing.T) { + params := graphql.ResolveParams{Context: context.Background(), Source: tt.in} + got, err := tt.resolver.ID(params) if (err != nil) != tt.wantErr { t.Errorf("corev2PipelineImpl.ID() error = %v, wantErr %v", err, tt.wantErr) return @@ -39,24 +72,55 @@ func Test_corev2PipelineImpl_ID(t *testing.T) { } } -func Test_corev2PipelineImp_ToJSON(t *testing.T) { +func Test_corev2_ToJSON(t *testing.T) { tests := []struct { - name string - in *corev2.Pipeline + name string + resolver interface { + ToJSON(p graphql.ResolveParams) (interface{}, error) + } + in interface{} want interface{} wantErr bool }{ { - name: "default", - in: corev2.FixturePipeline("name", "default"), - want: types.WrapResource(corev2.FixturePipeline("name", "default")), - wantErr: false, + name: "default", + resolver: &corev2PipelineImpl{}, + in: corev2.FixturePipeline("name", "default"), + want: types.WrapResource(corev2.FixturePipeline("name", "default")), + wantErr: false, + }, + { + name: "default", + resolver: &roleImpl{}, + in: corev2.FixtureRole("name", "default"), + want: types.WrapResource(corev2.FixtureRole("name", "default")), + wantErr: false, + }, + { + name: "default", + resolver: &roleBindingImpl{}, + in: corev2.FixtureRoleBinding("name", "default"), + want: types.WrapResource(corev2.FixtureRoleBinding("name", "default")), + wantErr: false, + }, + { + name: "default", + resolver: &clusterRoleImpl{}, + in: corev2.FixtureClusterRole("name"), + want: types.WrapResource(corev2.FixtureClusterRole("name")), + wantErr: false, + }, + { + name: "default", + resolver: &clusterRoleBindingImpl{}, + in: corev2.FixtureClusterRoleBinding("name"), + want: types.WrapResource(corev2.FixtureClusterRoleBinding("name")), + wantErr: false, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tr := &corev2PipelineImpl{} - got, err := tr.ToJSON(graphql.ResolveParams{Context: context.Background(), Source: tt.in}) + t.Run(fmt.Sprintf("%T/%s", tt.resolver, tt.name), func(t *testing.T) { + got, err := tt.resolver.ToJSON(graphql.ResolveParams{Context: context.Background(), Source: tt.in}) if (err != nil) != tt.wantErr { t.Errorf("corev2PipelineImpl.ToJSON() error = %v, wantErr %v", err, tt.wantErr) return @@ -68,27 +132,79 @@ func Test_corev2PipelineImp_ToJSON(t *testing.T) { } } -func Test_corev2PipelineImp_IsTypeOf(t *testing.T) { +func Test_corev2types_IsTypeOf(t *testing.T) { tests := []struct { - name string + name string + resolver interface { + IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool + } in interface{} want bool }{ { - name: "match", - in: corev2.FixturePipeline("name", "default"), - want: true, + name: "match", + resolver: &corev2PipelineImpl{}, + in: corev2.FixturePipeline("name", "default"), + want: true, + }, + { + name: "no match", + resolver: &corev2PipelineImpl{}, + in: corev2.FixtureEntity("name"), + want: false, + }, + { + name: "match", + resolver: &roleImpl{}, + in: corev2.FixtureRole("name", "default"), + want: true, + }, + { + name: "no match", + resolver: &roleImpl{}, + in: corev2.FixtureEntity("name"), + want: false, + }, + { + name: "match", + resolver: &roleBindingImpl{}, + in: corev2.FixtureRoleBinding("name", "default"), + want: true, + }, + { + name: "no match", + resolver: &roleBindingImpl{}, + in: corev2.FixtureEntity("name"), + want: false, + }, + { + name: "match", + resolver: &clusterRoleImpl{}, + in: corev2.FixtureClusterRole("name"), + want: true, + }, + { + name: "no match", + resolver: &clusterRoleImpl{}, + in: corev2.FixtureEntity("name"), + want: false, + }, + { + name: "match", + resolver: &clusterRoleBindingImpl{}, + in: corev2.FixtureClusterRoleBinding("name"), + want: true, }, { - name: "no match", - in: corev2.FixtureEntity("name"), - want: false, + name: "no match", + resolver: &clusterRoleBindingImpl{}, + in: corev2.FixtureEntity("name"), + want: false, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tr := &corev2PipelineImpl{} - got := tr.IsTypeOf(tt.in, graphql.IsTypeOfParams{Context: context.Background()}) + t.Run(fmt.Sprintf("%T/%s", tt.resolver, tt.name), func(t *testing.T) { + got := tt.resolver.IsTypeOf(tt.in, graphql.IsTypeOfParams{Context: context.Background()}) if !reflect.DeepEqual(got, tt.want) { t.Errorf("corev2PipelineImpl.ToJSON() = %v, want %v", got, tt.want) } diff --git a/backend/apid/graphql/node.go b/backend/apid/graphql/node.go index d4560120ed..3416cba4bb 100644 --- a/backend/apid/graphql/node.go +++ b/backend/apid/graphql/node.go @@ -135,7 +135,7 @@ func registerMutatorNodeResolver(register relay.NodeRegister, client GenericClie func registerClusterRoleNodeResolver(register relay.NodeRegister, client RBACClient) { register.RegisterResolver(relay.NodeResolver{ - ObjectType: schema.ClusterRoleType, + ObjectType: schema.CoreV2ClusterRoleType, Translator: globalid.ClusterRoleTranslator, Resolve: func(p relay.NodeResolverParams) (interface{}, error) { ctx := setContextFromComponents(p.Context, p.IDComponents) @@ -149,7 +149,7 @@ func registerClusterRoleNodeResolver(register relay.NodeRegister, client RBACCli func registerClusterRoleBindingNodeResolver(register relay.NodeRegister, client RBACClient) { register.RegisterResolver(relay.NodeResolver{ - ObjectType: schema.ClusterRoleBindingType, + ObjectType: schema.CoreV2ClusterRoleBindingType, Translator: globalid.ClusterRoleBindingTranslator, Resolve: func(p relay.NodeResolverParams) (interface{}, error) { ctx := setContextFromComponents(p.Context, p.IDComponents) @@ -163,7 +163,7 @@ func registerClusterRoleBindingNodeResolver(register relay.NodeRegister, client func registerRoleNodeResolver(register relay.NodeRegister, client RBACClient) { register.RegisterResolver(relay.NodeResolver{ - ObjectType: schema.RoleType, + ObjectType: schema.CoreV2RoleType, Translator: globalid.RoleTranslator, Resolve: func(p relay.NodeResolverParams) (interface{}, error) { ctx := setContextFromComponents(p.Context, p.IDComponents) @@ -177,7 +177,7 @@ func registerRoleNodeResolver(register relay.NodeRegister, client RBACClient) { func registerRoleBindingNodeResolver(register relay.NodeRegister, client RBACClient) { register.RegisterResolver(relay.NodeResolver{ - ObjectType: schema.RoleBindingType, + ObjectType: schema.CoreV2RoleBindingType, Translator: globalid.RoleBindingTranslator, Resolve: func(p relay.NodeResolverParams) (interface{}, error) { ctx := setContextFromComponents(p.Context, p.IDComponents) diff --git a/backend/apid/graphql/rbac.go b/backend/apid/graphql/rbac.go deleted file mode 100644 index b2fdda3dae..0000000000 --- a/backend/apid/graphql/rbac.go +++ /dev/null @@ -1,44 +0,0 @@ -package graphql - -import ( - corev2 "github.com/sensu/core/v2" - "github.com/sensu/sensu-go/backend/apid/graphql/globalid" - "github.com/sensu/sensu-go/backend/apid/graphql/schema" - "github.com/sensu/sensu-go/graphql" -) - -var _ schema.RuleFieldResolvers = (*ruleImpl)(nil) -var _ schema.RoleFieldResolvers = (*roleImpl)(nil) - -// -// Implement RuleFieldResolvers -// - -type ruleImpl struct { - schema.RuleAliases -} - -// IsTypeOf is used to determine if a given value is associated with the type -func (*ruleImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { - _, ok := s.(corev2.Rule) - return ok -} - -// -// Implement RoleFieldResolvers -// - -type roleImpl struct { - schema.RoleAliases -} - -// ID implements response to request for 'id' field. -func (*roleImpl) ID(p graphql.ResolveParams) (string, error) { - return globalid.RoleTranslator.EncodeToString(p.Context, p.Source), nil -} - -// IsTypeOf is used to determine if a given value is associated with the type -func (*roleImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { - _, ok := s.(*corev2.Role) - return ok -} diff --git a/backend/apid/graphql/schema/corev2.gql.go b/backend/apid/graphql/schema/corev2.gql.go index bb2a42a25f..5f266124af 100644 --- a/backend/apid/graphql/schema/corev2.gql.go +++ b/backend/apid/graphql/schema/corev2.gql.go @@ -74,3 +74,275 @@ var _ObjectExtensionTypeCoreV2PipelineExtensionOverridesDesc = graphql.ObjectDes "toJSON": _ObjTypeCoreV2PipelineExtensionOverridesToJSONHandler, }, } + +// CoreV2RoleExtensionOverridesFieldResolvers represents a collection of methods whose products represent the +// response values of the 'CoreV2RoleExtensionOverrides' type. +type CoreV2RoleExtensionOverridesFieldResolvers interface { + // ID implements response to request for 'id' field. + ID(p graphql.ResolveParams) (string, error) + + // ToJSON implements response to request for 'toJSON' field. + ToJSON(p graphql.ResolveParams) (interface{}, error) +} + +// RegisterCoreV2RoleExtensionOverrides registers CoreV2RoleExtensionOverrides object type with given service. +func RegisterCoreV2RoleExtensionOverrides(svc *graphql.Service, impl CoreV2RoleExtensionOverridesFieldResolvers) { + svc.RegisterObjectExtension(_ObjectExtensionTypeCoreV2RoleExtensionOverridesDesc, impl) +} + +func _ObjTypeCoreV2RoleExtensionOverridesIDHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ID(p graphql.ResolveParams) (string, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ID(frp) + } +} + +func _ObjTypeCoreV2RoleExtensionOverridesToJSONHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ToJSON(p graphql.ResolveParams) (interface{}, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ToJSON(frp) + } +} + +func _ObjectExtensionTypeCoreV2RoleExtensionOverridesConfigFn() graphql1.ObjectConfig { + return graphql1.ObjectConfig{ + Description: "", + Fields: graphql1.Fields{ + "id": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "The globally unique identifier of the record", + Name: "id", + Type: graphql1.NewNonNull(graphql1.ID), + }, + "toJSON": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "toJSON returns a REST API compatible representation of the resource. Handy for\nsharing snippets that can then be imported with `sensuctl create`.", + Name: "toJSON", + Type: graphql1.NewNonNull(graphql.OutputType("JSON")), + }, + }, + Interfaces: []*graphql1.Interface{ + graphql.Interface("Node"), + graphql.Interface("Resource")}, + Name: "CoreV2Role", + } +} + +// describe CoreV2RoleExtensionOverrides's configuration; kept private to avoid unintentional tampering of configuration at runtime. +var _ObjectExtensionTypeCoreV2RoleExtensionOverridesDesc = graphql.ObjectDesc{ + Config: _ObjectExtensionTypeCoreV2RoleExtensionOverridesConfigFn, + FieldHandlers: map[string]graphql.FieldHandler{ + "id": _ObjTypeCoreV2RoleExtensionOverridesIDHandler, + "toJSON": _ObjTypeCoreV2RoleExtensionOverridesToJSONHandler, + }, +} + +// CoreV2RoleBindingExtensionOverridesFieldResolvers represents a collection of methods whose products represent the +// response values of the 'CoreV2RoleBindingExtensionOverrides' type. +type CoreV2RoleBindingExtensionOverridesFieldResolvers interface { + // ID implements response to request for 'id' field. + ID(p graphql.ResolveParams) (string, error) + + // ToJSON implements response to request for 'toJSON' field. + ToJSON(p graphql.ResolveParams) (interface{}, error) +} + +// RegisterCoreV2RoleBindingExtensionOverrides registers CoreV2RoleBindingExtensionOverrides object type with given service. +func RegisterCoreV2RoleBindingExtensionOverrides(svc *graphql.Service, impl CoreV2RoleBindingExtensionOverridesFieldResolvers) { + svc.RegisterObjectExtension(_ObjectExtensionTypeCoreV2RoleBindingExtensionOverridesDesc, impl) +} + +func _ObjTypeCoreV2RoleBindingExtensionOverridesIDHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ID(p graphql.ResolveParams) (string, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ID(frp) + } +} + +func _ObjTypeCoreV2RoleBindingExtensionOverridesToJSONHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ToJSON(p graphql.ResolveParams) (interface{}, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ToJSON(frp) + } +} + +func _ObjectExtensionTypeCoreV2RoleBindingExtensionOverridesConfigFn() graphql1.ObjectConfig { + return graphql1.ObjectConfig{ + Description: "", + Fields: graphql1.Fields{ + "id": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "The globally unique identifier of the record", + Name: "id", + Type: graphql1.NewNonNull(graphql1.ID), + }, + "toJSON": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "toJSON returns a REST API compatible representation of the resource. Handy for\nsharing snippets that can then be imported with `sensuctl create`.", + Name: "toJSON", + Type: graphql1.NewNonNull(graphql.OutputType("JSON")), + }, + }, + Interfaces: []*graphql1.Interface{ + graphql.Interface("Node"), + graphql.Interface("Resource")}, + Name: "CoreV2RoleBinding", + } +} + +// describe CoreV2RoleBindingExtensionOverrides's configuration; kept private to avoid unintentional tampering of configuration at runtime. +var _ObjectExtensionTypeCoreV2RoleBindingExtensionOverridesDesc = graphql.ObjectDesc{ + Config: _ObjectExtensionTypeCoreV2RoleBindingExtensionOverridesConfigFn, + FieldHandlers: map[string]graphql.FieldHandler{ + "id": _ObjTypeCoreV2RoleBindingExtensionOverridesIDHandler, + "toJSON": _ObjTypeCoreV2RoleBindingExtensionOverridesToJSONHandler, + }, +} + +// CoreV2ClusterRoleExtensionOverridesFieldResolvers represents a collection of methods whose products represent the +// response values of the 'CoreV2ClusterRoleExtensionOverrides' type. +type CoreV2ClusterRoleExtensionOverridesFieldResolvers interface { + // ID implements response to request for 'id' field. + ID(p graphql.ResolveParams) (string, error) + + // ToJSON implements response to request for 'toJSON' field. + ToJSON(p graphql.ResolveParams) (interface{}, error) +} + +// RegisterCoreV2ClusterRoleExtensionOverrides registers CoreV2ClusterRoleExtensionOverrides object type with given service. +func RegisterCoreV2ClusterRoleExtensionOverrides(svc *graphql.Service, impl CoreV2ClusterRoleExtensionOverridesFieldResolvers) { + svc.RegisterObjectExtension(_ObjectExtensionTypeCoreV2ClusterRoleExtensionOverridesDesc, impl) +} + +func _ObjTypeCoreV2ClusterRoleExtensionOverridesIDHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ID(p graphql.ResolveParams) (string, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ID(frp) + } +} + +func _ObjTypeCoreV2ClusterRoleExtensionOverridesToJSONHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ToJSON(p graphql.ResolveParams) (interface{}, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ToJSON(frp) + } +} + +func _ObjectExtensionTypeCoreV2ClusterRoleExtensionOverridesConfigFn() graphql1.ObjectConfig { + return graphql1.ObjectConfig{ + Description: "", + Fields: graphql1.Fields{ + "id": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "The globally unique identifier of the record", + Name: "id", + Type: graphql1.NewNonNull(graphql1.ID), + }, + "toJSON": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "toJSON returns a REST API compatible representation of the resource. Handy for\nsharing snippets that can then be imported with `sensuctl create`.", + Name: "toJSON", + Type: graphql1.NewNonNull(graphql.OutputType("JSON")), + }, + }, + Interfaces: []*graphql1.Interface{ + graphql.Interface("Node"), + graphql.Interface("Resource")}, + Name: "CoreV2ClusterRole", + } +} + +// describe CoreV2ClusterRoleExtensionOverrides's configuration; kept private to avoid unintentional tampering of configuration at runtime. +var _ObjectExtensionTypeCoreV2ClusterRoleExtensionOverridesDesc = graphql.ObjectDesc{ + Config: _ObjectExtensionTypeCoreV2ClusterRoleExtensionOverridesConfigFn, + FieldHandlers: map[string]graphql.FieldHandler{ + "id": _ObjTypeCoreV2ClusterRoleExtensionOverridesIDHandler, + "toJSON": _ObjTypeCoreV2ClusterRoleExtensionOverridesToJSONHandler, + }, +} + +// CoreV2ClusterRoleBindingExtensionOverridesFieldResolvers represents a collection of methods whose products represent the +// response values of the 'CoreV2ClusterRoleBindingExtensionOverrides' type. +type CoreV2ClusterRoleBindingExtensionOverridesFieldResolvers interface { + // ID implements response to request for 'id' field. + ID(p graphql.ResolveParams) (string, error) + + // ToJSON implements response to request for 'toJSON' field. + ToJSON(p graphql.ResolveParams) (interface{}, error) +} + +// RegisterCoreV2ClusterRoleBindingExtensionOverrides registers CoreV2ClusterRoleBindingExtensionOverrides object type with given service. +func RegisterCoreV2ClusterRoleBindingExtensionOverrides(svc *graphql.Service, impl CoreV2ClusterRoleBindingExtensionOverridesFieldResolvers) { + svc.RegisterObjectExtension(_ObjectExtensionTypeCoreV2ClusterRoleBindingExtensionOverridesDesc, impl) +} + +func _ObjTypeCoreV2ClusterRoleBindingExtensionOverridesIDHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ID(p graphql.ResolveParams) (string, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ID(frp) + } +} + +func _ObjTypeCoreV2ClusterRoleBindingExtensionOverridesToJSONHandler(impl interface{}) graphql1.FieldResolveFn { + resolver := impl.(interface { + ToJSON(p graphql.ResolveParams) (interface{}, error) + }) + return func(frp graphql1.ResolveParams) (interface{}, error) { + return resolver.ToJSON(frp) + } +} + +func _ObjectExtensionTypeCoreV2ClusterRoleBindingExtensionOverridesConfigFn() graphql1.ObjectConfig { + return graphql1.ObjectConfig{ + Description: "", + Fields: graphql1.Fields{ + "id": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "The globally unique identifier of the record", + Name: "id", + Type: graphql1.NewNonNull(graphql1.ID), + }, + "toJSON": &graphql1.Field{ + Args: graphql1.FieldConfigArgument{}, + DeprecationReason: "", + Description: "toJSON returns a REST API compatible representation of the resource. Handy for\nsharing snippets that can then be imported with `sensuctl create`.", + Name: "toJSON", + Type: graphql1.NewNonNull(graphql.OutputType("JSON")), + }, + }, + Interfaces: []*graphql1.Interface{ + graphql.Interface("Node"), + graphql.Interface("Resource")}, + Name: "CoreV2ClusterRoleBinding", + } +} + +// describe CoreV2ClusterRoleBindingExtensionOverrides's configuration; kept private to avoid unintentional tampering of configuration at runtime. +var _ObjectExtensionTypeCoreV2ClusterRoleBindingExtensionOverridesDesc = graphql.ObjectDesc{ + Config: _ObjectExtensionTypeCoreV2ClusterRoleBindingExtensionOverridesConfigFn, + FieldHandlers: map[string]graphql.FieldHandler{ + "id": _ObjTypeCoreV2ClusterRoleBindingExtensionOverridesIDHandler, + "toJSON": _ObjTypeCoreV2ClusterRoleBindingExtensionOverridesToJSONHandler, + }, +} diff --git a/backend/apid/graphql/schema/corev2.graphql b/backend/apid/graphql/schema/corev2.graphql index 30908b5ba5..bc05a3559c 100644 --- a/backend/apid/graphql/schema/corev2.graphql +++ b/backend/apid/graphql/schema/corev2.graphql @@ -1,7 +1,74 @@ +# +# Pipeline +# + extend type CoreV2Pipeline implements Node & Resource @named(suffix: "Overrides") { "Unique global identifier used to reference resource." id: ID! + # "metadata contains name, namespace, labels and annotations of the record" + # metadata: ObjectMeta + + """ + toJSON returns a REST API compatible representation of the resource. Handy for + sharing snippets that can then be imported with `sensuctl create`. + """ + toJSON: JSON! +} + +# +# RBAC +# + +extend type CoreV2Role implements Node & Resource @named(suffix: "Overrides") { + "The globally unique identifier of the record" + id: ID! + + # "metadata contains name, namespace, labels and annotations of the record" + # metadata: ObjectMeta + + """ + toJSON returns a REST API compatible representation of the resource. Handy for + sharing snippets that can then be imported with `sensuctl create`. + """ + toJSON: JSON! +} + +extend type CoreV2RoleBinding implements Node & Resource @named(suffix: "Overrides") { + "The globally unique identifier of the record" + id: ID! + + # "metadata contains name, namespace, labels and annotations of the record" + # metadata: ObjectMeta + + """ + toJSON returns a REST API compatible representation of the resource. Handy for + sharing snippets that can then be imported with `sensuctl create`. + """ + toJSON: JSON! +} + +extend type CoreV2ClusterRole implements Node & Resource @named(suffix: "Overrides") { + "The globally unique identifier of the record" + id: ID! + + # "metadata contains name, namespace, labels and annotations of the record" + # metadata: ObjectMeta + + """ + toJSON returns a REST API compatible representation of the resource. Handy for + sharing snippets that can then be imported with `sensuctl create`. + """ + toJSON: JSON! +} + +extend type CoreV2ClusterRoleBinding implements Node & Resource @named(suffix: "Overrides") { + "The globally unique identifier of the record" + id: ID! + + # "metadata contains name, namespace, labels and annotations of the record" + # metadata: ObjectMeta + """ toJSON returns a REST API compatible representation of the resource. Handy for sharing snippets that can then be imported with `sensuctl create`. diff --git a/backend/apid/graphql/schema/rbac.gql.go b/backend/apid/graphql/schema/rbac.gql.go deleted file mode 100644 index f14694f5a8..0000000000 --- a/backend/apid/graphql/schema/rbac.gql.go +++ /dev/null @@ -1,894 +0,0 @@ -// Code generated by scripts/gengraphql.go. DO NOT EDIT. - -package schema - -import ( - errors "errors" - graphql1 "github.com/graphql-go/graphql" - graphql "github.com/sensu/sensu-go/graphql" -) - -// RuleFieldResolvers represents a collection of methods whose products represent the -// response values of the 'Rule' type. -type RuleFieldResolvers interface { - // Verbs implements response to request for 'verbs' field. - Verbs(p graphql.ResolveParams) ([]string, error) - - // Resources implements response to request for 'resources' field. - Resources(p graphql.ResolveParams) ([]string, error) - - // ResourceNames implements response to request for 'resourceNames' field. - ResourceNames(p graphql.ResolveParams) ([]string, error) -} - -// RuleAliases implements all methods on RuleFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type RuleAliases struct{} - -// Verbs implements response to request for 'verbs' field. -func (_ RuleAliases) Verbs(p graphql.ResolveParams) ([]string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.([]string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'verbs'") - } - return ret, err -} - -// Resources implements response to request for 'resources' field. -func (_ RuleAliases) Resources(p graphql.ResolveParams) ([]string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.([]string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'resources'") - } - return ret, err -} - -// ResourceNames implements response to request for 'resourceNames' field. -func (_ RuleAliases) ResourceNames(p graphql.ResolveParams) ([]string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.([]string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'resourceNames'") - } - return ret, err -} - -// RuleType Rule holds information that describes an action that can be taken -var RuleType = graphql.NewType("Rule", graphql.ObjectKind) - -// RegisterRule registers Rule object type with given service. -func RegisterRule(svc *graphql.Service, impl RuleFieldResolvers) { - svc.RegisterObject(_ObjectTypeRuleDesc, impl) -} -func _ObjTypeRuleVerbsHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Verbs(p graphql.ResolveParams) ([]string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Verbs(frp) - } -} - -func _ObjTypeRuleResourcesHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Resources(p graphql.ResolveParams) ([]string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Resources(frp) - } -} - -func _ObjTypeRuleResourceNamesHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - ResourceNames(p graphql.ResolveParams) ([]string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.ResourceNames(frp) - } -} - -func _ObjectTypeRuleConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "Rule holds information that describes an action that can be taken", - Fields: graphql1.Fields{ - "resourceNames": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "ResourceNames is an optional list of resource names that the rule applies\nto.", - Name: "resourceNames", - Type: graphql1.NewNonNull(graphql1.NewList(graphql1.NewNonNull(graphql1.String))), - }, - "resources": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Resources is a list of resources that this rule applies to. \"*\" represents\nall resources.", - Name: "resources", - Type: graphql1.NewNonNull(graphql1.NewList(graphql1.NewNonNull(graphql1.String))), - }, - "verbs": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Verbs is a list of verbs that apply to all of the listed resources for this\nrule. These include \"get\", \"list\", \"watch\", \"create\", \"update\", \"delete\".\nTODO: add support for \"patch\" (this is expensive and should be delayed\nuntil a further release). TODO: add support for \"watch\" (via websockets)", - Name: "verbs", - Type: graphql1.NewNonNull(graphql1.NewList(graphql1.NewNonNull(graphql1.String))), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see RuleFieldResolvers.") - }, - Name: "Rule", - } -} - -// describe Rule's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeRuleDesc = graphql.ObjectDesc{ - Config: _ObjectTypeRuleConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "resourceNames": _ObjTypeRuleResourceNamesHandler, - "resources": _ObjTypeRuleResourcesHandler, - "verbs": _ObjTypeRuleVerbsHandler, - }, -} - -// ClusterRoleFieldResolvers represents a collection of methods whose products represent the -// response values of the 'ClusterRole' type. -type ClusterRoleFieldResolvers interface { - // Rules implements response to request for 'rules' field. - Rules(p graphql.ResolveParams) (interface{}, error) - - // Name implements response to request for 'name' field. - Name(p graphql.ResolveParams) (string, error) -} - -// ClusterRoleAliases implements all methods on ClusterRoleFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type ClusterRoleAliases struct{} - -// Rules implements response to request for 'rules' field. -func (_ ClusterRoleAliases) Rules(p graphql.ResolveParams) (interface{}, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - return val, err -} - -// Name implements response to request for 'name' field. -func (_ ClusterRoleAliases) Name(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'name'") - } - return ret, err -} - -// ClusterRoleType ClusterRole applies to all namespaces within a cluster. -var ClusterRoleType = graphql.NewType("ClusterRole", graphql.ObjectKind) - -// RegisterClusterRole registers ClusterRole object type with given service. -func RegisterClusterRole(svc *graphql.Service, impl ClusterRoleFieldResolvers) { - svc.RegisterObject(_ObjectTypeClusterRoleDesc, impl) -} -func _ObjTypeClusterRoleRulesHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Rules(p graphql.ResolveParams) (interface{}, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Rules(frp) - } -} - -func _ObjTypeClusterRoleNameHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Name(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Name(frp) - } -} - -func _ObjectTypeClusterRoleConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "ClusterRole applies to all namespaces within a cluster.", - Fields: graphql1.Fields{ - "name": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Name of the ClusterRole", - Name: "name", - Type: graphql1.NewNonNull(graphql1.String), - }, - "rules": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "self descriptive", - Name: "rules", - Type: graphql1.NewList(graphql1.NewNonNull(graphql.OutputType("Rule"))), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see ClusterRoleFieldResolvers.") - }, - Name: "ClusterRole", - } -} - -// describe ClusterRole's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeClusterRoleDesc = graphql.ObjectDesc{ - Config: _ObjectTypeClusterRoleConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "name": _ObjTypeClusterRoleNameHandler, - "rules": _ObjTypeClusterRoleRulesHandler, - }, -} - -// RoleFieldResolvers represents a collection of methods whose products represent the -// response values of the 'Role' type. -type RoleFieldResolvers interface { - // Rules implements response to request for 'rules' field. - Rules(p graphql.ResolveParams) (interface{}, error) - - // Namespace implements response to request for 'namespace' field. - Namespace(p graphql.ResolveParams) (string, error) - - // Name implements response to request for 'name' field. - Name(p graphql.ResolveParams) (string, error) -} - -// RoleAliases implements all methods on RoleFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type RoleAliases struct{} - -// Rules implements response to request for 'rules' field. -func (_ RoleAliases) Rules(p graphql.ResolveParams) (interface{}, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - return val, err -} - -// Namespace implements response to request for 'namespace' field. -func (_ RoleAliases) Namespace(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'namespace'") - } - return ret, err -} - -// Name implements response to request for 'name' field. -func (_ RoleAliases) Name(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'name'") - } - return ret, err -} - -// RoleType Role applies only to a single namespace. -var RoleType = graphql.NewType("Role", graphql.ObjectKind) - -// RegisterRole registers Role object type with given service. -func RegisterRole(svc *graphql.Service, impl RoleFieldResolvers) { - svc.RegisterObject(_ObjectTypeRoleDesc, impl) -} -func _ObjTypeRoleRulesHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Rules(p graphql.ResolveParams) (interface{}, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Rules(frp) - } -} - -func _ObjTypeRoleNamespaceHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Namespace(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Namespace(frp) - } -} - -func _ObjTypeRoleNameHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Name(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Name(frp) - } -} - -func _ObjectTypeRoleConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "Role applies only to a single namespace.", - Fields: graphql1.Fields{ - "name": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Name of the Role", - Name: "name", - Type: graphql1.NewNonNull(graphql1.String), - }, - "namespace": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Namespace of the Role", - Name: "namespace", - Type: graphql1.NewNonNull(graphql1.String), - }, - "rules": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "self descriptive", - Name: "rules", - Type: graphql1.NewList(graphql1.NewNonNull(graphql.OutputType("Rule"))), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see RoleFieldResolvers.") - }, - Name: "Role", - } -} - -// describe Role's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeRoleDesc = graphql.ObjectDesc{ - Config: _ObjectTypeRoleConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "name": _ObjTypeRoleNameHandler, - "namespace": _ObjTypeRoleNamespaceHandler, - "rules": _ObjTypeRoleRulesHandler, - }, -} - -// RoleRefFieldResolvers represents a collection of methods whose products represent the -// response values of the 'RoleRef' type. -type RoleRefFieldResolvers interface { - // Type implements response to request for 'type' field. - Type(p graphql.ResolveParams) (string, error) - - // Name implements response to request for 'name' field. - Name(p graphql.ResolveParams) (string, error) -} - -// RoleRefAliases implements all methods on RoleRefFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type RoleRefAliases struct{} - -// Type implements response to request for 'type' field. -func (_ RoleRefAliases) Type(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'type'") - } - return ret, err -} - -// Name implements response to request for 'name' field. -func (_ RoleRefAliases) Name(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'name'") - } - return ret, err -} - -// RoleRefType RoleRef maps groups to Roles or ClusterRoles. -var RoleRefType = graphql.NewType("RoleRef", graphql.ObjectKind) - -// RegisterRoleRef registers RoleRef object type with given service. -func RegisterRoleRef(svc *graphql.Service, impl RoleRefFieldResolvers) { - svc.RegisterObject(_ObjectTypeRoleRefDesc, impl) -} -func _ObjTypeRoleRefTypeHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Type(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Type(frp) - } -} - -func _ObjTypeRoleRefNameHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Name(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Name(frp) - } -} - -func _ObjectTypeRoleRefConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "RoleRef maps groups to Roles or ClusterRoles.", - Fields: graphql1.Fields{ - "name": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Name of the resource being referenced", - Name: "name", - Type: graphql1.NewNonNull(graphql1.String), - }, - "type": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Type of role being referenced.", - Name: "type", - Type: graphql1.NewNonNull(graphql1.String), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see RoleRefFieldResolvers.") - }, - Name: "RoleRef", - } -} - -// describe RoleRef's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeRoleRefDesc = graphql.ObjectDesc{ - Config: _ObjectTypeRoleRefConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "name": _ObjTypeRoleRefNameHandler, - "type": _ObjTypeRoleRefTypeHandler, - }, -} - -// SubjectFieldResolvers represents a collection of methods whose products represent the -// response values of the 'Subject' type. -type SubjectFieldResolvers interface { - // Kind implements response to request for 'kind' field. - Kind(p graphql.ResolveParams) (string, error) - - // Name implements response to request for 'name' field. - Name(p graphql.ResolveParams) (string, error) -} - -// SubjectAliases implements all methods on SubjectFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type SubjectAliases struct{} - -// Kind implements response to request for 'kind' field. -func (_ SubjectAliases) Kind(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'kind'") - } - return ret, err -} - -// Name implements response to request for 'name' field. -func (_ SubjectAliases) Name(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'name'") - } - return ret, err -} - -// SubjectType self descriptive -var SubjectType = graphql.NewType("Subject", graphql.ObjectKind) - -// RegisterSubject registers Subject object type with given service. -func RegisterSubject(svc *graphql.Service, impl SubjectFieldResolvers) { - svc.RegisterObject(_ObjectTypeSubjectDesc, impl) -} -func _ObjTypeSubjectKindHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Kind(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Kind(frp) - } -} - -func _ObjTypeSubjectNameHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Name(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Name(frp) - } -} - -func _ObjectTypeSubjectConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "self descriptive", - Fields: graphql1.Fields{ - "kind": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Kind of object referenced (user or group)", - Name: "kind", - Type: graphql1.NewNonNull(graphql1.String), - }, - "name": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Name of the referenced object", - Name: "name", - Type: graphql1.NewNonNull(graphql1.String), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see SubjectFieldResolvers.") - }, - Name: "Subject", - } -} - -// describe Subject's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeSubjectDesc = graphql.ObjectDesc{ - Config: _ObjectTypeSubjectConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "kind": _ObjTypeSubjectKindHandler, - "name": _ObjTypeSubjectNameHandler, - }, -} - -// ClusterRoleBindingFieldResolvers represents a collection of methods whose products represent the -// response values of the 'ClusterRoleBinding' type. -type ClusterRoleBindingFieldResolvers interface { - // Subjects implements response to request for 'subjects' field. - Subjects(p graphql.ResolveParams) (interface{}, error) - - // RoleRef implements response to request for 'roleRef' field. - RoleRef(p graphql.ResolveParams) (interface{}, error) - - // Name implements response to request for 'name' field. - Name(p graphql.ResolveParams) (string, error) -} - -// ClusterRoleBindingAliases implements all methods on ClusterRoleBindingFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type ClusterRoleBindingAliases struct{} - -// Subjects implements response to request for 'subjects' field. -func (_ ClusterRoleBindingAliases) Subjects(p graphql.ResolveParams) (interface{}, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - return val, err -} - -// RoleRef implements response to request for 'roleRef' field. -func (_ ClusterRoleBindingAliases) RoleRef(p graphql.ResolveParams) (interface{}, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - return val, err -} - -// Name implements response to request for 'name' field. -func (_ ClusterRoleBindingAliases) Name(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'name'") - } - return ret, err -} - -/* -ClusterRoleBindingType ClusterRoleBinding grants the permissions defined in a ClusterRole referenced -to a user or a set of users -*/ -var ClusterRoleBindingType = graphql.NewType("ClusterRoleBinding", graphql.ObjectKind) - -// RegisterClusterRoleBinding registers ClusterRoleBinding object type with given service. -func RegisterClusterRoleBinding(svc *graphql.Service, impl ClusterRoleBindingFieldResolvers) { - svc.RegisterObject(_ObjectTypeClusterRoleBindingDesc, impl) -} -func _ObjTypeClusterRoleBindingSubjectsHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Subjects(p graphql.ResolveParams) (interface{}, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Subjects(frp) - } -} - -func _ObjTypeClusterRoleBindingRoleRefHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - RoleRef(p graphql.ResolveParams) (interface{}, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.RoleRef(frp) - } -} - -func _ObjTypeClusterRoleBindingNameHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Name(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Name(frp) - } -} - -func _ObjectTypeClusterRoleBindingConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "ClusterRoleBinding grants the permissions defined in a ClusterRole referenced\nto a user or a set of users", - Fields: graphql1.Fields{ - "name": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Name of the ClusterRoleBinding", - Name: "name", - Type: graphql1.NewNonNull(graphql1.String), - }, - "roleRef": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "RoleRef references a ClusterRole in the current namespace", - Name: "roleRef", - Type: graphql.OutputType("RoleRef"), - }, - "subjects": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Subjects holds references to the objects the ClusterRole applies to", - Name: "subjects", - Type: graphql1.NewList(graphql1.NewNonNull(graphql.OutputType("Subject"))), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see ClusterRoleBindingFieldResolvers.") - }, - Name: "ClusterRoleBinding", - } -} - -// describe ClusterRoleBinding's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeClusterRoleBindingDesc = graphql.ObjectDesc{ - Config: _ObjectTypeClusterRoleBindingConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "name": _ObjTypeClusterRoleBindingNameHandler, - "roleRef": _ObjTypeClusterRoleBindingRoleRefHandler, - "subjects": _ObjTypeClusterRoleBindingSubjectsHandler, - }, -} - -// RoleBindingFieldResolvers represents a collection of methods whose products represent the -// response values of the 'RoleBinding' type. -type RoleBindingFieldResolvers interface { - // Subjects implements response to request for 'subjects' field. - Subjects(p graphql.ResolveParams) (interface{}, error) - - // RoleRef implements response to request for 'roleRef' field. - RoleRef(p graphql.ResolveParams) (interface{}, error) - - // Namespace implements response to request for 'namespace' field. - Namespace(p graphql.ResolveParams) (string, error) - - // Name implements response to request for 'name' field. - Name(p graphql.ResolveParams) (string, error) -} - -// RoleBindingAliases implements all methods on RoleBindingFieldResolvers interface by using reflection to -// match name of field to a field on the given value. Intent is reduce friction -// of writing new resolvers by removing all the instances where you would simply -// have the resolvers method return a field. -type RoleBindingAliases struct{} - -// Subjects implements response to request for 'subjects' field. -func (_ RoleBindingAliases) Subjects(p graphql.ResolveParams) (interface{}, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - return val, err -} - -// RoleRef implements response to request for 'roleRef' field. -func (_ RoleBindingAliases) RoleRef(p graphql.ResolveParams) (interface{}, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - return val, err -} - -// Namespace implements response to request for 'namespace' field. -func (_ RoleBindingAliases) Namespace(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'namespace'") - } - return ret, err -} - -// Name implements response to request for 'name' field. -func (_ RoleBindingAliases) Name(p graphql.ResolveParams) (string, error) { - val, err := graphql.DefaultResolver(p.Source, p.Info.FieldName) - ret, ok := val.(string) - if err != nil { - return ret, err - } - if !ok { - return ret, errors.New("unable to coerce value for field 'name'") - } - return ret, err -} - -/* -RoleBindingType RoleBinding grants the permissions defined in a Role referenced to a user or -a set of users -*/ -var RoleBindingType = graphql.NewType("RoleBinding", graphql.ObjectKind) - -// RegisterRoleBinding registers RoleBinding object type with given service. -func RegisterRoleBinding(svc *graphql.Service, impl RoleBindingFieldResolvers) { - svc.RegisterObject(_ObjectTypeRoleBindingDesc, impl) -} -func _ObjTypeRoleBindingSubjectsHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Subjects(p graphql.ResolveParams) (interface{}, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Subjects(frp) - } -} - -func _ObjTypeRoleBindingRoleRefHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - RoleRef(p graphql.ResolveParams) (interface{}, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.RoleRef(frp) - } -} - -func _ObjTypeRoleBindingNamespaceHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Namespace(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Namespace(frp) - } -} - -func _ObjTypeRoleBindingNameHandler(impl interface{}) graphql1.FieldResolveFn { - resolver := impl.(interface { - Name(p graphql.ResolveParams) (string, error) - }) - return func(frp graphql1.ResolveParams) (interface{}, error) { - return resolver.Name(frp) - } -} - -func _ObjectTypeRoleBindingConfigFn() graphql1.ObjectConfig { - return graphql1.ObjectConfig{ - Description: "RoleBinding grants the permissions defined in a Role referenced to a user or\na set of users", - Fields: graphql1.Fields{ - "name": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Name of the RoleBinding", - Name: "name", - Type: graphql1.NewNonNull(graphql1.String), - }, - "namespace": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Namespace of the RoleBinding", - Name: "namespace", - Type: graphql1.NewNonNull(graphql1.String), - }, - "roleRef": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "RoleRef references a Role in the current namespace", - Name: "roleRef", - Type: graphql.OutputType("RoleRef"), - }, - "subjects": &graphql1.Field{ - Args: graphql1.FieldConfigArgument{}, - DeprecationReason: "", - Description: "Subjects holds references to the objects the Role applies to", - Name: "subjects", - Type: graphql1.NewList(graphql1.NewNonNull(graphql.OutputType("Subject"))), - }, - }, - Interfaces: []*graphql1.Interface{}, - IsTypeOf: func(_ graphql1.IsTypeOfParams) bool { - // NOTE: - // Panic by default. Intent is that when Service is invoked, values of - // these fields are updated with instantiated resolvers. If these - // defaults are called it is most certainly programmer err. - // If you're see this comment then: 'Whoops! Sorry, my bad.' - panic("Unimplemented; see RoleBindingFieldResolvers.") - }, - Name: "RoleBinding", - } -} - -// describe RoleBinding's configuration; kept private to avoid unintentional tampering of configuration at runtime. -var _ObjectTypeRoleBindingDesc = graphql.ObjectDesc{ - Config: _ObjectTypeRoleBindingConfigFn, - FieldHandlers: map[string]graphql.FieldHandler{ - "name": _ObjTypeRoleBindingNameHandler, - "namespace": _ObjTypeRoleBindingNamespaceHandler, - "roleRef": _ObjTypeRoleBindingRoleRefHandler, - "subjects": _ObjTypeRoleBindingSubjectsHandler, - }, -} diff --git a/backend/apid/graphql/schema/rbac.graphql b/backend/apid/graphql/schema/rbac.graphql deleted file mode 100644 index e51c6ddd15..0000000000 --- a/backend/apid/graphql/schema/rbac.graphql +++ /dev/null @@ -1,88 +0,0 @@ -""" -Rule holds information that describes an action that can be taken -""" -type Rule { - """ - Verbs is a list of verbs that apply to all of the listed resources for this - rule. These include "get", "list", "watch", "create", "update", "delete". - TODO: add support for "patch" (this is expensive and should be delayed - until a further release). TODO: add support for "watch" (via websockets) - """ - verbs: [String!]! - """ - Resources is a list of resources that this rule applies to. "*" represents - all resources. - """ - resources: [String!]! - """ - ResourceNames is an optional list of resource names that the rule applies - to. - """ - resourceNames: [String!]! -} - -""" -ClusterRole applies to all namespaces within a cluster. -""" -type ClusterRole { - rules: [Rule!] - "Name of the ClusterRole" - name: String! -} - -""" -Role applies only to a single namespace. -""" -type Role { - rules: [Rule!] - "Namespace of the Role" - namespace: String! - "Name of the Role" - name: String! -} - -""" -RoleRef maps groups to Roles or ClusterRoles. -""" -type RoleRef { - "Type of role being referenced." - type: String! - "Name of the resource being referenced" - name: String! -} - -type Subject { - "Kind of object referenced (user or group)" - kind: String! - "Name of the referenced object" - name: String! -} - -""" -ClusterRoleBinding grants the permissions defined in a ClusterRole referenced -to a user or a set of users -""" -type ClusterRoleBinding { - "Subjects holds references to the objects the ClusterRole applies to" - subjects: [Subject!] - "RoleRef references a ClusterRole in the current namespace" - roleRef: RoleRef - "Name of the ClusterRoleBinding" - name: String! -} - -""" -RoleBinding grants the permissions defined in a Role referenced to a user or -a set of users -""" -type RoleBinding { - "Subjects holds references to the objects the Role applies to" - subjects: [Subject!] - "RoleRef references a Role in the current namespace" - roleRef: RoleRef - "Namespace of the RoleBinding" - namespace: String! - "Name of the RoleBinding" - name: String! -} - diff --git a/backend/apid/graphql/service.go b/backend/apid/graphql/service.go index a9087ff73d..8a1637c696 100644 --- a/backend/apid/graphql/service.go +++ b/backend/apid/graphql/service.go @@ -171,13 +171,17 @@ func NewService(cfg ServiceConfig) (*Service, error) { schema.RegisterTimeWindowTimeRange(svc, &schema.TimeWindowTimeRangeAliases{}) // Register RBAC types - schema.RegisterClusterRole(svc, &schema.ClusterRoleAliases{}) - schema.RegisterClusterRoleBinding(svc, &schema.ClusterRoleBindingAliases{}) - schema.RegisterRole(svc, &schema.RoleAliases{}) - schema.RegisterRoleBinding(svc, &schema.RoleBindingAliases{}) - schema.RegisterRoleRef(svc, &schema.RoleRefAliases{}) - schema.RegisterRule(svc, &schema.RuleAliases{}) - schema.RegisterSubject(svc, &schema.SubjectAliases{}) + schema.RegisterCoreV2ClusterRole(svc, &clusterRoleImpl{}) + schema.RegisterCoreV2ClusterRoleExtensionOverrides(svc, &clusterRoleImpl{}) + schema.RegisterCoreV2ClusterRoleBinding(svc, &clusterRoleBindingImpl{}) + schema.RegisterCoreV2ClusterRoleBindingExtensionOverrides(svc, &clusterRoleBindingImpl{}) + schema.RegisterCoreV2Role(svc, &roleImpl{}) + schema.RegisterCoreV2RoleExtensionOverrides(svc, &roleImpl{}) + schema.RegisterCoreV2RoleBinding(svc, &roleBindingImpl{}) + schema.RegisterCoreV2RoleBindingExtensionOverrides(svc, &roleBindingImpl{}) + schema.RegisterCoreV2RoleRef(svc, &schema.CoreV2RoleRefAliases{}) + schema.RegisterCoreV2Rule(svc, &schema.CoreV2RuleAliases{}) + schema.RegisterCoreV2Subject(svc, &schema.CoreV2SubjectAliases{}) // Register user types schema.RegisterUser(svc, &userImpl{}) diff --git a/backend/apid/routers/graphql.go b/backend/apid/routers/graphql.go index cdb5a0ed36..205113b95a 100644 --- a/backend/apid/routers/graphql.go +++ b/backend/apid/routers/graphql.go @@ -85,6 +85,7 @@ func (r *GraphQLRouter) query(w http.ResponseWriter, req *http.Request) { Query: query, Variables: queryVars, SkipValidation: skipValidate, + IsAuthed: claims != nil, }) results = append(results, map[string]interface{}{ "data": result.Data, diff --git a/graphql/resolvers.go b/graphql/resolvers.go index 0a97576fbd..27789e9e74 100644 --- a/graphql/resolvers.go +++ b/graphql/resolvers.go @@ -233,8 +233,7 @@ type isTypeOfResolver interface { IsTypeOf(interface{}, IsTypeOfParams) bool } -func newIsTypeOfFn(impl interface{}) graphql.IsTypeOfFn { - resolver := impl.(isTypeOfResolver) +func newIsTypeOfFn(resolver isTypeOfResolver) graphql.IsTypeOfFn { return func(p graphql.IsTypeOfParams) bool { return resolver.IsTypeOf(p.Value, p) } diff --git a/graphql/service.go b/graphql/service.go index 0db9ae2f52..f7b6684a67 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -159,6 +159,7 @@ func (service *Service) Middleware() []Middleware { // QueryParams describe parameters of a GraphQL query. type QueryParams struct { + IsAuthed bool OperationName string Query string RootObject map[string]interface{} @@ -193,7 +194,14 @@ func (service *Service) Do(ctx context.Context, p QueryParams) *Result { return &graphql.Result{Errors: gqlerrors.FormatErrors(err)} } - // validate document + // run mandatory (un-skippable) validators + rules := MandatoryValidators() + validationResult := graphql.ValidateDocument(&schema, AST, rules) + if !validationResult.IsValid { + return &graphql.Result{Errors: validationResult.Errors} + } + + // run built-in validators e.g. schema type validation if !p.SkipValidation { validationFinishFn := MiddlewareHandleValidationDidStart(service, ¶ms) validationResult := graphql.ValidateDocument(&schema, AST, nil) @@ -208,7 +216,7 @@ func (service *Service) Do(ctx context.Context, p QueryParams) *Result { Schema: schema, AST: AST, Args: p.Variables, - Context: ctx, + Context: params.Context, }) } diff --git a/graphql/validators.go b/graphql/validators.go new file mode 100644 index 0000000000..40d2994159 --- /dev/null +++ b/graphql/validators.go @@ -0,0 +1,137 @@ +package graphql + +import ( + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/gqlerrors" + "github.com/graphql-go/graphql/language/ast" + "github.com/graphql-go/graphql/language/kinds" + "github.com/graphql-go/graphql/language/visitor" +) + +const ( + MaxQueryDepthLimit = 15 +) + +type maxDepthRule struct { + context *graphql.ValidationContext + depth int + depthLimit int +} + +func MandatoryValidators() []graphql.ValidationRuleFn { + return []graphql.ValidationRuleFn{MaxDepthRule(MaxQueryDepthLimit)} +} + +func MaxDepthRule(depthLimit int) graphql.ValidationRuleFn { + rule := newMaxDepthRule(depthLimit) + return rule.maxDepthRuleWithContext +} + +// provide MaxDepthRule with depth limit +func newMaxDepthRule(depthLimit int) *maxDepthRule { + rule := &maxDepthRule{ + depthLimit: depthLimit, + } + return rule +} + +func (r *maxDepthRule) maxDepthRuleWithContext(context *graphql.ValidationContext) *graphql.ValidationRuleInstance { + rule := &maxDepthRule{ + context: context, + depthLimit: r.depthLimit, + } + return &graphql.ValidationRuleInstance{VisitorOpts: rule.maxDepthVisitorOptions()} +} + +func (rule *maxDepthRule) maxDepthVisitorOptions() *visitor.VisitorOptions { + return &visitor.VisitorOptions{ + KindFuncMap: map[string]visitor.NamedVisitFuncs{ + kinds.OperationDefinition: { + Kind: func(p visitor.VisitFuncParams) (string, interface{}) { + node := p.Node.(ast.Node) + if node != nil { + maxDepth := validateMaxDepth(rule.context, node, 1, rule.depthLimit, nil) + rule.depth = maxDepth + } + return visitor.ActionNoChange, nil + }, + }, + }, + } +} + +// validate max depth of a GraphQL query with a depthLimit +func validateMaxDepth( + context *graphql.ValidationContext, + node ast.Node, + currentDepth int, + depthLimit int, + visited map[*ast.FragmentDefinition]bool, +) int { + // end recursion early if error reported + if errors := context.Errors(); len(errors) > 0 { + return -1 + } + + if currentDepth > depthLimit { + reportError(context, "Max depth exceeded", []ast.Node{node}) + return -1 + } + + // keep map of visited fragment spreads to prevent infinite loop + if visited == nil { + visited = map[*ast.FragmentDefinition]bool{} + } + + var selectionSet *ast.SelectionSet + + switch node.GetKind() { + case kinds.Field: + selectionSet = node.(*ast.Field).GetSelectionSet() + if selectionSet != nil { + selections := selectionSet.Selections + maxDepth := currentDepth + for _, selection := range selections { + nextDepth := validateMaxDepth(context, selection.(ast.Node), currentDepth+1, depthLimit, visited) + if nextDepth > maxDepth { + maxDepth = nextDepth + } + } + return maxDepth + } + return currentDepth + case kinds.FragmentSpread: + fragName := node.(*ast.FragmentSpread).Name.Value + fragment := context.Fragment(fragName) + if fragment == nil || visited[fragment] { + return currentDepth + } + visited[fragment] = true + // fragment spreads don't increase the depth + return validateMaxDepth(context, fragment, currentDepth, depthLimit, visited) + case kinds.InlineFragment: + selectionSet = node.(*ast.InlineFragment).GetSelectionSet() + case kinds.FragmentDefinition: + selectionSet = node.(*ast.FragmentDefinition).GetSelectionSet() + case kinds.OperationDefinition: + selectionSet = node.(*ast.OperationDefinition).GetSelectionSet() + } + + if selectionSet != nil { + selections := selectionSet.Selections + maxDepth := currentDepth + for _, selection := range selections { + // inline fragments, fragment definitions and operation definitions don't increase the depth + nextDepth := validateMaxDepth(context, selection.(ast.Node), currentDepth, depthLimit, visited) + if nextDepth > maxDepth { + maxDepth = nextDepth + } + } + return maxDepth + } + return currentDepth +} + +func reportError(context *graphql.ValidationContext, message string, nodes []ast.Node) { + context.ReportError(gqlerrors.NewError(message, nodes, "", nil, []int{}, nil)) +} diff --git a/graphql/validators_test.go b/graphql/validators_test.go new file mode 100644 index 0000000000..e17adeeb68 --- /dev/null +++ b/graphql/validators_test.go @@ -0,0 +1,174 @@ +package graphql + +import ( + "testing" + + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/language/ast" + "github.com/graphql-go/graphql/language/parser" + "github.com/graphql-go/graphql/language/visitor" +) + +var ( + schema *graphql.Schema + typeInfo *graphql.TypeInfo +) + +// init stubbed GraphQL schema +func init() { + // NOTE: the max-depth validator doesn't care about a valid schema, only depth of nodes in the graph + queryType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ + "foobar": &graphql.Field{ + Type: graphql.String, + }, + }, + }) + + schema, err := graphql.NewSchema(graphql.SchemaConfig{Query: queryType}) + if err != nil { + panic(err) + } + typeInfo = graphql.NewTypeInfo(&graphql.TypeInfoConfig{Schema: &schema}) +} + +// helper to parse a graphql query document +func parseQuery(t *testing.T, q string) *ast.Document { + t.Helper() + astDoc, err := parser.Parse(parser.ParseParams{Source: q}) + if err != nil { + t.Fatalf("parse failed: %s", err) + } + return astDoc +} + +// helper to assert query depth matches expected +func testDepth(t *testing.T, query string, depthLimit int, expectedDepth int) *maxDepthRule { + t.Helper() + astDoc := parseQuery(t, query) + context := graphql.NewValidationContext(schema, astDoc, typeInfo) + rule := newMaxDepthRule(depthLimit) + rule.context = context + visitor.Visit(astDoc, rule.maxDepthVisitorOptions(), nil) + if rule.depth != expectedDepth { + t.Fatalf("wrong depth expected: want=%d got=%d", expectedDepth, rule.depth) + } + return rule +} + +// helper to assert errors set when depth limit reached +func testMaxDepthError(t *testing.T, query string, depthLimit int) *maxDepthRule { + t.Helper() + astDoc := parseQuery(t, query) + context := graphql.NewValidationContext(schema, astDoc, typeInfo) + rule := newMaxDepthRule(depthLimit) + rule.context = context + visitor.Visit(astDoc, rule.maxDepthVisitorOptions(), nil) + if len(context.Errors()) == 0 { + t.Fatalf("expected errors but none returned") + } + return rule +} + +func TestMaxDepth(t *testing.T) { + testDepth(t, ` + query MyQuery { # depth 0 + postgres { # depth 1 + healthy # depth 2 + } + }`, 5, 2) +} + +func TestMaxDepthFragment(t *testing.T) { + testDepth(t, ` + query MyQuery { # depth 0 + pet { # depth 1 + breed # depth 2 + ...dog # depth 2 + } + } + fragment dog on Pet { + name # depth 2 + owner { + name # depth 3 + } + }`, 5, 3) +} + +// test don't break on cyclical fragment spread +func TestMaxDepthFragmentCycle(t *testing.T) { + testDepth(t, ` + fragment X on Query { ...Y } + fragment Y on Query { ...X } + query { + ...X + } + `, 5, 1) +} + +func TestMaxDepthInlineFragment(t *testing.T) { + testDepth(t, ` + query MyQuery { # depth 0 + pet { # depth 1 + breed # depth 2 + ... on Pet { # depth 2 + name # depth 2 + } + } + }`, 5, 2) +} + +func TestMaxDepthInlineFragmentNested(t *testing.T) { + testDepth(t, ` + query MyQuery { # depth 0 + pet { # depth 1 + breed # depth 2 + children { # depth 2 + ... on Pet { # depth 3 + name # depth 3 + } + } + } + }`, 5, 3) +} + +func TestMaxDepthFail(t *testing.T) { + testMaxDepthError(t, `query MyQuery { # depth 0 + postgres { # depth 1 + healthy # depth 2 <-- max depth reached here + } + }`, 1) +} + +func TestMaxDepthFailWithFragment(t *testing.T) { + testMaxDepthError(t, ` + query MyQuery { # depth 0 + pet { # depth 1 + breed # depth 2 + children { + ...dog # depth 3 + } + } + } + fragment dog on Pet { + name # depth 3 + owner { + name # depth 4 <-- max depth reached here + } + }`, 3) +} + +func TestMaxDepthFailWithNestedInlineFragment(t *testing.T) { + testMaxDepthError(t, ` + query MyQuery { # depth 0 + pet { # depth 1 + breed # depth 2 + children { # depth 2 + ... on Pet { # depth 3 + name # depth 3 + } + } + } + }`, 2) +}