-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39132 from michalvavrik/feature/access-token-name…
…d-client OIDC token propagation: add option to select named OIDC client and token exchange per REST client with the @accesstoken annotation
- Loading branch information
Showing
11 changed files
with
585 additions
and
52 deletions.
There are no files selected for viewing
This file contains 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
39 changes: 39 additions & 0 deletions
39
...loyment/src/main/java/io/quarkus/oidc/client/deployment/AccessTokenInstanceBuildItem.java
This file contains 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,39 @@ | ||
package io.quarkus.oidc.client.deployment; | ||
|
||
import java.util.Objects; | ||
|
||
import org.jboss.jandex.AnnotationTarget; | ||
|
||
import io.quarkus.builder.item.MultiBuildItem; | ||
|
||
/** | ||
* Represents one {@link io.quarkus.oidc.token.propagation.AccessToken} annotation instance. | ||
*/ | ||
public final class AccessTokenInstanceBuildItem extends MultiBuildItem { | ||
|
||
private final String clientName; | ||
private final boolean tokenExchange; | ||
private final AnnotationTarget annotationTarget; | ||
|
||
AccessTokenInstanceBuildItem(String clientName, Boolean tokenExchange, AnnotationTarget annotationTarget) { | ||
this.clientName = Objects.requireNonNull(clientName); | ||
this.tokenExchange = tokenExchange; | ||
this.annotationTarget = Objects.requireNonNull(annotationTarget); | ||
} | ||
|
||
public String getClientName() { | ||
return clientName; | ||
} | ||
|
||
public boolean exchangeTokenActivated() { | ||
return tokenExchange; | ||
} | ||
|
||
public AnnotationTarget getAnnotationTarget() { | ||
return annotationTarget; | ||
} | ||
|
||
public String targetClass() { | ||
return annotationTarget.asClass().name().toString(); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
...nt/src/main/java/io/quarkus/oidc/client/deployment/AccessTokenRequestFilterGenerator.java
This file contains 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,95 @@ | ||
package io.quarkus.oidc.client.deployment; | ||
|
||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.reflect.Modifier; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import jakarta.annotation.Priority; | ||
import jakarta.inject.Singleton; | ||
|
||
import io.quarkus.arc.deployment.GeneratedBeanBuildItem; | ||
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; | ||
import io.quarkus.arc.deployment.UnremovableBeanBuildItem; | ||
import io.quarkus.deployment.annotations.BuildProducer; | ||
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; | ||
import io.quarkus.gizmo.ClassCreator; | ||
|
||
public final class AccessTokenRequestFilterGenerator { | ||
|
||
private static final int AUTHENTICATION = 1000; | ||
|
||
private record ClientNameAndExchangeToken(String clientName, boolean exchangeTokenActivated) { | ||
} | ||
|
||
private final BuildProducer<UnremovableBeanBuildItem> unremovableBeansProducer; | ||
private final BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer; | ||
private final BuildProducer<GeneratedBeanBuildItem> generatedBeanProducer; | ||
private final Class<?> requestFilterClass; | ||
private final Map<ClientNameAndExchangeToken, String> cache = new HashMap<>(); | ||
|
||
public AccessTokenRequestFilterGenerator(BuildProducer<UnremovableBeanBuildItem> unremovableBeansProducer, | ||
BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer, | ||
BuildProducer<GeneratedBeanBuildItem> generatedBeanProducer, Class<?> requestFilterClass) { | ||
this.unremovableBeansProducer = unremovableBeansProducer; | ||
this.reflectiveClassProducer = reflectiveClassProducer; | ||
this.generatedBeanProducer = generatedBeanProducer; | ||
this.requestFilterClass = requestFilterClass; | ||
} | ||
|
||
public String generateClass(AccessTokenInstanceBuildItem instance) { | ||
return cache.computeIfAbsent( | ||
new ClientNameAndExchangeToken(instance.getClientName(), instance.exchangeTokenActivated()), i -> { | ||
var adaptor = new GeneratedBeanGizmoAdaptor(generatedBeanProducer); | ||
String className = createUniqueClassName(i); | ||
try (ClassCreator classCreator = ClassCreator.builder() | ||
.className(className) | ||
.superClass(requestFilterClass) | ||
.classOutput(adaptor) | ||
.build()) { | ||
classCreator.addAnnotation(Priority.class).add("value", AUTHENTICATION); | ||
classCreator.addAnnotation(Singleton.class); | ||
|
||
if (!i.clientName().isEmpty()) { | ||
try (var methodCreator = classCreator.getMethodCreator("getClientName", String.class)) { | ||
methodCreator.addAnnotation(Override.class.getName(), RetentionPolicy.CLASS); | ||
methodCreator.setModifiers(Modifier.PROTECTED); | ||
methodCreator.returnValue(methodCreator.load(i.clientName())); | ||
} | ||
} | ||
if (i.exchangeTokenActivated()) { | ||
try (var methodCreator = classCreator.getMethodCreator("isExchangeToken", boolean.class)) { | ||
methodCreator.addAnnotation(Override.class.getName(), RetentionPolicy.CLASS); | ||
methodCreator.setModifiers(Modifier.PROTECTED); | ||
methodCreator.returnBoolean(true); | ||
} | ||
} | ||
} | ||
unremovableBeansProducer.produce(UnremovableBeanBuildItem.beanClassNames(className)); | ||
reflectiveClassProducer | ||
.produce(ReflectiveClassBuildItem.builder(className).methods().fields().constructors().build()); | ||
return className; | ||
}); | ||
} | ||
|
||
private String createUniqueClassName(ClientNameAndExchangeToken i) { | ||
return "%s_%sClient_%sTokenExchange".formatted(requestFilterClass.getName(), clientName(i.clientName()), | ||
exchangeTokenName(i.exchangeTokenActivated())); | ||
} | ||
|
||
private static String clientName(String clientName) { | ||
if (clientName.isEmpty()) { | ||
return "Default"; | ||
} else { | ||
return clientName; | ||
} | ||
} | ||
|
||
private static String exchangeTokenName(boolean enabled) { | ||
if (enabled) { | ||
return "Enabled"; | ||
} else { | ||
return "Default"; | ||
} | ||
} | ||
} |
This file contains 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 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 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
Oops, something went wrong.