Skip to content

Commit

Permalink
fix differing realm support for custom context paths
Browse files Browse the repository at this point in the history
The original logic using a regex for parsing the realm was replaced by a more
straight-forward approach. Every handler that needs realm-specific paths now
gets a base url configuration injected, and the url configuration gets a
convenience wrapper method to extract realm (and hostname) from the routing
context based on the path parameter :realm.

Signed-off-by: Kai Helbig <kai.helbig@tngtech.com>
  • Loading branch information
ostrya committed Oct 18, 2024
1 parent 9451e29 commit d84f81f
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.tngtech.keycloakmock.impl;

import com.tngtech.keycloakmock.api.ServerConfig;
import io.vertx.ext.web.RoutingContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
Expand Down Expand Up @@ -60,6 +61,13 @@ public UrlConfiguration forRequestContext(
return new UrlConfiguration(this, requestHost, requestRealm);
}

@Nonnull
public UrlConfiguration forRequestContext(RoutingContext routingContext) {
String requestHostname = routingContext.request().getHeader("Host");
String requestRealm = routingContext.pathParam("realm");
return new UrlConfiguration(this, requestHostname, requestRealm);
}

@Nonnull
URI getBaseUrl() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import com.tngtech.keycloakmock.impl.handler.LogoutRoute;
import com.tngtech.keycloakmock.impl.handler.OptionalBasicAuthHandler;
import com.tngtech.keycloakmock.impl.handler.OutOfBandLoginRoute;
import com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler;
import com.tngtech.keycloakmock.impl.handler.ResourceFileHandler;
import com.tngtech.keycloakmock.impl.handler.TokenRoute;
import com.tngtech.keycloakmock.impl.handler.WellKnownRoute;
Expand Down Expand Up @@ -134,7 +133,6 @@ HttpServerOptions provideHttpServerOptions(
Router provideRouter(
@Nonnull UrlConfiguration defaultConfiguration,
@Nonnull Vertx vertx,
@Nonnull RequestUrlConfigurationHandler requestUrlConfigurationHandler,
@Nonnull CommonHandler commonHandler,
@Nonnull FailureHandler failureHandler,
@Nonnull JwksRoute jwksRoute,
Expand All @@ -154,7 +152,6 @@ Router provideRouter(
Router router = Router.router(vertx);
router
.route()
.handler(requestUrlConfigurationHandler)
.handler(commonHandler)
.failureHandler(failureHandler)
.failureHandler(ErrorHandler.create(vertx));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.tngtech.keycloakmock.impl.handler;

import static com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler.CTX_REQUEST_CONFIGURATION;

import com.tngtech.keycloakmock.impl.UrlConfiguration;
import com.tngtech.keycloakmock.impl.helper.RedirectHelper;
import com.tngtech.keycloakmock.impl.helper.UserInputSanitizer;
Expand Down Expand Up @@ -30,12 +28,16 @@ public class AuthenticationRoute implements Handler<RoutingContext> {

@Nonnull private final SessionRepository sessionRepository;
@Nonnull private final RedirectHelper redirectHelper;
@Nonnull private final UrlConfiguration baseConfiguration;

@Inject
AuthenticationRoute(
@Nonnull SessionRepository sessionRepository, @Nonnull RedirectHelper redirectHelper) {
@Nonnull SessionRepository sessionRepository,
@Nonnull RedirectHelper redirectHelper,
@Nonnull UrlConfiguration baseConfiguration) {
this.sessionRepository = sessionRepository;
this.redirectHelper = redirectHelper;
this.baseConfiguration = baseConfiguration;
}

@Override
Expand All @@ -59,7 +61,7 @@ public void handle(@Nonnull RoutingContext routingContext) {
.map(s -> Arrays.asList(s.split(",")))
.orElseGet(Collections::emptyList);

UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
UrlConfiguration requestConfiguration = baseConfiguration.forRequestContext(routingContext);

PersistentSession session =
request.toSession(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.tngtech.keycloakmock.impl.handler;

import static com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler.CTX_REQUEST_CONFIGURATION;
import static com.tngtech.keycloakmock.impl.helper.RedirectHelper.KEYCLOAK_SESSION_COOKIE;

import com.tngtech.keycloakmock.impl.UrlConfiguration;
Expand Down Expand Up @@ -35,15 +34,18 @@ public class LoginRoute implements Handler<RoutingContext> {
@Nonnull private final SessionRepository sessionRepository;
@Nonnull private final RedirectHelper redirectHelper;
@Nonnull private final TemplateEngine engine;
@Nonnull private final UrlConfiguration baseConfiguration;

@Inject
LoginRoute(
@Nonnull SessionRepository sessionRepository,
@Nonnull RedirectHelper redirectHelper,
@Nonnull TemplateEngine engine) {
@Nonnull TemplateEngine engine,
@Nonnull UrlConfiguration baseConfiguration) {
this.sessionRepository = sessionRepository;
this.redirectHelper = redirectHelper;
this.engine = engine;
this.baseConfiguration = baseConfiguration;
}

@Override
Expand Down Expand Up @@ -73,7 +75,7 @@ public void handle(@Nonnull RoutingContext routingContext) {
.setResponseMode(routingContext.queryParams().get(RESPONSE_MODE))
.build();

UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
UrlConfiguration requestConfiguration = baseConfiguration.forRequestContext(routingContext);
if (existingSession.isPresent()) {
PersistentSession oldSession = existingSession.get();
PersistentSession newSession =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.tngtech.keycloakmock.impl.handler;

import static com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler.CTX_REQUEST_CONFIGURATION;
import static com.tngtech.keycloakmock.impl.helper.RedirectHelper.KEYCLOAK_SESSION_COOKIE;

import com.tngtech.keycloakmock.impl.UrlConfiguration;
Expand Down Expand Up @@ -30,12 +29,16 @@ public class LogoutRoute implements Handler<RoutingContext> {

@Nonnull private final SessionRepository sessionRepository;
@Nonnull private final RedirectHelper redirectHelper;
@Nonnull private final UrlConfiguration baseConfiguration;

@Inject
LogoutRoute(
@Nonnull SessionRepository sessionRepository, @Nonnull RedirectHelper redirectHelper) {
@Nonnull SessionRepository sessionRepository,
@Nonnull RedirectHelper redirectHelper,
@Nonnull UrlConfiguration baseConfiguration) {
this.sessionRepository = sessionRepository;
this.redirectHelper = redirectHelper;
this.baseConfiguration = baseConfiguration;
}

@Override
Expand All @@ -44,7 +47,7 @@ public void handle(@Nonnull RoutingContext routingContext) {
if (redirectUri == null) { // for backwards compatibility:
redirectUri = routingContext.queryParams().get(LEGACY_REDIRECT_URI);
}
UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
UrlConfiguration requestConfiguration = baseConfiguration.forRequestContext(routingContext);
invalidateSession(routingContext);
routingContext
.response()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.tngtech.keycloakmock.impl.handler;

import static com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler.CTX_REQUEST_CONFIGURATION;

import com.tngtech.keycloakmock.impl.UrlConfiguration;
import com.tngtech.keycloakmock.impl.helper.TokenHelper;
import com.tngtech.keycloakmock.impl.session.AdHocSession;
Expand All @@ -27,11 +25,16 @@ public class TokenRoute implements Handler<RoutingContext> {

@Nonnull private final SessionRepository sessionRepository;
@Nonnull private final TokenHelper tokenHelper;
@Nonnull private final UrlConfiguration baseConfiguration;

@Inject
TokenRoute(@Nonnull SessionRepository sessionRepository, @Nonnull TokenHelper tokenHelper) {
TokenRoute(
@Nonnull SessionRepository sessionRepository,
@Nonnull TokenHelper tokenHelper,
@Nonnull UrlConfiguration baseConfiguration) {
this.sessionRepository = sessionRepository;
this.tokenHelper = tokenHelper;
this.baseConfiguration = baseConfiguration;
}

@Override
Expand All @@ -58,7 +61,7 @@ public void handle(@Nonnull RoutingContext routingContext) {
private void handleAuthorizationCodeFlow(RoutingContext routingContext) {
// here again we use the equality of authorization code and session ID
String sessionId = routingContext.request().getFormAttribute(CODE);
UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
UrlConfiguration requestConfiguration = baseConfiguration.forRequestContext(routingContext);
String token =
Optional.ofNullable(sessionRepository.getSession(sessionId))
.map(s -> tokenHelper.getToken(s, requestConfiguration))
Expand Down Expand Up @@ -105,7 +108,7 @@ private void handlePasswordFlow(RoutingContext routingContext) {
routingContext.fail(400);
return;
}
UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
UrlConfiguration requestConfiguration = baseConfiguration.forRequestContext(routingContext);
String password = routingContext.request().getFormAttribute("password");

Session session =
Expand Down Expand Up @@ -142,7 +145,8 @@ private void handleClientCredentialsFlow(RoutingContext routingContext) {
return;
}

final UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
final UrlConfiguration requestConfiguration =
baseConfiguration.forRequestContext(routingContext);

final Session session =
AdHocSession.fromClientIdUsernameAndPassword(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.tngtech.keycloakmock.impl.handler;

import static com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler.CTX_REQUEST_CONFIGURATION;

import com.tngtech.keycloakmock.impl.UrlConfiguration;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonArray;
Expand All @@ -16,12 +14,16 @@
@Singleton
public class WellKnownRoute implements Handler<RoutingContext> {

@Nonnull private final UrlConfiguration baseConfiguration;

@Inject
WellKnownRoute() {}
WellKnownRoute(@Nonnull UrlConfiguration baseConfiguration) {
this.baseConfiguration = baseConfiguration;
}

@Override
public void handle(@Nonnull RoutingContext routingContext) {
UrlConfiguration requestConfiguration = routingContext.get(CTX_REQUEST_CONFIGURATION);
UrlConfiguration requestConfiguration = baseConfiguration.forRequestContext(routingContext);
routingContext
.response()
.putHeader("content-type", "application/json")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import static com.tngtech.keycloakmock.api.ServerConfig.aServerConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.tngtech.keycloakmock.api.ServerConfig;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down Expand Up @@ -118,6 +122,22 @@ void context_parameters_are_used_correctly(
assertThat(urlConfiguration.getIssuer()).hasToString(expected);
}

@ParameterizedTest
@MethodSource("request_host_and_realm_and_expected")
void context_parameters_are_extracted_correctly(
String requestHost, String requestRealm, String expected) {
RoutingContext routingContext = mock();
HttpServerRequest httpServerRequest = mock();
when(routingContext.request()).thenReturn(httpServerRequest);
when(httpServerRequest.getHeader("Host")).thenReturn(requestHost);
when(routingContext.pathParam("realm")).thenReturn(requestRealm);

urlConfiguration =
new UrlConfiguration(aServerConfig().build()).forRequestContext(routingContext);

assertThat(urlConfiguration.getIssuer()).hasToString(expected);
}

@ParameterizedTest
@MethodSource("request_host_and_realm_and_expected_with_no_context_path")
void context_parameters_are_used_correctly_for_server_config_with_no_context_path(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.tngtech.keycloakmock.impl.handler;

import static com.tngtech.keycloakmock.impl.handler.RequestUrlConfigurationHandler.CTX_REQUEST_CONFIGURATION;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
Expand Down Expand Up @@ -46,7 +45,8 @@ class AuthenticationRouteTest {
@Mock private RoutingContext routingContext;
@Mock private HttpServerRequest request;
@Mock private HttpServerResponse response;
@Mock private UrlConfiguration urlConfiguration;
@Mock private UrlConfiguration baseConfiguration;
@Mock private UrlConfiguration contextConfiguration;
@Mock private SessionRequest sessionRequest;
@Mock private PersistentSession session;
@Mock private Cookie cookie;
Expand All @@ -62,33 +62,35 @@ void setup() {

@Test
void missing_session_causes_error() {
uut = new AuthenticationRoute(sessionRepository, redirectHelper);
uut = new AuthenticationRoute(sessionRepository, redirectHelper, baseConfiguration);

uut.handle(routingContext);

verify(sessionRepository).getRequest(SESSION_ID);
verify(routingContext).fail(404);
verifyNoMoreInteractions(urlConfiguration, sessionRepository, redirectHelper);
verifyNoMoreInteractions(
baseConfiguration, contextConfiguration, sessionRepository, redirectHelper);
}

@Test
void missing_username_causes_error() {
doReturn(sessionRequest).when(sessionRepository).getRequest(SESSION_ID);
doReturn(request).when(routingContext).request();

uut = new AuthenticationRoute(sessionRepository, redirectHelper);
uut = new AuthenticationRoute(sessionRepository, redirectHelper, baseConfiguration);

uut.handle(routingContext);

verify(sessionRepository).getRequest(SESSION_ID);
verify(routingContext).fail(400);
verifyNoMoreInteractions(urlConfiguration, sessionRepository, redirectHelper);
verifyNoMoreInteractions(
baseConfiguration, contextConfiguration, sessionRepository, redirectHelper);
}

@Test
void correct_token_is_created() {
setupValidRequest();
uut = new AuthenticationRoute(sessionRepository, redirectHelper);
uut = new AuthenticationRoute(sessionRepository, redirectHelper, baseConfiguration);

uut.handle(routingContext);

Expand All @@ -108,13 +110,13 @@ private void setupValidRequest() {
doReturn(request).when(routingContext).request();
doReturn(sessionRequest).when(sessionRepository).getRequest(SESSION_ID);
doReturn(session).when(sessionRequest).toSession(eq(USER), anyList());
doReturn(urlConfiguration).when(routingContext).get(CTX_REQUEST_CONFIGURATION);
doReturn(HOSTNAME).when(urlConfiguration).getHostname();
doReturn(contextConfiguration).when(baseConfiguration).forRequestContext(routingContext);
doReturn(HOSTNAME).when(contextConfiguration).getHostname();
doReturn(response).when(routingContext).response();
doReturn(response).when(response).addCookie(any(Cookie.class));
doReturn(response).when(response).putHeader(eq("location"), anyString());
doReturn(response).when(response).setStatusCode(anyInt());
doReturn(cookie).when(redirectHelper).getSessionCookie(session, urlConfiguration);
doReturn(REDIRECT_URI).when(redirectHelper).getRedirectLocation(session, urlConfiguration);
doReturn(cookie).when(redirectHelper).getSessionCookie(session, contextConfiguration);
doReturn(REDIRECT_URI).when(redirectHelper).getRedirectLocation(session, contextConfiguration);
}
}
Loading

0 comments on commit d84f81f

Please sign in to comment.