Skip to content

Commit

Permalink
Merge pull request #45621 from michalvavrik/feature/oidc-dev-test-reu…
Browse files Browse the repository at this point in the history
…se-vertx-when-possible

OIDC: reuse shared Vert.x instance in DEV and test mode whenever it is possible
  • Loading branch information
sberyozkin authored Jan 16, 2025
2 parents ad0a86b + 985c5a3 commit beee3db
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 177 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder
Map<String, String> keycloakUsers,
List<String> keycloakRealms,
boolean alwaysLogoutUserInDevUiOnReload,
HttpConfiguration httpConfiguration) {
HttpConfiguration httpConfiguration, boolean discoverMetadata, String authServerUrl) {
final CardPageBuildItem cardPage = new CardPageBuildItem();

// prepare provider component
Expand Down Expand Up @@ -82,7 +82,8 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder
authorizationUrl, tokenUrl, logoutUrl, webClientTimeout, grantOptions,
keycloakUsers, oidcProviderName, oidcApplicationType, oidcGrantType,
introspectionIsAvailable, keycloakAdminUrl, keycloakRealms, swaggerIsAvailable,
graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload);
graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload, discoverMetadata,
authServerUrl);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.Set;

import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.logging.Logger;

import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.deployment.Capabilities;
Expand All @@ -15,32 +14,22 @@
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ConfigurationBuildItem;
import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
import io.quarkus.devservices.oidc.OidcDevServicesConfigBuildItem;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.Provider;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.oidc.deployment.OidcBuildTimeConfig;
import io.quarkus.oidc.runtime.devui.OidcDevJsonRpcService;
import io.quarkus.oidc.runtime.devui.OidcDevServicesUtils;
import io.quarkus.oidc.runtime.devui.OidcDevUiRecorder;
import io.quarkus.oidc.runtime.providers.KnownOidcProviders;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
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;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpResponse;
import io.vertx.mutiny.ext.web.client.WebClient;

