Skip to content
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 @@ -218,7 +218,6 @@ public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
.transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
URI absoluteUri = URI.create(context.request().absoluteURI());

String userQuery = null;

Expand All @@ -233,12 +232,11 @@ public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
}
}

StringBuilder errorUri = new StringBuilder(buildUri(context,
isForceHttps(oidcTenantConfig),
absoluteUri.getAuthority(),
oidcTenantConfig.authentication().errorPath().get()));
StringBuilder errorUri = prepareRedirectPathBuilder(context, oidcTenantConfig,
oidcTenantConfig.authentication().errorPath().get());

errorUri.append('?')
.append(getRequestParametersAsQuery(absoluteUri, requestParams, oidcTenantConfig));
.append(getRequestParametersAsQuery(context, requestParams, oidcTenantConfig));
if (userQuery != null) {
errorUri.append('&').append(userQuery);
}
Expand Down Expand Up @@ -268,6 +266,17 @@ public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {

}

protected StringBuilder prepareRedirectPathBuilder(RoutingContext context, OidcTenantConfig oidcTenantConfig, String path) {
StringBuilder sb = new StringBuilder();

if (path.startsWith(HTTP_SCHEME)) {
sb.append(path);
} else {
sb.append(buildUri(context, isForceHttps(oidcTenantConfig), context.request().authority().toString(), path));
}
return sb;
}

