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 11e43c3..8026ed1 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
@@ -18,10 +18,8 @@
*/
package org.jasig.cas.client.session;
-import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
-import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import javax.servlet.ServletException;
@@ -50,7 +48,9 @@ public final class SingleSignOutHandler {
public final static String DEFAULT_LOGOUT_PARAMETER_NAME = "logoutRequest";
public final static String DEFAULT_FRONT_LOGOUT_PARAMETER_NAME = "SAMLRequest";
public final static String DEFAULT_RELAY_STATE_PARAMETER_NAME = "RelayState";
-
+
+ private final static int DECOMPRESSION_FACTOR = 10;
+
/** Logger instance */
private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -154,7 +154,7 @@ public final class SingleSignOutHandler {
*
* @return True if request contains authentication token, false otherwise.
*/
- protected boolean isTokenRequest(final HttpServletRequest request) {
+ private boolean isTokenRequest(final HttpServletRequest request) {
return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName,
this.safeParameters));
}
@@ -166,7 +166,7 @@ public final class SingleSignOutHandler {
*
* @return True if request is logout request, false otherwise.
*/
- protected boolean isBackChannelLogoutRequest(final HttpServletRequest request) {
+ private boolean isBackChannelLogoutRequest(final HttpServletRequest request) {
return "POST".equals(request.getMethod())
&& !isMultipartRequest(request)
&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,
@@ -180,7 +180,7 @@ public final class SingleSignOutHandler {
*
* @return True if request is logout request, false otherwise.
*/
- protected boolean isFrontChannelLogoutRequest(final HttpServletRequest request) {
+ private boolean isFrontChannelLogoutRequest(final HttpServletRequest request) {
return "GET".equals(request.getMethod())
&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.frontLogoutParameterName));
}
@@ -194,12 +194,17 @@ public final class SingleSignOutHandler {
*/
public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
if (isTokenRequest(request)) {
+ logger.trace("Received a token request");
recordSession(request);
+ return true;
+
} else if (isBackChannelLogoutRequest(request)) {
+ logger.trace("Received a back channel logout request");
destroySession(request);
- // Do not continue up filter chain
return false;
+
} else if (isFrontChannelLogoutRequest(request)) {
+ logger.trace("Received a front channel logout request");
destroySession(request);
// redirection url to the CAS server
final String redirectionUrl = computeRedirectionToServer(request);
@@ -207,10 +212,11 @@ public final class SingleSignOutHandler {
CommonUtils.sendRedirect(response, redirectionUrl);
}
return false;
+
} else {
logger.trace("Ignoring URI {}", request.getRequestURI());
+ return true;
}
- return true;
}
/**
@@ -219,7 +225,7 @@ public final class SingleSignOutHandler {
*
* @param request HTTP request containing an authentication token.
*/
- protected void recordSession(final HttpServletRequest request) {
+ private void recordSession(final HttpServletRequest request) {
final HttpSession session = request.getSession(this.eagerlyCreateSessions);
if (session == null) {
@@ -244,8 +250,7 @@ public final class SingleSignOutHandler {
* @param originalMessage the original logout message.
* @return the uncompressed logout message.
*/
- protected String uncompressLogoutMessage(final String originalMessage) {
- // base64 decode
+ private String uncompressLogoutMessage(final String originalMessage) {
final byte[] binaryMessage = Base64.decodeBase64(originalMessage);
Inflater decompresser = null;
@@ -253,39 +258,13 @@ public final class SingleSignOutHandler {
// decompress the bytes
decompresser = new Inflater();
decompresser.setInput(binaryMessage);
+ final byte[] result = new byte[binaryMessage.length * DECOMPRESSION_FACTOR];
- /* 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);
+ final int resultLength = decompresser.inflate(result);
// decode the bytes into a String
return new String(result, 0, resultLength, "UTF-8");
- } catch (DataFormatException e) {
- logger.error("Unable to decompress logout message", e);
- throw new RuntimeException(e);
- } catch (UnsupportedEncodingException e) {
+ } catch (Exception e) {
logger.error("Unable to decompress logout message", e);
throw new RuntimeException(e);
} finally {
@@ -300,8 +279,8 @@ public final class SingleSignOutHandler {
*
* @param request HTTP request containing a CAS logout message.
*/
- protected void destroySession(final HttpServletRequest request) {
- String logoutMessage;
+ private void destroySession(final HttpServletRequest request) {
+ final String logoutMessage;
// front channel logout -> the message needs to be base64 decoded + decompressed
if ("GET".equals(request.getMethod())) {
logoutMessage = uncompressLogoutMessage(CommonUtils.safeGetParameter(request,
@@ -341,12 +320,11 @@ public final class SingleSignOutHandler {
* @param request The HTTP request.
* @return the redirection url to the CAS server.
*/
- protected String computeRedirectionToServer(final HttpServletRequest request) {
- // relay state value
+ private String computeRedirectionToServer(final HttpServletRequest request) {
final String relayStateValue = CommonUtils.safeGetParameter(request, this.relayStateParameterName);
// if we have a state value -> redirect to the CAS server to continue the logout process
if (StringUtils.isNotBlank(relayStateValue)) {
- final StringBuffer buffer = new StringBuffer();
+ final StringBuilder buffer = new StringBuilder();
buffer.append(casServerUrlPrefix);
if (!this.casServerUrlPrefix.endsWith("/")) {
buffer.append("/");
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 30ee253..1ebc862 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
@@ -11,7 +11,7 @@ import org.apache.commons.codec.binary.Base64;
* Greatly inspired by the source code in the CAS server itself.
*
* @author Jerome Leleu
- * @since 3.3.1
+ * @since 3.4.0
*/
public final class LogoutMessageGenerator {
@@ -20,11 +20,11 @@ public final class LogoutMessageGenerator {
+ "IssueInstant=\"%s\">@NOT_USED@"
+ "%s";
- public static String generateBackChannelLogoutMessage(String sessionIndex) {
+ public static String generateBackChannelLogoutMessage(final String sessionIndex) {
return String.format(LOGOUT_REQUEST_TEMPLATE, new Date(), sessionIndex);
}
- public static String generateFrontChannelLogoutMessage(String sessionIndex) {
+ public static String generateFrontChannelLogoutMessage(final String sessionIndex) {
final String logoutMessage = generateBackChannelLogoutMessage(sessionIndex);
final Deflater deflater = new Deflater();
deflater.setInput(logoutMessage.getBytes(Charset.forName("ASCII")));
diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java
index c7670e5..afdf7bd 100644
--- a/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java
+++ b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
/**
@@ -38,144 +39,144 @@ public final class SingleSignOutHandlerTests {
private final static String ANOTHER_PARAMETER = "anotherParameter";
private final static String TICKET = "ST-xxxxxxxx";
private final static String URL = "http://mycasserver";
+ private final static String LOGOUT_PARAMETER_NAME = "logoutRequest2";
+ private final static String FRONT_LOGOUT_PARAMETER_NAME = "SAMLRequest2";
+ private final static String RELAY_STATE_PARAMETER_NAME = "RelayState2";
+ private final static String ARTIFACT_PARAMETER_NAME = "ticket2";
private SingleSignOutHandler handler;
private MockHttpServletRequest request;
- private final static String logoutParameterName = "logoutRequest2";
- private final static String frontLogoutParameterName = "SAMLRequest2";
- private final static String relayStateParameterName = "RelayState2";
- private final static String artifactParameterName = "ticket2";
+ private MockHttpServletResponse response;
@Before
public void setUp() throws Exception {
handler = new SingleSignOutHandler();
- handler.setLogoutParameterName(logoutParameterName);
- handler.setFrontLogoutParameterName(frontLogoutParameterName);
- handler.setRelayStateParameterName(relayStateParameterName);
- handler.setArtifactParameterName(artifactParameterName);
+ handler.setLogoutParameterName(LOGOUT_PARAMETER_NAME);
+ handler.setFrontLogoutParameterName(FRONT_LOGOUT_PARAMETER_NAME);
+ handler.setRelayStateParameterName(RELAY_STATE_PARAMETER_NAME);
+ handler.setArtifactParameterName(ARTIFACT_PARAMETER_NAME);
handler.setCasServerUrlPrefix(URL);
handler.init();
request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
}
@Test
- public void isBackChannelLogoutRequest() throws Exception {
- request.setParameter(logoutParameterName, TICKET);
- request.setMethod("POST");
-
- assertTrue(handler.isBackChannelLogoutRequest(request));
- }
-
- /**
- * Tests that a multipart request is not considered logoutRequest. Verifies issue CASC-147.
- *
- * @throws Exception
- */
- @Test
- public void isBackChannelLogoutRequestMultipart() throws Exception {
- request.setParameter(logoutParameterName, TICKET);
- request.setMethod("POST");
- request.setContentType("multipart/form-data");
-
- assertFalse(handler.isBackChannelLogoutRequest(request));
- }
-
- @Test
- public void isFrontChannelLogoutRequest() {
- request.setParameter(frontLogoutParameterName, TICKET);
- request.setMethod("GET");
- request.setQueryString(frontLogoutParameterName + "=" + TICKET);
-
- assertTrue(handler.isFrontChannelLogoutRequest(request));
- }
-
- @Test
- public void isFrontChannelLogoutRequestKO() {
- request.setParameter(ANOTHER_PARAMETER, TICKET);
- request.setMethod("GET");
- request.setQueryString(ANOTHER_PARAMETER + "=" + TICKET);
-
- assertFalse(handler.isFrontChannelLogoutRequest(request));
- }
-
- @Test
- public void recordSessionKOIfNoSession() {
+ public void tokenRequestKOIfNoSession() {
handler.setEagerlyCreateSessions(false);
request.setSession(null);
- request.setParameter(artifactParameterName, TICKET);
- request.setQueryString(artifactParameterName + "=" + TICKET);
- handler.recordSession(request);
+ request.setParameter(ARTIFACT_PARAMETER_NAME, TICKET);
+ request.setQueryString(ARTIFACT_PARAMETER_NAME + "=" + TICKET);
+ assertTrue(handler.process(request, response));
final SessionMappingStorage storage = handler.getSessionMappingStorage();
assertNull(storage.removeSessionByMappingId(TICKET));
}
@Test
- public void recordSessionOK() {
+ public void tokenRequestKOBadParameter() {
final MockHttpSession session = new MockHttpSession();
request.setSession(session);
- request.setParameter(artifactParameterName, TICKET);
- request.setQueryString(artifactParameterName + "=" + TICKET);
- handler.recordSession(request);
+ request.setParameter(ANOTHER_PARAMETER, TICKET);
+ request.setQueryString(ANOTHER_PARAMETER + "=" + TICKET);
+ assertTrue(handler.process(request, response));
+ final SessionMappingStorage storage = handler.getSessionMappingStorage();
+ assertNull(storage.removeSessionByMappingId(TICKET));
+ }
+
+ @Test
+ public void tokenRequestOK() {
+ final MockHttpSession session = new MockHttpSession();
+ request.setSession(session);
+ request.setParameter(ARTIFACT_PARAMETER_NAME, TICKET);
+ request.setQueryString(ARTIFACT_PARAMETER_NAME + "=" + TICKET);
+ assertTrue(handler.process(request, response));
final SessionMappingStorage storage = handler.getSessionMappingStorage();
assertEquals(session, storage.removeSessionByMappingId(TICKET));
}
-
- @Test
- public void destorySessionPOSTKONoSessionIndex() {
- final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage("");
- request.setParameter(logoutParameterName, logoutMessage);
- request.setMethod("POST");
- final MockHttpSession session = new MockHttpSession();
- handler.getSessionMappingStorage().addSessionById(TICKET, session);
- handler.destroySession(request);
- assertFalse(session.isInvalid());
- }
@Test
- public void destorySessionPOST() {
+ public void backChannelLogoutKOMultipart() {
final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET);
- request.setParameter(logoutParameterName, logoutMessage);
+ request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage);
request.setMethod("POST");
+ request.setContentType("multipart/form-data");
final MockHttpSession session = new MockHttpSession();
handler.getSessionMappingStorage().addSessionById(TICKET, session);
- handler.destroySession(request);
- assertTrue(session.isInvalid());
- }
-
- @Test
- public void destorySessionGETNoSessionIndex() {
- final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage("");
- request.setParameter(frontLogoutParameterName, logoutMessage);
- request.setQueryString(frontLogoutParameterName + "=" + logoutMessage);
- request.setMethod("GET");
- final MockHttpSession session = new MockHttpSession();
- handler.getSessionMappingStorage().addSessionById(TICKET, session);
- handler.destroySession(request);
+ assertTrue(handler.process(request, response));
assertFalse(session.isInvalid());
}
@Test
- public void destorySessionGET() {
- final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET);
- request.setParameter(frontLogoutParameterName, logoutMessage);
- request.setQueryString(frontLogoutParameterName + "=" + logoutMessage);
- request.setMethod("GET");
+ public void backChannelLogoutKONoSessionIndex() {
+ final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage("");
+ request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage);
+ request.setMethod("POST");
final MockHttpSession session = new MockHttpSession();
handler.getSessionMappingStorage().addSessionById(TICKET, session);
- handler.destroySession(request);
+ assertFalse(handler.process(request, response));
+ assertFalse(session.isInvalid());
+ }
+
+ @Test
+ public void backChannelLogoutOK() {
+ final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET);
+ request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage);
+ request.setMethod("POST");
+ final MockHttpSession session = new MockHttpSession();
+ handler.getSessionMappingStorage().addSessionById(TICKET, session);
+ assertFalse(handler.process(request, response));
assertTrue(session.isInvalid());
}
@Test
- public void computeRedirectionNoRelayState() {
- assertNull(handler.computeRedirectionToServer(request));
+ public void frontChannelLogoutKOBadParameter() {
+ final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET);
+ request.setParameter(ANOTHER_PARAMETER, logoutMessage);
+ request.setMethod("GET");
+ request.setQueryString(ANOTHER_PARAMETER + "=" + logoutMessage);
+ final MockHttpSession session = new MockHttpSession();
+ handler.getSessionMappingStorage().addSessionById(TICKET, session);
+ assertTrue(handler.process(request, response));
+ assertFalse(session.isInvalid());
}
@Test
- public void computeRedirection() {
- request.setParameter(relayStateParameterName, TICKET);
- request.setQueryString(relayStateParameterName + "=" + TICKET);
- assertEquals(URL + "/logout?_eventId=next&" + relayStateParameterName + "=" + TICKET,
- handler.computeRedirectionToServer(request));
+ public void frontChannelLogoutKONoSessionIndex() {
+ final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage("");
+ request.setParameter(FRONT_LOGOUT_PARAMETER_NAME, logoutMessage);
+ request.setQueryString(FRONT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage);
+ request.setMethod("GET");
+ final MockHttpSession session = new MockHttpSession();
+ handler.getSessionMappingStorage().addSessionById(TICKET, session);
+ assertFalse(handler.process(request, response));
+ assertFalse(session.isInvalid());
+ }
+
+ @Test
+ public void frontChannelLogoutOK() {
+ final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET);
+ request.setParameter(FRONT_LOGOUT_PARAMETER_NAME, logoutMessage);
+ request.setQueryString(FRONT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage);
+ request.setMethod("GET");
+ final MockHttpSession session = new MockHttpSession();
+ handler.getSessionMappingStorage().addSessionById(TICKET, session);
+ assertFalse(handler.process(request, response));
+ assertTrue(session.isInvalid());
+ assertNull(response.getRedirectedUrl());
+ }
+
+ @Test
+ public void frontChannelLogoutRelayStateOK() {
+ final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET);
+ request.setParameter(FRONT_LOGOUT_PARAMETER_NAME, logoutMessage);
+ request.setParameter(RELAY_STATE_PARAMETER_NAME, TICKET);
+ request.setQueryString(FRONT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage + "&" + RELAY_STATE_PARAMETER_NAME + "=" + TICKET);
+ request.setMethod("GET");
+ final MockHttpSession session = new MockHttpSession();
+ handler.getSessionMappingStorage().addSessionById(TICKET, session);
+ assertFalse(handler.process(request, response));
+ assertTrue(session.isInvalid());
+ assertEquals(URL + "/logout?_eventId=next&" + RELAY_STATE_PARAMETER_NAME + "=" + TICKET,
+ response.getRedirectedUrl());
}
}