public class OidcDevUIProcessor extends AbstractDevUIProcessor {
static volatile Vertx vertxInstance;
private static final Logger LOG = Logger.getLogger(OidcDevUIProcessor.class);

private static final String TENANT_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "tenant-enabled";
private static final String DISCOVERY_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "discovery-enabled";
Expand All @@ -57,9 +46,9 @@ public class OidcDevUIProcessor extends AbstractDevUIProcessor {

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep(onlyIf = IsDevelopment.class)
@Consume(CoreVertxBuildItem.class) // metadata discovery requires Vertx instance
@Consume(RuntimeConfigSetupCompleteBuildItem.class)
void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem,
Capabilities capabilities,
void prepareOidcDevConsole(Capabilities capabilities,
HttpConfiguration httpConfiguration,
BeanContainerBuildItem beanContainer,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
Expand All @@ -76,33 +65,8 @@ void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem,
? oidcDevServicesConfigBuildItem.get().getConfig().get(AUTH_SERVER_URL_CONFIG_KEY)
: getAuthServerUrl(providerConfig);
if (authServerUrl != null) {
if (vertxInstance == null) {
vertxInstance = Vertx.vertx();

Runnable closeTask = new Runnable() {
@Override
public void run() {
if (vertxInstance != null) {
try {
vertxInstance.close();
} catch (Throwable t) {
LOG.error("Failed to close Vertx instance", t);
}
}
vertxInstance = null;
}
};
closeBuildItem.addCloseTask(closeTask, true);
}
JsonObject metadata = null;
if (isDiscoveryEnabled(providerConfig)) {
metadata = discoverMetadata(authServerUrl);
if (metadata == null) {
return;
}
}
boolean discoverMetadata = isDiscoveryEnabled(providerConfig);
String providerName = tryToGetProviderName(authServerUrl);
boolean metadataNotNull = metadata != null;

final String keycloakAdminUrl;
if (KEYCLOAK.equals(providerName)) {
Expand All @@ -116,12 +80,10 @@ public void run() {
getApplicationType(providerConfig),
oidcConfig.devui().grant().type().isPresent() ? oidcConfig.devui().grant().type().get().getGrantType()
: "code",
metadataNotNull ? metadata.getString("authorization_endpoint") : null,
metadataNotNull ? metadata.getString("token_endpoint") : null,
metadataNotNull ? metadata.getString("end_session_endpoint") : null,
metadataNotNull
? (metadata.containsKey("introspection_endpoint") || metadata.containsKey("userinfo_endpoint"))
: checkProviderUserInfoRequired(providerConfig),
null,
null,
null,
checkProviderUserInfoRequired(providerConfig),
beanContainer,
oidcConfig.devui().webClientTimeout(),
oidcConfig.devui().grantOptions(),
Expand All @@ -131,7 +93,7 @@ public void run() {
null,
null,
true,
httpConfiguration);
httpConfiguration, discoverMetadata, authServerUrl);
cardPageProducer.produce(cardPage);
}
}
Expand All @@ -141,14 +103,14 @@ JsonRPCProvidersBuildItem produceOidcDevJsonRpcService() {
return new JsonRPCProvidersBuildItem(OidcDevJsonRpcService.class);
}

private boolean checkProviderUserInfoRequired(OidcTenantConfig providerConfig) {
private static boolean checkProviderUserInfoRequired(OidcTenantConfig providerConfig) {
if (providerConfig != null) {
return providerConfig.authentication.userInfoRequired.orElse(false);
return providerConfig.authentication().userInfoRequired().orElse(false);
}
return false;
}

private String tryToGetProviderName(String authServerUrl) {
private static String tryToGetProviderName(String authServerUrl) {
if (authServerUrl.contains("/realms/")) {
return KEYCLOAK;
}
Expand All @@ -163,28 +125,6 @@ private String tryToGetProviderName(String authServerUrl) {
return null;
}

private JsonObject discoverMetadata(String authServerUrl) {
WebClient client = OidcDevServicesUtils.createWebClient(vertxInstance);
try {
String metadataUrl = authServerUrl + OidcConstants.WELL_KNOWN_CONFIGURATION;
LOG.infof("OIDC Dev Console: discovering the provider metadata at %s", metadataUrl);

HttpResponse<Buffer> resp = client.getAbs(metadataUrl)
.putHeader(HttpHeaders.ACCEPT.toString(), "application/json").send().await().indefinitely();
if (resp.statusCode() == 200) {
return resp.bodyAsJsonObject();
} else {
LOG.errorf("OIDC metadata discovery failed: %s", resp.bodyAsString());
return null;
}
} catch (Throwable t) {
LOG.infof("OIDC metadata can not be discovered: %s", t.toString());
return null;
} finally {
client.close();
}
}

private static String getConfigProperty(String name) {
return ConfigProvider.getConfig().getValue(name, String.class);
}
Expand All @@ -195,7 +135,7 @@ private static boolean isOidcTenantEnabled() {

private static boolean isDiscoveryEnabled(OidcTenantConfig providerConfig) {
return ConfigProvider.getConfig().getOptionalValue(DISCOVERY_ENABLED_CONFIG_KEY, Boolean.class)
.orElse((providerConfig != null ? providerConfig.discoveryEnabled.orElse(true) : true));
.orElse((providerConfig != null ? providerConfig.discoveryEnabled().orElse(true) : true));
}

private static boolean getBooleanProperty(String name) {
Expand All @@ -210,7 +150,7 @@ private static String getAuthServerUrl(OidcTenantConfig providerConfig) {
try {
return getConfigProperty(AUTH_SERVER_URL_CONFIG_KEY);
} catch (Exception ex) {
return providerConfig != null ? providerConfig.authServerUrl.get() : null;
return providerConfig != null ? providerConfig.authServerUrl().get() : null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void produceProviderComponent(Optional<KeycloakDevServicesConfigBuildItem> confi
users,
keycloakRealms,
configProps.get().isContainerRestarted(),
httpConfiguration);
httpConfiguration, false, null);
// 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,8 +2,6 @@

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

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;

import org.eclipse.microprofile.config.ConfigProvider;
Expand All @@ -21,17 +19,8 @@ public class OidcDevJsonRpcService {
@Inject
OidcDevLoginObserver oidcDevTokensObserver;

private Vertx vertx;

@PostConstruct
public void startup() {
vertx = Vertx.vertx();
}

@PreDestroy
public void shutdown() {
vertx.close();
}
@Inject
Vertx vertx;

@NonBlocking
public OidcDevUiRuntimePropertiesDTO getProperties() {
Expand Down Expand Up @@ -72,7 +61,7 @@ public Multi<Boolean> streamOidcLoginEvent() {
return oidcDevTokensObserver.streamOidcLoginEvent();
}

public void hydrate(OidcDevUiRpcSvcPropertiesBean properties, HttpConfiguration httpConfiguration) {
void hydrate(OidcDevUiRpcSvcPropertiesBean properties, HttpConfiguration httpConfiguration) {
this.props = properties;
this.httpConfiguration = httpConfiguration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@
import java.util.List;
import java.util.Map;

import org.jboss.logging.Logger;

import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.oidc.runtime.OidcConfig;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.core.runtime.VertxCoreRecorder;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpResponse;
import io.vertx.mutiny.ext.web.client.WebClient;

@Recorder
public class OidcDevUiRecorder {

private static final Logger LOG = Logger.getLogger(OidcDevUiRecorder.class);

private final RuntimeValue<OidcConfig> oidcConfigRuntimeValue;

public OidcDevUiRecorder(RuntimeValue<OidcConfig> oidcConfigRuntimeValue) {
Expand All @@ -31,8 +42,18 @@ public RuntimeValue<OidcDevUiRpcSvcPropertiesBean> getRpcServiceProperties(Strin
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) {

boolean graphqlIsAvailable, String swaggerUiPath, String graphqlUiPath, boolean alwaysLogoutUserInDevUiOnReload,
boolean discoverMetadata, String authServerUrl) {
if (discoverMetadata) {
JsonObject metadata = discoverMetadata(authServerUrl);
if (metadata != null) {
authorizationUrl = metadata.getString("authorization_endpoint");
tokenUrl = metadata.getString("token_endpoint");
logoutUrl = metadata.getString("end_session_endpoint");
introspectionIsAvailable = metadata.containsKey("introspection_endpoint")
|| metadata.containsKey("userinfo_endpoint");
}
}
return new RuntimeValue<OidcDevUiRpcSvcPropertiesBean>(
new OidcDevUiRpcSvcPropertiesBean(authorizationUrl, tokenUrl, logoutUrl,
webClientTimeout, grantOptions, oidcUsers, oidcProviderName, oidcApplicationType, oidcGrantType,
Expand All @@ -48,4 +69,25 @@ public Handler<RoutingContext> logoutHandler() {
return new OidcDevSessionLogoutHandler();
}

private static JsonObject discoverMetadata(String authServerUrl) {
WebClient client = OidcDevServicesUtils.createWebClient(VertxCoreRecorder.getVertx().get());
try {
String metadataUrl = authServerUrl + OidcConstants.WELL_KNOWN_CONFIGURATION;
LOG.infof("OIDC Dev Console: discovering the provider metadata at %s", metadataUrl);

HttpResponse<Buffer> resp = client.getAbs(metadataUrl)
.putHeader(HttpHeaders.ACCEPT.toString(), "application/json").send().await().indefinitely();
if (resp.statusCode() == 200) {
return resp.bodyAsJsonObject();
} else {
LOG.errorf("OIDC metadata discovery failed: %s", resp.bodyAsString());
return null;
}
} catch (Throwable t) {
LOG.infof("OIDC metadata can not be discovered: %s", t.toString());
return null;
} finally {
client.close();
}
}
}
Loading

0 comments on commit beee3db

Please sign in to comment.