From 755fd78fa0ee0f2a67417a119382c63e02c1091e Mon Sep 17 00:00:00 2001
From: Alexandre Rocha Wendling <alexandrerw@celepar.pr.gov.br>
Date: Tue, 16 Jul 2024 14:15:23 +0000
Subject: [PATCH] Proxy ticket service and proxy ticket validation Proxy endpoints improvements suggested by Jacek Kowalski Add ticket type to storage key Rename isreuse to isReusable Remove "parsing" of "codeUUID" that is String, not UUID Improve error reporting in CAS ticket validation

---
 src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java |   69 ++++++++++++++++++++++++----------
 1 files changed, 49 insertions(+), 20 deletions(-)

diff --git a/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java b/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java
index dd08b5b..52dc060 100644
--- a/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java
+++ b/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java
@@ -1,21 +1,23 @@
 package org.keycloak.protocol.cas;
 
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
 import org.apache.http.HttpEntity;
 import org.jboss.logging.Logger;
 import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.events.Details;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.forms.login.LoginFormsProvider;
 import org.keycloak.models.*;
 import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.cas.endpoints.AbstractValidateEndpoint;
 import org.keycloak.protocol.cas.utils.LogoutHelper;
-import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.ErrorPage;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.sessions.AuthenticationSessionModel;
 
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.net.URI;
 
@@ -25,14 +27,22 @@
     public static final String LOGIN_PROTOCOL = "cas";
 
     public static final String SERVICE_PARAM = "service";
+    public static final String TARGET_PARAM = "TARGET";
     public static final String RENEW_PARAM = "renew";
     public static final String GATEWAY_PARAM = "gateway";
     public static final String TICKET_PARAM = "ticket";
     public static final String FORMAT_PARAM = "format";
+    public static final String PGTURL_PARAM = "pgtUrl";
+    public static final String TARGET_SERVICE_PARAM = "targetService";
+    public static final String PGT_PARAM = "pgt";
 
     public static final String TICKET_RESPONSE_PARAM = "ticket";
+    public static final String SAMLART_RESPONSE_PARAM = "SAMLart";
 
     public static final String SERVICE_TICKET_PREFIX = "ST-";
+    public static final String PROXY_GRANTING_TICKET_IOU_PREFIX = "PGTIOU-";
+    public static final String PROXY_GRANTING_TICKET_PREFIX = "PGT-";
+    public static final String PROXY_TICKET_PREFIX = "PT-";
     public static final String SESSION_SERVICE_TICKET = "service_ticket";
 
     public static final String LOGOUT_REDIRECT_URI = "CAS_LOGOUT_REDIRECT_URI";
@@ -85,15 +95,22 @@
     }
 
     @Override
-    public Response authenticated(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        ClientSessionCode<AuthenticatedClientSessionModel> accessCode = new ClientSessionCode<>(session, realm, clientSession);
+    public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+        AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession();
 
-        String service = clientSession.getRedirectUri();
+        String service = authSession.getRedirectUri();
         //TODO validate service
 
-        String code = accessCode.getOrGenerateCode();
         KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(service);
-        uriBuilder.queryParam(TICKET_RESPONSE_PARAM, SERVICE_TICKET_PREFIX + code);
+
+        String loginTicket = AbstractValidateEndpoint.getST(session, clientSession, service);
+
+        if (authSession.getClientNotes().containsKey(CASLoginProtocol.TARGET_PARAM)) {
+            // This was a SAML 1.1 auth request so return the ticket ID as "SAMLart" instead of "ticket"
+            uriBuilder.queryParam(SAMLART_RESPONSE_PARAM, loginTicket);
+        } else {
+            uriBuilder.queryParam(TICKET_RESPONSE_PARAM, loginTicket);
+        }
 
         URI redirectUri = uriBuilder.build();
 
@@ -103,11 +120,16 @@
 
     @Override
     public Response sendError(AuthenticationSessionModel authSession, Error error) {
-        return Response.serverError().entity(error).build();
+        if (authSession.getClientNotes().containsKey(CASLoginProtocol.GATEWAY_PARAM)) {
+            if (error == Error.PASSIVE_INTERACTION_REQUIRED || error == Error.PASSIVE_LOGIN_REQUIRED) {
+                return Response.status(302).location(URI.create(authSession.getRedirectUri())).build();
+            }
+        }
+        return ErrorPage.error(session, authSession, Response.Status.INTERNAL_SERVER_ERROR, error.name());
     }
 
     @Override
-    public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+    public Response backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
         String logoutUrl = clientSession.getRedirectUri();
         String serviceTicket = clientSession.getNote(CASLoginProtocol.SESSION_SERVICE_TICKET);
         //check if session is fully authenticated (i.e. serviceValidate has been called)
@@ -115,12 +137,12 @@
             sendSingleLogoutRequest(logoutUrl, serviceTicket);
         }
         ClientModel client = clientSession.getClient();
-        new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession);
+        return new ResourceAdminManager(session).logoutClientSession(realm, client, clientSession);
     }
 
     private void sendSingleLogoutRequest(String logoutUrl, String serviceTicket) {
-        HttpEntity requestEntity = LogoutHelper.buildSingleLogoutRequest(serviceTicket);
         try {
+            HttpEntity requestEntity = LogoutHelper.buildSingleLogoutRequest(serviceTicket);
             LogoutHelper.postWithRedirect(session, logoutUrl, requestEntity);
             logger.debug("Sent CAS single logout for service " + logoutUrl);
         } catch (IOException e) {
@@ -135,17 +157,24 @@
     }
 
     @Override
-    public Response finishLogout(UserSessionModel userSession) {
+    public Response finishBrowserLogout(UserSessionModel userSession, AuthenticationSessionModel logoutSession) {
         String redirectUri = userSession.getNote(CASLoginProtocol.LOGOUT_REDIRECT_URI);
 
-        event.event(EventType.LOGOUT);
-        event.user(userSession.getUser()).session(userSession).success();
-        LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class).setSuccess("Logout successful");
+        event.event(EventType.LOGOUT)
+            .user(userSession.getUser())
+            .session(userSession)
+            .detail(Details.USERNAME, userSession.getUser().getUsername());
+
         if (redirectUri != null) {
-            infoPage.setAttribute("pageRedirectUri", redirectUri);
-        } else {
-            infoPage.setAttribute("skipLink", true);
+            event.detail(Details.REDIRECT_URI, redirectUri);
+            event.success();
+            return Response.status(302).location(URI.create(redirectUri)).build();
         }
+
+        event.success();
+
+        LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class).setSuccess("Logout successful");
+        infoPage.setAttribute("skipLink", true);
         return infoPage.createInfoPage();
     }
 

--
Gitblit v1.9.1