From 058484d434fd58c59ae9e4487cbc32daa4fbd78e Mon Sep 17 00:00:00 2001 From: idawda Date: Thu, 30 Jan 2025 17:52:07 +0530 Subject: [PATCH 1/4] Update instrumentation for GraphQL to support case where query parsing & execution are done separately --- .../ParseAndValidate_Instrumentation.java | 9 ------ .../ExecutionStrategy_Instrumentation.java | 32 +++++++++++++++++++ .../Execution_Instrumentation.java} | 10 +++--- 3 files changed, 38 insertions(+), 13 deletions(-) delete mode 100644 instrumentation-security/graphql-java-16.2/src/main/java/graphql/ParseAndValidate_Instrumentation.java create mode 100644 instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java rename instrumentation-security/graphql-java-16.2/src/main/java/graphql/{GraphQL_Instrumentation.java => execution/Execution_Instrumentation.java} (75%) diff --git a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/ParseAndValidate_Instrumentation.java b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/ParseAndValidate_Instrumentation.java deleted file mode 100644 index 84c3ddf2b..000000000 --- a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/ParseAndValidate_Instrumentation.java +++ /dev/null @@ -1,9 +0,0 @@ -package graphql; - -import com.newrelic.api.agent.weaver.MatchType; -import com.newrelic.api.agent.weaver.Weave; - -@Weave(originalName = "graphql.ParseAndValidate", type = MatchType.ExactClass) -public class ParseAndValidate_Instrumentation { - -} diff --git a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java new file mode 100644 index 000000000..df06787e0 --- /dev/null +++ b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java @@ -0,0 +1,32 @@ +package graphql.execution; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.HttpRequestCustomDataTypeEnum; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import graphql.ExecutionInput; +import graphql.ExecutionResult; + +import java.util.concurrent.CompletableFuture; + +@Weave(type = MatchType.Interface, originalName = "graphql.execution.ExecutionStrategy") +public class ExecutionStrategy_Instrumentation { + + public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { + try { + if (NewRelicSecurity.isHookProcessingActive()) { + HttpRequest request = NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(); + ExecutionInput executionInput = executionContext.getExecutionInput(); + if (executionInput.getQuery() != null && !executionInput.getQuery().isEmpty()) { + request.getCustomDataType().put("*.query", HttpRequestCustomDataTypeEnum.GRAPHQL_QUERY.name()); + } + if (executionInput.getVariables() != null && !executionInput.getVariables().isEmpty()) { + request.getCustomDataType().put("*.variables", HttpRequestCustomDataTypeEnum.GRAPHQL_VARIABLE.name()); + } + } + } catch (Exception ignored) {} + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/GraphQL_Instrumentation.java b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/Execution_Instrumentation.java similarity index 75% rename from instrumentation-security/graphql-java-16.2/src/main/java/graphql/GraphQL_Instrumentation.java rename to instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/Execution_Instrumentation.java index bf6f1bbe8..7f1908d69 100644 --- a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/GraphQL_Instrumentation.java +++ b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/Execution_Instrumentation.java @@ -1,4 +1,4 @@ -package graphql; +package graphql.execution; import com.newrelic.api.agent.security.NewRelicSecurity; import com.newrelic.api.agent.security.schema.HttpRequest; @@ -6,16 +6,18 @@ import com.newrelic.api.agent.weaver.MatchType; import com.newrelic.api.agent.weaver.Weave; import com.newrelic.api.agent.weaver.Weaver; +import graphql.ExecutionInput; +import graphql.ExecutionResult; import graphql.execution.instrumentation.InstrumentationState; import graphql.language.Document; import graphql.schema.GraphQLSchema; import java.util.concurrent.CompletableFuture; -@Weave(originalName = "graphql.GraphQL", type = MatchType.ExactClass) -public class GraphQL_Instrumentation { +@Weave(originalName = "graphql.execution.Execution", type = MatchType.ExactClass) +public class Execution_Instrumentation { - private CompletableFuture execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { + public CompletableFuture execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState) { try { if (NewRelicSecurity.isHookProcessingActive()) { HttpRequest request = NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(); From bc89db22f8e9b9f50030e0148fe30984c76e6dda Mon Sep 17 00:00:00 2001 From: idawda Date: Thu, 20 Feb 2025 14:07:59 +0530 Subject: [PATCH 2/4] NR-370079: Add UTs for GraphQL instrumentation support --- .../instrumentation/graphql/GraphQLTest.java | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 instrumentation-security/graphql-java-16.2/src/test/java/com/nr/agent/security/instrumentation/graphql/GraphQLTest.java diff --git a/instrumentation-security/graphql-java-16.2/src/test/java/com/nr/agent/security/instrumentation/graphql/GraphQLTest.java b/instrumentation-security/graphql-java-16.2/src/test/java/com/nr/agent/security/instrumentation/graphql/GraphQLTest.java new file mode 100644 index 000000000..9f9f0f969 --- /dev/null +++ b/instrumentation-security/graphql-java-16.2/src/test/java/com/nr/agent/security/instrumentation/graphql/GraphQLTest.java @@ -0,0 +1,237 @@ +package com.nr.agent.security.instrumentation.graphql; + +import com.newrelic.agent.security.introspec.InstrumentationTestConfig; +import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; +import com.newrelic.agent.security.introspec.SecurityIntrospector; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.HttpRequestCustomDataTypeEnum; +import com.newrelic.security.test.marker.Java8IncompatibleTest; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.execution.AsyncExecutionStrategy; +import graphql.execution.DefaultValueUnboxer; +import graphql.execution.Execution; +import graphql.execution.ExecutionContextBuilder; +import graphql.execution.ExecutionId; +import graphql.execution.ExecutionStepInfo; +import graphql.execution.ExecutionStrategy; +import graphql.execution.ExecutionStrategyParameters; +import graphql.execution.MergedField; +import graphql.execution.SubscriptionExecutionStrategy; +import graphql.execution.ValueUnboxer; +import graphql.execution.instrumentation.Instrumentation; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters; +import graphql.language.Document; +import graphql.language.Field; +import graphql.language.OperationDefinition; +import graphql.language.SelectionSet; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.StaticDataFetcher; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import graphql.schema.idl.TypeRuntimeWiring; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; +import org.dataloader.DataLoaderRegistry; +import org.junit.After; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +@RunWith(SecurityInstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "graphql" }) +@Category({ Java8IncompatibleTest.class }) +public class GraphQLTest { + + private static final String TEST_ARG = "testArg"; + + private static GraphQL graphQL; + private static GraphQLSchema graphQLSchema; + + @BeforeClass + public static void initialize() { + String schema = "type Query{hello(" + TEST_ARG + ": String): String}"; + + SchemaParser schemaParser = new SchemaParser(); + TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema); + + RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() + .type("Query", builder -> builder.dataFetcher("hello", + new StaticDataFetcher("world"))) + .build(); + + SchemaGenerator schemaGenerator = new SchemaGenerator(); + graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); + + graphQL = GraphQL.newGraphQL(graphQLSchema).build(); + } + + @After + public void cleanUp() { + SecurityInstrumentationTestRunner.getIntrospector().clear(); + } + + @Test + public void testQueryWithNoArg() { + trace(createRunnable("{hello}")); + assertCustomDataType(); + } + + @Test + public void testQueryWithArg() { + trace(createRunnable("{hello (" + TEST_ARG + ": \"fo)o\")}")); + assertCustomDataType(); + } + + @Test + public void testQueryWithVariables() throws ExecutionException, InterruptedException { + // Graphql query with variables + ExecutionInput executionInput = ExecutionInput.newExecutionInput().executionId(ExecutionId.generate()).query("{hello($arg: String!)}").variables(Collections.singletonMap("arg", "world")).build(); + Instrumentation instrumentation = new SimpleInstrumentation(); + InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(graphQLSchema, executionInput)); + + Execution execution = new Execution(new AsyncExecutionStrategy(), new AsyncExecutionStrategy(), new SubscriptionExecutionStrategy(), instrumentation, new DefaultValueUnboxer()); + ExecutionId executionId = executionInput.getExecutionId(); + CompletableFuture ans = execution.execute(Document.newDocument() + .definition(OperationDefinition.newOperationDefinition() + .selectionSet(SelectionSet.newSelectionSet().build()) + .operation(OperationDefinition.Operation.QUERY).build()) + .build(), + graphQLSchema, + executionId, + executionInput, + instrumentationState + ); + ans.get(); + assertCustomDataTypes(); + } + + @Test + public void testQueryWithoutVariables() throws ExecutionException, InterruptedException { + // Graphql query without variables + ExecutionInput executionInput = ExecutionInput.newExecutionInput().executionId(ExecutionId.generate()).query("{hello}").build(); + Instrumentation instrumentation = new SimpleInstrumentation(); + InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(graphQLSchema, executionInput)); + + Execution execution = new Execution(new AsyncExecutionStrategy(), new AsyncExecutionStrategy(), new SubscriptionExecutionStrategy(), instrumentation, new DefaultValueUnboxer()); + ExecutionId executionId = executionInput.getExecutionId(); + CompletableFuture ans = execution.execute(Document.newDocument() + .definition(OperationDefinition.newOperationDefinition() + .selectionSet(SelectionSet.newSelectionSet().build()) + .operation(OperationDefinition.Operation.QUERY).build()) + .build(), + graphQLSchema, + executionId, + executionInput, + instrumentationState + ); + ans.get(); + assertCustomDataType(); + } + + @Test + public void testParsingException() { + //when + trace(createRunnable("cause a parse error")); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + HttpRequest request = introspector.getSecurityMetaData().getRequest(); + Assert.assertTrue(request.getCustomDataType().isEmpty()); + } + + @Test + public void validationException() { + trace(createRunnable("{noSuchField}")); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + HttpRequest request = introspector.getSecurityMetaData().getRequest(); + Assert.assertTrue(request.getCustomDataType().isEmpty()); + } + + @Test + public void testResolverException() { + //given + String query = "{hello " + + "\n" + + "bye}"; + + //when + trace(createRunnable(query, graphWithResolverException())); + assertCustomDataType(); + } + + @Trace(dispatcher = true) + private void trace(Runnable runnable) { + runnable.run(); + } + + private Runnable createRunnable(final String query) { + return () -> graphQL.execute(query); + } + + private Runnable createRunnable(final String query, GraphQL graphql) { + return () -> graphql.execute(query); + } + + private GraphQL graphWithResolverException() { + String schema = "type Query{hello(" + TEST_ARG + ": String): String" + + "\n" + + "bye: String!}"; + + SchemaParser schemaParser = new SchemaParser(); + TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema); + + RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() + .type(TypeRuntimeWiring.newTypeWiring("Query") + .dataFetcher("hello", environment -> { + throw new RuntimeException("waggle"); + }) + .dataFetcher("bye", environment -> null) + ) + .build(); + + SchemaGenerator schemaGenerator = new SchemaGenerator(); + GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); + + return GraphQL.newGraphQL(graphQLSchema).build(); + } + + + private void assertCustomDataType() { + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + HttpRequest request = introspector.getSecurityMetaData().getRequest(); + Assert.assertFalse(request.getCustomDataType().isEmpty()); + Assert.assertEquals(Collections.singletonMap("*.query", HttpRequestCustomDataTypeEnum.GRAPHQL_QUERY.name()), request.getCustomDataType()); + } + + private void assertCustomDataTypes() { + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + HttpRequest request = introspector.getSecurityMetaData().getRequest(); + Assert.assertFalse(request.getCustomDataType().isEmpty()); + Map expectedCustomDataType = new HashMap<>(); + expectedCustomDataType.put("*.query", HttpRequestCustomDataTypeEnum.GRAPHQL_QUERY.name()); + expectedCustomDataType.put("*.variables", HttpRequestCustomDataTypeEnum.GRAPHQL_VARIABLE.name()); + + Assert.assertEquals(expectedCustomDataType, request.getCustomDataType()); + } +} From 191c4dc071385424e7948b881628e2672ab46221 Mon Sep 17 00:00:00 2001 From: idawda Date: Wed, 9 Apr 2025 16:21:38 +0530 Subject: [PATCH 3/4] Instrumentation Support for Graphql 23.0 --- .../ExecutionStrategy_Instrumentation.java | 2 +- .../graphql-java-23.0/build.gradle | 23 +++++++++++++ .../ExecutionStrategy_Instrumentation.java | 32 +++++++++++++++++ .../execution/Execution_Instrumentation.java | 34 +++++++++++++++++++ settings.gradle | 1 + 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 instrumentation-security/graphql-java-23.0/build.gradle create mode 100644 instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java create mode 100644 instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/Execution_Instrumentation.java diff --git a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java index df06787e0..2ce120fbc 100644 --- a/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java +++ b/instrumentation-security/graphql-java-16.2/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java @@ -11,7 +11,7 @@ import java.util.concurrent.CompletableFuture; -@Weave(type = MatchType.Interface, originalName = "graphql.execution.ExecutionStrategy") +@Weave(type = MatchType.BaseClass, originalName = "graphql.execution.ExecutionStrategy") public class ExecutionStrategy_Instrumentation { public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { diff --git a/instrumentation-security/graphql-java-23.0/build.gradle b/instrumentation-security/graphql-java-23.0/build.gradle new file mode 100644 index 000000000..11249d5eb --- /dev/null +++ b/instrumentation-security/graphql-java-23.0/build.gradle @@ -0,0 +1,23 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("com.graphql-java:graphql-java:23.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.graphql-java-23.0' } +} + +verifyInstrumentation { + passesOnly('com.graphql-java:graphql-java:[23.0,)') + fails('com.graphql-java:graphql-java:[16.0,23.0)') + excludeRegex('com.graphql-java:graphql-java:(0.0.0|201|202).*') + excludeRegex('com.graphql-java:graphql-java:.*(vTEST|-beta|-alpha1|-nf-execution|-rc|-TEST).*') + exclude('com.graphql-java:graphql-java:15.0') +} + +site { + title 'GraphQL Java' + type 'Framework' +} diff --git a/instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java b/instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java new file mode 100644 index 000000000..2ce120fbc --- /dev/null +++ b/instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/ExecutionStrategy_Instrumentation.java @@ -0,0 +1,32 @@ +package graphql.execution; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.HttpRequestCustomDataTypeEnum; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import graphql.ExecutionInput; +import graphql.ExecutionResult; + +import java.util.concurrent.CompletableFuture; + +@Weave(type = MatchType.BaseClass, originalName = "graphql.execution.ExecutionStrategy") +public class ExecutionStrategy_Instrumentation { + + public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { + try { + if (NewRelicSecurity.isHookProcessingActive()) { + HttpRequest request = NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(); + ExecutionInput executionInput = executionContext.getExecutionInput(); + if (executionInput.getQuery() != null && !executionInput.getQuery().isEmpty()) { + request.getCustomDataType().put("*.query", HttpRequestCustomDataTypeEnum.GRAPHQL_QUERY.name()); + } + if (executionInput.getVariables() != null && !executionInput.getVariables().isEmpty()) { + request.getCustomDataType().put("*.variables", HttpRequestCustomDataTypeEnum.GRAPHQL_VARIABLE.name()); + } + } + } catch (Exception ignored) {} + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/Execution_Instrumentation.java b/instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/Execution_Instrumentation.java new file mode 100644 index 000000000..2a31f2333 --- /dev/null +++ b/instrumentation-security/graphql-java-23.0/src/main/java/graphql/execution/Execution_Instrumentation.java @@ -0,0 +1,34 @@ +package graphql.execution; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.schema.HttpRequest; +import com.newrelic.api.agent.security.schema.HttpRequestCustomDataTypeEnum; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import graphql.EngineRunningState; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.language.Document; +import graphql.schema.GraphQLSchema; + +import java.util.concurrent.CompletableFuture; + +@Weave(originalName = "graphql.execution.Execution", type = MatchType.ExactClass) +public class Execution_Instrumentation { + + public CompletableFuture execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState, EngineRunningState engineRunningState) { try { + if (NewRelicSecurity.isHookProcessingActive()) { + HttpRequest request = NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(); + if (executionInput.getQuery() != null && !executionInput.getQuery().isEmpty()) { + request.getCustomDataType().put("*.query", HttpRequestCustomDataTypeEnum.GRAPHQL_QUERY.name()); + } + if (executionInput.getVariables() != null && !executionInput.getVariables().isEmpty()) { + request.getCustomDataType().put("*.variables", HttpRequestCustomDataTypeEnum.GRAPHQL_VARIABLE.name()); + } + } + } catch (Exception ignored) {} + return Weaver.callOriginal(); + } +} diff --git a/settings.gradle b/settings.gradle index afec08f66..0cc4f97c2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -217,6 +217,7 @@ include 'instrumentation:solr-7.0.0' include 'instrumentation:solr-8.0.0' include 'instrumentation:solr-9.0.0' include 'instrumentation:graphql-java-16.2' +include 'instrumentation:graphql-java-23.0' include 'instrumentation:websphere-liberty-profile-environment-8.5.5.5' include 'instrumentation:http4s-blaze-server-2.12_0.21' include 'instrumentation:http4s-blaze-server-2.12_0.22' From d9668f696eeed80aca577497fa7f417c982a023e Mon Sep 17 00:00:00 2001 From: idawda Date: Wed, 9 Apr 2025 17:16:32 +0530 Subject: [PATCH 4/4] Utilise java 11 to compile graphql-23 support --- instrumentation-security/graphql-java-23.0/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/instrumentation-security/graphql-java-23.0/build.gradle b/instrumentation-security/graphql-java-23.0/build.gradle index 11249d5eb..c99691331 100644 --- a/instrumentation-security/graphql-java-23.0/build.gradle +++ b/instrumentation-security/graphql-java-23.0/build.gradle @@ -9,6 +9,12 @@ jar { manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.graphql-java-23.0' } } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + verifyInstrumentation { passesOnly('com.graphql-java:graphql-java:[23.0,)') fails('com.graphql-java:graphql-java:[16.0,23.0)')