mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-26 03:23:55 +03:00
add disable introspection
This commit is contained in:
parent
5977b1648c
commit
a784ef15e5
|
@ -20,7 +20,7 @@ Example
|
|||
Here is how you would implement depth-limiting on your schema.
|
||||
|
||||
.. code:: python
|
||||
from graphene.validators import depth_limit_validator
|
||||
from graphene.validation import depth_limit_validator
|
||||
|
||||
# The following schema doesn't execute queries
|
||||
# which have a depth more than 20.
|
||||
|
|
6
graphene/utils/is_introspection_key.py
Normal file
6
graphene/utils/is_introspection_key.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
def is_introspection_key(key):
|
||||
# from: https://spec.graphql.org/June2018/#sec-Schema
|
||||
# > All types and directives defined within a schema must not have a name which
|
||||
# > begins with "__" (two underscores), as this is used exclusively
|
||||
# > by GraphQL’s introspection system.
|
||||
return str(node.name.value).startswith("__")
|
8
graphene/validation/__init__.py
Normal file
8
graphene/validation/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from .depth_limit import depth_limit_validator
|
||||
from .disable_introspection import disable_introspection
|
||||
|
||||
|
||||
__all__ = [
|
||||
"depth_limit_validator",
|
||||
"disable_introspection"
|
||||
]
|
|
@ -29,6 +29,7 @@ import re
|
|||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from graphql import GraphQLError
|
||||
from graphql.validation import ValidationContext, ValidationRule
|
||||
from graphql.language import (
|
||||
DefinitionNode,
|
||||
FieldNode,
|
||||
|
@ -38,7 +39,8 @@ from graphql.language import (
|
|||
Node,
|
||||
OperationDefinitionNode,
|
||||
)
|
||||
from graphql.validation import ValidationContext, ValidationRule
|
||||
|
||||
from ..utils.is_introspection_key import is_introspection_key
|
||||
|
||||
|
||||
IgnoreType = Union[Callable[[str], bool], re.Pattern, str]
|
||||
|
@ -121,11 +123,7 @@ def determine_depth(
|
|||
return depth_so_far
|
||||
|
||||
if isinstance(node, FieldNode):
|
||||
# from: https://spec.graphql.org/June2018/#sec-Schema
|
||||
# > All types and directives defined within a schema must not have a name which
|
||||
# > begins with "__" (two underscores), as this is used exclusively
|
||||
# > by GraphQL’s introspection system.
|
||||
should_ignore = str(node.name.value).startswith("__") or is_ignored(
|
||||
should_ignore = is_introspection_key(node.name.value) or is_ignored(
|
||||
node, ignore
|
||||
)
|
||||
|
22
graphene/validation/disable_introspection.py
Normal file
22
graphene/validation/disable_introspection.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from graphql import GraphQLError
|
||||
from graphql.language import FieldNode
|
||||
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
|
||||
|
||||
self.report_error(
|
||||
GraphQLError(
|
||||
f"Cannot query '{field_name}': introspection is disabled.",
|
||||
node,
|
||||
)
|
||||
)
|
||||
|
||||
return DisableIntrospection
|
33
graphene/validation/tests/test_disable_introspection.py
Normal file
33
graphene/validation/tests/test_disable_introspection.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from graphql import parse, validate
|
||||
|
||||
from ...types import Schema, ObjectType, String
|
||||
from ..disable_introspection import disable_introspection
|
||||
|
||||
|
||||
class Query(ObjectType):
|
||||
name = String(
|
||||
required=True
|
||||
)
|
||||
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
|
||||
def run_query(query: str):
|
||||
document = parse(query)
|
||||
|
||||
result = None
|
||||
|
||||
def callback(query_depths):
|
||||
nonlocal result
|
||||
result = query_depths
|
||||
|
||||
errors = validate(
|
||||
schema.graphql_schema,
|
||||
document,
|
||||
rules=(
|
||||
disable_introspection(),
|
||||
),
|
||||
)
|
||||
|
||||
return errors, result
|
|
@ -1,6 +0,0 @@
|
|||
from .depth_limit_validator import depth_limit_validator
|
||||
|
||||
|
||||
__all__ = [
|
||||
"depth_limit_validator"
|
||||
]
|
|
@ -1,279 +0,0 @@
|
|||
import re
|
||||
|
||||
from pytest import raises
|
||||
from graphql import parse, get_introspection_query, validate
|
||||
|
||||
from ...types import Schema, ObjectType, Interface
|
||||
from ...types import String, Int, List, Field
|
||||
from ..depth_limit_validator import depth_limit_validator
|
||||
|
||||
|
||||
class PetType(Interface):
|
||||
name = String(required=True)
|
||||
|
||||
class meta:
|
||||
name = "Pet"
|
||||
|
||||
|
||||
class CatType(ObjectType):
|
||||
class meta:
|
||||
name = "Cat"
|
||||
interfaces = (PetType,)
|
||||
|
||||
|
||||
class DogType(ObjectType):
|
||||
class meta:
|
||||
name = "Dog"
|
||||
interfaces = (PetType,)
|
||||
|
||||
|
||||
class AddressType(ObjectType):
|
||||
street = String(required=True)
|
||||
number = Int(required=True)
|
||||
city = String(required=True)
|
||||
country = String(required=True)
|
||||
|
||||
class Meta:
|
||||
name = "Address"
|
||||
|
||||
|
||||
class HumanType(ObjectType):
|
||||
name = String(required=True)
|
||||
email = String(required=True)
|
||||
address = Field(AddressType, required=True)
|
||||
pets = List(PetType, required=True)
|
||||
|
||||
class Meta:
|
||||
name = "Human"
|
||||
|
||||
|
||||
class Query(ObjectType):
|
||||
user = Field(
|
||||
HumanType,
|
||||
required=True,
|
||||
name=String()
|
||||
)
|
||||
version = String(
|
||||
required=True
|
||||
)
|
||||
user1 = Field(
|
||||
HumanType,
|
||||
required=True
|
||||
)
|
||||
user2 = Field(
|
||||
HumanType,
|
||||
required=True
|
||||
)
|
||||
user3 = Field(
|
||||
HumanType,
|
||||
required=True
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def resolve_user(root, info, name=None):
|
||||
pass
|
||||
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
|
||||
def run_query(query: str, max_depth: int, ignore=None):
|
||||
document = parse(query)
|
||||
|
||||
result = None
|
||||
|
||||
def callback(query_depths):
|
||||
nonlocal result
|
||||
result = query_depths
|
||||
|
||||
errors = validate(
|
||||
schema.graphql_schema,
|
||||
document,
|
||||
rules=(
|
||||
depth_limit_validator(
|
||||
max_depth=max_depth,
|
||||
ignore=ignore,
|
||||
callback=callback
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return errors, result
|
||||
|
||||
|
||||
def test_should_count_depth_without_fragment():
|
||||
query = """
|
||||
query read0 {
|
||||
version
|
||||
}
|
||||
query read1 {
|
||||
version
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
query read2 {
|
||||
matt: user(name: "matt") {
|
||||
email
|
||||
}
|
||||
andy: user(name: "andy") {
|
||||
email
|
||||
address {
|
||||
city
|
||||
}
|
||||
}
|
||||
}
|
||||
query read3 {
|
||||
matt: user(name: "matt") {
|
||||
email
|
||||
}
|
||||
andy: user(name: "andy") {
|
||||
email
|
||||
address {
|
||||
city
|
||||
}
|
||||
pets {
|
||||
name
|
||||
owner {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
expected = {"read0": 0, "read1": 1, "read2": 2, "read3": 3}
|
||||
|
||||
errors, result = run_query(query, 10)
|
||||
assert not errors
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_should_count_with_fragments():
|
||||
query = """
|
||||
query read0 {
|
||||
... on Query {
|
||||
version
|
||||
}
|
||||
}
|
||||
query read1 {
|
||||
version
|
||||
user {
|
||||
... on Human {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment humanInfo on Human {
|
||||
email
|
||||
}
|
||||
fragment petInfo on Pet {
|
||||
name
|
||||
owner {
|
||||
name
|
||||
}
|
||||
}
|
||||
query read2 {
|
||||
matt: user(name: "matt") {
|
||||
...humanInfo
|
||||
}
|
||||
andy: user(name: "andy") {
|
||||
...humanInfo
|
||||
address {
|
||||
city
|
||||
}
|
||||
}
|
||||
}
|
||||
query read3 {
|
||||
matt: user(name: "matt") {
|
||||
...humanInfo
|
||||
}
|
||||
andy: user(name: "andy") {
|
||||
... on Human {
|
||||
email
|
||||
}
|
||||
address {
|
||||
city
|
||||
}
|
||||
pets {
|
||||
...petInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
expected = {"read0": 0, "read1": 1, "read2": 2, "read3": 3}
|
||||
|
||||
errors, result = run_query(query, 10)
|
||||
assert not errors
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_should_ignore_the_introspection_query():
|
||||
errors, result = run_query(get_introspection_query(), 10)
|
||||
assert not errors
|
||||
assert result == {"IntrospectionQuery": 0}
|
||||
|
||||
|
||||
def test_should_catch_very_deep_query():
|
||||
query = """{
|
||||
user {
|
||||
pets {
|
||||
owner {
|
||||
pets {
|
||||
owner {
|
||||
pets {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
errors, result = run_query(query, 4)
|
||||
|
||||
assert len(errors) == 1
|
||||
assert errors[0].message == "'anonymous' exceeds maximum operation depth of 4"
|
||||
|
||||
|
||||
def test_should_ignore_field():
|
||||
query = """
|
||||
query read1 {
|
||||
user { address { city } }
|
||||
}
|
||||
query read2 {
|
||||
user1 { address { city } }
|
||||
user2 { address { city } }
|
||||
user3 { address { city } }
|
||||
}
|
||||
"""
|
||||
|
||||
errors, result = run_query(
|
||||
query,
|
||||
10,
|
||||
ignore=[
|
||||
"user1",
|
||||
re.compile("user2"),
|
||||
lambda field_name: field_name == "user3",
|
||||
],
|
||||
)
|
||||
|
||||
expected = {"read1": 2, "read2": 0}
|
||||
assert not errors
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_should_raise_invalid_ignore():
|
||||
query = """
|
||||
query read1 {
|
||||
user { address { city } }
|
||||
}
|
||||
"""
|
||||
with raises(ValueError, match="Invalid ignore option:"):
|
||||
run_query(
|
||||
query,
|
||||
10,
|
||||
ignore=[True],
|
||||
)
|
Loading…
Reference in New Issue
Block a user