From e404b61c960eb063c31bef70213928c42008b6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ma=C5=82ek?= Date: Mon, 7 Nov 2022 21:40:59 +0100 Subject: [PATCH] Match Apereo CAS behavior with regard to single log-out CAS "official" client libraries expect to receive single log-out requests as logoutRequest POST parameter, which is not described in CAS protocol specs. Fixes #57 --- .../org/keycloak/protocol/cas/CASLoginProtocol.java | 2 +- .../keycloak/protocol/cas/utils/LogoutHelper.java | 11 +++++++++-- .../org/keycloak/protocol/cas/LogoutHelperTest.java | 12 +++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java b/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java index 7eb67fc..d8b7a86 100644 --- a/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java +++ b/src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java @@ -143,8 +143,8 @@ public Response backchannelLogout(UserSessionModel userSession, AuthenticatedCli } 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) { diff --git a/src/main/java/org/keycloak/protocol/cas/utils/LogoutHelper.java b/src/main/java/org/keycloak/protocol/cas/utils/LogoutHelper.java index ec365cd..5e26e8d 100644 --- a/src/main/java/org/keycloak/protocol/cas/utils/LogoutHelper.java +++ b/src/main/java/org/keycloak/protocol/cas/utils/LogoutHelper.java @@ -2,8 +2,11 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; +import org.apache.http.message.BasicNameValuePair; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.keycloak.connections.httpclient.HttpClientProvider; @@ -16,6 +19,8 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; +import java.util.LinkedList; +import java.util.List; public class LogoutHelper { //although it looks alike, the CAS SLO protocol has nothing to do with SAML; so we build the format @@ -25,12 +30,14 @@ public class LogoutHelper { " $SESSION_IDENTIFIER\n" + ""; - public static HttpEntity buildSingleLogoutRequest(String serviceTicket) { + public static HttpEntity buildSingleLogoutRequest(String serviceTicket) throws IOException { String id = "ID_" + UUID.randomUUID().toString(); String issueInstant = new SimpleDateFormat("yyyy-MM-dd'T'H:mm:ss").format(new Date()); String document = TEMPLATE.replace("$ID", id).replace("$ISSUE_INSTANT", issueInstant) .replace("$SESSION_IDENTIFIER", serviceTicket); - return new StringEntity(document, ContentType.APPLICATION_XML.withCharset(StandardCharsets.UTF_8)); + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair("logoutRequest", document)); + return new UrlEncodedFormEntity(parameters); } public static void postWithRedirect(KeycloakSession session, String url, HttpEntity postBody) throws IOException { diff --git a/src/test/java/org/keycloak/protocol/cas/LogoutHelperTest.java b/src/test/java/org/keycloak/protocol/cas/LogoutHelperTest.java index 76169df..b8b6a40 100644 --- a/src/test/java/org/keycloak/protocol/cas/LogoutHelperTest.java +++ b/src/test/java/org/keycloak/protocol/cas/LogoutHelperTest.java @@ -1,6 +1,8 @@ package org.keycloak.protocol.cas; import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; import org.junit.Test; import org.keycloak.protocol.cas.utils.LogoutHelper; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; @@ -8,6 +10,9 @@ import org.w3c.dom.Document; import org.w3c.dom.Node; +import java.util.List; +import java.util.stream.Collectors; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -15,7 +20,12 @@ public class LogoutHelperTest { @Test public void testLogoutRequest() throws Exception { HttpEntity requestEntity = LogoutHelper.buildSingleLogoutRequest("ST-test"); - Document doc = DocumentUtil.getDocument(requestEntity.getContent()); + + List parameters = URLEncodedUtils.parse(requestEntity).stream().filter(parameter -> "logoutRequest".equals(parameter.getName())).collect(Collectors.toList()); + assertEquals(1, parameters.size()); + + String logoutRequest = parameters.get(0).getValue(); + Document doc = DocumentUtil.getDocument(logoutRequest); assertEquals("LogoutRequest", doc.getDocumentElement().getLocalName()); assertEquals(JBossSAMLURIConstants.PROTOCOL_NSURI.get(), doc.getDocumentElement().getNamespaceURI());