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

Allow to combine the discovered and locally configured OIDC metadata #22007

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 @@ -44,16 +44,31 @@ public OidcConfigurationMetadata(String tokenUri,
}

public OidcConfigurationMetadata(JsonObject wellKnownConfig) {
this.tokenUri = wellKnownConfig.getString(TOKEN_ENDPOINT);
this.introspectionUri = wellKnownConfig.getString(INTROSPECTION_ENDPOINT);
this.authorizationUri = wellKnownConfig.getString(AUTHORIZATION_ENDPOINT);
this.jsonWebKeySetUri = wellKnownConfig.getString(JWKS_ENDPOINT);
this.userInfoUri = wellKnownConfig.getString(USERINFO_ENDPOINT);
this.endSessionUri = wellKnownConfig.getString(END_SESSION_ENDPOINT);
this.issuer = wellKnownConfig.getString(ISSUER);
this(wellKnownConfig, null);
}

public OidcConfigurationMetadata(JsonObject wellKnownConfig, OidcConfigurationMetadata fallbackConfig) {
this.tokenUri = getMetadataValue(wellKnownConfig, TOKEN_ENDPOINT,
fallbackConfig == null ? null : fallbackConfig.tokenUri);
this.introspectionUri = getMetadataValue(wellKnownConfig, INTROSPECTION_ENDPOINT,
fallbackConfig == null ? null : fallbackConfig.introspectionUri);
this.authorizationUri = getMetadataValue(wellKnownConfig, AUTHORIZATION_ENDPOINT,
fallbackConfig == null ? null : fallbackConfig.authorizationUri);
this.jsonWebKeySetUri = getMetadataValue(wellKnownConfig, JWKS_ENDPOINT,
fallbackConfig == null ? null : fallbackConfig.jsonWebKeySetUri);
this.userInfoUri = getMetadataValue(wellKnownConfig, USERINFO_ENDPOINT,
fallbackConfig == null ? null : fallbackConfig.userInfoUri);
this.endSessionUri = getMetadataValue(wellKnownConfig, END_SESSION_ENDPOINT,
fallbackConfig == null ? null : fallbackConfig.endSessionUri);
this.issuer = getMetadataValue(wellKnownConfig, ISSUER, fallbackConfig == null ? null : fallbackConfig.issuer);
this.json = wellKnownConfig;
}

private static String getMetadataValue(JsonObject wellKnownConfig, String propertyName, String fallbackValue) {
String value = wellKnownConfig.getString(propertyName);
return value != null ? value : fallbackValue;
}

public String getTokenUri() {
return tokenUri;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,21 +279,13 @@ protected static Uni<OidcProviderClient> createOidcClientUni(OidcTenantConfig oi

Uni<OidcConfigurationMetadata> metadataUni = null;
if (!oidcConfig.discoveryEnabled) {
String tokenUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath);
String introspectionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString,
oidcConfig.introspectionPath);
String authorizationUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString,
oidcConfig.authorizationPath);
String jwksUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.jwksPath);
String userInfoUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.userInfoPath);
String endSessionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.endSessionPath);
metadataUni = Uni.createFrom().item(new OidcConfigurationMetadata(tokenUri,
introspectionUri, authorizationUri, jwksUri, userInfoUri, endSessionUri,
oidcConfig.token.issuer.orElse(null)));
metadataUni = Uni.createFrom().item(createLocalMetadata(oidcConfig, authServerUriString));
} else {
final long connectionDelayInMillisecs = OidcCommonUtils.getConnectionDelayInMillis(oidcConfig);
metadataUni = OidcCommonUtils.discoverMetadata(client, authServerUriString, connectionDelayInMillisecs)
.onItem().transform(json -> new OidcConfigurationMetadata(json));
.onItem()
.transform(
json -> new OidcConfigurationMetadata(json, createLocalMetadata(oidcConfig, authServerUriString)));
}
return metadataUni.onItemOrFailure()
.transformToUni(new BiFunction<OidcConfigurationMetadata, Throwable, Uni<? extends OidcProviderClient>>() {
Expand Down Expand Up @@ -321,6 +313,20 @@ public Uni<OidcProviderClient> apply(OidcConfigurationMetadata metadata, Throwab
});
}

private static OidcConfigurationMetadata createLocalMetadata(OidcTenantConfig oidcConfig, String authServerUriString) {
String tokenUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath);
String introspectionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString,
oidcConfig.introspectionPath);
String authorizationUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString,
oidcConfig.authorizationPath);
String jwksUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.jwksPath);
String userInfoUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.userInfoPath);
String endSessionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.endSessionPath);
return new OidcConfigurationMetadata(tokenUri,
introspectionUri, authorizationUri, jwksUri, userInfoUri, endSessionUri,
oidcConfig.token.issuer.orElse(null));
}

