Skip to content

Commit

Permalink
Merge pull request #44590 from phillip-kruger/otel-devui
Browse files Browse the repository at this point in the history
Change the JsonRPC Service for OIDC to use recorder
  • Loading branch information
sberyozkin authored Nov 24, 2024
2 parents 3280e47 + 0d98259 commit 8220502
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
import java.util.List;
import java.util.Map;

import jakarta.inject.Singleton;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.ConfigurationBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.quarkus.oidc.runtime.devui.OidcDevUiRecorder;
import io.quarkus.oidc.runtime.devui.OidcDevUiRpcSvcPropertiesBean;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.runtime.HttpConfiguration;

public abstract class AbstractDevUIProcessor {
protected static final String CONFIG_PREFIX = "quarkus.oidc.";
Expand All @@ -30,14 +29,16 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder
String tokenUrl,
String logoutUrl,
boolean introspectionIsAvailable,
BuildProducer<SyntheticBeanBuildItem> beanProducer,
BeanContainerBuildItem beanContainer,
Duration webClientTimeout,
Map<String, Map<String, String>> grantOptions, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
Map<String, Map<String, String>> grantOptions,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
ConfigurationBuildItem configurationBuildItem,
String keycloakAdminUrl,
Map<String, String> keycloakUsers,
List<String> keycloakRealms,
boolean alwaysLogoutUserInDevUiOnReload) {
boolean alwaysLogoutUserInDevUiOnReload,
HttpConfiguration httpConfiguration) {
final CardPageBuildItem cardPage = new CardPageBuildItem();

// prepare provider component
Expand Down Expand Up @@ -69,17 +70,13 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder

cardPage.addBuildTimeData("devRoot", nonApplicationRootPathBuildItem.getNonApplicationRootPath());

// pass down properties used by RPC service
beanProducer.produce(
SyntheticBeanBuildItem.configure(OidcDevUiRpcSvcPropertiesBean.class).unremovable()
.supplier(recorder.prepareRpcServiceProperties(authorizationUrl, tokenUrl, logoutUrl,
webClientTimeout, grantOptions, keycloakUsers, oidcProviderName, oidcApplicationType,
oidcGrantType, introspectionIsAvailable, keycloakAdminUrl, keycloakRealms,
swaggerIsAvailable, graphqlIsAvailable, swaggerUiPath, graphqlUiPath,
alwaysLogoutUserInDevUiOnReload))
.scope(Singleton.class)
.setRuntimeInit()
.done());
RuntimeValue<OidcDevUiRpcSvcPropertiesBean> runtimeProperties = recorder.getRpcServiceProperties(
authorizationUrl, tokenUrl, logoutUrl, webClientTimeout, grantOptions,
keycloakUsers, oidcProviderName, oidcApplicationType, oidcGrantType,
introspectionIsAvailable, keycloakAdminUrl, keycloakRealms, swaggerIsAvailable,
graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload);

recorder.createJsonRPCService(beanContainer.getValue(), runtimeProperties, httpConfiguration);

return cardPage;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.logging.Logger;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -30,6 +30,7 @@
import io.quarkus.oidc.runtime.providers.KnownOidcProviders;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonObject;
Expand Down Expand Up @@ -61,7 +62,8 @@ public class OidcDevUIProcessor extends AbstractDevUIProcessor {
@Consume(RuntimeConfigSetupCompleteBuildItem.class)
void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem,
Capabilities capabilities,
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer,
HttpConfiguration httpConfiguration,
BeanContainerBuildItem beanContainer,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
BuildProducer<CardPageBuildItem> cardPageProducer,
ConfigurationBuildItem configurationBuildItem,
Expand All @@ -72,7 +74,6 @@ void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem,
final OidcTenantConfig providerConfig = getProviderConfig();
final String authServerUrl = getAuthServerUrl(providerConfig);
if (authServerUrl != null) {

if (vertxInstance == null) {
vertxInstance = Vertx.vertx();

Expand All @@ -91,7 +92,6 @@ public void run() {
};
closeBuildItem.addCloseTask(closeTask, true);
}

JsonObject metadata = null;
if (isDiscoveryEnabled(providerConfig)) {
metadata = discoverMetadata(authServerUrl);
Expand Down Expand Up @@ -120,15 +120,16 @@ public void run() {
metadataNotNull
? (metadata.containsKey("introspection_endpoint") || metadata.containsKey("userinfo_endpoint"))
: checkProviderUserInfoRequired(providerConfig),
syntheticBeanBuildItemBuildProducer,
beanContainer,
oidcConfig.devui().webClientTimeout(),
oidcConfig.devui().grantOptions(),
nonApplicationRootPathBuildItem,
configurationBuildItem,
keycloakAdminUrl,
null,
null,
true);
true,
httpConfiguration);
cardPageProducer.produce(cardPage);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.util.Map;
import java.util.Optional;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -24,6 +24,7 @@
import io.quarkus.oidc.runtime.devui.OidcDevJsonRpcService;
import io.quarkus.oidc.runtime.devui.OidcDevUiRecorder;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.runtime.HttpConfiguration;

public class KeycloakDevUIProcessor extends AbstractDevUIProcessor {

Expand All @@ -34,9 +35,10 @@ public class KeycloakDevUIProcessor extends AbstractDevUIProcessor {
@Consume(RuntimeConfigSetupCompleteBuildItem.class)
void produceProviderComponent(Optional<KeycloakDevServicesConfigBuildItem> configProps,
BuildProducer<KeycloakAdminPageBuildItem> keycloakAdminPageProducer,
HttpConfiguration httpConfiguration,
OidcDevUiRecorder recorder,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer,
BeanContainerBuildItem beanContainer,
ConfigurationBuildItem configurationBuildItem,
Capabilities capabilities) {
final String keycloakAdminUrl = KeycloakDevServicesConfigBuildItem.getKeycloakUrl(configProps);
Expand All @@ -58,15 +60,16 @@ void produceProviderComponent(Optional<KeycloakDevServicesConfigBuildItem> confi
realmUrl + "/protocol/openid-connect/token",
realmUrl + "/protocol/openid-connect/logout",
true,
syntheticBeanBuildItemBuildProducer,
beanContainer,
oidcConfig.devui().webClientTimeout(),
oidcConfig.devui().grantOptions(),
nonApplicationRootPathBuildItem,
configurationBuildItem,
keycloakAdminUrl,
users,
keycloakRealms,
configProps.get().isContainerRestarted());
configProps.get().isContainerRestarted(),
httpConfiguration);
// use same card page so that both pages appear on the same card
var keycloakAdminPageItem = new KeycloakAdminPageBuildItem(cardPageBuildItem);
keycloakAdminPageProducer.produce(keycloakAdminPageItem);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,114 +2,69 @@

import static io.quarkus.oidc.runtime.devui.OidcDevServicesUtils.getTokens;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

import org.eclipse.microprofile.config.ConfigProvider;

import io.quarkus.arc.Arc;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.smallrye.common.annotation.NonBlocking;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Vertx;

public class OidcDevJsonRpcService {
private OidcDevUiRpcSvcPropertiesBean props;
private HttpConfiguration httpConfiguration;

private final String authorizationUrl;
private final String tokenUrl;
private final String logoutUrl;
private final Duration timeout;
private final Map<String, String> codeGrantOptions;
private final Map<String, String> passwordGrantOptions;
private final Map<String, String> clientCredGrantOptions;
private final Map<String, String> oidcUserToPassword;
private final int httpPort;
private final SmallRyeConfig config;
private final Vertx vertx;
private final String oidcProviderName;
private final String oidcApplicationType;
private final String oidcGrantType;
private final boolean introspectionIsAvailable;
private final String keycloakAdminUrl;
private final List<String> keycloakRealms;
private final boolean swaggerIsAvailable;
private final boolean graphqlIsAvailable;
private final String swaggerUiPath;
private final String graphqlUiPath;
private final boolean alwaysLogoutUserInDevUiOnReload;
private final String propertiesStateId;

public OidcDevJsonRpcService(HttpConfiguration httpConfiguration, SmallRyeConfig config, Vertx vertx) {
private Vertx vertx;

// we need to inject properties bean lazily as 'OidcDevJsonRpcService' is also produced when OIDC DEV UI is not
// we must always produce it when in DEV mode because we can't check for 'KeycloakDevServicesConfigBuildItem'
// due to circular reference: JSON RPC provider is additional bean and 'LoggingSetupBuildItem' used by
// 'KeycloakDevServicesProcessor' is created with combined index
final var propsInstanceHandle = Arc.container().instance(OidcDevUiRpcSvcPropertiesBean.class);
final OidcDevUiRpcSvcPropertiesBean props;
if (propsInstanceHandle.isAvailable()) {
props = propsInstanceHandle.get();
} else {
// OIDC Dev UI is disabled, but this RPC service still gets initialized by Quarkus DEV UI
props = new OidcDevUiRpcSvcPropertiesBean(null, null, null, null, Map.of(), Map.of(), null, null, null, false, null,
List.of(), false, false, null, null, false);
}
@PostConstruct
public void startup() {
vertx = Vertx.vertx();
}

this.httpPort = httpConfiguration.port;
this.config = config;
this.vertx = vertx;
this.authorizationUrl = props.getAuthorizationUrl();
this.tokenUrl = props.getTokenUrl();
this.logoutUrl = props.getLogoutUrl();
this.timeout = props.getWebClientTimeout();
this.codeGrantOptions = props.getCodeGrantOptions();
this.passwordGrantOptions = props.getPasswordGrantOptions();
this.clientCredGrantOptions = props.getClientCredGrantOptions();
this.oidcUserToPassword = props.getOidcUsers();
this.oidcProviderName = props.getOidcProviderName();
this.oidcApplicationType = props.getOidcApplicationType();
this.oidcGrantType = props.getOidcGrantType();
this.introspectionIsAvailable = props.isIntrospectionIsAvailable();
this.keycloakAdminUrl = props.getKeycloakAdminUrl();
this.keycloakRealms = props.getKeycloakRealms();
this.swaggerIsAvailable = props.isSwaggerIsAvailable();
this.graphqlIsAvailable = props.isGraphqlIsAvailable();
this.swaggerUiPath = props.getSwaggerUiPath();
this.graphqlUiPath = props.getGraphqlUiPath();
this.alwaysLogoutUserInDevUiOnReload = props.isAlwaysLogoutUserInDevUiOnReload();
this.propertiesStateId = props.getPropertiesStateId();
@PreDestroy
public void shutdown() {
vertx.close();
}

@NonBlocking
public OidcDevUiRuntimePropertiesDTO getProperties() {
return new OidcDevUiRuntimePropertiesDTO(authorizationUrl, tokenUrl, logoutUrl, config, httpPort,
oidcProviderName, oidcApplicationType, oidcGrantType, introspectionIsAvailable, keycloakAdminUrl,
keycloakRealms, swaggerIsAvailable, graphqlIsAvailable, swaggerUiPath, graphqlUiPath,
alwaysLogoutUserInDevUiOnReload, propertiesStateId);
return new OidcDevUiRuntimePropertiesDTO(props.getAuthorizationUrl(), props.getTokenUrl(), props.getLogoutUrl(),
ConfigProvider.getConfig(), httpConfiguration.port,
props.getOidcProviderName(), props.getOidcApplicationType(), props.getOidcGrantType(),
props.isIntrospectionIsAvailable(), props.getKeycloakAdminUrl(),
props.getKeycloakRealms(), props.isSwaggerIsAvailable(), props.isGraphqlIsAvailable(), props.getSwaggerUiPath(),
props.getGraphqlUiPath(),
props.isAlwaysLogoutUserInDevUiOnReload(), props.getPropertiesStateId());
}

public Uni<String> exchangeCodeForTokens(String tokenUrl, String clientId, String clientSecret,
String authorizationCode, String redirectUri) {
return getTokens(tokenUrl, clientId, clientSecret, authorizationCode, redirectUri, vertx, codeGrantOptions)
.ifNoItem().after(timeout).fail();
return getTokens(tokenUrl, clientId, clientSecret, authorizationCode, redirectUri, vertx, props.getCodeGrantOptions())
.ifNoItem().after(props.getWebClientTimeout()).fail();
}

public Uni<Integer> testServiceWithToken(String token, String serviceUrl) {
return OidcDevServicesUtils
.testServiceWithToken(serviceUrl, token, vertx)
.ifNoItem().after(timeout).fail();
.ifNoItem().after(props.getWebClientTimeout()).fail();
}

public Uni<String> testServiceWithPassword(String tokenUrl, String serviceUrl, String clientId,
String clientSecret, String username, String password) {
return OidcDevServicesUtils.testServiceWithPassword(tokenUrl, serviceUrl, clientId, clientSecret, username,
password, vertx, timeout, passwordGrantOptions, oidcUserToPassword);
password, vertx, props.getWebClientTimeout(), props.getPasswordGrantOptions(), props.getOidcUsers());
}

public Uni<String> testServiceWithClientCred(String tokenUrl, String serviceUrl, String clientId,
String clientSecret) {
return OidcDevServicesUtils.testServiceWithClientCred(tokenUrl, serviceUrl, clientId, clientSecret, vertx,
timeout, clientCredGrantOptions);
props.getWebClientTimeout(), props.getClientCredGrantOptions());
}

public void hydrate(OidcDevUiRpcSvcPropertiesBean properties, HttpConfiguration httpConfiguration) {
this.props = properties;
this.httpConfiguration = httpConfiguration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,31 @@
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.http.runtime.HttpConfiguration;

@Recorder
public class OidcDevUiRecorder {
public Supplier<OidcDevUiRpcSvcPropertiesBean> prepareRpcServiceProperties(String authorizationUrl, String tokenUrl,

public void createJsonRPCService(BeanContainer beanContainer,
RuntimeValue<OidcDevUiRpcSvcPropertiesBean> oidcDevUiRpcSvcPropertiesBean, HttpConfiguration httpConfiguration) {
OidcDevJsonRpcService jsonRpcService = beanContainer.beanInstance(OidcDevJsonRpcService.class);
jsonRpcService.hydrate(oidcDevUiRpcSvcPropertiesBean.getValue(), httpConfiguration);
}

public RuntimeValue<OidcDevUiRpcSvcPropertiesBean> getRpcServiceProperties(String authorizationUrl, String tokenUrl,
String logoutUrl, Duration webClientTimeout, Map<String, Map<String, String>> grantOptions,
Map<String, String> oidcUsers, String oidcProviderName, String oidcApplicationType, String oidcGrantType,
boolean introspectionIsAvailable, String keycloakAdminUrl, List<String> keycloakRealms, boolean swaggerIsAvailable,
boolean graphqlIsAvailable, String swaggerUiPath, String graphqlUiPath, boolean alwaysLogoutUserInDevUiOnReload) {
return new Supplier<OidcDevUiRpcSvcPropertiesBean>() {
@Override
public OidcDevUiRpcSvcPropertiesBean get() {
return new OidcDevUiRpcSvcPropertiesBean(authorizationUrl, tokenUrl, logoutUrl,

return new RuntimeValue<OidcDevUiRpcSvcPropertiesBean>(
new OidcDevUiRpcSvcPropertiesBean(authorizationUrl, tokenUrl, logoutUrl,
webClientTimeout, grantOptions, oidcUsers, oidcProviderName, oidcApplicationType, oidcGrantType,
introspectionIsAvailable, keycloakAdminUrl, keycloakRealms, swaggerIsAvailable,
graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload);
}
};
graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload));
}
}

0 comments on commit 8220502

Please sign in to comment.