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 109791d..1126218 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 @@ -21,7 +21,11 @@ package org.jasig.cas.client.session; import java.io.IOException; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; 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. @@ -34,15 +38,25 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter { private static final SingleSignOutHandler handler = new SingleSignOutHandler(); + /** The prefix url of the CAS server */ + private String casServerUrlPrefix; + + /** Parameter name that stores the state of the CAS server webflow for the callback */ + private String relayStateParameterName = SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME; + public void init(final FilterConfig filterConfig) throws ServletException { if (!isIgnoreInitConfiguration()) { - handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket")); + handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", + SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME)); handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", - "logoutRequest")); + SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME)); + setRelayStateParameterName(getPropertyFromInitParams(filterConfig, "relayStateParameterName", + SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME)); handler.setArtifactParameterOverPost(parseBoolean(getPropertyFromInitParams(filterConfig, "artifactParameterOverPost", "false"))); handler.setEagerlyCreateSessions(parseBoolean(getPropertyFromInitParams(filterConfig, "eagerlyCreateSessions", "true"))); + setCasServerUrlPrefix(getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null)); } handler.init(); } @@ -55,20 +69,51 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter { handler.setLogoutParameterName(name); } + public void setRelayStateParameterName(final String name) { + this.relayStateParameterName = name; + handler.setRelayStateParameterName(name); + } + public void setSessionMappingStorage(final SessionMappingStorage storage) { handler.setSessionMappingStorage(storage); } + public void setCasServerUrlPrefix(final String casServerUrlPrefix) { + CommonUtils.assertNotNull(casServerUrlPrefix, "casServerUrlPrefix cannot be null."); + this.casServerUrlPrefix = casServerUrlPrefix; + } + public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpServletResponse response = (HttpServletResponse) servletResponse; if (handler.isTokenRequest(request)) { handler.recordSession(request); - } else if (handler.isLogoutRequest(request)) { + } else if (handler.isBackChannelLogoutRequest(request)) { handler.destroySession(request); // Do not continue up filter chain return; + } else if (handler.isFrontChannelLogoutRequest(request)) { + handler.destroySession(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 + if (StringUtils.isNotBlank(relayStateValue)) { + final StringBuffer buffer = new StringBuffer(); + buffer.append(casServerUrlPrefix); + if (!this.casServerUrlPrefix.endsWith("/")) { + buffer.append("/"); + } + buffer.append("logout?_eventId=next&"); + buffer.append(this.relayStateParameterName); + buffer.append("="); + buffer.append(CommonUtils.urlEncode(relayStateValue)); + final String redirectUrl = buffer.toString(); + logger.debug("Redirecting back to the CAS server: {}", redirectUrl); + CommonUtils.sendRedirect(response, redirectUrl); + } + return; } else { logger.trace("Ignoring URI {}", request.getRequestURI()); } 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 943aa6b..873d322 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,14 +18,18 @@ */ 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; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; + +import org.apache.commons.codec.binary.Base64; import org.jasig.cas.client.util.CommonUtils; -import org.jasig.cas.client.util.ReflectUtils; import org.jasig.cas.client.util.XmlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +44,10 @@ import org.slf4j.LoggerFactory; */ public final class SingleSignOutHandler { + public final static String DEFAULT_ARTIFACT_PARAMETER_NAME = "ticket"; + public final static String DEFAULT_LOGOUT_PARAMETER_NAME = "logoutRequest"; + public final static String DEFAULT_RELAY_STATE_PARAMETER_NAME = "RelayState"; + /** Logger instance */ private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -47,11 +55,14 @@ public final class SingleSignOutHandler { private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage(); /** The name of the artifact parameter. This is used to capture the session identifier. */ - private String artifactParameterName = "ticket"; + private String artifactParameterName = DEFAULT_ARTIFACT_PARAMETER_NAME; /** Parameter name that stores logout request */ - private String logoutParameterName = "logoutRequest"; + private String logoutParameterName = DEFAULT_LOGOUT_PARAMETER_NAME; + /** Parameter name that stores the state of the CAS server webflow for the callback */ + private String relayStateParameterName = DEFAULT_RELAY_STATE_PARAMETER_NAME; + private boolean artifactParameterOverPost = false; private boolean eagerlyCreateSessions = true; @@ -84,6 +95,13 @@ public final class SingleSignOutHandler { this.logoutParameterName = name; } + /** + * @param name Name of parameter containing the state of the CAS server webflow. + */ + public void setRelayStateParameterName(final String name) { + this.relayStateParameterName = name; + } + public void setEagerlyCreateSessions(final boolean eagerlyCreateSessions) { this.eagerlyCreateSessions = eagerlyCreateSessions; } @@ -95,6 +113,7 @@ public final class SingleSignOutHandler { CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null."); CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null."); CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannot be null."); + CommonUtils.assertNotNull(this.relayStateParameterName, "relayStateParameterName cannot be null."); if (this.artifactParameterOverPost) { this.safeParameters = Arrays.asList(this.logoutParameterName, this.artifactParameterName); @@ -116,19 +135,31 @@ public final class SingleSignOutHandler { } /** - * Determines whether the given request is a CAS logout request. + * Determines whether the given request is a CAS back channel logout request. * * @param request HTTP request. * * @return True if request is logout request, false otherwise. */ - public boolean isLogoutRequest(final HttpServletRequest request) { + public boolean isBackChannelLogoutRequest(final HttpServletRequest request) { return "POST".equals(request.getMethod()) && !isMultipartRequest(request) && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters)); } + /** + * Determines whether the given request is a CAS front channel logout request. + * + * @param request HTTP request. + * + * @return True if request is logout request, false otherwise. + */ + public boolean isFrontChannelLogoutRequest(final HttpServletRequest request) { + return "GET".equals(request.getMethod()) + && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName)); + } + /** * Associates a token request with the current HTTP session by recording the mapping * in the the configured {@link SessionMappingStorage} container. @@ -154,14 +185,47 @@ public final class SingleSignOutHandler { sessionMappingStorage.addSessionById(token, session); } + /** + * Uncompress a logout message (base64 + deflate). + * + * @param originalMessage the original logout message. + * @return the uncompressed logout message. + */ + private String uncompressLogoutMessage(final String originalMessage) { + // base64 decode + final byte[] binaryMessage = Base64.decodeBase64(originalMessage); + + try { + // decompress the bytes + final Inflater decompresser = new Inflater(); + decompresser.setInput(binaryMessage); + 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"); + } catch (DataFormatException e) { + logger.error("Unable to decompress logout message", e); + throw new RuntimeException(e); + } catch (UnsupportedEncodingException e) { + logger.error("Unable to decompress logout message", e); + throw new RuntimeException(e); + } + } + /** * Destroys the current HTTP session for the given CAS logout request. * * @param request HTTP request containing a CAS logout message. */ public void destroySession(final HttpServletRequest request) { - final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, + String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters); + // front channel request -> the message needs to be base64 decoded + decompressed + if ("GET".equals(request.getMethod())) { + logoutMessage = uncompressLogoutMessage(logoutMessage); + } logger.trace("Logout request:\n{}", logoutMessage); final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex"); diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java index c767c56..be705f4 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java @@ -160,10 +160,19 @@ public final class CommonUtils { */ public static String constructRedirectUrl(final String casServerLoginUrl, final String serviceParameterName, final String serviceUrl, final boolean renew, final boolean gateway) { + return casServerLoginUrl + (casServerLoginUrl.contains("?") ? "&" : "?") + serviceParameterName + "=" + + urlEncode(serviceUrl) + (renew ? "&renew=true" : "") + (gateway ? "&gateway=true" : ""); + } + + /** + * Url encode a value using UTF-8 encoding. + * + * @param value the value to encode. + * @return the encoded value. + */ + public static String urlEncode(String value) { try { - return casServerLoginUrl + (casServerLoginUrl.contains("?") ? "&" : "?") + serviceParameterName + "=" - + URLEncoder.encode(serviceUrl, "UTF-8") + (renew ? "&renew=true" : "") - + (gateway ? "&gateway=true" : ""); + return URLEncoder.encode(value, "UTF-8"); } catch (final UnsupportedEncodingException e) { throw new RuntimeException(e); } 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 new file mode 100644 index 0000000..e30254d --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/session/LogoutMessageGenerator.java @@ -0,0 +1,37 @@ +package org.jasig.cas.client.session; + +import java.nio.charset.Charset; +import java.util.zip.Deflater; + +import org.apache.commons.codec.binary.Base64; + +/** + * Logout message generator to perform tests on Single Sign Out feature. + * Greatly inspired by the source code in the CAS server itself. + * + * @author Jerome Leleu + * @since 3.3.1 + */ +public final class LogoutMessageGenerator { + + private static final String LOGOUT_REQUEST_TEMPLATE = + "@NOT_USED@" + + "%s"; + + public static String generateLogoutMessage(String sessionIndex) { + return String.format(LOGOUT_REQUEST_TEMPLATE, sessionIndex); + } + + public static String generateCompressedLogoutMessage(String sessionIndex) { + final String logoutMessage = generateLogoutMessage(sessionIndex); + final Deflater deflater = new Deflater(); + deflater.setInput(logoutMessage.getBytes(Charset.forName("ASCII"))); + deflater.finish(); + final byte[] buffer = new byte[logoutMessage.length()]; + final int resultSize = deflater.deflate(buffer); + final byte[] output = new byte[resultSize]; + System.arraycopy(buffer, 0, output, 0, resultSize); + return Base64.encodeBase64String(output); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutFilterTests.java new file mode 100644 index 0000000..82ecd52 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutFilterTests.java @@ -0,0 +1,118 @@ +/* + * Licensed to Jasig under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Jasig licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.client.session; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; + +/** + * Tests {@link SingleSignOutFilter}. + * + * @author Jerome Leleu + * @since 3.3.1 + */ +public class SingleSignOutFilterTests { + + private final static String CAS_SERVER_URL_PREFIX = "http://myhost.com/mycasserver"; + private final static String TICKET = "ST-yyyyy"; + private final static String RELAY_STATE = "e1s1"; + + private SingleSignOutFilter filter = new SingleSignOutFilter(); + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private MockFilterChain filterChain; + + @Before + public void setUp() throws Exception { + filter = new SingleSignOutFilter(); + filter.setCasServerUrlPrefix(CAS_SERVER_URL_PREFIX); + filter.setIgnoreInitConfiguration(true); + filter.init(new MockFilterConfig()); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + filterChain = new MockFilterChain(); + } + + @Test(expected = IllegalArgumentException.class) + public void initWithoutCasServerUrlPrefix() throws ServletException { + filter = new SingleSignOutFilter(); + filter.init(new MockFilterConfig()); + } + + @Test + public void tokenRequest() throws IOException, ServletException { + request.setParameter(SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME, TICKET); + request.setQueryString(SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME + "=" + TICKET); + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + filter.doFilter(request, response, filterChain); + assertEquals(session, SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().removeSessionByMappingId(TICKET)); + } + + @Test + public void backChannelRequest() throws IOException, ServletException { + request.setParameter(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME, LogoutMessageGenerator.generateLogoutMessage(TICKET)); + request.setMethod("POST"); + final MockHttpSession session = new MockHttpSession(); + SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().addSessionById(TICKET, session); + filter.doFilter(request, response, filterChain); + assertNull(SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().removeSessionByMappingId(TICKET)); + } + + @Test + public void frontChannelRequest() throws IOException, ServletException { + final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(TICKET); + request.setParameter(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME, logoutMessage); + request.setQueryString(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage); + request.setMethod("GET"); + final MockHttpSession session = new MockHttpSession(); + SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().addSessionById(TICKET, session); + filter.doFilter(request, response, filterChain); + assertNull(SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().removeSessionByMappingId(TICKET)); + assertNull(response.getRedirectedUrl()); + } + + @Test + public void frontChannelRequestRelayState() throws IOException, ServletException { + final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(TICKET); + request.setParameter(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME, logoutMessage); + request.setParameter(SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME, RELAY_STATE); + request.setQueryString(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage + "&" + + SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME + "=" + RELAY_STATE); + request.setMethod("GET"); + final MockHttpSession session = new MockHttpSession(); + SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().addSessionById(TICKET, session); + filter.doFilter(request, response, filterChain); + assertNull(SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().removeSessionByMappingId(TICKET)); + assertEquals(CAS_SERVER_URL_PREFIX + "/logout?_eventId=next&" + + SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME + "=" + RELAY_STATE, response.getRedirectedUrl()); + } +} 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 new file mode 100644 index 0000000..1479536 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java @@ -0,0 +1,160 @@ +/* + * Licensed to Jasig under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Jasig licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.client.session; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +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.MockHttpSession; + +/** + * @author Matt Brown + * @version $Revision$ $Date$ + * @since 3.2.1 + */ +public final class SingleSignOutHandlerTests { + + private final static String ANOTHER_PARAMETER = "anotherParameter"; + private final static String TICKET = "ST-xxxxxxxx"; + + private SingleSignOutHandler handler; + private MockHttpServletRequest request; + private final static String logoutParameterName = "logoutRequest"; + + @Before + public void setUp() throws Exception { + handler = new SingleSignOutHandler(); + handler.setLogoutParameterName(logoutParameterName); + handler.init(); + request = new MockHttpServletRequest(); + } + + @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(logoutParameterName, TICKET); + request.setMethod("GET"); + request.setQueryString(logoutParameterName + "=" + 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() { + handler.setEagerlyCreateSessions(false); + request.setSession(null); + request.setParameter(SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME, TICKET); + request.setQueryString(SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME + "=" + TICKET); + handler.recordSession(request); + final SessionMappingStorage storage = handler.getSessionMappingStorage(); + assertNull(storage.removeSessionByMappingId(TICKET)); + } + + @Test + public void recordSessionOK() { + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + request.setParameter(SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME, TICKET); + request.setQueryString(SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME + "=" + TICKET); + handler.recordSession(request); + final SessionMappingStorage storage = handler.getSessionMappingStorage(); + assertEquals(session, storage.removeSessionByMappingId(TICKET)); + } + + @Test + public void destorySessionPOSTKONoSessionIndex() { + final String logoutMessage = LogoutMessageGenerator.generateLogoutMessage(""); + 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() { + final String logoutMessage = LogoutMessageGenerator.generateLogoutMessage(TICKET); + request.setParameter(logoutParameterName, logoutMessage); + request.setMethod("POST"); + final MockHttpSession session = new MockHttpSession(); + handler.getSessionMappingStorage().addSessionById(TICKET, session); + handler.destroySession(request); + assertTrue(session.isInvalid()); + } + + @Test + public void destorySessionGETNoSessionIndex() { + final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(""); + request.setParameter(logoutParameterName, logoutMessage); + request.setQueryString(logoutParameterName + "=" + logoutMessage); + request.setMethod("GET"); + final MockHttpSession session = new MockHttpSession(); + handler.getSessionMappingStorage().addSessionById(TICKET, session); + handler.destroySession(request); + assertFalse(session.isInvalid()); + } + + @Test + public void destorySessionGET() { + final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(TICKET); + request.setParameter(logoutParameterName, logoutMessage); + request.setQueryString(logoutParameterName + "=" + logoutMessage); + request.setMethod("GET"); + final MockHttpSession session = new MockHttpSession(); + handler.getSessionMappingStorage().addSessionById(TICKET, session); + handler.destroySession(request); + assertTrue(session.isInvalid()); + } +} 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 deleted file mode 100644 index 0e42830..0000000 --- a/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignoutHandlerTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Jasig under one or more contributor license - * agreements. See the NOTICE file distributed with this work - * for additional information regarding copyright ownership. - * Jasig licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a - * copy of the License at the following location: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.jasig.cas.client.session; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import org.junit.Before; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -/** - * @author Matt Brown - * @version $Revision$ $Date$ - * @since 3.2.1 - */ -public final class SingleSignoutHandlerTests { - - private SingleSignOutHandler handler; - private MockHttpServletRequest request; - private final static String logoutParameterName = "logoutRequest"; - - @Before - public void setUp() throws Exception { - handler = new SingleSignOutHandler(); - handler.setLogoutParameterName(logoutParameterName); - handler.init(); - request = new MockHttpServletRequest(); - } - - @Test - public void isLogoutRequest() throws Exception { - request.setParameter(logoutParameterName, "true"); - request.setMethod("POST"); - - assertTrue(handler.isLogoutRequest(request)); - } - - /** - * Tests that a multipart request is not considered logoutRequest. Verifies issue CASC-147. - * - * @throws Exception - */ - @Test - public void isLogoutRequestMultipart() throws Exception { - request.setParameter(logoutParameterName, "true"); - request.setMethod("POST"); - request.setContentType("multipart/form-data"); - - assertFalse(handler.isLogoutRequest(request)); - } - -} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java index f6e686c..66d49f1 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java @@ -167,4 +167,8 @@ public final class CommonUtilsTests extends TestCase { final String responsedContent = CommonUtils.getResponseFromServer(new URL("http://localhost:8090"), new HttpsURLConnectionFactory(), null); assertEquals(RESPONSE, responsedContent); } + + public void testUrlEncode() { + assertEquals("this+is+a+very+special+parameter+with+%3D%25%2F", CommonUtils.urlEncode("this is a very special parameter with =%/")); + } }