private static String filterRedirect(RoutingContext context,
TenantConfigContext tenantContext, String redirectUri, Redirect.Location location) {
List<OidcRedirectFilter> redirectFilters = tenantContext.getOidcRedirectFilters(location);
Expand Down Expand Up @@ -332,11 +341,11 @@ private void removeStateCookies(OidcTenantConfig oidcTenantConfig, RoutingContex

}

private String getRequestParametersAsQuery(URI requestUri, MultiMap requestParams, OidcTenantConfig oidcConfig) {
private String getRequestParametersAsQuery(RoutingContext context, MultiMap requestParams, OidcTenantConfig oidcConfig) {
if (ResponseMode.FORM_POST == oidcConfig.authentication().responseMode().orElse(ResponseMode.QUERY)) {
return OidcCommonUtils.encodeForm(new io.vertx.mutiny.core.MultiMap(requestParams)).toString();
} else {
return requestUri.getRawQuery();
return context.request().query();
}
}

Expand Down Expand Up @@ -505,11 +514,9 @@ private Uni<SecurityIdentity> autoRefreshIsNotPossible(RoutingContext context, T
}

private Uni<SecurityIdentity> redirectToSessionExpiredPage(RoutingContext context, TenantConfigContext configContext) {
URI absoluteUri = URI.create(context.request().absoluteURI());
StringBuilder sessionExpired = new StringBuilder(buildUri(context,
isForceHttps(configContext.oidcConfig()),
absoluteUri.getAuthority(),
configContext.oidcConfig().authentication().sessionExpiredPath().get()));
StringBuilder sessionExpired = prepareRedirectPathBuilder(context, configContext.oidcConfig(),
configContext.oidcConfig().authentication().sessionExpiredPath().get());

String sessionExpiredUri = sessionExpired.toString();
LOG.debugf("Session Expired URI: %s", sessionExpiredUri);
return removeSessionCookie(context, configContext.oidcConfig())
Expand Down Expand Up @@ -956,17 +963,32 @@ public SecurityIdentity apply(SecurityIdentity identity) {
if (removeRedirectParams || finalUserPath != null
|| finalUserQuery != null) {

URI absoluteUri = URI.create(context.request().absoluteURI());
StringBuilder finalUriWithoutQuery = new StringBuilder();

StringBuilder finalUriWithoutQuery = new StringBuilder(buildUri(context,
isForceHttps(configContext.oidcConfig()),
absoluteUri.getAuthority(),
(finalUserPath != null ? finalUserPath
: absoluteUri.getRawPath())));
String redirectPath = configContext.oidcConfig().authentication()
.redirectPath().orElse(null);
if (redirectPath != null && redirectPath.startsWith(HTTP_SCHEME)) {
// This is the actual URI that OIDC provider used to redirect the user back to Quarkus
if (finalUserPath == null) {
// No need to restore the original request path
finalUriWithoutQuery.append(redirectPath);
} else {
URI redirectUri = URI.create(redirectPath);
finalUriWithoutQuery.append(
buildUri(redirectUri.getScheme(), redirectUri.getAuthority(), "",
finalUserPath));
}
} else {
finalUriWithoutQuery.append(
buildUri(context, isForceHttps(configContext.oidcConfig()),
context.request().authority().toString(),
(finalUserPath != null ? finalUserPath
: context.request().path())));
}

if (!removeRedirectParams) {
finalUriWithoutQuery.append('?')
.append(getRequestParametersAsQuery(absoluteUri, requestParams,
.append(getRequestParametersAsQuery(context, requestParams,
configContext.oidcConfig()));
}
if (finalUserQuery != null) {
Expand Down Expand Up @@ -1375,6 +1397,10 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho
}
}
}
return buildUri(scheme, authority, forwardedPrefix, path);
}

private static String buildUri(String scheme, String authority, String forwardedPrefix, String path) {
return new StringBuilder(scheme).append("://")
.append(authority)
.append(forwardedPrefix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ public String resolve(RoutingContext context) {
}
}

if (path.endsWith("tenant-absolute-redirect") || path.endsWith("tenant-absolute-redirect/callback")) {
return "tenant-absolute-redirect";
}

if (path.endsWith("tenant-restore-path-absolute-redirect")
|| path.endsWith("tenant-restore-path-absolute-redirect/callback")) {
return "tenant-restore-path-absolute-redirect";
}

if (path.contains("tenant-xhr")) {
return "tenant-xhr";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.it.keycloak;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;

import io.quarkus.security.Authenticated;

@Path("/tenant-absolute-redirect")
public class TenantAbsoluteRedirect {

@Context
UriInfo ui;

@GET
@Authenticated
public String getTenant() {
throw new RuntimeException("/tenant-absolute-redirect/callback is a callback method");
}

@GET
@Authenticated
@Path("/callback")
public String getTenantCallback() {
return ui.getAbsolutePath().toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.it.keycloak;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;

import io.quarkus.security.Authenticated;

@Path("/tenant-restore-path-absolute-redirect")
public class TenantRestorePathAbsoluteRedirect {

@Context
UriInfo ui;

@GET
@Authenticated
public String getTenant() {
return ui.getAbsolutePath().toString();
}

@GET
@Authenticated
@Path("/callback")
public String getTenantCallback() {
throw new RuntimeException("/tenant-restore-path-absolute-redirect must be restored");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,23 @@ quarkus.oidc.tenant-nonce.resource-metadata.resource=/metadata
quarkus.oidc.tenant-nonce.resource-metadata.scopes=read
quarkus.oidc.tenant-nonce.resource-metadata.authorization-server=http://localhost:8080/q/oidc

quarkus.oidc.tenant-absolute-redirect.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc.tenant-absolute-redirect.client-id=quarkus-app
quarkus.oidc.tenant-absolute-redirect.credentials.secret=secret
quarkus.oidc.tenant-absolute-redirect.authentication.scopes=profile,email,phone
quarkus.oidc.tenant-absolute-redirect.authentication.redirect-path=http://localhost:8081/tenant-absolute-redirect/callback
quarkus.oidc.tenant-absolute-redirect.authentication.extra-params.max-age=60
quarkus.oidc.tenant-absolute-redirect.application-type=web-app

quarkus.oidc.tenant-restore-path-absolute-redirect.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc.tenant-restore-path-absolute-redirect.client-id=quarkus-app
quarkus.oidc.tenant-restore-path-absolute-redirect.credentials.secret=secret
quarkus.oidc.tenant-restore-path-absolute-redirect.authentication.scopes=profile,email,phone
quarkus.oidc.tenant-restore-path-absolute-redirect.authentication.redirect-path=http://localhost:8081/tenant-restore-path-absolute-redirect/callback
quarkus.oidc.tenant-restore-path-absolute-redirect.authentication.extra-params.max-age=60
quarkus.oidc.tenant-restore-path-absolute-redirect.authentication.restore-path-after-redirect=true
quarkus.oidc.tenant-restore-path-absolute-redirect.application-type=web-app

quarkus.oidc.tenant-javascript.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc.tenant-javascript.client-id=quarkus-app
quarkus.oidc.tenant-javascript.credentials.secret=secret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,122 @@ public void testCodeFlowMissingNonce() throws Exception {
}
}

@Test
public void testCodeFlowAbsoluteRedirect() throws Exception {
try (final WebClient webClient = createWebClient()) {
webClient.getOptions().setRedirectEnabled(false);

WebResponse webResponse = webClient
.loadWebResponse(
new WebRequest(
URI.create("http://localhost:8081/tenant-absolute-redirect?custom=customValue").toURL()));
String keycloakUrl = webResponse.getResponseHeaderValue("location");
verifyLocationHeader(webClient, keycloakUrl, "tenant-absolute-redirect", "tenant-absolute-redirect%2Fcallback",
false);

HtmlPage page = webClient.getPage(keycloakUrl);

assertEquals("Sign in to quarkus", page.getTitleText());
HtmlForm loginForm = page.getForms().get(0);
loginForm.getInputByName("username").setValueAttribute("alice");
loginForm.getInputByName("password").setValueAttribute("alice");

webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webResponse = loginForm.getButtonByName("login").click().getWebResponse();
webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);

// This is a redirect from the OIDC server to the endpoint, with technical paramerts like `code`
// but without a request specific query parameter
String endpointLocation = webResponse.getResponseHeaderValue("location");

URI endpointLocationUri = URI.create(endpointLocation);
assertEquals("http://localhost:8081/tenant-absolute-redirect/callback",
endpointLocationUri.getScheme() + "://" + endpointLocationUri.getAuthority()
+ endpointLocationUri.getPath());
assertTrue(endpointLocationUri.getQuery().contains("code="));
assertTrue(endpointLocationUri.getQuery().contains("state="));
assertFalse(endpointLocationUri.getQuery().contains("custom="));

// This is a final redirect dropping the technical parameters like `code`
// but restoring the custom query parameter
webResponse = webClient.loadWebResponse(new WebRequest(endpointLocationUri.toURL()));
endpointLocation = webResponse.getResponseHeaderValue("location");
endpointLocationUri = URI.create(endpointLocation);
assertEquals("http://localhost:8081/tenant-absolute-redirect/callback",
endpointLocationUri.getScheme() + "://" + endpointLocationUri.getAuthority()
+ endpointLocationUri.getPath());

assertFalse(endpointLocationUri.getQuery().contains("code="));
assertFalse(endpointLocationUri.getQuery().contains("state="));
assertTrue(endpointLocationUri.getQuery().contains("custom=customValue"));

webResponse = webClient.loadWebResponse(new WebRequest(endpointLocationUri.toURL()));
assertEquals(200, webResponse.getStatusCode());
assertEquals("http://localhost:8081/tenant-absolute-redirect/callback", webResponse.getContentAsString());

webClient.getCookieManager().clearCookies();
}
}

@Test
public void testCodeFlowRestorePathAbsoluteRedirect() throws Exception {
try (final WebClient webClient = createWebClient()) {
webClient.getOptions().setRedirectEnabled(false);

WebResponse webResponse = webClient
.loadWebResponse(
new WebRequest(
URI.create("http://localhost:8081/tenant-restore-path-absolute-redirect?custom=customValue")
.toURL()));
String keycloakUrl = webResponse.getResponseHeaderValue("location");
verifyLocationHeader(webClient, keycloakUrl, "tenant-restore-path-absolute-redirect",
"tenant-restore-path-absolute-redirect%2Fcallback",
false);

HtmlPage page = webClient.getPage(keycloakUrl);

assertEquals("Sign in to quarkus", page.getTitleText());
HtmlForm loginForm = page.getForms().get(0);
loginForm.getInputByName("username").setValueAttribute("alice");
loginForm.getInputByName("password").setValueAttribute("alice");

webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webResponse = loginForm.getButtonByName("login").click().getWebResponse();
webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);

// This is a redirect from the OIDC server to the endpoint, with technical paramerts like `code`
// but without a request specific query parameter
String endpointLocation = webResponse.getResponseHeaderValue("location");

URI endpointLocationUri = URI.create(endpointLocation);
assertEquals("http://localhost:8081/tenant-restore-path-absolute-redirect/callback",
endpointLocationUri.getScheme() + "://" + endpointLocationUri.getAuthority()
+ endpointLocationUri.getPath());
assertTrue(endpointLocationUri.getQuery().contains("code="));
assertTrue(endpointLocationUri.getQuery().contains("state="));
assertFalse(endpointLocationUri.getQuery().contains("custom="));

// This is a final redirect dropping the technical parameters like `code`
// but restoring the custom query parameter, as well as the original request path
webResponse = webClient.loadWebResponse(new WebRequest(endpointLocationUri.toURL()));
endpointLocation = webResponse.getResponseHeaderValue("location");
endpointLocationUri = URI.create(endpointLocation);
assertEquals("http://localhost:8081/tenant-restore-path-absolute-redirect",
endpointLocationUri.getScheme() + "://" + endpointLocationUri.getAuthority()
+ endpointLocationUri.getPath());

assertFalse(endpointLocationUri.getQuery().contains("code="));
assertFalse(endpointLocationUri.getQuery().contains("state="));
assertTrue(endpointLocationUri.getQuery().contains("custom=customValue"));

webResponse = webClient.loadWebResponse(new WebRequest(endpointLocationUri.toURL()));
assertEquals(200, webResponse.getStatusCode());
assertEquals("http://localhost:8081/tenant-restore-path-absolute-redirect", webResponse.getContentAsString());

webClient.getCookieManager().clearCookies();
}
}

@Test
public void testCodeFlowForceHttpsRedirectUriAndPkceMissingCodeVerifier() throws Exception {
try (final WebClient webClient = createWebClient()) {
Expand Down
Loading