diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java index 5f4b749c4eae1..1385aa5929874 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java @@ -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."; @@ -30,14 +29,16 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder String tokenUrl, String logoutUrl, boolean introspectionIsAvailable, - BuildProducer beanProducer, + BeanContainerBuildItem beanContainer, Duration webClientTimeout, - Map> grantOptions, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + Map> grantOptions, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, ConfigurationBuildItem configurationBuildItem, String keycloakAdminUrl, Map keycloakUsers, List keycloakRealms, - boolean alwaysLogoutUserInDevUiOnReload) { + boolean alwaysLogoutUserInDevUiOnReload, + HttpConfiguration httpConfiguration) { final CardPageBuildItem cardPage = new CardPageBuildItem(); // prepare provider component @@ -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 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; } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java index f76c97c96b782..fa5437300c4c8 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java @@ -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; @@ -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; @@ -61,7 +62,8 @@ public class OidcDevUIProcessor extends AbstractDevUIProcessor { @Consume(RuntimeConfigSetupCompleteBuildItem.class) void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem, Capabilities capabilities, - BuildProducer syntheticBeanBuildItemBuildProducer, + HttpConfiguration httpConfiguration, + BeanContainerBuildItem beanContainer, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, BuildProducer cardPageProducer, ConfigurationBuildItem configurationBuildItem, @@ -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(); @@ -91,7 +92,6 @@ public void run() { }; closeBuildItem.addCloseTask(closeTask, true); } - JsonObject metadata = null; if (isDiscoveryEnabled(providerConfig)) { metadata = discoverMetadata(authServerUrl); @@ -120,7 +120,7 @@ public void run() { metadataNotNull ? (metadata.containsKey("introspection_endpoint") || metadata.containsKey("userinfo_endpoint")) : checkProviderUserInfoRequired(providerConfig), - syntheticBeanBuildItemBuildProducer, + beanContainer, oidcConfig.devui().webClientTimeout(), oidcConfig.devui().grantOptions(), nonApplicationRootPathBuildItem, @@ -128,7 +128,8 @@ public void run() { keycloakAdminUrl, null, null, - true); + true, + httpConfiguration); cardPageProducer.produce(cardPage); } } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java index 6a73ec05d4e59..544b27569da94 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java @@ -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; @@ -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 { @@ -34,9 +35,10 @@ public class KeycloakDevUIProcessor extends AbstractDevUIProcessor { @Consume(RuntimeConfigSetupCompleteBuildItem.class) void produceProviderComponent(Optional configProps, BuildProducer keycloakAdminPageProducer, + HttpConfiguration httpConfiguration, OidcDevUiRecorder recorder, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - BuildProducer syntheticBeanBuildItemBuildProducer, + BeanContainerBuildItem beanContainer, ConfigurationBuildItem configurationBuildItem, Capabilities capabilities) { final String keycloakAdminUrl = KeycloakDevServicesConfigBuildItem.getKeycloakUrl(configProps); @@ -58,7 +60,7 @@ void produceProviderComponent(Optional confi realmUrl + "/protocol/openid-connect/token", realmUrl + "/protocol/openid-connect/logout", true, - syntheticBeanBuildItemBuildProducer, + beanContainer, oidcConfig.devui().webClientTimeout(), oidcConfig.devui().grantOptions(), nonApplicationRootPathBuildItem, @@ -66,7 +68,8 @@ void produceProviderComponent(Optional confi 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); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java index 0c26e16a76d05..2ab60d98f32fd 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java @@ -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 codeGrantOptions; - private final Map passwordGrantOptions; - private final Map clientCredGrantOptions; - private final Map 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 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 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 testServiceWithToken(String token, String serviceUrl) { return OidcDevServicesUtils .testServiceWithToken(serviceUrl, token, vertx) - .ifNoItem().after(timeout).fail(); + .ifNoItem().after(props.getWebClientTimeout()).fail(); } public Uni 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 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; + } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java index a30dda814db89..7cec424c34251 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java @@ -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 prepareRpcServiceProperties(String authorizationUrl, String tokenUrl, + + public void createJsonRPCService(BeanContainer beanContainer, + RuntimeValue oidcDevUiRpcSvcPropertiesBean, HttpConfiguration httpConfiguration) { + OidcDevJsonRpcService jsonRpcService = beanContainer.beanInstance(OidcDevJsonRpcService.class); + jsonRpcService.hydrate(oidcDevUiRpcSvcPropertiesBean.getValue(), httpConfiguration); + } + + public RuntimeValue getRpcServiceProperties(String authorizationUrl, String tokenUrl, String logoutUrl, Duration webClientTimeout, Map> grantOptions, Map oidcUsers, String oidcProviderName, String oidcApplicationType, String oidcGrantType, boolean introspectionIsAvailable, String keycloakAdminUrl, List keycloakRealms, boolean swaggerIsAvailable, boolean graphqlIsAvailable, String swaggerUiPath, String graphqlUiPath, boolean alwaysLogoutUserInDevUiOnReload) { - return new Supplier() { - @Override - public OidcDevUiRpcSvcPropertiesBean get() { - return new OidcDevUiRpcSvcPropertiesBean(authorizationUrl, tokenUrl, logoutUrl, + + return new RuntimeValue( + new OidcDevUiRpcSvcPropertiesBean(authorizationUrl, tokenUrl, logoutUrl, webClientTimeout, grantOptions, oidcUsers, oidcProviderName, oidcApplicationType, oidcGrantType, introspectionIsAvailable, keycloakAdminUrl, keycloakRealms, swaggerIsAvailable, - graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload); - } - }; + graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload)); } }