-
Notifications
You must be signed in to change notification settings - Fork 1.2k
20.x Backport of PR 3526 and PR 3527 #3530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
be5be15
Backport of PR 3526 with adjustments for v20
dondonz bdff76f
Add Amazon as vendor (Adoptium disappeared) and Java 8 fixes
dondonz 0faf38c
Remove vendor - I only had to do this for local run
dondonz 15c0380
Backport Good Faith Introspection PR 3527
dondonz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/main/java/graphql/introspection/GoodFaithIntrospection.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| 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.ExecutionContext; | ||
| import graphql.language.SourceLocation; | ||
| import graphql.normalized.ExecutableNormalizedField; | ||
| import graphql.normalized.ExecutableNormalizedOperation; | ||
| import graphql.schema.FieldCoordinates; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
|
|
||
| import static graphql.schema.FieldCoordinates.coordinates; | ||
|
|
||
| /** | ||
| * This {@link graphql.execution.instrumentation.Instrumentation} ensure that a submitted introspection query is done in | ||
| * good faith. | ||
| * <p> | ||
| * 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. | ||
| * <p> | ||
| * 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". | ||
| * <p> | ||
| * 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); | ||
|
|
||
| /** | ||
| * @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<FieldCoordinates, Integer> ALLOWED_FIELD_INSTANCES = new HashMap<>(); | ||
|
|
||
| static { | ||
| ALLOWED_FIELD_INSTANCES.put(coordinates("Query", "__schema"), 1); | ||
| ALLOWED_FIELD_INSTANCES.put(coordinates("Query", "__type"), 1); | ||
| ALLOWED_FIELD_INSTANCES.put(coordinates("__Type", "fields"), 1); | ||
| ALLOWED_FIELD_INSTANCES.put(coordinates("__Type", "inputFields"), 1); | ||
| ALLOWED_FIELD_INSTANCES.put(coordinates("__Type", "interfaces"), 1); | ||
| ALLOWED_FIELD_INSTANCES.put(coordinates("__Type", "possibleTypes"), 1); | ||
| } | ||
|
|
||
| public static Optional<ExecutionResult> checkIntrospection(ExecutionContext executionContext) { | ||
| if (isIntrospectionEnabled(executionContext.getGraphQLContext())) { | ||
| ExecutableNormalizedOperation operation = executionContext.getNormalizedQueryTree().get(); | ||
| ImmutableListMultimap<FieldCoordinates, ExecutableNormalizedField> coordinatesToENFs = operation.getCoordinatesToNormalizedFields(); | ||
| for (Map.Entry<FieldCoordinates, Integer> entry : ALLOWED_FIELD_INSTANCES.entrySet()) { | ||
| FieldCoordinates coordinates = entry.getKey(); | ||
| Integer allowSize = entry.getValue(); | ||
| ImmutableList<ExecutableNormalizedField> normalizedFields = coordinatesToENFs.get(coordinates); | ||
| if (normalizedFields.size() > allowSize) { | ||
| BadFaithIntrospectionError error = new BadFaithIntrospectionError(coordinates.toString()); | ||
| return Optional.of(ExecutionResult.newExecutionResult().addError(error).build()); | ||
| } | ||
| } | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| private static boolean isIntrospectionEnabled(GraphQLContext graphQlContext) { | ||
| if (!isEnabledJvmWide()) { | ||
| return false; | ||
| } | ||
| return !graphQlContext.getOrDefault(GOOD_FAITH_INTROSPECTION_DISABLED, false); | ||
| } | ||
|
|
||
| public static class BadFaithIntrospectionError implements GraphQLError { | ||
| private final String message; | ||
|
|
||
| public BadFaithIntrospectionError(String qualifiedField) { | ||
| this.message = String.format("This request is not asking for introspection in good faith - %s is present too often!", qualifiedField); | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return message; | ||
| } | ||
|
|
||
| @Override | ||
| public ErrorClassification getErrorType() { | ||
| return ErrorClassification.errorClassification("BadFaithIntrospection"); | ||
| } | ||
|
|
||
| @Override | ||
| public List<SourceLocation> getLocations() { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
src/main/java/graphql/introspection/IntrospectionDisabledError.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package graphql.introspection; | ||
|
|
||
| import graphql.ErrorClassification; | ||
| import graphql.ErrorType; | ||
| import graphql.GraphQLError; | ||
| import graphql.Internal; | ||
| import graphql.language.SourceLocation; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| @Internal | ||
| public class IntrospectionDisabledError implements GraphQLError { | ||
|
|
||
| private final List<SourceLocation> locations; | ||
|
|
||
| public IntrospectionDisabledError(SourceLocation sourceLocation) { | ||
| locations = sourceLocation == null ? Collections.emptyList() : Collections.singletonList(sourceLocation); | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return "Introspection has been disabled for this request"; | ||
| } | ||
|
|
||
| @Override | ||
| public List<SourceLocation> getLocations() { | ||
| return locations; | ||
| } | ||
|
|
||
| @Override | ||
| public ErrorClassification getErrorType() { | ||
| return ErrorClassification.errorClassification("IntrospectionDisabled"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,10 +12,15 @@ | |
| * This field visibility will prevent Introspection queries from being performed. Technically this puts your | ||
| * system in contravention of <a href="https://spec.graphql.org/October2021/#sec-Introspection">the specification</a> | ||
| * but some production systems want this lock down in place. | ||
| * | ||
| * @deprecated This is no longer the best way to prevent Introspection - {@link graphql.introspection.Introspection#enabledJvmWide(boolean)} | ||
| * can be used instead | ||
| */ | ||
| @PublicApi | ||
| @Deprecated // Deprecated since 2024-03-16 | ||
| public class NoIntrospectionGraphqlFieldVisibility implements GraphqlFieldVisibility { | ||
|
|
||
| @Deprecated // Deprecated since 2024-03-16 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java 8 special: the deprecated annotation in Java 8 doesn't have the "since" argument |
||
| public static NoIntrospectionGraphqlFieldVisibility NO_INTROSPECTION_FIELD_VISIBILITY = new NoIntrospectionGraphqlFieldVisibility(); | ||
|
|
||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is different to master - in Java 8 there's no Map.of so I have to use this static block like a caveman