Skip to content

Commit

Permalink
Merge pull request #36375 from jmartisk/graphql-client-oidc
Browse files Browse the repository at this point in the history
OIDC client integration for GraphQL clients
  • Loading branch information
sberyozkin authored Oct 11, 2023
2 parents 380795a + bd2beff commit e0ca7c6
Show file tree
Hide file tree
Showing 22 changed files with 636 additions and 2 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,16 @@
<artifactId>quarkus-oidc-client-reactive-filter-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-graphql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-graphql-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-token-propagation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-graphql</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-reactive-filter</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-graphql-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-reactive-filter-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,66 @@ 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=${keycloak.url}
quarkus.oidc-client.oidc-client-for-graphql.grant.type=password
quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.username=${username}
quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.password=${password}
quarkus.oidc-client.oidc-client-for-graphql.client-id=${quarkus.oidc.client-id}
quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.value=${keycloak.credentials.secret}
quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.method=POST
----

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<String>`) 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<String> 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]
Expand Down
6 changes: 6 additions & 0 deletions docs/src/main/asciidoc/smallrye-graphql-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,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]

128 changes: 128 additions & 0 deletions extensions/oidc-client-graphql/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-oidc-client-graphql-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-oidc-client-graphql-deployment</artifactId>
<name>Quarkus - OpenID Connect Client GraphQL integration - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-graphql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql-client-deployment</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-deployment</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>test-keycloak</id>
<activation>
<property>
<name>test-containers</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
<systemPropertyVariables>
<keycloak.docker.image>${keycloak.docker.legacy.image}</keycloak.docker.image>
<keycloak.use.https>false</keycloak.use.https>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -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<FeatureBuildItem> 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<String, String> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
@@ -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();

}
Loading

0 comments on commit e0ca7c6

Please sign in to comment.