From f07d6a29c52f78b3b6866992237a6a2470747d9c Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Thu, 16 Mar 2023 14:27:31 -0700 Subject: [PATCH] feat: intropsection and custom validation Signed-off-by: QuentinN42 --- docs/security/customvalidation.rst | 42 +++++++++++++++ docs/security/index.rst | 3 +- docs/security/introspection.rst | 52 +++++++++++++++++++ docs/security/queryvalidation.rst | 82 ------------------------------ 4 files changed, 96 insertions(+), 83 deletions(-) create mode 100644 docs/security/customvalidation.rst create mode 100644 docs/security/introspection.rst delete mode 100644 docs/security/queryvalidation.rst diff --git a/docs/security/customvalidation.rst b/docs/security/customvalidation.rst new file mode 100644 index 00000000..3580df2b --- /dev/null +++ b/docs/security/customvalidation.rst @@ -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 \ No newline at end of file diff --git a/docs/security/index.rst b/docs/security/index.rst index cb7029b4..ac2c8735 100644 --- a/docs/security/index.rst +++ b/docs/security/index.rst @@ -17,7 +17,8 @@ the `Django documentation`_ on how to secure your API. :maxdepth: 1 maxdepth - queryvalidation + introspection + customvalidation We have seen the most efficient way to secure your GraphQL API. diff --git a/docs/security/introspection.rst b/docs/security/introspection.rst new file mode 100644 index 00000000..533d599d --- /dev/null +++ b/docs/security/introspection.rst @@ -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, + ) + ) diff --git a/docs/security/queryvalidation.rst b/docs/security/queryvalidation.rst deleted file mode 100644 index d0d174c9..00000000 --- a/docs/security/queryvalidation.rst +++ /dev/null @@ -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 `_ -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, - ) - ) -