mirror of
https://github.com/graphql-python/graphene.git
synced 2024-12-01 22:14:02 +03:00
feat: intropsection and custom validation
Signed-off-by: QuentinN42 <quentin@lieumont.fr>
This commit is contained in:
parent
84c5559ded
commit
f07d6a29c5
42
docs/security/customvalidation.rst
Normal file
42
docs/security/customvalidation.rst
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
Implementing custom validators
|
||||||
|
==============================
|
||||||
|
|
||||||
|
GraphQL uses query validators to check if Query AST is valid and can be executed. Every GraphQL server implements
|
||||||
|
standard query validators. For example, there is an validator that tests if queried field exists on queried type, that
|
||||||
|
makes query fail with "Cannot query field on type" error if it doesn't.
|
||||||
|
|
||||||
|
If you need more complex validation than presented before, you can implement your own query validators. All custom query
|
||||||
|
validators should extend the `ValidationRule`_ base class importable from the ``graphql.validation.rules`` module. Query
|
||||||
|
validators are visitor classes. They are instantiated at the time of query validation with one required argument
|
||||||
|
(context: ASTValidationContext). In order to perform validation, your validator class should define one or more of
|
||||||
|
``enter_*`` and ``leave_*`` methods. For possible enter/leave items as well as details on function documentation, please
|
||||||
|
see contents of the visitor module. To make validation fail, you should call validator's report_error method with the
|
||||||
|
instance of GraphQLError describing failure reason.
|
||||||
|
|
||||||
|
Implementing your custom validators
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
Here is an example query validator that only allows queries fields with a name of even length.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from graphql import GraphQLError
|
||||||
|
from graphql.language import FieldNode
|
||||||
|
from graphql.validation import ValidationRule
|
||||||
|
|
||||||
|
|
||||||
|
class MyCustomValidationRule(ValidationRule):
|
||||||
|
def enter_field(self, node: FieldNode, *_args):
|
||||||
|
if len(node.name.value) % 2 == 0:
|
||||||
|
# Here the query length is even, so we allow it.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Here the query length is odd, so we don't want to allow it.
|
||||||
|
# Calling self.report_error will make the query fail with the error message.
|
||||||
|
self.report_error(
|
||||||
|
GraphQLError(
|
||||||
|
f"Cannot query '{field_name}': length is odd.", node,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
.. _ValidationRule: https://github.com/graphql-python/graphql-core/blob/v3.0.5/src/graphql/validation/rules/__init__.py#L37
|
|
@ -17,7 +17,8 @@ the `Django documentation`_ on how to secure your API.
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
maxdepth
|
maxdepth
|
||||||
queryvalidation
|
introspection
|
||||||
|
customvalidation
|
||||||
|
|
||||||
We have seen the most efficient way to secure your GraphQL API.
|
We have seen the most efficient way to secure your GraphQL API.
|
||||||
|
|
||||||
|
|
52
docs/security/introspection.rst
Normal file
52
docs/security/introspection.rst
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
Disable Introspection
|
||||||
|
=====================
|
||||||
|
|
||||||
|
What is the introspection ?
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The introspection query is a query that allows you to ask the server what queries and mutations are supported. If you
|
||||||
|
comes from REST, you can view it as a openapi or swagger schema.
|
||||||
|
|
||||||
|
Disabling it or not ?
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Depending if you are building a private or a public API, you might want to disable introspection or not. If you are
|
||||||
|
building a public API, the introspection allows consumers (developers) to know what they can do with your API. If you
|
||||||
|
disable it, it will be harder for them to use your API. But if you are building a private API, the only consumers of
|
||||||
|
your API will be your own developers. In this case, you might want to keep the introspection open in staging
|
||||||
|
environments but close it in production to reduce the attack surface.
|
||||||
|
|
||||||
|
Keep in mind that disabling introspection does not prevent hackers to send queries to your API. It just makes it harder
|
||||||
|
to know what they can do with it.
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Graphene provides a validation rule to disable introspection. It ensures that your schema cannot be introspected. You
|
||||||
|
just need to import the ``DisableIntrospection`` class from ``graphene.validation``.
|
||||||
|
|
||||||
|
|
||||||
|
Here is a code example of how you can disable introspection for your schema.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from graphql import validate, parse
|
||||||
|
from graphene import ObjectType, Schema, String
|
||||||
|
from graphene.validation import DisableIntrospection
|
||||||
|
|
||||||
|
|
||||||
|
class MyQuery(ObjectType):
|
||||||
|
name = String(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
schema = Schema(query=MyQuery)
|
||||||
|
|
||||||
|
# introspection queries will not be executed.
|
||||||
|
|
||||||
|
validation_errors = validate(
|
||||||
|
schema=schema.graphql_schema,
|
||||||
|
document_ast=parse('THE QUERY'),
|
||||||
|
rules=(
|
||||||
|
DisableIntrospection,
|
||||||
|
)
|
||||||
|
)
|
|
@ -1,82 +0,0 @@
|
||||||
Query Validation
|
|
||||||
================
|
|
||||||
GraphQL uses query validators to check if Query AST is valid and can be executed. Every GraphQL server implements
|
|
||||||
standard query validators. For example, there is an validator that tests if queried field exists on queried type, that
|
|
||||||
makes query fail with "Cannot query field on type" error if it doesn't.
|
|
||||||
|
|
||||||
To help with common use cases, graphene provides a few validation rules out of the box.
|
|
||||||
|
|
||||||
|
|
||||||
Disable Introspection
|
|
||||||
---------------------
|
|
||||||
the disable introspection validation rule ensures that your schema cannot be introspected.
|
|
||||||
This is a useful security measure in production environments.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Here is how you would disable introspection for your schema.
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from graphql import validate, parse
|
|
||||||
from graphene import ObjectType, Schema, String
|
|
||||||
from graphene.validation import DisableIntrospection
|
|
||||||
|
|
||||||
|
|
||||||
class MyQuery(ObjectType):
|
|
||||||
name = String(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
schema = Schema(query=MyQuery)
|
|
||||||
|
|
||||||
# introspection queries will not be executed.
|
|
||||||
|
|
||||||
validation_errors = validate(
|
|
||||||
schema=schema.graphql_schema,
|
|
||||||
document_ast=parse('THE QUERY'),
|
|
||||||
rules=(
|
|
||||||
DisableIntrospection,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
Implementing custom validators
|
|
||||||
------------------------------
|
|
||||||
All custom query validators should extend the `ValidationRule <https://github.com/graphql-python/graphql-core/blob/v3.0.5/src/graphql/validation/rules/__init__.py#L37>`_
|
|
||||||
base class importable from the graphql.validation.rules module. Query validators are visitor classes. They are
|
|
||||||
instantiated at the time of query validation with one required argument (context: ASTValidationContext). In order to
|
|
||||||
perform validation, your validator class should define one or more of enter_* and leave_* methods. For possible
|
|
||||||
enter/leave items as well as details on function documentation, please see contents of the visitor module. To make
|
|
||||||
validation fail, you should call validator's report_error method with the instance of GraphQLError describing failure
|
|
||||||
reason. Here is an example query validator that visits field definitions in GraphQL query and fails query validation
|
|
||||||
if any of those fields are blacklisted:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from graphql import GraphQLError
|
|
||||||
from graphql.language import FieldNode
|
|
||||||
from graphql.validation import ValidationRule
|
|
||||||
|
|
||||||
|
|
||||||
my_blacklist = (
|
|
||||||
"disallowed_field",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_blacklisted_field(field_name: str):
|
|
||||||
return field_name.lower() in my_blacklist
|
|
||||||
|
|
||||||
|
|
||||||
class BlackListRule(ValidationRule):
|
|
||||||
def enter_field(self, node: FieldNode, *_args):
|
|
||||||
field_name = node.name.value
|
|
||||||
if not is_blacklisted_field(field_name):
|
|
||||||
return
|
|
||||||
|
|
||||||
self.report_error(
|
|
||||||
GraphQLError(
|
|
||||||
f"Cannot query '{field_name}': field is blacklisted.", node,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user