mirror of https://github.com/jacekkow/keycloak-protocol-cas

Mateusz Małek
2022-11-07 281a7ed1c82d92bbf09c18ca1bbdff19d1bc0e05
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
3 files modified
25 ■■■■ changed files
src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java 2 ●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/utils/LogoutHelper.java 11 ●●●● patch | view | raw | blame | history
src/test/java/org/keycloak/protocol/cas/LogoutHelperTest.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java
@@ -143,8 +143,8 @@
    }
    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) {
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 @@
            "  <samlp:SessionIndex>$SESSION_IDENTIFIER</samlp:SessionIndex>\n" +
            "</samlp:LogoutRequest>";
    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<NameValuePair> 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 {
src/test/java/org/keycloak/protocol/cas/LogoutHelperTest.java
@@ -1,12 +1,17 @@
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;
import org.keycloak.saml.common.util.DocumentUtil;
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 @@
    @Test
    public void testLogoutRequest() throws Exception {
        HttpEntity requestEntity = LogoutHelper.buildSingleLogoutRequest("ST-test");
        Document doc = DocumentUtil.getDocument(requestEntity.getContent());
        List<NameValuePair> 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());