Skip to content

Commit

Permalink
Merge pull request #3540 from graphql-java/21.x-backport-enf-introspe…
Browse files Browse the repository at this point in the history
…ction

21.x backport of #3539 ENF restriction for introspection
  • Loading branch information
dondonz committed Mar 26, 2024
2 parents 5876cc8 + aadb3a4 commit 25667a1
Show file tree
Hide file tree
Showing 11 changed files with 1,231 additions and 521 deletions.
2 changes: 1 addition & 1 deletion src/main/java/graphql/execution/ExecutionContext.java
Expand Up @@ -80,7 +80,7 @@ public class ExecutionContext {
this.errors.set(builder.errors);
this.localContext = builder.localContext;
this.executionInput = builder.executionInput;
queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables));
this.queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables));
}


Expand Down
35 changes: 34 additions & 1 deletion src/main/java/graphql/introspection/GoodFaithIntrospection.java
Expand Up @@ -18,6 +18,8 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

import static graphql.normalized.ExecutableNormalizedOperationFactory.Options;
import static graphql.normalized.ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation;
import static graphql.schema.FieldCoordinates.coordinates;

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

private static final AtomicBoolean ENABLED_STATE = new AtomicBoolean(true);
/**
* This is the maximum number of executable fields that can be in a good faith introspection query
*/
public static final int GOOD_FAITH_MAX_FIELDS_COUNT = 500;
/**
* This is the maximum depth a good faith introspection query can be
*/
public static final int GOOD_FAITH_MAX_DEPTH_COUNT = 20;

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

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

/**
* This makes an executable operation limited in size then which suits a good faith introspection query. This helps guard
* against malicious queries.
*
* @param executionContext the execution context
*
* @return an executable operation
*/
private static ExecutableNormalizedOperation mkOperation(ExecutionContext executionContext) {
Options options = Options.defaultOptions()
.maxFieldsCount(GOOD_FAITH_MAX_FIELDS_COUNT)
.maxChildrenDepth(GOOD_FAITH_MAX_DEPTH_COUNT)
.locale(executionContext.getLocale())
.graphQLContext(executionContext.getGraphQLContext());

return createExecutableNormalizedOperation(executionContext.getGraphQLSchema(),
executionContext.getOperationDefinition(),
executionContext.getFragmentsByName(),
executionContext.getCoercedVariables(),
options);

}

private static boolean isIntrospectionEnabled(GraphQLContext graphQlContext) {
if (!isEnabledJvmWide()) {
return false;
Expand Down
24 changes: 12 additions & 12 deletions src/main/java/graphql/introspection/Introspection.java
Expand Up @@ -115,20 +115,20 @@ public static boolean isEnabledJvmWide() {
*/
public static Optional<ExecutionResult> isIntrospectionSensible(MergedSelectionSet mergedSelectionSet, ExecutionContext executionContext) {
GraphQLContext graphQLContext = executionContext.getGraphQLContext();
MergedField schemaField = mergedSelectionSet.getSubField(SchemaMetaFieldDef.getName());
if (schemaField != null) {
if (!isIntrospectionEnabled(graphQLContext)) {
return mkDisabledError(schemaField);
}
}
MergedField typeField = mergedSelectionSet.getSubField(TypeMetaFieldDef.getName());
if (typeField != null) {
if (!isIntrospectionEnabled(graphQLContext)) {
return mkDisabledError(typeField);

boolean isIntrospection = false;
for (String key : mergedSelectionSet.getKeys()) {
String fieldName = mergedSelectionSet.getSubField(key).getName();
if (fieldName.equals(SchemaMetaFieldDef.getName())
|| fieldName.equals(TypeMetaFieldDef.getName())) {
if (!isIntrospectionEnabled(graphQLContext)) {
return mkDisabledError(mergedSelectionSet.getSubField(key));
}
isIntrospection = true;
break;
}
}
if (schemaField != null || typeField != null)
{
if (isIntrospection) {
return GoodFaithIntrospection.checkIntrospection(executionContext);
}
return Optional.empty();
Expand Down
Expand Up @@ -31,6 +31,8 @@ public class ExecutableNormalizedOperation {
private final Map<ExecutableNormalizedField, MergedField> normalizedFieldToMergedField;
private final Map<ExecutableNormalizedField, QueryDirectives> normalizedFieldToQueryDirectives;
private final ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields;
private final int operationFieldCount;
private final int operationDepth;

public ExecutableNormalizedOperation(
OperationDefinition.Operation operation,
Expand All @@ -39,15 +41,18 @@ public ExecutableNormalizedOperation(
ImmutableListMultimap<Field, ExecutableNormalizedField> fieldToNormalizedField,
Map<ExecutableNormalizedField, MergedField> normalizedFieldToMergedField,
Map<ExecutableNormalizedField, QueryDirectives> normalizedFieldToQueryDirectives,
ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields
) {
ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToNormalizedFields,
int operationFieldCount,
int operationDepth) {
this.operation = operation;
this.operationName = operationName;
this.topLevelFields = topLevelFields;
this.fieldToNormalizedField = fieldToNormalizedField;
this.normalizedFieldToMergedField = normalizedFieldToMergedField;
this.normalizedFieldToQueryDirectives = normalizedFieldToQueryDirectives;
this.coordinatesToNormalizedFields = coordinatesToNormalizedFields;
this.operationFieldCount = operationFieldCount;
this.operationDepth = operationDepth;
}

/**
Expand All @@ -64,6 +69,20 @@ public String getOperationName() {
return operationName;
}

/**
* @return This returns how many {@link ExecutableNormalizedField}s are in the operation.
*/
public int getOperationFieldCount() {
return operationFieldCount;
}

/**
* @return This returns the depth of the operation
*/
public int getOperationDepth() {
return operationDepth;
}

/**
* This multimap shows how a given {@link ExecutableNormalizedField} maps to a one or more field coordinate in the schema
*
Expand Down

0 comments on commit 25667a1

Please sign in to comment.