Skip to content

Commit fc6f304

Browse files
committed
Cherry pick ENF introspection and make ExecutableNormalizedOperationFactory closer to structure on master
1 parent 5876cc8 commit fc6f304

11 files changed

+1144
-426
lines changed

src/main/java/graphql/execution/ExecutionContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public class ExecutionContext {
8080
this.errors.set(builder.errors);
8181
this.localContext = builder.localContext;
8282
this.executionInput = builder.executionInput;
83-
queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables));
83+
this.queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables));
8484
}
8585

8686

src/main/java/graphql/introspection/GoodFaithIntrospection.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.util.Optional;
1919
import java.util.concurrent.atomic.AtomicBoolean;
2020

21+
import static graphql.normalized.ExecutableNormalizedOperationFactory.Options;
22+
import static graphql.normalized.ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation;
2123
import static graphql.schema.FieldCoordinates.coordinates;
2224

2325
/**
@@ -44,6 +46,14 @@ public class GoodFaithIntrospection {
4446
public static final String GOOD_FAITH_INTROSPECTION_DISABLED = "GOOD_FAITH_INTROSPECTION_DISABLED";
4547

4648
private static final AtomicBoolean ENABLED_STATE = new AtomicBoolean(true);
49+
/**
50+
* This is the maximum number of executable fields that can be in a good faith introspection query
51+
*/
52+
public static final int GOOD_FAITH_MAX_FIELDS_COUNT = 500;
53+
/**
54+
* This is the maximum depth a good faith introspection query can be
55+
*/
56+
public static final int GOOD_FAITH_MAX_DEPTH_COUNT = 20;
4757

4858
/**
4959
* @return true if good faith introspection is enabled
@@ -75,7 +85,7 @@ public static boolean enabledJvmWide(boolean flag) {
7585

7686
public static Optional<ExecutionResult> checkIntrospection(ExecutionContext executionContext) {
7787
if (isIntrospectionEnabled(executionContext.getGraphQLContext())) {
78-
ExecutableNormalizedOperation operation = executionContext.getNormalizedQueryTree().get();
88+
ExecutableNormalizedOperation operation = mkOperation(executionContext);
7989
ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToENFs = operation.getCoordinatesToNormalizedFields();
8090
for (Map.Entry<FieldCoordinates, Integer> entry : ALLOWED_FIELD_INSTANCES.entrySet()) {
8191
FieldCoordinates coordinates = entry.getKey();
@@ -90,6 +100,29 @@ public static Optional<ExecutionResult> checkIntrospection(ExecutionContext exec
90100
return Optional.empty();
91101
}
92102

103+
/**
104+
* This makes an executable operation limited in size then which suits a good faith introspection query. This helps guard
105+
* against malicious queries.
106+
*
107+
* @param executionContext the execution context
108+
*
109+
* @return an executable operation
110+
*/
111+
private static ExecutableNormalizedOperation mkOperation(ExecutionContext executionContext) {
112+
Options options = Options.defaultOptions()
113+
.maxFieldsCount(GOOD_FAITH_MAX_FIELDS_COUNT)
114+
.maxChildrenDepth(GOOD_FAITH_MAX_DEPTH_COUNT)
115+
.locale(executionContext.getLocale())
116+
.graphQLContext(executionContext.getGraphQLContext());
117+
118+
return createExecutableNormalizedOperation(executionContext.getGraphQLSchema(),
119+
executionContext.getOperationDefinition(),
120+
executionContext.getFragmentsByName(),
121+
executionContext.getCoercedVariables(),
122+
options);
123+
124+
}
125+
93126
private static boolean isIntrospectionEnabled(GraphQLContext graphQlContext) {
94127
if (!isEnabledJvmWide()) {
95128
return false;

src/main/java/graphql/introspection/Introspection.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,20 @@ public static boolean isEnabledJvmWide() {
115115
*/
116116
public static Optional<ExecutionResult> isIntrospectionSensible(MergedSelectionSet mergedSelectionSet, ExecutionContext executionContext) {
117117
GraphQLContext graphQLContext = executionContext.getGraphQLContext();
118-
MergedField schemaField = mergedSelectionSet.getSubField(SchemaMetaFieldDef.getName());
119-
if (schemaField != null) {
120-
if (!isIntrospectionEnabled(graphQLContext)) {
121-
return mkDisabledError(schemaField);
122-
}
123-
}
124-
MergedField typeField = mergedSelectionSet.getSubField(TypeMetaFieldDef.getName());
125-
if (typeField != null) {
126-
if (!isIntrospectionEnabled(graphQLContext)) {
127-
return mkDisabledError(typeField);
118+
119+
boolean isIntrospection = false;
120+
for (String key : mergedSelectionSet.getKeys()) {
121+
String fieldName = mergedSelectionSet.getSubField(key).getName();
122+
if (fieldName.equals(SchemaMetaFieldDef.getName())
123+
|| fieldName.equals(TypeMetaFieldDef.getName())) {
124+
if (!isIntrospectionEnabled(graphQLContext)) {
125+
return mkDisabledError(mergedSelectionSet.getSubField(key));
126+
}
127+
isIntrospection = true;
128+
break;
128129
}
129130
}
130-
if (schemaField != null || typeField != null)
131-
{
131+
if (isIntrospection) {
132132
return GoodFaithIntrospection.checkIntrospection(executionContext);
133133
}
134134
return Optional.empty();

src/main/java/graphql/normalized/ExecutableNormalizedOperation.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public class ExecutableNormalizedOperation {
3131
private final Map<ExecutableNormalizedField, MergedField> normalizedFieldToMergedField;
3232
private final Map<ExecutableNormalizedField, QueryDirectives> normalizedFieldToQueryDirectives;
3333
private final ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields;
34+
private final int operationFieldCount;
35+
private final int operationDepth;
3436

3537
public ExecutableNormalizedOperation(
3638
OperationDefinition.Operation operation,
@@ -39,15 +41,18 @@ public ExecutableNormalizedOperation(
3941
ImmutableListMultimap<Field, ExecutableNormalizedField> fieldToNormalizedField,
4042
Map<ExecutableNormalizedField, MergedField> normalizedFieldToMergedField,
4143
Map<ExecutableNormalizedField, QueryDirectives> normalizedFieldToQueryDirectives,
42-
ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields
43-
) {
44+
ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields,
45+
int operationFieldCount,
46+
int operationDepth) {
4447
this.operation = operation;
4548
this.operationName = operationName;
4649
this.topLevelFields = topLevelFields;
4750
this.fieldToNormalizedField = fieldToNormalizedField;
4851
this.normalizedFieldToMergedField = normalizedFieldToMergedField;
4952
this.normalizedFieldToQueryDirectives = normalizedFieldToQueryDirectives;
5053
this.coordinatesToNormalizedFields = coordinatesToNormalizedFields;
54+
this.operationFieldCount = operationFieldCount;
55+
this.operationDepth = operationDepth;
5156
}
5257

5358
/**
@@ -64,6 +69,20 @@ public String getOperationName() {
6469
return operationName;
6570
}
6671

72+
/**
73+
* @return This returns how many {@link ExecutableNormalizedField}s are in the operation.
74+
*/
75+
public int getOperationFieldCount() {
76+
return operationFieldCount;
77+
}
78+
79+
/**
80+
* @return This returns the depth of the operation
81+
*/
82+
public int getOperationDepth() {
83+
return operationDepth;
84+
}
85+
6786
/**
6887
* This multimap shows how a given {@link ExecutableNormalizedField} maps to a one or more field coordinate in the schema
6988
*

0 commit comments

Comments
 (0)