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

OIDC: reuse shared Vert.x instance in DEV and test mode whenever it is possible #45621

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 @@ -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
Loading