Skip to content

Commit

Permalink
Store state in cookie instead of service URL
Browse files Browse the repository at this point in the history
  • Loading branch information
kberzinch committed Sep 18, 2024
1 parent 79fdaff commit 3a80431
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
package io.github.johnjcool.keycloak.broker.cas;

import static io.github.johnjcool.keycloak.broker.cas.util.UrlHelper.PROVIDER_PARAMETER_STATE;
import static io.github.johnjcool.keycloak.broker.cas.util.UrlHelper.PROVIDER_PARAMETER_TICKET;
import static io.github.johnjcool.keycloak.broker.cas.util.UrlHelper.createAuthenticationUrl;
import static io.github.johnjcool.keycloak.broker.cas.util.UrlHelper.createLogoutUrl;
import static io.github.johnjcool.keycloak.broker.cas.util.UrlHelper.createValidateServiceUrl;

import io.github.johnjcool.keycloak.broker.cas.model.ServiceResponse;
import io.github.johnjcool.keycloak.broker.cas.model.Success;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.core.*;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
Expand Down Expand Up @@ -48,6 +43,8 @@ public class CasIdentityProvider extends AbstractIdentityProvider<CasIdentityPro

public static final String USER_ATTRIBUTES = "UserAttributes";

private static final String STATE_COOKIE_NAME = "__Host-cas_state";

private static final Unmarshaller unmarshaller;

