diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index ef9746cae..814628dff 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -166,6 +166,7 @@ private function securitySection() ->children() ->append($this->securityQuerySection('query_max_depth', QueryDepth::DISABLED)) ->append($this->securityQuerySection('query_max_complexity', QueryComplexity::DISABLED)) + ->booleanNode('enable_introspection')->defaultTrue()->end() ->booleanNode('handle_cors')->defaultFalse()->end() ->end() ->end(); diff --git a/DependencyInjection/OverblogGraphQLExtension.php b/DependencyInjection/OverblogGraphQLExtension.php index f185e2078..535b4629d 100644 --- a/DependencyInjection/OverblogGraphQLExtension.php +++ b/DependencyInjection/OverblogGraphQLExtension.php @@ -162,6 +162,11 @@ private function treatConfigs(array $configs, ContainerBuilder $container, $forc private function setSecurity(array $config, ContainerBuilder $container) { + if (false === $config['security']['enable_introspection']) { + $executorDefinition = $container->getDefinition($this->getAlias().'.request_executor'); + $executorDefinition->addMethodCall('disableIntrospectionQuery'); + } + foreach ($config['security'] as $key => $value) { $container->setParameter(sprintf('%s.%s', $this->getAlias(), $key), $value); } diff --git a/README.md b/README.md index 518b629a6..f32c0a02a 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Documentation - [Fields public control](Resources/doc/security/fields-public-control.md) - [Limiting query depth](Resources/doc/security/limiting-query-depth.md) - [Query complexity analysis](Resources/doc/security/query-complexity-analysis.md) + - [Disable introspection](Resources/doc/security/disable_introspection.md) - [Errors handling](Resources/doc/error-handling/index.md) - [Events](Resources/doc/events/index.md) diff --git a/Request/Executor.php b/Request/Executor.php index d01501031..9c08717f9 100644 --- a/Request/Executor.php +++ b/Request/Executor.php @@ -7,6 +7,7 @@ use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Type\Schema; use GraphQL\Validator\DocumentValidator; +use GraphQL\Validator\Rules\DisableIntrospection; use GraphQL\Validator\Rules\QueryComplexity; use GraphQL\Validator\Rules\QueryDepth; use Overblog\GraphQLBundle\Event\Events; @@ -113,6 +114,11 @@ public function setMaxQueryComplexity($maxQueryComplexity) $queryComplexity->setMaxQueryComplexity($maxQueryComplexity); } + public function disableIntrospectionQuery() + { + DocumentValidator::addRule(new DisableIntrospection()); + } + /** * @param null|string $schemaName * @param array $request diff --git a/Resources/doc/security/disable_introspection.md b/Resources/doc/security/disable_introspection.md new file mode 100644 index 000000000..33924a6cb --- /dev/null +++ b/Resources/doc/security/disable_introspection.md @@ -0,0 +1,19 @@ +Disable introspection +===================== + +This bundle supports [webonyx/graphql-php validation rule to disable introspection queries](http://webonyx.github.io/graphql-php/security/#disabling-introspection). + +Introspection is a mechanism for fetching schema structure. It is used by tools like GraphiQL for auto-completion, query validation, etc. + +It means that anybody can get a full description of your schema by sending a special query containing meta fields __type and __schema. + +If you are not planning to expose your API to the general public, it makes sense to disable this feature in production. By disabling, tools like GraphiQL won't work anymore. + +```yaml +#app/config/config.yml +overblog_graphql: + security: + enable_introspection: '%kernel.debug%' +``` + +Introspection is enabled by default. diff --git a/Resources/doc/security/index.md b/Resources/doc/security/index.md index be362d504..078f93e98 100644 --- a/Resources/doc/security/index.md +++ b/Resources/doc/security/index.md @@ -6,5 +6,6 @@ Security * [Fields public control](fields-public-control.md) * [Limiting query depth](limiting-query-depth.md) * [Query complexity analysis](query-complexity-analysis.md) +* [Disable introspection](disable_introspection.md) Next step [handling errors](../error-handling/index.md) diff --git a/Tests/Functional/App/config/disableIntrospection/config.yml b/Tests/Functional/App/config/disableIntrospection/config.yml new file mode 100644 index 000000000..1f8a4d63c --- /dev/null +++ b/Tests/Functional/App/config/disableIntrospection/config.yml @@ -0,0 +1,17 @@ +imports: + - { resource: ../config.yml } + - { resource: ../connection/services.yml } + +overblog_graphql: + security: + enable_introspection: false + definitions: + class_namespace: "Overblog\\GraphQLBundle\\QueryComplexity\\__DEFINITIONS__" + schema: + query: Query + mutation: ~ + mappings: + types: + - + type: yaml + dir: "%kernel.root_dir%/config/queryComplexity/mapping" diff --git a/Tests/Functional/Security/DisableIntrospectionTest.php b/Tests/Functional/Security/DisableIntrospectionTest.php new file mode 100644 index 000000000..7f1335bf5 --- /dev/null +++ b/Tests/Functional/Security/DisableIntrospectionTest.php @@ -0,0 +1,39 @@ + [ + [ + 'message' => 'GraphQL introspection is not allowed, but the query contained __schema or __type', + 'category' => 'graphql', + 'locations' => [ + [ + 'line' => 2, + 'column' => 3, + ], + ], + ], + ], + ]; + + $this->assertResponse($this->introspectionQuery, $expected, self::ANONYMOUS_USER, 'disableIntrospection'); + } +}