private static boolean isServiceApp(OidcTenantConfig oidcConfig) {
return ApplicationType.SERVICE.equals(oidcConfig.applicationType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,19 @@ public OidcTenantConfig get() {
OidcTenantConfig config = new OidcTenantConfig();
config.setTenantId("tenant-oidc");
String uri = context.request().absoluteURI();
// authServerUri points to the JAX-RS `OidcResource`, root path is `/oidc`
String authServerUri = path.contains("tenant-opaque")
? uri.replace("/tenant-opaque/tenant-oidc/api/user", "/oidc")
: uri.replace("/tenant/tenant-oidc/api/user", "/oidc");
config.setAuthServerUrl(authServerUri);
config.setClientId("client");
config.setAllowTokenIntrospectionCache(false);
// auto-discovery in Quarkus is enabled but the OIDC server returns an empty document, set the required endpoints in the config
// try the path relative to the authServerUri
config.setJwksPath("jwks");
// try the absolute URI
config.setIntrospectionPath(authServerUri + "/introspect");

return config;
} else if ("tenant-oidc-no-discovery".equals(tenantId)) {
OidcTenantConfig config = new OidcTenantConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class OidcResource {
private volatile int jwkEndpointCallCount;
private volatile int introspectionEndpointCallCount;
private volatile int userInfoEndpointCallCount;
private volatile boolean enableDiscovery = true;

@PostConstruct
public void init() throws Exception {
Expand All @@ -39,13 +40,17 @@ public void init() throws Exception {
@Produces("application/json")
@Path(".well-known/openid-configuration")
public String discovery() {
final String baseUri = ui.getBaseUriBuilder().path("oidc").build().toString();
return "{" +
" \"token_endpoint\":" + "\"" + baseUri + "/token\"," +
" \"introspection_endpoint\":" + "\"" + baseUri + "/introspect\"," +
" \"userinfo_endpoint\":" + "\"" + baseUri + "/userinfo\"," +
" \"jwks_uri\":" + "\"" + baseUri + "/jwks\"" +
" }";
if (enableDiscovery) {
final String baseUri = ui.getBaseUriBuilder().path("oidc").build().toString();
return "{" +
" \"token_endpoint\":" + "\"" + baseUri + "/token\"," +
" \"introspection_endpoint\":" + "\"" + baseUri + "/introspect\"," +
" \"userinfo_endpoint\":" + "\"" + baseUri + "/userinfo\"," +
" \"jwks_uri\":" + "\"" + baseUri + "/jwks\"" +
" }";
} else {
return "{}";
}
}

@GET
Expand Down Expand Up @@ -161,6 +166,20 @@ public boolean disableIntrospection() {
return introspection;
}

@POST
@Path("enable-discovery")
public boolean setDiscovery() {
enableDiscovery = true;
return enableDiscovery;
}

@POST
@Path("disable-discovery")
public boolean disableDiscovery() {
enableDiscovery = false;
return enableDiscovery;
}

@POST
@Path("enable-rotate")
public boolean setRotate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ public void testSimpleOidcJwtWithJwkRefresh() {
RestAssured.when().post("/oidc/jwk-endpoint-call-count").then().body(equalTo("0"));
RestAssured.when().post("/oidc/introspection-endpoint-call-count").then().body(equalTo("0"));
RestAssured.when().post("/oidc/disable-introspection").then().body(equalTo("false"));
RestAssured.when().post("/oidc/disable-discovery").then().body(equalTo("false"));
// Quarkus OIDC is initialized with JWK set with kid '1' as part of the discovery process
// Now enable the rotation
RestAssured.when().post("/oidc/enable-rotate").then().body(equalTo("true"));
Expand Down Expand Up @@ -307,6 +308,7 @@ public void testSimpleOidcJwtWithJwkRefresh() {
// both requests with kid `3` and with the opaque token required the remote introspection
RestAssured.when().get("/oidc/introspection-endpoint-call-count").then().body(equalTo("3"));
RestAssured.when().post("/oidc/disable-introspection").then().body(equalTo("false"));
RestAssured.when().post("/oidc/enable-discovery").then().body(equalTo("true"));
RestAssured.when().post("/oidc/disable-rotate").then().body(equalTo("false"));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.quarkus.it.keycloak;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.containing;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand Down Expand Up @@ -32,6 +35,7 @@ public class CodeFlowAuthorizationTest {

@Test
public void testCodeFlow() throws IOException {
defineCodeFlowLogoutStub();
try (final WebClient webClient = createWebClient()) {
webClient.getOptions().setRedirectEnabled(true);
HtmlPage page = webClient.getPage("http://localhost:8081/code-flow");
Expand Down Expand Up @@ -90,6 +94,16 @@ private void defineCodeFlowAuthorizationOauth2TokenStub() {
+ "}")));
}

private void defineCodeFlowLogoutStub() {
wireMockServer.stubFor(
get(urlPathMatching("/auth/realms/quarkus/protocol/openid-connect/end-session"))
.willReturn(aResponse()
.withHeader("Location",
"{{request.query.returnTo}}?clientId={{request.query.client_id}}")
.withStatus(302)
.withTransformers("response-template")));
}

private Cookie getSessionCookie(WebClient webClient, String tenantId) {
return webClient.getCookieManager().getCookie("q_session" + (tenantId == null ? "" : "_" + tenantId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,6 @@ public Map<String, String> start() {
.withStatus(302)
.withTransformers("response-template")));

// Logout Request
server.stubFor(
get(urlPathMatching("/auth/realms/quarkus/protocol/openid-connect/end-session"))
.willReturn(aResponse()
.withHeader("Location",
"{{request.query.returnTo}}?clientId={{request.query.client_id}}")
.withStatus(302)
.withTransformers("response-template")));

LOG.infof("Keycloak started in mock mode: %s", server.baseUrl());
Map<String, String> conf = new HashMap<>();
conf.put("keycloak.url", server.baseUrl() + "/auth");
Expand Down