diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 932fd097fb628..c6e0226ad6735 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -868,6 +868,16 @@ quarkus-oidc-client-reactive-filter-deployment ${project.version} + + io.quarkus + quarkus-oidc-client-graphql + ${project.version} + + + io.quarkus + quarkus-oidc-client-graphql-deployment + ${project.version} + io.quarkus quarkus-oidc-token-propagation diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java index 6e8d59d8fd423..cebff1c71face 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java @@ -71,6 +71,7 @@ public enum Feature { OIDC_CLIENT, OIDC_CLIENT_FILTER, OIDC_CLIENT_REACTIVE_FILTER, + OIDC_CLIENT_GRAPHQL_CLIENT_INTEGRATION, OIDC_TOKEN_PROPAGATION, OIDC_TOKEN_PROPAGATION_REACTIVE, OPENSHIFT_CLIENT, diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index 22c2cca510f18..cbdbc050e4dfc 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -1565,6 +1565,19 @@ + + io.quarkus + quarkus-oidc-client-graphql + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-oidc-client-reactive-filter diff --git a/docs/pom.xml b/docs/pom.xml index d28aa46df1760..38070aa3d12c1 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -1581,6 +1581,19 @@ + + io.quarkus + quarkus-oidc-client-graphql-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-oidc-client-reactive-filter-deployment diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index f875bc2f8931a..6e1ac30f88cd2 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -1117,6 +1117,62 @@ The `quarkus-oidc-token-propagation-reactive` extension provides `io.quarkus.oid The `quarkus-oidc-token-propagation-reactive` extension (as opposed to the non-reactive `quarkus-oidc-token-propagation` extension) does not currently support the exchanging or resigning the tokens before the propagation. However, these features may be added in the future. +[[oidc-client-graphql-client]] +== GraphQL client integration + +The `quarkus-oidc-client-graphql` extension provides a way to integrate an +OIDC client into xref:smallrye-graphql-client.adoc[GraphQL clients]. This +works similarly as with REST clients. When this extension is present, any +configured (that means NOT created programmatically via the builder, but via +configuration properties) GraphQL client will attempt to use the OIDC client +to obtain an access token and set it as an `Authorization` header value. +OIDC client will also refresh expired access tokens. + +To configure which OIDC client should be used by GraphQL client, select one of the configured OIDC clients with the `quarkus.oidc-client-graphql.client-name` property, for example: + +---- +quarkus.oidc-client-graphql.client-name=oidc-client-for-graphql + +# example declaration of the OIDC client itself +quarkus.oidc-client.oidc-client-for-graphql.auth-server-url=http://example.com/realms/quarkus +quarkus.oidc-client.annotation.grant-options.password.username=${username} +quarkus.oidc-client.annotation.grant-options.password.password=${password} +---- + +NOTE: If you don't specify the `quarkus.oidc-client-graphql.client-name` property, +GraphQL clients will use the default OIDC client (without an explicit name). + +Specifically for typesafe GraphQL clients, you can override this on a +per-client basis by annotating the `GraphQLClientApi` interface with +`@io.quarkus.oidc.client.filter.OidcClientFilter`. For example: + +[source,java] +---- +@GraphQLClientApi(configKey = "order-client") +@OidcClientFilter("oidc-client-for-graphql") +public interface OrdersGraphQLClient { + // queries, mutations and subscriptions go here... +} +---- + +To be able to use this with a programmatically created GraphQL client, both +builders (`VertxDynamicGraphQLClientBuilder` and +`VertxTypesafeGraphQLClientBuilder`) contain a method `dynamicHeader(String, +Uni`) that allows you to plug in a header that might change for +every request. To plug an OIDC client into it, use + +[source,java] +---- +@Inject +OidcClients oidcClients; + +VertxTypesafeGraphQLClientBuilder builder = ....; +Uni tokenUni = oidcClients.getClient("OIDC_CLIENT_NAME") + .getTokens().map(t -> "Bearer " + t.getAccessToken()); +builder.dynamicHeader("Authorization", tokenUni); +VertxDynamicGraphQLClient client = builder.build(); +---- + == References * xref:security-openid-connect-client.adoc[OpenID Connect Client and Token Propagation Quickstart] diff --git a/docs/src/main/asciidoc/smallrye-graphql-client.adoc b/docs/src/main/asciidoc/smallrye-graphql-client.adoc index aeb2c51ec46e9..417820fb770f1 100644 --- a/docs/src/main/asciidoc/smallrye-graphql-client.adoc +++ b/docs/src/main/asciidoc/smallrye-graphql-client.adoc @@ -346,3 +346,9 @@ formats it for better readability by humans, for example by piping the output th This example showed how to use both the dynamic and typesafe GraphQL clients to call an external GraphQL service and explained the difference between the client types. + +== References + +* xref:security-openid-connect-client-reference.adoc#oidc-client-graphql-client[Integrating OIDC clients into GraphQL clients] +* https://smallrye.io/smallrye-graphql/latest/[Upstream SmallRye GraphQL Client documentation] + diff --git a/extensions/oidc-client-graphql/deployment/pom.xml b/extensions/oidc-client-graphql/deployment/pom.xml new file mode 100644 index 0000000000000..ec964b1a93152 --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/pom.xml @@ -0,0 +1,128 @@ + + + + quarkus-oidc-client-graphql-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-oidc-client-graphql-deployment + Quarkus - OpenID Connect Client GraphQL integration - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-oidc-client-graphql + + + io.quarkus + quarkus-oidc-client-deployment + + + io.quarkus + quarkus-smallrye-graphql-client-deployment + + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-test-keycloak-server + test + + + junit + junit + + + + + io.quarkus + quarkus-oidc-deployment + test + + + io.quarkus + quarkus-smallrye-graphql-deployment + test + + + io.quarkus + quarkus-resteasy-reactive-deployment + test + + + + + + + src/test/resources + true + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + maven-surefire-plugin + + true + + + + + + + test-keycloak + + + test-containers + + + + + + maven-surefire-plugin + + false + + ${keycloak.docker.legacy.image} + false + + + + + + + + diff --git a/extensions/oidc-client-graphql/deployment/src/main/java/io/quarkus/oidc/client/graphql/OidcGraphQLClientIntegrationProcessor.java b/extensions/oidc-client-graphql/deployment/src/main/java/io/quarkus/oidc/client/graphql/OidcGraphQLClientIntegrationProcessor.java new file mode 100644 index 0000000000000..5cc32a72e59c1 --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/main/java/io/quarkus/oidc/client/graphql/OidcGraphQLClientIntegrationProcessor.java @@ -0,0 +1,57 @@ +package io.quarkus.oidc.client.graphql; + +import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; + +import java.util.HashMap; +import java.util.Map; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; + +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.deployment.Feature; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.oidc.client.graphql.runtime.OidcClientGraphQLConfig; +import io.quarkus.oidc.client.graphql.runtime.OidcGraphQLClientIntegrationRecorder; + +public class OidcGraphQLClientIntegrationProcessor { + + private static final DotName GRAPHQL_CLIENT_API = DotName + .createSimple("io.smallrye.graphql.client.typesafe.api.GraphQLClientApi"); + + private static final String OIDC_CLIENT_FILTER = "io.quarkus.oidc.client.filter.OidcClientFilter"; + + @BuildStep + void feature(BuildProducer featureProducer) { + featureProducer.produce(new FeatureBuildItem(Feature.OIDC_CLIENT_GRAPHQL_CLIENT_INTEGRATION)); + } + + @BuildStep + @Record(RUNTIME_INIT) + void initialize(BeanContainerBuildItem containerBuildItem, + OidcGraphQLClientIntegrationRecorder recorder, + OidcClientGraphQLConfig config, + BeanArchiveIndexBuildItem index) { + Map configKeysToOidcClients = new HashMap<>(); + for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) { + ClassInfo clazz = annotation.target().asClass(); + AnnotationInstance oidcClient = clazz.annotation(OIDC_CLIENT_FILTER); + if (oidcClient != null) { + String oidcClientName = oidcClient.valueWithDefault(index.getIndex(), "value").asString(); + AnnotationValue configKeyValue = annotation.value("configKey"); + String configKey = configKeyValue != null ? configKeyValue.asString() : null; + String actualConfigKey = (configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString(); + if (oidcClientName != null && !oidcClientName.isEmpty()) { + configKeysToOidcClients.put(actualConfigKey, oidcClientName); + } + } + } + recorder.enhanceGraphQLClientConfigurationWithOidc(configKeysToOidcClients, config.clientName.orElse(null)); + } +} diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/AnnotationTypesafeGraphQLClient.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/AnnotationTypesafeGraphQLClient.java new file mode 100644 index 0000000000000..e598ce5f6f50d --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/AnnotationTypesafeGraphQLClient.java @@ -0,0 +1,15 @@ +package io.quarkus.oidc.client.graphql; + +import org.eclipse.microprofile.graphql.Query; + +import io.quarkus.oidc.client.filter.OidcClientFilter; +import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi; + +@GraphQLClientApi(configKey = "typesafe-annotation") +@OidcClientFilter("annotation") +public interface AnnotationTypesafeGraphQLClient { + + @Query + String principalName(); + +} diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/DefaultTypesafeGraphQLClient.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/DefaultTypesafeGraphQLClient.java new file mode 100644 index 0000000000000..630c360e77bce --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/DefaultTypesafeGraphQLClient.java @@ -0,0 +1,15 @@ +package io.quarkus.oidc.client.graphql; + +import org.eclipse.microprofile.graphql.Query; + +import io.quarkus.oidc.client.filter.OidcClientFilter; +import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi; + +@GraphQLClientApi(configKey = "typesafe-default") +@OidcClientFilter +public interface DefaultTypesafeGraphQLClient { + + @Query + String principalName(); + +} diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientResource.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientResource.java new file mode 100644 index 0000000000000..5fdb09359dfea --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientResource.java @@ -0,0 +1,43 @@ +package io.quarkus.oidc.client.graphql; + +import java.util.concurrent.ExecutionException; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.smallrye.graphql.client.GraphQLClient; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; + +@Path("/oidc-graphql-client") +public class GraphQLClientResource { + + @Inject + AnnotationTypesafeGraphQLClient annotationTypesafeClient; + + @Inject + DefaultTypesafeGraphQLClient defaultTypesafeClient; + + @GET + @Path("/annotation-typesafe") + public String typesafeClientAnnotation() { + return annotationTypesafeClient.principalName(); + } + + @GET + @Path("/default-typesafe") + public String typesafeClientDefault() { + return defaultTypesafeClient.principalName(); + } + + @Inject + @GraphQLClient("default-dynamic") + DynamicGraphQLClient dynamicClient; + + @GET + @Path("/default-dynamic") + public String dynamicClientDefault() throws ExecutionException, InterruptedException { + return dynamicClient.executeSync("query { principalName }").getData().getString("principalName"); + } + +} diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientUsingOidcClientTest.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientUsingOidcClientTest.java new file mode 100644 index 0000000000000..112fb47baf5cb --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientUsingOidcClientTest.java @@ -0,0 +1,62 @@ +package io.quarkus.oidc.client.graphql; + +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; +import io.restassured.RestAssured; + +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +public class GraphQLClientUsingOidcClientTest { + + private static final Class[] testClasses = { + ProtectedResource.class, + GraphQLClientResource.class, + AnnotationTypesafeGraphQLClient.class, + DefaultTypesafeGraphQLClient.class + }; + + @RegisterExtension + static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(testClasses) + .addAsResource("application.properties", "application.properties")); + + @Test + public void typesafeClientAnnotation() { + // OidcClient selected via @OidcClient("clientName") + RestAssured.when().get("/oidc-graphql-client/annotation-typesafe") + .then() + .statusCode(200) + .body(equalTo("jdoe")); + } + + @Test + public void typesafeClientWithDefault() { + // OidcClient selected via @OidcClient without a value - this should resort to the default client + RestAssured.when().get("/oidc-graphql-client/default-typesafe") + .then() + .statusCode(200) + .body(equalTo("alice")); + // just to make sure it works more than once + RestAssured.when().get("/oidc-graphql-client/default-typesafe") + .then() + .statusCode(200) + .body(equalTo("alice")); + } + + @Test + public void dynamicClientWithDefault() { + // dynamic clients should always resort to the default (`quarkus.oidc-client-graphql.client-name`), + // because currently we don't have a way to override it + RestAssured.when().get("/oidc-graphql-client/default-dynamic") + .then() + .statusCode(200) + .body(equalTo("alice")); + } + +} diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/ProtectedResource.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/ProtectedResource.java new file mode 100644 index 0000000000000..93f8bb23230ba --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/ProtectedResource.java @@ -0,0 +1,23 @@ +package io.quarkus.oidc.client.graphql; + +import java.security.Principal; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; + +import io.quarkus.security.Authenticated; + +@GraphQLApi +@Authenticated +public class ProtectedResource { + + @Inject + Principal principal; + + @Query + public String principalName() { + return principal.getName(); + } +} diff --git a/extensions/oidc-client-graphql/deployment/src/test/resources/application.properties b/extensions/oidc-client-graphql/deployment/src/test/resources/application.properties new file mode 100644 index 0000000000000..721c8e9ab2883 --- /dev/null +++ b/extensions/oidc-client-graphql/deployment/src/test/resources/application.properties @@ -0,0 +1,27 @@ +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ +quarkus.oidc.client-id=quarkus-service-app +quarkus.oidc.credentials.secret=secret + +quarkus.oidc-client.annotation.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.annotation.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.annotation.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.annotation.credentials.client-secret.method=POST +quarkus.oidc-client.annotation.grant.type=password +quarkus.oidc-client.annotation.grant-options.password.username=jdoe +quarkus.oidc-client.annotation.grant-options.password.password=jdoe + +quarkus.oidc-client.configured-default.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.configured-default.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.configured-default.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.configured-default.credentials.client-secret.method=POST +quarkus.oidc-client.configured-default.grant.type=password +quarkus.oidc-client.configured-default.grant-options.password.username=alice +quarkus.oidc-client.configured-default.grant-options.password.password=alice + +# FIXME: avoid hardcoding the URL +quarkus.smallrye-graphql-client.typesafe-annotation.url=http://localhost:8080/graphql +quarkus.smallrye-graphql-client.typesafe-default.url=http://localhost:8080/graphql +quarkus.smallrye-graphql-client.default-dynamic.url=http://localhost:8080/graphql + +# default OIDC client for GraphQL clients +quarkus.oidc-client-graphql.client-name=configured-default \ No newline at end of file diff --git a/extensions/oidc-client-graphql/pom.xml b/extensions/oidc-client-graphql/pom.xml new file mode 100644 index 0000000000000..6f85166404778 --- /dev/null +++ b/extensions/oidc-client-graphql/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-oidc-client-graphql-parent + Quarkus - OpenID Connect Client GraphQL integration + pom + + deployment + runtime + + diff --git a/extensions/oidc-client-graphql/runtime/pom.xml b/extensions/oidc-client-graphql/runtime/pom.xml new file mode 100644 index 0000000000000..0fd99ca208623 --- /dev/null +++ b/extensions/oidc-client-graphql/runtime/pom.xml @@ -0,0 +1,60 @@ + + + + quarkus-oidc-client-graphql-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-oidc-client-graphql + Quarkus - OpenID Connect Client GraphQL integration - Runtime + Use GraphQL client and get and refresh access tokens with OpenId Connect Client and send them as HTTP Authorization Bearer tokens + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-oidc-client + + + io.quarkus + quarkus-smallrye-graphql-client + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcClientGraphQLConfig.java b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcClientGraphQLConfig.java new file mode 100644 index 0000000000000..7a78cd7137009 --- /dev/null +++ b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcClientGraphQLConfig.java @@ -0,0 +1,18 @@ +package io.quarkus.oidc.client.graphql.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "oidc-client-graphql", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class OidcClientGraphQLConfig { + + /** + * Name of the configured OidcClient used by GraphQL clients. You can override this configuration + * for typesafe clients with the `io.quarkus.oidc.client.filter.OidcClientFilter` annotation. + */ + @ConfigItem + public Optional clientName; +} diff --git a/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcGraphQLClientIntegrationRecorder.java b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcGraphQLClientIntegrationRecorder.java new file mode 100644 index 0000000000000..38a8ebc9dcb6c --- /dev/null +++ b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcGraphQLClientIntegrationRecorder.java @@ -0,0 +1,44 @@ +package io.quarkus.oidc.client.graphql.runtime; + +import java.util.Map; + +import io.quarkus.arc.Arc; +import io.quarkus.oidc.client.OidcClients; +import io.quarkus.oidc.client.Tokens; +import io.quarkus.runtime.annotations.Recorder; +import io.smallrye.graphql.client.impl.GraphQLClientsConfiguration; +import io.smallrye.mutiny.Uni; + +@Recorder +public class OidcGraphQLClientIntegrationRecorder { + + public void enhanceGraphQLClientConfigurationWithOidc(Map configKeysToOidcClients, + String defaultOidcClientName) { + OidcClients oidcClients = Arc.container().instance(OidcClients.class).get(); + GraphQLClientsConfiguration configs = GraphQLClientsConfiguration.getInstance(); + configs.getClients().forEach((graphQLClientKey, value) -> { + String oidcClient = configKeysToOidcClients.get(graphQLClientKey); + if (oidcClient == null) { + oidcClient = defaultOidcClientName; + } + Map> dynamicHeaders = configs.getClient(graphQLClientKey).getDynamicHeaders(); + dynamicHeaders.put("Authorization", getToken(oidcClients, oidcClient)); + }); + } + + public Uni getToken(OidcClients clients, String oidcClientId) { + Uni tokenUni = clients.getClient("MY_OIDC_CLIENT").getTokens().map(t -> "Bearer " + t.getAccessToken()); + if (oidcClientId == null) { + return clients.getClient() + .getTokens() + .map(Tokens::getAccessToken) + .map(token -> "Bearer " + token); + } else { + return clients.getClient(oidcClientId) + .getTokens() + .map(Tokens::getAccessToken) + .map(token -> "Bearer " + token); + } + } + +} diff --git a/extensions/oidc-client-graphql/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/oidc-client-graphql/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..33682cd9a1adf --- /dev/null +++ b/extensions/oidc-client-graphql/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,16 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "OpenID Connect Client integration for GraphQL client" +metadata: + keywords: + - "oauth2" + - "openid-connect" + - "oidc" + - "oidc-client" + - "graphql-client" + guide: "https://quarkus.io/guides/security-openid-connect-client" + categories: + - "security" + status: "experimental" + config: + - "quarkus.oidc-client." diff --git a/extensions/pom.xml b/extensions/pom.xml index 746699977266a..5734b8bde14df 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -139,6 +139,7 @@ oidc-client oidc-client-filter oidc-client-reactive-filter + oidc-client-graphql oidc-token-propagation oidc-token-propagation-reactive oidc-db-token-state-manager diff --git a/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java b/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java index b74277457375d..7fec562a3f912 100644 --- a/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java +++ b/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java @@ -170,7 +170,8 @@ void initializeClientSupport(BuildProducer syntheticBean shortNamesToQualifiedNames.put(clazz.name().withoutPackagePrefix(), clazz.name().toString()); AnnotationValue configKeyValue = annotation.value("configKey"); String configKey = configKeyValue != null ? configKeyValue.asString() : null; - knownConfigKeys.add((configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString()); + String actualConfigKey = (configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString(); + knownConfigKeys.add(actualConfigKey); } for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT)) { @@ -181,7 +182,8 @@ void initializeClientSupport(BuildProducer syntheticBean knownConfigKeys.add(configKey); } - RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames, knownConfigKeys); + RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames, + knownConfigKeys); DotName supportClassName = DotName.createSimple(GraphQLClientSupport.class.getName()); SyntheticBeanBuildItem bean = SyntheticBeanBuildItem diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java index 840b244746a53..ac23a5b57b453 100644 --- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java +++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java @@ -115,6 +115,7 @@ private GraphQLClientConfiguration toSmallRyeNativeConfiguration(GraphQLClientCo .ifPresent(transformed::setExecuteSingleOperationsOverWebsocket); quarkusConfig.websocketInitializationTimeout.ifPresent(transformed::setWebsocketInitializationTimeout); quarkusConfig.allowUnexpectedResponseFields.ifPresent(transformed::setAllowUnexpectedResponseFields); + transformed.setDynamicHeaders(new HashMap<>()); return transformed; }