All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.introspection.GoodFaithIntrospection Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.introspection;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import graphql.ErrorClassification;
import graphql.ExecutionResult;
import graphql.GraphQLContext;
import graphql.GraphQLError;
import graphql.PublicApi;
import graphql.execution.AbortExecutionException;
import graphql.execution.ExecutionContext;
import graphql.language.SourceLocation;
import graphql.normalized.ExecutableNormalizedField;
import graphql.normalized.ExecutableNormalizedOperation;
import graphql.schema.FieldCoordinates;

import java.util.List;
import java.util.Map;
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;

/**
 * This {@link graphql.execution.instrumentation.Instrumentation} ensure that a submitted introspection query is done in
 * good faith.
 * 

* There are attack vectors where a crafted introspection query can cause the engine to spend too much time * producing introspection data. This is especially true on large schemas with lots of types and fields. *

* Schemas form a cyclic graph and hence it's possible to send in introspection queries that can reference those cycles * and in large schemas this can be expensive and perhaps a "denial of service". *

* This instrumentation only allows one __schema field or one __type field to be present, and it does not allow the `__Type` fields * to form a cycle, i.e., that can only be present once. This allows the standard and common introspection queries to work * so tooling such as graphiql can work. */ @PublicApi public class GoodFaithIntrospection { /** * Placing a boolean value under this key in the per request {@link GraphQLContext} will enable * or disable Good Faith Introspection on that request. */ 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 */ public static boolean isEnabledJvmWide() { return ENABLED_STATE.get(); } /** * This allows you to disable good faith introspection, which is on by default. * * @param flag the desired state * * @return the previous state */ public static boolean enabledJvmWide(boolean flag) { return ENABLED_STATE.getAndSet(flag); } private static final Map ALLOWED_FIELD_INSTANCES = Map.of( coordinates("Query", "__schema"), 1 , coordinates("Query", "__type"), 1 , coordinates("__Type", "fields"), 1 , coordinates("__Type", "inputFields"), 1 , coordinates("__Type", "interfaces"), 1 , coordinates("__Type", "possibleTypes"), 1 ); public static Optional checkIntrospection(ExecutionContext executionContext) { if (isIntrospectionEnabled(executionContext.getGraphQLContext())) { ExecutableNormalizedOperation operation; try { operation = mkOperation(executionContext); } catch (AbortExecutionException e) { BadFaithIntrospectionError error = BadFaithIntrospectionError.tooBigOperation(e.getMessage()); return Optional.of(ExecutionResult.newExecutionResult().addError(error).build()); } ImmutableListMultimap coordinatesToENFs = operation.getCoordinatesToNormalizedFields(); for (Map.Entry entry : ALLOWED_FIELD_INSTANCES.entrySet()) { FieldCoordinates coordinates = entry.getKey(); Integer allowSize = entry.getValue(); ImmutableList normalizedFields = coordinatesToENFs.get(coordinates); if (normalizedFields.size() > allowSize) { BadFaithIntrospectionError error = BadFaithIntrospectionError.tooManyFields(coordinates.toString()); return Optional.of(ExecutionResult.newExecutionResult().addError(error).build()); } } } 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) throws AbortExecutionException { 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; } return !graphQlContext.getBoolean(GOOD_FAITH_INTROSPECTION_DISABLED, false); } public static class BadFaithIntrospectionError implements GraphQLError { private final String message; public static BadFaithIntrospectionError tooManyFields(String fieldCoordinate) { return new BadFaithIntrospectionError(String.format("This request is not asking for introspection in good faith - %s is present too often!", fieldCoordinate)); } public static BadFaithIntrospectionError tooBigOperation(String message) { return new BadFaithIntrospectionError(String.format("This request is not asking for introspection in good faith - the query is too big: %s", message)); } private BadFaithIntrospectionError(String message) { this.message = message; } @Override public String getMessage() { return message; } @Override public ErrorClassification getErrorType() { return ErrorClassification.errorClassification("BadFaithIntrospection"); } @Override public List getLocations() { return null; } @Override public String toString() { return "BadFaithIntrospectionError{" + "message='" + message + '\'' + '}'; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy