diff --git a/docs/execution/queryvalidation.rst b/docs/execution/queryvalidation.rst index 2d58f7ab..8402b9ea 100644 --- a/docs/execution/queryvalidation.rst +++ b/docs/execution/queryvalidation.rst @@ -16,7 +16,7 @@ queries. It takes in the following arguments. - ``ignore`` Stops recursive depth checking based on a field name. Either a string or regexp to match the name, or a function that returns a boolean - ``callback`` Called each time validation runs. Receives an Object which is a map of the depths for each operation. -Example +Usage ------- Here is how you would implement depth-limiting on your schema. @@ -33,7 +33,7 @@ Here is how you would implement depth-limiting on your schema. schema = Schema(query=MyQuery) - # Queries which have a depth more than 20 + # queries which have a depth more than 20 # will not be executed. validation_errors = validate( @@ -47,6 +47,39 @@ Here is how you would implement depth-limiting on your schema. ) +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, + document_ast=parse('THE QUERY'), + rules=( + DisableIntrospection, + ) + ) + + Implementing custom validators ------------------------------ All custom query validators should extend the `ValidationRule `_ @@ -56,7 +89,7 @@ perform validation, your validator class should define one or more of enter_* an 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 fields: +if any of those fields are blacklisted: .. code:: python from graphql import GraphQLError @@ -70,7 +103,7 @@ if any of those fields are blacklisted fields: def is_blacklisted_field(field_name: str): - return key.lower() in my_blacklist + return field_name.lower() in my_blacklist class BlackListRule(ValidationRule): diff --git a/graphene/validation/__init__.py b/graphene/validation/__init__.py index 03e4605c..f338e2d0 100644 --- a/graphene/validation/__init__.py +++ b/graphene/validation/__init__.py @@ -1,8 +1,8 @@ from .depth_limit import depth_limit_validator -from .disable_introspection import disable_introspection +from .disable_introspection import DisableIntrospection __all__ = [ - "depth_limit_validator", - "disable_introspection" + "DisableIntrospection", + "depth_limit_validator" ] diff --git a/graphene/validation/depth_limit.py b/graphene/validation/depth_limit.py index 4136555d..8363a6c9 100644 --- a/graphene/validation/depth_limit.py +++ b/graphene/validation/depth_limit.py @@ -116,7 +116,7 @@ def determine_depth( if depth_so_far > max_depth: context.report_error( GraphQLError( - f"'{operation_name}' exceeds maximum operation depth of {max_depth}", + f"'{operation_name}' exceeds maximum operation depth of {max_depth}.", [node], ) ) @@ -172,7 +172,7 @@ def determine_depth( ) ) else: - raise Exception(f"Depth crawler cannot handle: {node.kind}") # pragma: no cover + raise Exception(f"Depth crawler cannot handle: {node.kind}.") # pragma: no cover def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bool: @@ -191,6 +191,6 @@ def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bo if rule(field_name): return True else: - raise ValueError(f"Invalid ignore option: {rule}") + raise ValueError(f"Invalid ignore option: {rule}.") return False diff --git a/graphene/validation/disable_introspection.py b/graphene/validation/disable_introspection.py index eb24be55..4c83050e 100644 --- a/graphene/validation/disable_introspection.py +++ b/graphene/validation/disable_introspection.py @@ -5,18 +5,15 @@ from graphql.validation import ValidationRule from ..utils.is_introspection_key import is_introspection_key -def disable_introspection(): - class DisableIntrospection(ValidationRule): - def enter_field(self, node: FieldNode, *_args): - field_name = node.name.value - if not is_introspection_key(field_name): - return +class DisableIntrospection(ValidationRule): + def enter_field(self, node: FieldNode, *_args): + field_name = node.name.value + if not is_introspection_key(field_name): + return - self.report_error( - GraphQLError( - f"Cannot query '{field_name}': introspection is disabled.", - node, - ) + self.report_error( + GraphQLError( + f"Cannot query '{field_name}': introspection is disabled.", + node, ) - - return DisableIntrospection + ) diff --git a/graphene/validation/tests/test_depth_limit_validator.py b/graphene/validation/tests/test_depth_limit_validator.py index ea62f999..499adbcc 100644 --- a/graphene/validation/tests/test_depth_limit_validator.py +++ b/graphene/validation/tests/test_depth_limit_validator.py @@ -235,7 +235,7 @@ def test_should_catch_very_deep_query(): errors, result = run_query(query, 4) assert len(errors) == 1 - assert errors[0].message == "'anonymous' exceeds maximum operation depth of 4" + assert errors[0].message == "'anonymous' exceeds maximum operation depth of 4." def test_should_ignore_field(): diff --git a/graphene/validation/tests/test_disable_introspection.py b/graphene/validation/tests/test_disable_introspection.py index b7f0b83f..06019900 100644 --- a/graphene/validation/tests/test_disable_introspection.py +++ b/graphene/validation/tests/test_disable_introspection.py @@ -1,7 +1,7 @@ from graphql import parse, validate from ...types import Schema, ObjectType, String -from ..disable_introspection import disable_introspection +from ..disable_introspection import DisableIntrospection class Query(ObjectType): @@ -9,6 +9,10 @@ class Query(ObjectType): required=True ) + @staticmethod + def resolve_name(root, info): + return "Hello world!" + schema = Schema(query=Query) @@ -16,14 +20,24 @@ schema = Schema(query=Query) def run_query(query: str): document = parse(query) - result = None - errors = validate( schema=schema.graphql_schema, document_ast=document, rules=( - disable_introspection(), + DisableIntrospection, ), ) - return errors, result + return errors + + +def test_disallows_introspection_queries(): + errors = run_query("{ __schema { queryType { name } } }") + + assert len(errors) == 1 + assert errors[0].message == "Cannot query '__schema': introspection is disabled." + + +def test_allows_non_introspection_queries(): + errors = run_query("{ name }") + assert len(errors) == 0