Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the JsonRPC Service for OIDC to use recorder #44590

Merged
merged 1 commit into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}