Skip to content

Commit bde4ae1

Browse files
committed
Add NoSchemaIntrospectionCustomRule
Replicates graphql/graphql-js@00077f1
1 parent ad38c76 commit bde4ae1

File tree

5 files changed

+184
-0
lines changed

5 files changed

+184
-0
lines changed

src/graphql/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@
320320
UniqueFieldDefinitionNamesRule,
321321
UniqueDirectiveNamesRule,
322322
PossibleTypeExtensionsRule,
323+
# Custom validation rules
324+
NoSchemaIntrospectionCustomRule,
323325
)
324326

325327
# Create, format, and print GraphQL errors.
@@ -654,6 +656,7 @@
654656
"UniqueFieldDefinitionNamesRule",
655657
"UniqueDirectiveNamesRule",
656658
"PossibleTypeExtensionsRule",
659+
"NoSchemaIntrospectionCustomRule",
657660
"GraphQLError",
658661
"GraphQLSyntaxError",
659662
"located_error",

src/graphql/validation/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@
104104
from .rules.unique_directive_names import UniqueDirectiveNamesRule
105105
from .rules.possible_type_extensions import PossibleTypeExtensionsRule
106106

107+
# Optional rules not defined by the GraphQL Specification
108+
from .rules.custom.no_schema_introspection import NoSchemaIntrospectionCustomRule
109+
107110
__all__ = [
108111
"validate",
109112
"ASTValidationContext",
@@ -146,4 +149,5 @@
146149
"UniqueFieldDefinitionNamesRule",
147150
"UniqueDirectiveNamesRule",
148151
"PossibleTypeExtensionsRule",
152+
"NoSchemaIntrospectionCustomRule",
149153
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""graphql.validation.rules.custom package"""
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Any
2+
3+
from ....error import GraphQLError
4+
from ....language import FieldNode
5+
from ....type import get_named_type, is_introspection_type
6+
from .. import ValidationRule
7+
8+
__all__ = ["NoSchemaIntrospectionCustomRule"]
9+
10+
11+
class NoSchemaIntrospectionCustomRule(ValidationRule):
12+
"""Prohibit introspection queries
13+
14+
A GraphQL document is only valid if all fields selected are not fields that
15+
return an introspection type.
16+
17+
Note: This rule is optional and is not part of the Validation section of the
18+
GraphQL Specification. This rule effectively disables introspection, which
19+
does not reflect best practices and should only be done if absolutely necessary.
20+
"""
21+
22+
def enter_field(self, node: FieldNode, *_args: Any) -> None:
23+
type_ = get_named_type(self.context.get_type())
24+
if type_ and is_introspection_type(type_):
25+
self.report_error(
26+
GraphQLError(
27+
"GraphQL introspection has been disabled, but the requested query"
28+
f" contained the field '{node.name.value}'.",
29+
node,
30+
)
31+
)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from functools import partial
2+
3+
from graphql.utilities import build_schema
4+
from graphql.validation import NoSchemaIntrospectionCustomRule
5+
6+
from .harness import assert_validation_errors
7+
8+
schema = build_schema(
9+
"""
10+
type Query {
11+
someQuery: SomeType
12+
}
13+
14+
type SomeType {
15+
someField: String
16+
introspectionField: __EnumValue
17+
}
18+
"""
19+
)
20+
21+
assert_errors = partial(
22+
assert_validation_errors, NoSchemaIntrospectionCustomRule, schema=schema
23+
)
24+
25+
assert_valid = partial(assert_errors, errors=[])
26+
27+
28+
def describe_validate_prohibit_introspection_queries():
29+
def ignores_valid_fields_including_typename():
30+
assert_valid(
31+
"""
32+
{
33+
someQuery {
34+
__typename
35+
someField
36+
}
37+
}
38+
"""
39+
)
40+
41+
def ignores_fields_not_in_the_schema():
42+
assert_valid(
43+
"""
44+
{
45+
__introspect
46+
}
47+
"""
48+
)
49+
50+
def reports_error_when_a_field_with_an_introspection_type_is_requested():
51+
assert_errors(
52+
"""
53+
{
54+
__schema {
55+
queryType {
56+
name
57+
}
58+
}
59+
}
60+
""",
61+
[
62+
{
63+
"message": "GraphQL introspection has been disabled,"
64+
" but the requested query contained the field '__schema'.",
65+
"locations": [(3, 15)],
66+
},
67+
{
68+
"message": "GraphQL introspection has been disabled,"
69+
" but the requested query contained the field 'queryType'.",
70+
"locations": [(4, 17)],
71+
},
72+
],
73+
)
74+
75+
def reports_error_when_a_field_with_introspection_type_is_requested_and_aliased():
76+
assert_errors(
77+
"""
78+
{
79+
s: __schema {
80+
queryType {
81+
name
82+
}
83+
}
84+
}
85+
""",
86+
[
87+
{
88+
"message": "GraphQL introspection has been disabled,"
89+
" but the requested query contained the field '__schema'.",
90+
"locations": [(3, 15)],
91+
},
92+
{
93+
"message": "GraphQL introspection has been disabled,"
94+
" but the requested query contained the field 'queryType'.",
95+
"locations": [(4, 17)],
96+
},
97+
],
98+
)
99+
100+
def reports_error_when_using_a_fragment_with_a_field_with_an_introspection_type():
101+
assert_errors(
102+
"""
103+
{
104+
...QueryFragment
105+
}
106+
107+
fragment QueryFragment on Query {
108+
__schema {
109+
queryType {
110+
name
111+
}
112+
}
113+
}
114+
""",
115+
[
116+
{
117+
"message": "GraphQL introspection has been disabled,"
118+
" but the requested query contained the field '__schema'.",
119+
"locations": [(7, 15)],
120+
},
121+
{
122+
"message": "GraphQL introspection has been disabled,"
123+
" but the requested query contained the field 'queryType'.",
124+
"locations": [(8, 17)],
125+
},
126+
],
127+
)
128+
129+
def reports_error_for_non_standard_introspection_fields():
130+
assert_errors(
131+
"""
132+
{
133+
someQuery {
134+
introspectionField
135+
}
136+
}
137+
""",
138+
[
139+
{
140+
"message": "GraphQL introspection has been disabled, but"
141+
" the requested query contained the field 'introspectionField'.",
142+
"locations": [(4, 17)],
143+
},
144+
],
145+
)

0 commit comments

Comments
 (0)