static {
Expand All @@ -65,13 +62,15 @@ public CasIdentityProvider(

@Override
public Response performLogin(final AuthenticationRequest request) {
try {
URI authenticationUrl = createAuthenticationUrl(getConfig(), request).build();
return Response.seeOther(authenticationUrl).build();
} catch (Exception e) {
throw new IdentityBrokerException(
"Could not send authentication request to cas provider.", e);
}
return Response.seeOther(createAuthenticationUrl(getConfig(), request).build())
.cookie(
new NewCookie.Builder(STATE_COOKIE_NAME)
.value(request.getState().getEncoded())
.httpOnly(true)
.secure(true)
.path("/")
.build())
.build();
}

@Override
Expand All @@ -80,7 +79,7 @@ public Response keycloakInitiatedBrowserLogout(
final UserSessionModel userSession,
final UriInfo uriInfo,
final RealmModel realm) {
URI logoutUrl = createLogoutUrl(getConfig(), userSession, realm, uriInfo).build();
URI logoutUrl = createLogoutUrl(getConfig(), realm, uriInfo).build();
return Response.status(302).location(logoutUrl).build();
}

Expand All @@ -101,7 +100,6 @@ public Endpoint callback(
public static final class Endpoint {
private final AuthenticationCallback callback;
private final RealmModel realm;
private final EventBuilder event;
private final KeycloakSession session;
private final ClientConnection clientConnection;
private final HttpHeaders headers;
Expand All @@ -115,7 +113,6 @@ public static final class Endpoint {
final CasIdentityProvider provider) {
this.callback = callback;
this.realm = realm;
this.event = event;
this.provider = provider;
this.session = provider.session;
this.headers = session.getContext().getRequestHeaders();
Expand All @@ -126,19 +123,12 @@ public static final class Endpoint {
@GET
public Response authResponse(
@QueryParam(PROVIDER_PARAMETER_TICKET) final String ticket,
@QueryParam(PROVIDER_PARAMETER_STATE) final String state) {
try {
BrokeredIdentityContext federatedIdentity =
getFederatedIdentity(config, ticket, session.getContext().getUri(), state);

return callback.authenticated(federatedIdentity);
} catch (Exception e) {
logger.error("Failed to complete CAS authentication", e);
}
event.event(EventType.LOGIN);
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
return ErrorPage.error(
session, null, Status.EXPECTATION_FAILED, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
@CookieParam(STATE_COOKIE_NAME) final Cookie stateCookie) {
BrokeredIdentityContext federatedIdentity =
getFederatedIdentity(
config, ticket, session.getContext().getUri(), stateCookie.getValue());

return callback.authenticated(federatedIdentity);
}

@GET
Expand Down Expand Up @@ -177,11 +167,7 @@ private BrokeredIdentityContext getFederatedIdentity(
logger.debug("Current state value: " + state);
try (SimpleHttp.Response response =
SimpleHttp.doGet(
createValidateServiceUrl(config, ticket, uriInfo, state)
.build()
.toURL()
.toString()
.replace("+", "%2B"),
createValidateServiceUrl(config, ticket, uriInfo).build().toURL().toString(),
session)
.asResponse()) {
if (response.getStatus() != 200) {
Expand Down Expand Up @@ -216,7 +202,7 @@ private BrokeredIdentityContext getFederatedIdentity(
user.getContextData().put(USER_ATTRIBUTES, success.getAttributes());
user.setIdp(provider);
AuthenticationSessionModel authSession =
this.callback.getAndVerifyAuthenticationSession(state.replace(' ', '+'));
this.callback.getAndVerifyAuthenticationSession(state);
session.getContext().setAuthenticationSession(authSession);
user.setAuthenticationSession(authSession);
return user;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import jakarta.ws.rs.core.UriInfo;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.services.resources.IdentityBrokerService;
import org.keycloak.services.resources.RealmsResource;

Expand All @@ -15,7 +14,6 @@ public final class UrlHelper {
private static final String PROVIDER_PARAMETER_RENEW = "renew";
private static final String PROVIDER_PARAMETER_GATEWAY = "gateway";
public static final String PROVIDER_PARAMETER_TICKET = "ticket";
public static final String PROVIDER_PARAMETER_STATE = "state";

private UrlHelper() {
// util
Expand All @@ -25,9 +23,7 @@ public static UriBuilder createAuthenticationUrl(
final CasIdentityProviderConfig config, final AuthenticationRequest request) {
UriBuilder builder =
UriBuilder.fromUri(config.getCasServerLoginUrl())
.queryParam(
PROVIDER_PARAMETER_SERVICE,
createServiceUrl(request.getRedirectUri(), request.getState().getEncoded()));
.queryParam(PROVIDER_PARAMETER_SERVICE, request.getRedirectUri());
if (config.isRenew()) {
builder.queryParam(PROVIDER_PARAMETER_RENEW, config.isRenew());
}
Expand All @@ -38,39 +34,26 @@ public static UriBuilder createAuthenticationUrl(
}

public static UriBuilder createValidateServiceUrl(
final CasIdentityProviderConfig config,
final String ticket,
final UriInfo uriInfo,
final String state) {
final CasIdentityProviderConfig config, final String ticket, final UriInfo uriInfo) {
UriBuilder builder =
UriBuilder.fromUri(config.getCasServiceValidateUrl())
.queryParam(PROVIDER_PARAMETER_TICKET, ticket)
.queryParam(
PROVIDER_PARAMETER_SERVICE,
createServiceUrl(uriInfo.getAbsolutePath().toString(), state));
.queryParam(PROVIDER_PARAMETER_SERVICE, uriInfo.getAbsolutePath().toString());
if (config.isRenew()) {
builder.queryParam(PROVIDER_PARAMETER_RENEW, config.isRenew());
}
return builder;
}

public static UriBuilder createLogoutUrl(
final CasIdentityProviderConfig config,
final UserSessionModel userSession,
final RealmModel realm,
final UriInfo uriInfo) {
final CasIdentityProviderConfig config, final RealmModel realm, final UriInfo uriInfo) {
final String redirect =
RealmsResource.brokerUrl(uriInfo)
.path(IdentityBrokerService.class, "getEndpoint")
.path(CasIdentityProvider.Endpoint.class, "logoutResponse")
.queryParam(PROVIDER_PARAMETER_STATE, userSession.getId())
.build(realm.getName(), config.getAlias())
.toString();
return UriBuilder.fromUri(config.getCasServerLogoutUrl())
.queryParam(PROVIDER_PARAMETER_SERVICE, redirect);
}

private static String createServiceUrl(final String serviceUrlPrefix, final String state) {
return String.format("%s?%s=%s", serviceUrlPrefix, PROVIDER_PARAMETER_STATE, state);
}
}

0 comments on commit 3a80431

Please sign in to comment.