From 6aa237926885bc2ae452db2eef2d1b32ee172bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LELEU?= Date: Fri, 21 Mar 2014 14:10:33 +0100 Subject: [PATCH] CASC-220: Support front channel SLO logout Updates after Misagh's code review --- .../client/session/SingleSignOutFilter.java | 21 +----- .../client/session/SingleSignOutHandler.java | 75 ++++++++++++++++--- .../session/LogoutMessageGenerator.java | 7 +- .../client/tomcat/v6/SingleSignOutValve.java | 36 +++------ .../client/tomcat/v7/SingleSignOutValve.java | 35 +++------ 5 files changed, 90 insertions(+), 84 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java index 92235dc..33b2094 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jasig.cas.client.util.AbstractConfigurationFilter; -import org.jasig.cas.client.util.CommonUtils; /** * Implements the Single Sign Out protocol. It handles registering the session and destroying the session. @@ -85,25 +84,9 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter { final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpServletResponse response = (HttpServletResponse) servletResponse; - if (handler.isTokenRequest(request)) { - handler.recordSession(request); - } else if (handler.isBackChannelLogoutRequest(request)) { - handler.destroySession(request); - // Do not continue up filter chain - return; - } else if (handler.isFrontChannelLogoutRequest(request)) { - handler.destroySession(request); - // redirection url to the CAS server - final String redirectionUrl = handler.computeRedirectionToServer(request); - if (redirectionUrl != null) { - CommonUtils.sendRedirect(response, redirectionUrl); - } - return; - } else { - logger.trace("Ignoring URI {}", request.getRequestURI()); + if (handler.process(request, response)) { + filterChain.doFilter(servletRequest, servletResponse); } - - filterChain.doFilter(servletRequest, servletResponse); } public void destroy() { diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java index 1b40afd..11e43c3 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java @@ -26,6 +26,7 @@ import java.util.zip.Inflater; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.codec.binary.Base64; @@ -153,7 +154,7 @@ public final class SingleSignOutHandler { * * @return True if request contains authentication token, false otherwise. */ - public boolean isTokenRequest(final HttpServletRequest request) { + protected boolean isTokenRequest(final HttpServletRequest request) { return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters)); } @@ -165,7 +166,7 @@ public final class SingleSignOutHandler { * * @return True if request is logout request, false otherwise. */ - public boolean isBackChannelLogoutRequest(final HttpServletRequest request) { + protected boolean isBackChannelLogoutRequest(final HttpServletRequest request) { return "POST".equals(request.getMethod()) && !isMultipartRequest(request) && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, @@ -179,18 +180,46 @@ public final class SingleSignOutHandler { * * @return True if request is logout request, false otherwise. */ - public boolean isFrontChannelLogoutRequest(final HttpServletRequest request) { + protected boolean isFrontChannelLogoutRequest(final HttpServletRequest request) { return "GET".equals(request.getMethod()) && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.frontLogoutParameterName)); } + /** + * Process a request regarding the SLO process: record the session or destroy it. + * + * @param request the incoming HTTP request. + * @param response the HTTP response. + * @return if the request should continue to be processed. + */ + public boolean process(final HttpServletRequest request, final HttpServletResponse response) { + if (isTokenRequest(request)) { + recordSession(request); + } else if (isBackChannelLogoutRequest(request)) { + destroySession(request); + // Do not continue up filter chain + return false; + } else if (isFrontChannelLogoutRequest(request)) { + destroySession(request); + // redirection url to the CAS server + final String redirectionUrl = computeRedirectionToServer(request); + if (redirectionUrl != null) { + CommonUtils.sendRedirect(response, redirectionUrl); + } + return false; + } else { + logger.trace("Ignoring URI {}", request.getRequestURI()); + } + return true; + } + /** * Associates a token request with the current HTTP session by recording the mapping * in the the configured {@link SessionMappingStorage} container. * * @param request HTTP request containing an authentication token. */ - public void recordSession(final HttpServletRequest request) { + protected void recordSession(final HttpServletRequest request) { final HttpSession session = request.getSession(this.eagerlyCreateSessions); if (session == null) { @@ -215,17 +244,41 @@ public final class SingleSignOutHandler { * @param originalMessage the original logout message. * @return the uncompressed logout message. */ - private String uncompressLogoutMessage(final String originalMessage) { + protected String uncompressLogoutMessage(final String originalMessage) { // base64 decode final byte[] binaryMessage = Base64.decodeBase64(originalMessage); + Inflater decompresser = null; try { // decompress the bytes - final Inflater decompresser = new Inflater(); + decompresser = new Inflater(); decompresser.setInput(binaryMessage); + + /* The received logout message is compressed, so this number (10) is the multiplier of the original size + * of the logout message (binaryMessage.length) to compute the size of the buffer where the logout message + * will be decompressed. + * It's somehow the decompression factor. + * + * For the buffer, we could also have a fixed size for the buffer (like 10k), but I thought that ten times + * would be a sufficient multiplier... + * + * A real test: + * String sessionIndex = "ST-45-fs45646r84ffs1d31f554f5d4f64fg6r8eq5s4d6f4fddsf46-cas"; + * String bm = LogoutMessageGenerator.generateBackChannelLogoutMessage(sessionIndex); + * System.out.println("bm.size = " + bm.length()); + * String fm = new String(Base64.decodeBase64(LogoutMessageGenerator. + * generateFrontChannelLogoutMessage(sessionIndex))); + * System.out.println("fm.size = " + fm.length()); + * + * And the result: + * bm.size = 354 + * fm.size = 224 + * + * So ten times is enough, it's even too much... + */ byte[] result = new byte[binaryMessage.length * 10]; + int resultLength = decompresser.inflate(result); - decompresser.end(); // decode the bytes into a String return new String(result, 0, resultLength, "UTF-8"); @@ -235,6 +288,10 @@ public final class SingleSignOutHandler { } catch (UnsupportedEncodingException e) { logger.error("Unable to decompress logout message", e); throw new RuntimeException(e); + } finally { + if (decompresser != null) { + decompresser.end(); + } } } @@ -243,7 +300,7 @@ public final class SingleSignOutHandler { * * @param request HTTP request containing a CAS logout message. */ - public void destroySession(final HttpServletRequest request) { + protected void destroySession(final HttpServletRequest request) { String logoutMessage; // front channel logout -> the message needs to be base64 decoded + decompressed if ("GET".equals(request.getMethod())) { @@ -284,7 +341,7 @@ public final class SingleSignOutHandler { * @param request The HTTP request. * @return the redirection url to the CAS server. */ - public String computeRedirectionToServer(final HttpServletRequest request) { + protected String computeRedirectionToServer(final HttpServletRequest request) { // relay state value final String relayStateValue = CommonUtils.safeGetParameter(request, this.relayStateParameterName); // if we have a state value -> redirect to the CAS server to continue the logout process diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/session/LogoutMessageGenerator.java b/cas-client-core/src/test/java/org/jasig/cas/client/session/LogoutMessageGenerator.java index b43ea65..30ee253 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/session/LogoutMessageGenerator.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/session/LogoutMessageGenerator.java @@ -1,6 +1,7 @@ package org.jasig.cas.client.session; import java.nio.charset.Charset; +import java.util.Date; import java.util.zip.Deflater; import org.apache.commons.codec.binary.Base64; @@ -16,13 +17,13 @@ public final class LogoutMessageGenerator { private static final String LOGOUT_REQUEST_TEMPLATE = "@NOT_USED@" + + "IssueInstant=\"%s\">@NOT_USED@" + "%s"; public static String generateBackChannelLogoutMessage(String sessionIndex) { - return String.format(LOGOUT_REQUEST_TEMPLATE, sessionIndex); + return String.format(LOGOUT_REQUEST_TEMPLATE, new Date(), sessionIndex); } - + public static String generateFrontChannelLogoutMessage(String sessionIndex) { final String logoutMessage = generateBackChannelLogoutMessage(sessionIndex); final Deflater deflater = new Deflater(); diff --git a/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java b/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java index d483e41..9941651 100644 --- a/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java +++ b/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java @@ -28,7 +28,6 @@ import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.jasig.cas.client.session.SessionMappingStorage; import org.jasig.cas.client.session.SingleSignOutHandler; -import org.jasig.cas.client.util.CommonUtils; /** * Handles logout request messages sent from the CAS server by ending the current @@ -46,57 +45,41 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio private final SingleSignOutHandler handler = new SingleSignOutHandler(); public void setArtifactParameterName(final String name) { - handler.setArtifactParameterName(name); + this.handler.setArtifactParameterName(name); } public void setLogoutParameterName(final String name) { - handler.setLogoutParameterName(name); + this.handler.setLogoutParameterName(name); } public void setFrontLogoutParameterName(final String name) { - handler.setFrontLogoutParameterName(name); + this.handler.setFrontLogoutParameterName(name); } public void setRelayStateParameterName(final String name) { - handler.setRelayStateParameterName(name); + this.handler.setRelayStateParameterName(name); } public void setCasServerUrlPrefix(final String casServerUrlPrefix) { - handler.setCasServerUrlPrefix(casServerUrlPrefix); + this.handler.setCasServerUrlPrefix(casServerUrlPrefix); } public void setSessionMappingStorage(final SessionMappingStorage storage) { - handler.setSessionMappingStorage(storage); + this.handler.setSessionMappingStorage(storage); } /** {@inheritDoc} */ public void start() throws LifecycleException { super.start(); - handler.init(); + this.handler.init(); logger.info("Startup completed."); } /** {@inheritDoc} */ public void invoke(final Request request, final Response response) throws IOException, ServletException { - if (this.handler.isTokenRequest(request)) { - this.handler.recordSession(request); - request.getSessionInternal(true).addSessionListener(this); - } else if (this.handler.isBackChannelLogoutRequest(request)) { - this.handler.destroySession(request); - // Do not proceed up valve chain - return; - } else if (this.handler.isFrontChannelLogoutRequest(request)) { - this.handler.destroySession(request); - // redirection url to the CAS server - final String redirectionUrl = handler.computeRedirectionToServer(request); - if (redirectionUrl != null) { - CommonUtils.sendRedirect(response, redirectionUrl); - } - return; - } else { - logger.debug("Ignoring URI {}", request.getRequestURI()); + if (this.handler.process(request, response)) { + getNext().invoke(request, response); } - getNext().invoke(request, response); } /** {@inheritDoc} */ @@ -111,5 +94,4 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio protected String getName() { return NAME; } - } diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java index 8ecc865..62ac214 100644 --- a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java @@ -29,7 +29,6 @@ import org.apache.catalina.connector.Response; import org.apache.catalina.valves.ValveBase; import org.jasig.cas.client.session.SessionMappingStorage; import org.jasig.cas.client.session.SingleSignOutHandler; -import org.jasig.cas.client.util.CommonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,50 +49,34 @@ public class SingleSignOutValve extends ValveBase implements SessionListener { private final SingleSignOutHandler handler = new SingleSignOutHandler(); public void setArtifactParameterName(final String name) { - handler.setArtifactParameterName(name); + this.handler.setArtifactParameterName(name); } public void setLogoutParameterName(final String name) { - handler.setLogoutParameterName(name); + this.handler.setLogoutParameterName(name); } public void setFrontLogoutParameterName(final String name) { - handler.setFrontLogoutParameterName(name); + this.handler.setFrontLogoutParameterName(name); } public void setRelayStateParameterName(final String name) { - handler.setRelayStateParameterName(name); + this.handler.setRelayStateParameterName(name); } public void setCasServerUrlPrefix(final String casServerUrlPrefix) { - handler.setCasServerUrlPrefix(casServerUrlPrefix); + this.handler.setCasServerUrlPrefix(casServerUrlPrefix); } public void setSessionMappingStorage(final SessionMappingStorage storage) { - handler.setSessionMappingStorage(storage); + this.handler.setSessionMappingStorage(storage); } /** {@inheritDoc} */ public void invoke(final Request request, final Response response) throws IOException, ServletException { - if (this.handler.isTokenRequest(request)) { - this.handler.recordSession(request); - request.getSessionInternal(true).addSessionListener(this); - } else if (this.handler.isBackChannelLogoutRequest(request)) { - this.handler.destroySession(request); - // Do not proceed up valve chain - return; - } else if (this.handler.isFrontChannelLogoutRequest(request)) { - this.handler.destroySession(request); - // redirection url to the CAS server - final String redirectionUrl = handler.computeRedirectionToServer(request); - if (redirectionUrl != null) { - CommonUtils.sendRedirect(response, redirectionUrl); - } - return; - } else { - logger.debug("Ignoring URI {}", request.getRequestURI()); + if (this.handler.process(request, response)) { + getNext().invoke(request, response); } - getNext().invoke(request, response); } /** {@inheritDoc} */ @@ -108,7 +91,7 @@ public class SingleSignOutValve extends ValveBase implements SessionListener { protected void startInternal() throws LifecycleException { super.startInternal(); logger.info("Starting..."); - handler.init(); + this.handler.init(); logger.info("Startup completed."); } }