From c857e4610b613fcb1154681456a22bbee6abdd02 Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Thu, 27 Feb 2014 22:02:19 -0700 Subject: [PATCH 01/25] CAS-219: Provide support for certain urls to be excluded from CAS filters. --- .../authentication/AuthenticationFilter.java | 8 ++++ .../cas/client/util/AbstractCasFilter.java | 33 +++++++++++++ .../AbstractTicketValidationFilter.java | 14 ++++-- .../AuthenticationFilterTests.java | 42 +++++++++++++---- .../Cas10TicketValidationFilterTests.java | 46 +++++++++++++++++++ .../Saml11TicketValidationFilterTests.java | 46 +++++++++++++++++++ .../Saml11TicketValidatorTests.java | 15 +++++- 7 files changed, 191 insertions(+), 13 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index 2e73556..c00be68 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -98,8 +98,16 @@ public class AuthenticationFilter extends AbstractCasFilter { public final 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 (isRequestUrlExcluded(request)) { + logger.debug("Request is ignored."); + filterChain.doFilter(request, response); + return; + } + final HttpSession session = request.getSession(false); final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null; diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java index 78f5f7b..7b4d5cf 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java @@ -18,6 +18,10 @@ */ package org.jasig.cas.client.util; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -48,6 +52,9 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { /** Defines the parameter to look for for the service. */ private String serviceParameterName = "service"; + /** Url pattern for this filter to exclude and ignore. **/ + private Pattern ignorePattern = null; + /** Sets where response.encodeUrl should be called on service urls when constructed. */ private boolean encodeServiceUrl = true; @@ -72,6 +79,12 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { setEncodeServiceUrl(parseBoolean(getPropertyFromInitParams(filterConfig, "encodeServiceUrl", "true"))); logger.trace("Loading encodeServiceUrl property: {}", this.encodeServiceUrl); + final String ignorePattern = getPropertyFromInitParams(filterConfig, "ignorePattern", null); + if (ignorePattern != null) { + setIgnorePattern(Pattern.compile(ignorePattern)); + logger.trace("Loading ignorePattern property: {}", this.ignorePattern.pattern()); + } + initInternal(filterConfig); } init(); @@ -148,6 +161,10 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { return this.serviceParameterName; } + public final void setIgnorePattern(final Pattern patternToIgnore) { + this.ignorePattern = patternToIgnore; + } + /** * Template method to allow you to change how you retrieve the ticket. * @@ -157,4 +174,20 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { protected String retrieveTicketFromRequest(final HttpServletRequest request) { return CommonUtils.safeGetParameter(request, getArtifactParameterName()); } + + protected boolean isRequestUrlExcluded(final HttpServletRequest request) { + boolean result = false; + if (this.ignorePattern != null) { + final StringBuffer urlBuffer = request.getRequestURL(); + if (request.getQueryString() != null) { + urlBuffer.append("?").append(request.getQueryString()); + } + final String requestUri = urlBuffer.toString(); + logger.debug("Checking [{}] against pattern [{}]", requestUri, this.ignorePattern.pattern()); + result = this.ignorePattern.matcher(requestUri).find(); + } else { + logger.debug("Ignore pattern is not defined"); + } + return result; + } } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java index 58f57c4..836b63a 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java @@ -190,13 +190,19 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter { public final 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 (isRequestUrlExcluded(request)) { + logger.debug("Request is ignored."); + filterChain.doFilter(request, response); + return; + } + if (!preFilter(servletRequest, servletResponse, filterChain)) { return; } - - final HttpServletRequest request = (HttpServletRequest) servletRequest; - final HttpServletResponse response = (HttpServletResponse) servletResponse; + final String ticket = retrieveTicketFromRequest(request); if (CommonUtils.isNotBlank(ticket)) { diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index 396c536..34d6686 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -37,7 +37,6 @@ import org.springframework.mock.web.*; * Tests for the AuthenticationFilter. * * @author Scott Battaglia - * @version $Revision: 11753 $ $Date: 2007-01-03 13:37:26 -0500 (Wed, 03 Jan 2007) $ * @since 3.0 */ public final class AuthenticationFilterTests { @@ -50,11 +49,10 @@ public final class AuthenticationFilterTests { @Before public void setUp() throws Exception { - // TODO CAS_SERVICE_URL, false, CAS_LOGIN_URL this.filter = new AuthenticationFilter(); final MockFilterConfig config = new MockFilterConfig(); config.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); - config.addInitParameter("service", "https://localhost:8443/service"); + config.addInitParameter("service", CAS_SERVICE_URL); this.filter.init(config); } @@ -184,7 +182,7 @@ public final class AuthenticationFilterTests { final AuthenticationFilter f = new AuthenticationFilter(); final MockFilterConfig config = new MockFilterConfig(); config.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); - config.addInitParameter("service", "https://localhost:8443/service"); + config.addInitParameter("service", CAS_SERVICE_URL); config.addInitParameter("renew", "true"); try { f.init(config); @@ -198,8 +196,8 @@ public final class AuthenticationFilterTests { public void testAllowsRenewContextParam() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); final MockServletContext context = new MockServletContext(); - context.addInitParameter("casServerLoginUrl", "https://cas.example.com/login"); - context.addInitParameter("service", "https://localhost:8443/service"); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + context.addInitParameter("service", CAS_SERVICE_URL); context.addInitParameter("renew", "true"); f.init(new MockFilterConfig(context)); final Field renewField = AuthenticationFilter.class.getDeclaredField("renew"); @@ -211,10 +209,38 @@ public final class AuthenticationFilterTests { public void customRedirectStrategy() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); final MockServletContext context = new MockServletContext(); - context.addInitParameter("casServerLoginUrl", "https://cas.example.com/login"); - context.addInitParameter("service", "https://localhost:8443/service"); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + context.addInitParameter("service", CAS_SERVICE_URL); context.addInitParameter("authenticationRedirectStrategyClass", "org.jasig.cas.client.authentication.FacesCompatibleAuthenticationRedirectStrategy"); f.init(new MockFilterConfig(context)); } + + @Test + public void testIgnorePatterns() throws Exception { + final AuthenticationFilter f = new AuthenticationFilter(); + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + + context.addInitParameter("ignorePattern", "=valueTo(\\w+)"); + context.addInitParameter("service", CAS_SERVICE_URL); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String URL = CAS_SERVICE_URL + "?param=valueToIgnore"; + request.setRequestURI(URL); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + f.doFilter(request, response, filterChain); + assertNull(response.getRedirectedUrl()); + } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java index e8daab4..174b9f7 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java @@ -20,8 +20,19 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + import org.junit.Test; import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; /** @@ -54,4 +65,39 @@ public class Cas10TicketValidationFilterTests { assertTrue(validator instanceof Cas10TicketValidator); assertTrue(((Cas10TicketValidator) validator).isRenew()); } + + @Test + public void testIgnorePatterns() throws Exception { + final Cas10TicketValidationFilter f = new Cas10TicketValidationFilter(); + + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerUrlPrefix", "https://cas.example.com"); + context.addInitParameter("serverName", "https://localhost:8443"); + + context.addInitParameter("ignorePattern", "=valueTo(\\w+)"); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String URL = "https://localhost:8443/?param=valueToIgnore"; + request.setRequestURI(URL); + request.setQueryString("ticket=ST-1234"); + request.setParameter("ticket", "ST-1234"); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + try { + f.doFilter(request, response, filterChain); + } catch (final Exception e) { + fail("The validation request should have been ignored"); + } + + } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java index 804b46f..c0e267e 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java @@ -20,8 +20,19 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + import org.junit.Test; import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; /** @@ -54,4 +65,39 @@ public class Saml11TicketValidationFilterTests { assertTrue(validator instanceof Saml11TicketValidator); assertTrue(((Saml11TicketValidator) validator).isRenew()); } + + @Test + public void testIgnorePatterns() throws Exception { + final Saml11TicketValidationFilter f = new Saml11TicketValidationFilter(); + + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerUrlPrefix", "https://cas.example.com"); + context.addInitParameter("serverName", "https://localhost:8443"); + + context.addInitParameter("ignorePattern", "=valueTo(\\w+)"); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String URL = "https://localhost:8443/?param=valueToIgnore"; + request.setRequestURI(URL); + request.setQueryString("SAMLart=ST-1234"); + request.setParameter("SAMLart", "ST-1234"); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + try { + f.doFilter(request, response, filterChain); + } catch (final Exception e) { + fail("The validation request should have been ignored"); + } + + } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java index 951e610..9e90749 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java @@ -20,8 +20,16 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; + +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Date; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + import org.jasig.cas.client.PublicTestHttpServer; import org.jasig.cas.client.util.CommonUtils; import org.joda.time.DateTime; @@ -30,6 +38,11 @@ import org.joda.time.Interval; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.mock.web.MockServletContext; /** * @author Scott Battaglia @@ -137,7 +150,7 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes fail(e.toString()); } } - + private Interval currentTimeRangeInterval() { return new Interval(new DateTime(DateTimeZone.UTC).minus(5000), new DateTime(DateTimeZone.UTC).plus(200000000)); } From 44d1413fa706f8b923a3e9a345ab5aee2f016fd5 Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Sun, 2 Mar 2014 03:03:24 -0700 Subject: [PATCH 02/25] CAS-219: Provide support for certain urls to be excluded from CAS filters. --- .../org/jasig/cas/client/util/AbstractCasFilter.java | 2 -- .../client/validation/Saml11TicketValidatorTests.java | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java index 7b4d5cf..558cefb 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java @@ -185,8 +185,6 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { final String requestUri = urlBuffer.toString(); logger.debug("Checking [{}] against pattern [{}]", requestUri, this.ignorePattern.pattern()); result = this.ignorePattern.matcher(requestUri).find(); - } else { - logger.debug("Ignore pattern is not defined"); } return result; } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java index 9e90749..41b58ec 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java @@ -21,15 +21,9 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Date; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - import org.jasig.cas.client.PublicTestHttpServer; import org.jasig.cas.client.util.CommonUtils; import org.joda.time.DateTime; @@ -38,11 +32,6 @@ import org.joda.time.Interval; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.springframework.mock.web.MockFilterConfig; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.mock.web.MockServletContext; /** * @author Scott Battaglia From b9ac92cf255d767cae937ef468ef6e20d20b980e Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Fri, 7 Mar 2014 01:52:50 -0700 Subject: [PATCH 03/25] CASC-219: Provide support for certain urls to be excluded from CAS filters. --- .../cas/client/util/AbstractCasFilter.java | 53 +++++++++++++++---- .../AbstractTicketValidationFilter.java | 8 +-- .../AuthenticationFilterTests.java | 29 ++++++++++ .../Cas10TicketValidationFilterTests.java | 35 ------------ .../Saml11TicketValidationFilterTests.java | 34 ------------ 5 files changed, 73 insertions(+), 86 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java index 558cefb..70a5a43 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java @@ -18,8 +18,6 @@ */ package org.jasig.cas.client.util; -import java.util.Collections; -import java.util.List; import java.util.regex.Pattern; import javax.servlet.FilterConfig; @@ -38,11 +36,20 @@ import javax.servlet.http.HttpServletResponse; *

Please note that one of the two above parameters must be set.

* * @author Scott Battaglia - * @version $Revision$ $Date$ + * @author Misagh Moayyed * @since 3.1 */ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { + /** + * Enumeration that defines pattern types. + * @since 3.3.1 + */ + public enum IgnorePatternTypes { + NONE, + REGEX; + } + /** Represents the constant for where the assertion will be located in memory. */ public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_"; @@ -52,8 +59,15 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { /** Defines the parameter to look for for the service. */ private String serviceParameterName = "service"; - /** Url pattern for this filter to exclude and ignore. **/ - private Pattern ignorePattern = null; + /** Url pattern for this filter to exclude and ignore. + * @since 3.3.1 + **/ + private String ignorePattern = null; + + /** Denotes the pattern type. + * @since 3.3.1 + */ + private IgnorePatternTypes ignorePatternType = null; /** Sets where response.encodeUrl should be called on service urls when constructed. */ private boolean encodeServiceUrl = true; @@ -81,15 +95,20 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { final String ignorePattern = getPropertyFromInitParams(filterConfig, "ignorePattern", null); if (ignorePattern != null) { - setIgnorePattern(Pattern.compile(ignorePattern)); - logger.trace("Loading ignorePattern property: {}", this.ignorePattern.pattern()); + setIgnorePattern(ignorePattern); + logger.trace("Loading ignorePattern property: {}", ignorePattern); } + setIgnorePatternType(Enum.valueOf(IgnorePatternTypes.class, getPropertyFromInitParams(filterConfig, "ignorePatternType", + IgnorePatternTypes.REGEX.name()))); + logger.trace("Loading ignorePatternType property: {}", ignorePatternType); + initInternal(filterConfig); } init(); } + /** Controls the ordering of filter initialization and checking by defining a method that runs before the init. * @param filterConfig the original filter configuration. * @throws ServletException if there is a problem. @@ -161,10 +180,14 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { return this.serviceParameterName; } - public final void setIgnorePattern(final Pattern patternToIgnore) { + public final void setIgnorePattern(final String patternToIgnore) { this.ignorePattern = patternToIgnore; } + public final void setIgnorePatternType(final IgnorePatternTypes patternType) { + this.ignorePatternType = patternType; + } + /** * Template method to allow you to change how you retrieve the ticket. * @@ -183,8 +206,18 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { urlBuffer.append("?").append(request.getQueryString()); } final String requestUri = urlBuffer.toString(); - logger.debug("Checking [{}] against pattern [{}]", requestUri, this.ignorePattern.pattern()); - result = this.ignorePattern.matcher(requestUri).find(); + logger.debug("Checking [{}] against pattern [{}]", requestUri, this.ignorePattern); + + + switch (this.ignorePatternType) { + case NONE: + result = requestUri.contains(this.ignorePattern); + break; + case REGEX: + result = Pattern.compile(this.ignorePattern).matcher(requestUri).find(); + break; + } + } return result; } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java index 836b63a..ac5c7af 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java @@ -192,13 +192,7 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter { final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpServletResponse response = (HttpServletResponse) servletResponse; - - if (isRequestUrlExcluded(request)) { - logger.debug("Request is ignored."); - filterChain.doFilter(request, response); - return; - } - + if (!preFilter(servletRequest, servletResponse, filterChain)) { return; } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index 34d6686..b8c1bfe 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -243,4 +243,33 @@ public final class AuthenticationFilterTests { f.doFilter(request, response, filterChain); assertNull(response.getRedirectedUrl()); } + + @Test + public void testIgnorePatternsWithNoRegex() throws Exception { + final AuthenticationFilter f = new AuthenticationFilter(); + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + + context.addInitParameter("ignorePattern", "=valueToIgnore"); + context.addInitParameter("ignorePatternType", "NONE"); + context.addInitParameter("service", CAS_SERVICE_URL); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String URL = CAS_SERVICE_URL + "?param=valueToIgnore"; + request.setRequestURI(URL); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + f.doFilter(request, response, filterChain); + assertNull(response.getRedirectedUrl()); + } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java index 174b9f7..74e6d7a 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java @@ -65,39 +65,4 @@ public class Cas10TicketValidationFilterTests { assertTrue(validator instanceof Cas10TicketValidator); assertTrue(((Cas10TicketValidator) validator).isRenew()); } - - @Test - public void testIgnorePatterns() throws Exception { - final Cas10TicketValidationFilter f = new Cas10TicketValidationFilter(); - - final MockServletContext context = new MockServletContext(); - context.addInitParameter("casServerUrlPrefix", "https://cas.example.com"); - context.addInitParameter("serverName", "https://localhost:8443"); - - context.addInitParameter("ignorePattern", "=valueTo(\\w+)"); - f.init(new MockFilterConfig(context)); - - final MockHttpServletRequest request = new MockHttpServletRequest(); - final String URL = "https://localhost:8443/?param=valueToIgnore"; - request.setRequestURI(URL); - request.setQueryString("ticket=ST-1234"); - request.setParameter("ticket", "ST-1234"); - - final MockHttpSession session = new MockHttpSession(); - request.setSession(session); - - final MockHttpServletResponse response = new MockHttpServletResponse(); - - final FilterChain filterChain = new FilterChain() { - public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { - } - }; - - try { - f.doFilter(request, response, filterChain); - } catch (final Exception e) { - fail("The validation request should have been ignored"); - } - - } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java index c0e267e..4ba6a36 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java @@ -66,38 +66,4 @@ public class Saml11TicketValidationFilterTests { assertTrue(((Saml11TicketValidator) validator).isRenew()); } - @Test - public void testIgnorePatterns() throws Exception { - final Saml11TicketValidationFilter f = new Saml11TicketValidationFilter(); - - final MockServletContext context = new MockServletContext(); - context.addInitParameter("casServerUrlPrefix", "https://cas.example.com"); - context.addInitParameter("serverName", "https://localhost:8443"); - - context.addInitParameter("ignorePattern", "=valueTo(\\w+)"); - f.init(new MockFilterConfig(context)); - - final MockHttpServletRequest request = new MockHttpServletRequest(); - final String URL = "https://localhost:8443/?param=valueToIgnore"; - request.setRequestURI(URL); - request.setQueryString("SAMLart=ST-1234"); - request.setParameter("SAMLart", "ST-1234"); - - final MockHttpSession session = new MockHttpSession(); - request.setSession(session); - - final MockHttpServletResponse response = new MockHttpServletResponse(); - - final FilterChain filterChain = new FilterChain() { - public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { - } - }; - - try { - f.doFilter(request, response, filterChain); - } catch (final Exception e) { - fail("The validation request should have been ignored"); - } - - } } From 71b366cbf367a8b989c532c82b5b0ff663ec7179 Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Fri, 7 Mar 2014 11:17:14 -0700 Subject: [PATCH 04/25] CASC-219: Provide support for certain urls to be excluded from CAS filters. --- .../authentication/AuthenticationFilter.java | 39 +++++++++++- .../ExactUrlPatternMatcherStrategy.java | 38 +++++++++++ .../RegexUrlPatternMatcherStrategy.java | 41 ++++++++++++ .../UrlPatternMatcherStrategy.java | 39 ++++++++++++ .../cas/client/util/AbstractCasFilter.java | 63 ------------------- .../AbstractTicketValidationFilter.java | 10 +-- .../AuthenticationFilterTests.java | 2 +- .../Cas10TicketValidationFilterTests.java | 13 +--- .../Saml11TicketValidationFilterTests.java | 14 +---- .../Saml11TicketValidatorTests.java | 6 +- 10 files changed, 164 insertions(+), 101 deletions(-) create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index c00be68..c175442 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -19,10 +19,12 @@ package org.jasig.cas.client.authentication; import java.io.IOException; + import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; + import org.jasig.cas.client.util.AbstractCasFilter; import org.jasig.cas.client.util.CommonUtils; import org.jasig.cas.client.util.ReflectUtils; @@ -42,11 +44,11 @@ import org.jasig.cas.client.validation.Assertion; *

Please see AbstractCasFilter for additional properties.

* * @author Scott Battaglia - * @version $Revision: 11768 $ $Date: 2007-02-07 15:44:16 -0500 (Wed, 07 Feb 2007) $ + * @author Misagh Moayyed * @since 3.0 */ public class AuthenticationFilter extends AbstractCasFilter { - + /** * The URL to the CAS Server login. */ @@ -64,6 +66,8 @@ public class AuthenticationFilter extends AbstractCasFilter { private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl(); + private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null; + private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy(); protected void initInternal(final FilterConfig filterConfig) throws ServletException { @@ -75,7 +79,22 @@ public class AuthenticationFilter extends AbstractCasFilter { logger.trace("Loaded renew parameter: {}", this.renew); setGateway(parseBoolean(getPropertyFromInitParams(filterConfig, "gateway", "false"))); logger.trace("Loaded gateway parameter: {}", this.gateway); - + + final String ignorePattern = getPropertyFromInitParams(filterConfig, "ignorePattern", null); + logger.trace("Loaded ignorePattern parameter: {}", ignorePattern); + + final String ignoreUrlMatcherClass = getPropertyFromInitParams(filterConfig, "ignoreUrlPatternMatcherStrategyClass", null); + logger.trace("Loaded ignoreUrlPatternMatcherStrategyClass parameter: {}", ignoreUrlMatcherClass); + + if (ignorePattern != null ) { + if (ignoreUrlMatcherClass != null) { + this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass); + } else { + this.ignoreUrlPatternMatcherStrategyClass = new RegexUrlPatternMatcherStrategy(); + } + this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern); + } + final String gatewayStorageClass = getPropertyFromInitParams(filterConfig, "gatewayStorageClass", null); if (gatewayStorageClass != null) { @@ -159,4 +178,18 @@ public class AuthenticationFilter extends AbstractCasFilter { public final void setGatewayStorage(final GatewayResolver gatewayStorage) { this.gatewayStorage = gatewayStorage; } + + private boolean isRequestUrlExcluded(final HttpServletRequest request) { + + if (this.ignoreUrlPatternMatcherStrategyClass == null) { + return false; + } + + final StringBuffer urlBuffer = request.getRequestURL(); + if (request.getQueryString() != null) { + urlBuffer.append("?").append(request.getQueryString()); + } + final String requestUri = urlBuffer.toString(); + return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri); + } } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java new file mode 100644 index 0000000..befe6ee --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java @@ -0,0 +1,38 @@ +/* + * 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.authentication; + +/** + * A pattern matcher that looks inside the url to find the exact pattern specified. + * + * @author Misagh Moayyed + * @since 3.3.1 + */ +public class ExactUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { + + private String pattern; + + public boolean matches(final String url) { + return url.contains(this.pattern); + } + + public void setPattern(final String pattern) { + this.pattern = pattern; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java new file mode 100644 index 0000000..7bb54e8 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java @@ -0,0 +1,41 @@ +/* + * 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.authentication; + +import java.util.regex.Pattern; + +/** + * A pattern matcher that looks inside the url to find the pattern,. that + * is assumed to have been specified via regular expressions syntax. + * + * @author Misagh Moayyed + * @since 3.3.1 + */ +public class RegexUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { + + private Pattern pattern; + + public boolean matches(final String url) { + return this.pattern.matcher(url).find(); + } + + public void setPattern(final String pattern) { + this.pattern = Pattern.compile(pattern); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java new file mode 100644 index 0000000..e72470e --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java @@ -0,0 +1,39 @@ +/* + * 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.authentication; +/** + * Defines an abstraction by which request urls can be matches against a given pattern. + * + * @author Misagh Moayyed + * @since 3.3.1 + */ +public interface UrlPatternMatcherStrategy { + /** + * Execute the match between the given pattern and the url + * @param url the request url typically with query strings included + * @return true if match is successful + */ + boolean matches(final String url); + + /** + * The pattern against which the url is compared + * @param pattern + */ + void setPattern(final String pattern); +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java index 70a5a43..8cf47ef 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java @@ -18,8 +18,6 @@ */ package org.jasig.cas.client.util; -import java.util.regex.Pattern; - import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -40,15 +38,6 @@ import javax.servlet.http.HttpServletResponse; * @since 3.1 */ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { - - /** - * Enumeration that defines pattern types. - * @since 3.3.1 - */ - public enum IgnorePatternTypes { - NONE, - REGEX; - } /** Represents the constant for where the assertion will be located in memory. */ public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_"; @@ -58,16 +47,6 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { /** Defines the parameter to look for for the service. */ private String serviceParameterName = "service"; - - /** Url pattern for this filter to exclude and ignore. - * @since 3.3.1 - **/ - private String ignorePattern = null; - - /** Denotes the pattern type. - * @since 3.3.1 - */ - private IgnorePatternTypes ignorePatternType = null; /** Sets where response.encodeUrl should be called on service urls when constructed. */ private boolean encodeServiceUrl = true; @@ -92,16 +71,6 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { logger.trace("Loading serviceParameterName property: {} ", this.serviceParameterName); setEncodeServiceUrl(parseBoolean(getPropertyFromInitParams(filterConfig, "encodeServiceUrl", "true"))); logger.trace("Loading encodeServiceUrl property: {}", this.encodeServiceUrl); - - final String ignorePattern = getPropertyFromInitParams(filterConfig, "ignorePattern", null); - if (ignorePattern != null) { - setIgnorePattern(ignorePattern); - logger.trace("Loading ignorePattern property: {}", ignorePattern); - } - - setIgnorePatternType(Enum.valueOf(IgnorePatternTypes.class, getPropertyFromInitParams(filterConfig, "ignorePatternType", - IgnorePatternTypes.REGEX.name()))); - logger.trace("Loading ignorePatternType property: {}", ignorePatternType); initInternal(filterConfig); } @@ -179,14 +148,6 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { public final String getServiceParameterName() { return this.serviceParameterName; } - - public final void setIgnorePattern(final String patternToIgnore) { - this.ignorePattern = patternToIgnore; - } - - public final void setIgnorePatternType(final IgnorePatternTypes patternType) { - this.ignorePatternType = patternType; - } /** * Template method to allow you to change how you retrieve the ticket. @@ -197,28 +158,4 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { protected String retrieveTicketFromRequest(final HttpServletRequest request) { return CommonUtils.safeGetParameter(request, getArtifactParameterName()); } - - protected boolean isRequestUrlExcluded(final HttpServletRequest request) { - boolean result = false; - if (this.ignorePattern != null) { - final StringBuffer urlBuffer = request.getRequestURL(); - if (request.getQueryString() != null) { - urlBuffer.append("?").append(request.getQueryString()); - } - final String requestUri = urlBuffer.toString(); - logger.debug("Checking [{}] against pattern [{}]", requestUri, this.ignorePattern); - - - switch (this.ignorePatternType) { - case NONE: - result = requestUri.contains(this.ignorePattern); - break; - case REGEX: - result = Pattern.compile(this.ignorePattern).matcher(requestUri).find(); - break; - } - - } - return result; - } } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java index ac5c7af..1d27314 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java @@ -190,13 +190,13 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter { public final 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 (!preFilter(servletRequest, servletResponse, filterChain)) { return; } - + + final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpServletResponse response = (HttpServletResponse) servletResponse; final String ticket = retrieveTicketFromRequest(request); if (CommonUtils.isNotBlank(ticket)) { @@ -254,4 +254,4 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter { public final void setUseSession(final boolean useSession) { this.useSession = useSession; } -} +} \ No newline at end of file diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index b8c1bfe..f9b8b8f 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -251,7 +251,7 @@ public final class AuthenticationFilterTests { context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); context.addInitParameter("ignorePattern", "=valueToIgnore"); - context.addInitParameter("ignorePatternType", "NONE"); + context.addInitParameter("ignoreUrlPatternMatcherStrategyClass", ExactUrlPatternMatcherStrategy.class.getName()); context.addInitParameter("service", CAS_SERVICE_URL); f.init(new MockFilterConfig(context)); diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java index 74e6d7a..8ccdb2f 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidationFilterTests.java @@ -20,19 +20,8 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - import org.junit.Test; import org.springframework.mock.web.MockFilterConfig; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; /** @@ -65,4 +54,4 @@ public class Cas10TicketValidationFilterTests { assertTrue(validator instanceof Cas10TicketValidator); assertTrue(((Cas10TicketValidator) validator).isRenew()); } -} +} \ No newline at end of file diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java index 4ba6a36..53d1875 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java @@ -20,19 +20,8 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - import org.junit.Test; import org.springframework.mock.web.MockFilterConfig; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; /** @@ -65,5 +54,4 @@ public class Saml11TicketValidationFilterTests { assertTrue(validator instanceof Saml11TicketValidator); assertTrue(((Saml11TicketValidator) validator).isRenew()); } - -} +} \ No newline at end of file diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java index 41b58ec..417db57 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java @@ -20,10 +20,8 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; - import java.io.UnsupportedEncodingException; import java.util.Date; - import org.jasig.cas.client.PublicTestHttpServer; import org.jasig.cas.client.util.CommonUtils; import org.joda.time.DateTime; @@ -139,8 +137,8 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes fail(e.toString()); } } - + private Interval currentTimeRangeInterval() { return new Interval(new DateTime(DateTimeZone.UTC).minus(5000), new DateTime(DateTimeZone.UTC).plus(200000000)); } -} +} \ No newline at end of file From 75584a2c33b3c4ed2f923d2bc68256856e4a084f Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Sun, 9 Mar 2014 23:13:04 -0400 Subject: [PATCH 05/25] CASC-214 Improve Service Url Construction to Add Non-Standard Ports if Missing from Configuration Problem: sometimes the port is missing from the configuration. This generates the wrong service url. Solution: Add the server port if the server configuration does not have one. QA Notes: Added unit tests to confirm behavior (and old unit tests still pass) --- .../jasig/cas/client/util/CommonUtils.java | 28 +++++++++++++++++++ .../cas/client/util/CommonUtilsTests.java | 21 ++++++++++++++ 2 files changed, 49 insertions(+) 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..2b0d380 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 @@ -225,6 +225,21 @@ public final class CommonUtils { return serverNames[0]; } + private static boolean serverNameContainsPort(final boolean containsScheme, final String serverName) { + if (!containsScheme && serverName.contains(":")) { + return true; + } + + final int schemeIndex = serverName.indexOf(":"); + final int portIndex = serverName.lastIndexOf(":"); + return schemeIndex != portIndex; + } + + private static boolean requestIsOnStandardPort(final HttpServletRequest request) { + final int serverPort = request.getServerPort(); + return serverPort == 80 || serverPort == 443; + } + /** * Constructs a service url from the HttpServletRequest or from the given * serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a @@ -250,11 +265,24 @@ public final class CommonUtils { final String serverName = findMatchingServerName(request, serverNames); + boolean containsScheme = true; if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) { buffer.append(request.isSecure() ? "https://" : "http://"); + containsScheme = false; } buffer.append(serverName); + + final boolean serverNameContainsPort = serverNameContainsPort(containsScheme, serverName); + System.out.println("serverNameContainsPort " + serverNameContainsPort); + final boolean requestIsOnStandardPort = requestIsOnStandardPort(request); + System.out.println("requestIsOnStandardPort " + requestIsOnStandardPort); + + if (!serverNameContainsPort(containsScheme, serverName) && !requestIsOnStandardPort(request)) { + buffer.append(":"); + buffer.append(request.getServerPort()); + } + buffer.append(request.getRequestURI()); if (CommonUtils.isNotBlank(request.getQueryString())) { 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..43967ab 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 @@ -136,6 +136,27 @@ public final class CommonUtilsTests extends TestCase { assertEquals(CONST_MY_URL, constructedUrl); } + private void constructUrlNonStandardPortAndNoPortInConfigTest(final String serverNameList) { + final String CONST_MY_URL = "https://www.myserver.com:555/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.addHeader("Host", "www.myserver.com"); + request.setScheme("https"); + request.setSecure(true); + request.setServerPort(555); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, + serverNameList, "ticket", false); + assertEquals(CONST_MY_URL, constructedUrl); + } + + public void testConstructUrlNonStandardPortAndNoScheme() { + constructUrlNonStandardPortAndNoPortInConfigTest("www.myserver.com"); + } + + public void testConstructUrlNonStandardPortAndScheme() { + constructUrlNonStandardPortAndNoPortInConfigTest("https://www.myserver.com"); + } + public void testConstructUrlWithMultipleHostsNoPortsOrProtocol() { final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); From a4df6582eea1b1873a2f0b9c344e1e961abfe6ad Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Sun, 9 Mar 2014 23:18:14 -0400 Subject: [PATCH 06/25] Removed debug statements used to confirm private methods worked. --- .../src/main/java/org/jasig/cas/client/util/CommonUtils.java | 5 ----- 1 file changed, 5 deletions(-) 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 2b0d380..8fb90c7 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 @@ -273,11 +273,6 @@ public final class CommonUtils { buffer.append(serverName); - final boolean serverNameContainsPort = serverNameContainsPort(containsScheme, serverName); - System.out.println("serverNameContainsPort " + serverNameContainsPort); - final boolean requestIsOnStandardPort = requestIsOnStandardPort(request); - System.out.println("requestIsOnStandardPort " + requestIsOnStandardPort); - if (!serverNameContainsPort(containsScheme, serverName) && !requestIsOnStandardPort(request)) { buffer.append(":"); buffer.append(request.getServerPort()); From b97d03d12647491d70b08490db70b44323488b9a Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Mon, 10 Mar 2014 05:22:44 -0700 Subject: [PATCH 07/25] CASC-219: Cleaned up formatting issues --- .../jasig/cas/client/authentication/AuthenticationFilter.java | 1 - .../cas/client/authentication/UrlPatternMatcherStrategy.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index c175442..9a6f084 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -180,7 +180,6 @@ public class AuthenticationFilter extends AbstractCasFilter { } private boolean isRequestUrlExcluded(final HttpServletRequest request) { - if (this.ignoreUrlPatternMatcherStrategyClass == null) { return false; } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java index e72470e..2117a2a 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java @@ -29,11 +29,11 @@ public interface UrlPatternMatcherStrategy { * @param url the request url typically with query strings included * @return true if match is successful */ - boolean matches(final String url); + boolean matches(String url); /** * The pattern against which the url is compared * @param pattern */ - void setPattern(final String pattern); + void setPattern(String pattern); } From 3773fc9e54919512997450dadefb5a33e3bc847b Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Tue, 11 Mar 2014 04:24:27 -0700 Subject: [PATCH 08/25] CASC-219: updated javadocs and added map to keep track of pattern matchers --- .../authentication/AuthenticationFilter.java | 32 +++++++++++-------- .../UrlPatternMatcherStrategy.java | 5 ++- .../AuthenticationFilterTests.java | 4 +-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index 9a6f084..900342c 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -19,6 +19,8 @@ package org.jasig.cas.client.authentication; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; @@ -66,10 +68,18 @@ public class AuthenticationFilter extends AbstractCasFilter { private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl(); + private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy(); + private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null; - private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy(); - + private final Map> PATTERN_MATCHER_TYPES = + new HashMap>(); + + public AuthenticationFilter() { + this.PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class); + this.PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class); + } + protected void initInternal(final FilterConfig filterConfig) throws ServletException { if (!isIgnoreInitConfiguration()) { super.initInternal(filterConfig); @@ -83,16 +93,17 @@ public class AuthenticationFilter extends AbstractCasFilter { final String ignorePattern = getPropertyFromInitParams(filterConfig, "ignorePattern", null); logger.trace("Loaded ignorePattern parameter: {}", ignorePattern); - final String ignoreUrlMatcherClass = getPropertyFromInitParams(filterConfig, "ignoreUrlPatternMatcherStrategyClass", null); - logger.trace("Loaded ignoreUrlPatternMatcherStrategyClass parameter: {}", ignoreUrlMatcherClass); + final String ignoreUrlPatternType = getPropertyFromInitParams(filterConfig, "ignoreUrlPatternType", "REGEX"); + logger.trace("Loaded ignoreUrlPatternType parameter: {}", ignoreUrlPatternType); if (ignorePattern != null ) { + final Class ignoreUrlMatcherClass = this.PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType); if (ignoreUrlMatcherClass != null) { - this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass); + this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName()); + this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern); } else { - this.ignoreUrlPatternMatcherStrategyClass = new RegexUrlPatternMatcherStrategy(); + logger.trace("Could not find and load: {}", ignoreUrlMatcherClass); } - this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern); } final String gatewayStorageClass = getPropertyFromInitParams(filterConfig, "gatewayStorageClass", null); @@ -100,13 +111,6 @@ public class AuthenticationFilter extends AbstractCasFilter { if (gatewayStorageClass != null) { this.gatewayStorage = ReflectUtils.newInstance(gatewayStorageClass); } - - final String authenticationRedirectStrategyClass = getPropertyFromInitParams(filterConfig, - "authenticationRedirectStrategyClass", null); - - if (authenticationRedirectStrategyClass != null) { - this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass); - } } } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java index 2117a2a..a2e70e9 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java @@ -19,7 +19,10 @@ package org.jasig.cas.client.authentication; /** * Defines an abstraction by which request urls can be matches against a given pattern. - * + * New instances for all extensions for this strategy interface will be created per + * each request. The client will ultimately invoke the {@link #matches(String)} method + * having already applied and set the pattern via the {@link #setPattern(String)} method. + * The pattern itself will be retrieved via the client configuration. * @author Misagh Moayyed * @since 3.3.1 */ diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index f9b8b8f..d52e18e 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -245,13 +245,13 @@ public final class AuthenticationFilterTests { } @Test - public void testIgnorePatternsWithNoRegex() throws Exception { + public void testIgnorePatternsWithExactMatching() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); final MockServletContext context = new MockServletContext(); context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); context.addInitParameter("ignorePattern", "=valueToIgnore"); - context.addInitParameter("ignoreUrlPatternMatcherStrategyClass", ExactUrlPatternMatcherStrategy.class.getName()); + context.addInitParameter("ignoreUrlPatternType", "EXACT"); context.addInitParameter("service", CAS_SERVICE_URL); f.init(new MockFilterConfig(context)); From 511bce24711a91dc47c8a2e71b44efdce9f66b80 Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Tue, 11 Mar 2014 05:04:59 -0700 Subject: [PATCH 09/25] CASC-219: allowed extensions for ignoring urls --- .../authentication/AuthenticationFilter.java | 20 ++++++- .../AuthenticationFilterTests.java | 58 +++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index 900342c..d2fa5ef 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -96,13 +96,20 @@ public class AuthenticationFilter extends AbstractCasFilter { final String ignoreUrlPatternType = getPropertyFromInitParams(filterConfig, "ignoreUrlPatternType", "REGEX"); logger.trace("Loaded ignoreUrlPatternType parameter: {}", ignoreUrlPatternType); - if (ignorePattern != null ) { + if (ignorePattern != null) { final Class ignoreUrlMatcherClass = this.PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType); if (ignoreUrlMatcherClass != null) { this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName()); - this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern); } else { - logger.trace("Could not find and load: {}", ignoreUrlMatcherClass); + try { + logger.trace("Assuming {} is a qualfiied class name...", ignoreUrlPatternType); + this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType); + } catch (final IllegalArgumentException e) { + logger.warn("Could not instantiate class [{}]: [{}]", ignoreUrlPatternType, e.getMessage()); + } + } + if (this.ignoreUrlPatternMatcherStrategyClass != null) { + this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern); } } @@ -111,6 +118,13 @@ public class AuthenticationFilter extends AbstractCasFilter { if (gatewayStorageClass != null) { this.gatewayStorage = ReflectUtils.newInstance(gatewayStorageClass); } + + final String authenticationRedirectStrategyClass = getPropertyFromInitParams(filterConfig, + "authenticationRedirectStrategyClass", null); + + if (authenticationRedirectStrategyClass != null) { + this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass); + } } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index d52e18e..d006188 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -272,4 +272,62 @@ public final class AuthenticationFilterTests { f.doFilter(request, response, filterChain); assertNull(response.getRedirectedUrl()); } + + @Test + public void testIgnorePatternsWithExactClassname() throws Exception { + final AuthenticationFilter f = new AuthenticationFilter(); + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + + context.addInitParameter("ignorePattern", "=valueToIgnore"); + context.addInitParameter("ignoreUrlPatternType", ExactUrlPatternMatcherStrategy.class.getName()); + context.addInitParameter("service", CAS_SERVICE_URL); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String URL = CAS_SERVICE_URL + "?param=valueToIgnore"; + request.setRequestURI(URL); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + f.doFilter(request, response, filterChain); + assertNull(response.getRedirectedUrl()); + } + + @Test + public void testIgnorePatternsWithInvalidClassname() throws Exception { + final AuthenticationFilter f = new AuthenticationFilter(); + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + + context.addInitParameter("ignorePattern", "=valueToIgnore"); + context.addInitParameter("ignoreUrlPatternType", "unknown.class.name"); + context.addInitParameter("service", CAS_SERVICE_URL); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String URL = CAS_SERVICE_URL + "?param=valueToIgnore"; + request.setRequestURI(URL); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + f.doFilter(request, response, filterChain); + System.out.println(response.getRedirectedUrl()); + } } From dfb3629dc2f65f47a7c4ac616979f99e7a060e40 Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Tue, 11 Mar 2014 07:11:53 -0700 Subject: [PATCH 10/25] CASC-219: updated internal map to be a static ref instead --- .../cas/client/authentication/AuthenticationFilter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index d2fa5ef..25d525b 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -72,12 +72,12 @@ public class AuthenticationFilter extends AbstractCasFilter { private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null; - private final Map> PATTERN_MATCHER_TYPES = + private static final Map> PATTERN_MATCHER_TYPES = new HashMap>(); - public AuthenticationFilter() { - this.PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class); - this.PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class); + static { + PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class); + PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class); } protected void initInternal(final FilterConfig filterConfig) throws ServletException { From 248643cf309bc4b2daf838ad5f108c83705d49ab Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Tue, 11 Mar 2014 08:46:00 -0700 Subject: [PATCH 11/25] CASC-219: switched warn to error, fixed typo and this refs --- .../cas/client/authentication/AuthenticationFilter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index 25d525b..c7e225d 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -50,7 +50,6 @@ import org.jasig.cas.client.validation.Assertion; * @since 3.0 */ public class AuthenticationFilter extends AbstractCasFilter { - /** * The URL to the CAS Server login. */ @@ -97,15 +96,15 @@ public class AuthenticationFilter extends AbstractCasFilter { logger.trace("Loaded ignoreUrlPatternType parameter: {}", ignoreUrlPatternType); if (ignorePattern != null) { - final Class ignoreUrlMatcherClass = this.PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType); + final Class ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType); if (ignoreUrlMatcherClass != null) { this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName()); } else { try { - logger.trace("Assuming {} is a qualfiied class name...", ignoreUrlPatternType); + logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType); this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType); } catch (final IllegalArgumentException e) { - logger.warn("Could not instantiate class [{}]: [{}]", ignoreUrlPatternType, e.getMessage()); + logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e); } } if (this.ignoreUrlPatternMatcherStrategyClass != null) { From 00ee9e378dfaa89557b1eb74c0bfd76681b670dd Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Wed, 12 Mar 2014 07:37:59 -0700 Subject: [PATCH 12/25] CASC-219: renamed matcher class to match behavior --- .../jasig/cas/client/authentication/AuthenticationFilter.java | 2 +- ...ategy.java => ContainsPatternUrlPatternMatcherStrategy.java} | 2 +- .../cas/client/authentication/AuthenticationFilterTests.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename cas-client-core/src/main/java/org/jasig/cas/client/authentication/{ExactUrlPatternMatcherStrategy.java => ContainsPatternUrlPatternMatcherStrategy.java} (92%) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index c7e225d..8ab5d81 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -75,7 +75,7 @@ public class AuthenticationFilter extends AbstractCasFilter { new HashMap>(); static { - PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class); + PATTERN_MATCHER_TYPES.put("EXACT", ContainsPatternUrlPatternMatcherStrategy.class); PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class); } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java similarity index 92% rename from cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java rename to cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java index befe6ee..1532481 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java @@ -24,7 +24,7 @@ package org.jasig.cas.client.authentication; * @author Misagh Moayyed * @since 3.3.1 */ -public class ExactUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { +public class ContainsPatternUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { private String pattern; diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index d006188..d5cffe6 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -280,7 +280,7 @@ public final class AuthenticationFilterTests { context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); context.addInitParameter("ignorePattern", "=valueToIgnore"); - context.addInitParameter("ignoreUrlPatternType", ExactUrlPatternMatcherStrategy.class.getName()); + context.addInitParameter("ignoreUrlPatternType", ContainsPatternUrlPatternMatcherStrategy.class.getName()); context.addInitParameter("service", CAS_SERVICE_URL); f.init(new MockFilterConfig(context)); From 0a8fd79a442e5a6106546fbec74a1c6edad544fa Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Thu, 13 Mar 2014 03:20:39 -0700 Subject: [PATCH 13/25] CASC-219: changed exact to contains --- .../jasig/cas/client/authentication/AuthenticationFilter.java | 2 +- .../cas/client/authentication/AuthenticationFilterTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index 8ab5d81..42b6817 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -75,7 +75,7 @@ public class AuthenticationFilter extends AbstractCasFilter { new HashMap>(); static { - PATTERN_MATCHER_TYPES.put("EXACT", ContainsPatternUrlPatternMatcherStrategy.class); + PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class); PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class); } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index d5cffe6..3320a0d 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -251,7 +251,7 @@ public final class AuthenticationFilterTests { context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); context.addInitParameter("ignorePattern", "=valueToIgnore"); - context.addInitParameter("ignoreUrlPatternType", "EXACT"); + context.addInitParameter("ignoreUrlPatternType", "CONTAINS"); context.addInitParameter("service", CAS_SERVICE_URL); f.init(new MockFilterConfig(context)); From d2b93a237f61725b4bc78eb34ffccfe90713124c Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Thu, 13 Mar 2014 08:26:22 -0700 Subject: [PATCH 14/25] CASC-219: added exact-matcher strategy --- .../authentication/AuthenticationFilter.java | 1 + .../ExactUrlPatternMatcherStrategy.java | 22 ++++++++++ .../AuthenticationFilterTests.java | 40 ++++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java index 42b6817..b77c64c 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -77,6 +77,7 @@ public class AuthenticationFilter extends AbstractCasFilter { static { PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class); PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class); + PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class); } protected void initInternal(final FilterConfig filterConfig) throws ServletException { diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java new file mode 100644 index 0000000..0a5b42f --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java @@ -0,0 +1,22 @@ +package org.jasig.cas.client.authentication; + +/** + * A pattern matcher that produces a successful match if the pattern + * specified matches the given url exactly and equally. + * + * @author Misagh Moayyed + * @since 3.3.1 + */ +public class ExactUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { + + private String pattern; + + public boolean matches(final String url) { + return url.equals(this.pattern); + } + + public void setPattern(final String pattern) { + this.pattern = pattern; + } + +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java index 3320a0d..48479a5 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -19,13 +19,17 @@ package org.jasig.cas.client.authentication; import static org.junit.Assert.*; + import java.io.IOException; import java.lang.reflect.Field; +import java.net.URL; import java.net.URLEncoder; + import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; + import org.jasig.cas.client.util.AbstractCasFilter; import org.jasig.cas.client.validation.AssertionImpl; import org.junit.After; @@ -245,7 +249,7 @@ public final class AuthenticationFilterTests { } @Test - public void testIgnorePatternsWithExactMatching() throws Exception { + public void testIgnorePatternsWithContainsMatching() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); final MockServletContext context = new MockServletContext(); context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); @@ -273,6 +277,40 @@ public final class AuthenticationFilterTests { assertNull(response.getRedirectedUrl()); } + @Test + public void testIgnorePatternsWithExactMatching() throws Exception { + final AuthenticationFilter f = new AuthenticationFilter(); + final MockServletContext context = new MockServletContext(); + context.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + + final URL url = new URL(CAS_SERVICE_URL + "?param=valueToIgnore"); + + context.addInitParameter("ignorePattern", url.toExternalForm()); + context.addInitParameter("ignoreUrlPatternType", "EXACT"); + context.addInitParameter("service", CAS_SERVICE_URL); + f.init(new MockFilterConfig(context)); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setScheme(url.getProtocol()); + request.setServerName(url.getHost()); + request.setServerPort(url.getPort()); + request.setQueryString(url.getQuery()); + request.setRequestURI(url.getPath()); + + final MockHttpSession session = new MockHttpSession(); + request.setSession(session); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final FilterChain filterChain = new FilterChain() { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + } + }; + + f.doFilter(request, response, filterChain); + assertNull(response.getRedirectedUrl()); + } + @Test public void testIgnorePatternsWithExactClassname() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); From 47f825871ea19d1cd54df890f7e84e1051caa392 Mon Sep 17 00:00:00 2001 From: LELEU Jerome Date: Thu, 13 Mar 2014 19:03:41 +0100 Subject: [PATCH 15/25] CASC-220: Support front channel SLO logout First commit --- .../client/session/SingleSignOutFilter.java | 51 +++++- .../client/session/SingleSignOutHandler.java | 76 ++++++++- .../jasig/cas/client/util/CommonUtils.java | 15 +- .../session/LogoutMessageGenerator.java | 37 ++++ .../session/SingleSignOutFilterTests.java | 118 +++++++++++++ .../session/SingleSignOutHandlerTests.java | 160 ++++++++++++++++++ .../session/SingleSignoutHandlerTests.java | 68 -------- .../cas/client/util/CommonUtilsTests.java | 4 + 8 files changed, 449 insertions(+), 80 deletions(-) create mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/session/LogoutMessageGenerator.java create mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutFilterTests.java create mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java delete mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignoutHandlerTests.java 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 =%/")); + } } From 9b33321cc251e633232ae1d10d4eee49b3d39d19 Mon Sep 17 00:00:00 2001 From: LELEU Jerome Date: Fri, 14 Mar 2014 10:52:42 +0100 Subject: [PATCH 16/25] CASC-220: Support front channel SLO logout specific front logout parameter + Tomcat valves update --- .../client/session/SingleSignOutFilter.java | 47 +++++-------- .../client/session/SingleSignOutHandler.java | 68 +++++++++++++++++-- .../session/LogoutMessageGenerator.java | 6 +- .../session/SingleSignOutFilterTests.java | 15 ++-- .../session/SingleSignOutHandlerTests.java | 51 ++++++++++---- .../client/tomcat/v6/SingleSignOutValve.java | 23 ++++++- .../client/tomcat/v7/SingleSignOutValve.java | 23 ++++++- 7 files changed, 168 insertions(+), 65 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 1126218..92235dc 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 @@ -23,7 +23,6 @@ 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; @@ -38,25 +37,21 @@ 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", SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME)); handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME)); - setRelayStateParameterName(getPropertyFromInitParams(filterConfig, "relayStateParameterName", + handler.setFrontLogoutParameterName(getPropertyFromInitParams(filterConfig, "frontLogoutParameterName", + SingleSignOutHandler.DEFAULT_FRONT_LOGOUT_PARAMETER_NAME)); + handler.setRelayStateParameterName(getPropertyFromInitParams(filterConfig, "relayStateParameterName", SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME)); + handler.setCasServerUrlPrefix(getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null)); handler.setArtifactParameterOverPost(parseBoolean(getPropertyFromInitParams(filterConfig, "artifactParameterOverPost", "false"))); handler.setEagerlyCreateSessions(parseBoolean(getPropertyFromInitParams(filterConfig, "eagerlyCreateSessions", "true"))); - setCasServerUrlPrefix(getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null)); } handler.init(); } @@ -69,20 +64,22 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter { handler.setLogoutParameterName(name); } + public void setFrontLogoutParameterName(final String name) { + handler.setFrontLogoutParameterName(name); + } + public void setRelayStateParameterName(final String name) { - this.relayStateParameterName = name; handler.setRelayStateParameterName(name); } + public void setCasServerUrlPrefix(final String casServerUrlPrefix) { + handler.setCasServerUrlPrefix(casServerUrlPrefix); + } + 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; @@ -96,22 +93,10 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter { 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); + // redirection url to the CAS server + final String redirectionUrl = handler.computeRedirectionToServer(request); + if (redirectionUrl != null) { + CommonUtils.sendRedirect(response, redirectionUrl); } return; } else { 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 873d322..1b40afd 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 @@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; import org.jasig.cas.client.util.CommonUtils; import org.jasig.cas.client.util.XmlUtils; import org.slf4j.Logger; @@ -46,6 +47,7 @@ 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_FRONT_LOGOUT_PARAMETER_NAME = "SAMLRequest"; public final static String DEFAULT_RELAY_STATE_PARAMETER_NAME = "RelayState"; /** Logger instance */ @@ -57,12 +59,18 @@ public final class SingleSignOutHandler { /** The name of the artifact parameter. This is used to capture the session identifier. */ private String artifactParameterName = DEFAULT_ARTIFACT_PARAMETER_NAME; - /** Parameter name that stores logout request */ + /** Parameter name that stores logout request for back channel SLO */ private String logoutParameterName = DEFAULT_LOGOUT_PARAMETER_NAME; + /** Parameter name that stores logout request for front channel SLO */ + private String frontLogoutParameterName = DEFAULT_FRONT_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; + /** The prefix url of the CAS server */ + private String casServerUrlPrefix; + private boolean artifactParameterOverPost = false; private boolean eagerlyCreateSessions = true; @@ -89,12 +97,26 @@ public final class SingleSignOutHandler { } /** - * @param name Name of parameter containing CAS logout request message. + * @param name Name of parameter containing CAS logout request message for back channel SLO. */ public void setLogoutParameterName(final String name) { this.logoutParameterName = name; } + /** + * @param casServerUrlPrefix The prefix url of the CAS server. + */ + public void setCasServerUrlPrefix(final String casServerUrlPrefix) { + this.casServerUrlPrefix = casServerUrlPrefix; + } + + /** + * @param name Name of parameter containing CAS logout request message for front channel SLO. + */ + public void setFrontLogoutParameterName(final String name) { + this.frontLogoutParameterName = name; + } + /** * @param name Name of parameter containing the state of the CAS server webflow. */ @@ -112,8 +134,10 @@ public final class SingleSignOutHandler { public void init() { CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null."); CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null."); + CommonUtils.assertNotNull(this.frontLogoutParameterName, "frontLogoutParameterName cannot be null."); CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannot be null."); CommonUtils.assertNotNull(this.relayStateParameterName, "relayStateParameterName cannot be null."); + CommonUtils.assertNotNull(this.casServerUrlPrefix, "casServerUrlPrefix cannot be null."); if (this.artifactParameterOverPost) { this.safeParameters = Arrays.asList(this.logoutParameterName, this.artifactParameterName); @@ -157,7 +181,7 @@ public final class SingleSignOutHandler { */ public boolean isFrontChannelLogoutRequest(final HttpServletRequest request) { return "GET".equals(request.getMethod()) - && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName)); + && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.frontLogoutParameterName)); } /** @@ -220,11 +244,13 @@ public final class SingleSignOutHandler { * @param request HTTP request containing a CAS logout message. */ public void destroySession(final HttpServletRequest request) { - String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, - this.safeParameters); - // front channel request -> the message needs to be base64 decoded + decompressed + String logoutMessage; + // front channel logout -> the message needs to be base64 decoded + decompressed if ("GET".equals(request.getMethod())) { - logoutMessage = uncompressLogoutMessage(logoutMessage); + logoutMessage = uncompressLogoutMessage(CommonUtils.safeGetParameter(request, + this.frontLogoutParameterName)); + } else { + logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters); } logger.trace("Logout request:\n{}", logoutMessage); @@ -251,6 +277,34 @@ public final class SingleSignOutHandler { } } + /** + * Compute the redirection url to the CAS server when it's a front channel SLO + * (depending on the relay state parameter). + * + * @param request The HTTP request. + * @return the redirection url to the CAS server. + */ + public 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 + 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("Redirection url to the CAS server: {}", redirectUrl); + return redirectUrl; + } + return null; + } + private boolean isMultipartRequest(final HttpServletRequest request) { return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart"); } 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 e30254d..b43ea65 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 @@ -19,12 +19,12 @@ public final class LogoutMessageGenerator { + "IssueInstant=\"\">@NOT_USED@" + "%s"; - public static String generateLogoutMessage(String sessionIndex) { + public static String generateBackChannelLogoutMessage(String sessionIndex) { return String.format(LOGOUT_REQUEST_TEMPLATE, sessionIndex); } - public static String generateCompressedLogoutMessage(String sessionIndex) { - final String logoutMessage = generateLogoutMessage(sessionIndex); + public static String generateFrontChannelLogoutMessage(String sessionIndex) { + final String logoutMessage = generateBackChannelLogoutMessage(sessionIndex); final Deflater deflater = new Deflater(); deflater.setInput(logoutMessage.getBytes(Charset.forName("ASCII"))); deflater.finish(); 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 index 82ecd52..64114f9 100644 --- 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 @@ -79,7 +79,8 @@ public class SingleSignOutFilterTests { @Test public void backChannelRequest() throws IOException, ServletException { - request.setParameter(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME, LogoutMessageGenerator.generateLogoutMessage(TICKET)); + request.setParameter(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME, + LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET)); request.setMethod("POST"); final MockHttpSession session = new MockHttpSession(); SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().addSessionById(TICKET, session); @@ -89,9 +90,9 @@ public class SingleSignOutFilterTests { @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); + final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET); + request.setParameter(SingleSignOutHandler.DEFAULT_FRONT_LOGOUT_PARAMETER_NAME, logoutMessage); + request.setQueryString(SingleSignOutHandler.DEFAULT_FRONT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage); request.setMethod("GET"); final MockHttpSession session = new MockHttpSession(); SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage().addSessionById(TICKET, session); @@ -102,10 +103,10 @@ public class SingleSignOutFilterTests { @Test public void frontChannelRequestRelayState() throws IOException, ServletException { - final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(TICKET); - request.setParameter(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME, logoutMessage); + final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET); + request.setParameter(SingleSignOutHandler.DEFAULT_FRONT_LOGOUT_PARAMETER_NAME, logoutMessage); request.setParameter(SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME, RELAY_STATE); - request.setQueryString(SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage + "&" + + request.setQueryString(SingleSignOutHandler.DEFAULT_FRONT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage + "&" + SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME + "=" + RELAY_STATE); request.setMethod("GET"); final MockHttpSession session = new MockHttpSession(); 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 1479536..c7670e5 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 @@ -37,15 +37,23 @@ 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 SingleSignOutHandler handler; private MockHttpServletRequest request; - private final static String logoutParameterName = "logoutRequest"; + private final static String logoutParameterName = "logoutRequest2"; + private final static String frontLogoutParameterName = "SAMLRequest2"; + private final static String relayStateParameterName = "RelayState2"; + private final static String artifactParameterName = "ticket2"; @Before public void setUp() throws Exception { handler = new SingleSignOutHandler(); handler.setLogoutParameterName(logoutParameterName); + handler.setFrontLogoutParameterName(frontLogoutParameterName); + handler.setRelayStateParameterName(relayStateParameterName); + handler.setArtifactParameterName(artifactParameterName); + handler.setCasServerUrlPrefix(URL); handler.init(); request = new MockHttpServletRequest(); } @@ -74,9 +82,9 @@ public final class SingleSignOutHandlerTests { @Test public void isFrontChannelLogoutRequest() { - request.setParameter(logoutParameterName, TICKET); + request.setParameter(frontLogoutParameterName, TICKET); request.setMethod("GET"); - request.setQueryString(logoutParameterName + "=" + TICKET); + request.setQueryString(frontLogoutParameterName + "=" + TICKET); assertTrue(handler.isFrontChannelLogoutRequest(request)); } @@ -94,8 +102,8 @@ public final class SingleSignOutHandlerTests { 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); + request.setParameter(artifactParameterName, TICKET); + request.setQueryString(artifactParameterName + "=" + TICKET); handler.recordSession(request); final SessionMappingStorage storage = handler.getSessionMappingStorage(); assertNull(storage.removeSessionByMappingId(TICKET)); @@ -105,8 +113,8 @@ public final class SingleSignOutHandlerTests { 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); + request.setParameter(artifactParameterName, TICKET); + request.setQueryString(artifactParameterName + "=" + TICKET); handler.recordSession(request); final SessionMappingStorage storage = handler.getSessionMappingStorage(); assertEquals(session, storage.removeSessionByMappingId(TICKET)); @@ -114,7 +122,7 @@ public final class SingleSignOutHandlerTests { @Test public void destorySessionPOSTKONoSessionIndex() { - final String logoutMessage = LogoutMessageGenerator.generateLogoutMessage(""); + final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(""); request.setParameter(logoutParameterName, logoutMessage); request.setMethod("POST"); final MockHttpSession session = new MockHttpSession(); @@ -125,7 +133,7 @@ public final class SingleSignOutHandlerTests { @Test public void destorySessionPOST() { - final String logoutMessage = LogoutMessageGenerator.generateLogoutMessage(TICKET); + final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET); request.setParameter(logoutParameterName, logoutMessage); request.setMethod("POST"); final MockHttpSession session = new MockHttpSession(); @@ -136,9 +144,9 @@ public final class SingleSignOutHandlerTests { @Test public void destorySessionGETNoSessionIndex() { - final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(""); - request.setParameter(logoutParameterName, logoutMessage); - request.setQueryString(logoutParameterName + "=" + logoutMessage); + 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); @@ -148,13 +156,26 @@ public final class SingleSignOutHandlerTests { @Test public void destorySessionGET() { - final String logoutMessage = LogoutMessageGenerator.generateCompressedLogoutMessage(TICKET); - request.setParameter(logoutParameterName, logoutMessage); - request.setQueryString(logoutParameterName + "=" + logoutMessage); + final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET); + 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(session.isInvalid()); } + + @Test + public void computeRedirectionNoRelayState() { + assertNull(handler.computeRedirectionToServer(request)); + } + + @Test + public void computeRedirection() { + request.setParameter(relayStateParameterName, TICKET); + request.setQueryString(relayStateParameterName + "=" + TICKET); + assertEquals(URL + "/logout?_eventId=next&" + relayStateParameterName + "=" + TICKET, + handler.computeRedirectionToServer(request)); + } } 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 1d95f43..d483e41 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,6 +28,7 @@ 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 @@ -52,6 +53,18 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio handler.setLogoutParameterName(name); } + public void setFrontLogoutParameterName(final String name) { + handler.setFrontLogoutParameterName(name); + } + + public void setRelayStateParameterName(final String name) { + handler.setRelayStateParameterName(name); + } + + public void setCasServerUrlPrefix(final String casServerUrlPrefix) { + handler.setCasServerUrlPrefix(casServerUrlPrefix); + } + public void setSessionMappingStorage(final SessionMappingStorage storage) { handler.setSessionMappingStorage(storage); } @@ -68,10 +81,18 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio if (this.handler.isTokenRequest(request)) { this.handler.recordSession(request); request.getSessionInternal(true).addSessionListener(this); - } else if (this.handler.isLogoutRequest(request)) { + } 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()); } 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 c1bee5b..8ecc865 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,6 +29,7 @@ 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; @@ -56,6 +57,18 @@ public class SingleSignOutValve extends ValveBase implements SessionListener { handler.setLogoutParameterName(name); } + public void setFrontLogoutParameterName(final String name) { + handler.setFrontLogoutParameterName(name); + } + + public void setRelayStateParameterName(final String name) { + handler.setRelayStateParameterName(name); + } + + public void setCasServerUrlPrefix(final String casServerUrlPrefix) { + handler.setCasServerUrlPrefix(casServerUrlPrefix); + } + public void setSessionMappingStorage(final SessionMappingStorage storage) { handler.setSessionMappingStorage(storage); } @@ -65,10 +78,18 @@ public class SingleSignOutValve extends ValveBase implements SessionListener { if (this.handler.isTokenRequest(request)) { this.handler.recordSession(request); request.getSessionInternal(true).addSessionListener(this); - } else if (this.handler.isLogoutRequest(request)) { + } 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()); } From ed4802f9432812ab2fe2f5ae5864131d57f6a20e Mon Sep 17 00:00:00 2001 From: Misagh Moayyed Date: Mon, 17 Mar 2014 00:18:56 -0700 Subject: [PATCH 17/25] CASC-219: added final --- .../ContainsPatternUrlPatternMatcherStrategy.java | 2 +- .../client/authentication/ExactUrlPatternMatcherStrategy.java | 2 +- .../client/authentication/RegexUrlPatternMatcherStrategy.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java index 1532481..48c9f5f 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java @@ -24,7 +24,7 @@ package org.jasig.cas.client.authentication; * @author Misagh Moayyed * @since 3.3.1 */ -public class ContainsPatternUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { +public final class ContainsPatternUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { private String pattern; diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java index 0a5b42f..b476a30 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java @@ -7,7 +7,7 @@ package org.jasig.cas.client.authentication; * @author Misagh Moayyed * @since 3.3.1 */ -public class ExactUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { +public final class ExactUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { private String pattern; diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java index 7bb54e8..a941459 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java @@ -27,7 +27,7 @@ import java.util.regex.Pattern; * @author Misagh Moayyed * @since 3.3.1 */ -public class RegexUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { +public final class RegexUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { private Pattern pattern; From 72766daac55eef179d10a9755933f0d3f5c47176 Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Tue, 18 Mar 2014 19:26:45 -0400 Subject: [PATCH 18/25] Added in missing license header. --- .../ExactUrlPatternMatcherStrategy.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java index b476a30..64f20eb 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java @@ -1,3 +1,21 @@ +/* + * 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.authentication; /** From 896958337a68196f0e23cd4f19fbae8a862161e0 Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Wed, 19 Mar 2014 20:50:45 -0400 Subject: [PATCH 19/25] Updated to parent pom 39 --- pom.xml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 09cd1fd..4272a51 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ org.jasig.parent jasig-parent - 38 + 39 4.0.0 org.jasig.cas.client @@ -12,7 +12,7 @@ Jasig CAS Client for Java - Jasig CAS Client for Java is the integration point forapplications that want to speak with a CAS + Jasig CAS Client for Java is the integration point for applications that want to speak with a CAS server, either via the CAS 1.0 or CAS 2.0 protocol. http://www.jasig.org/cas @@ -103,15 +103,6 @@ - - org.apache.maven.plugins - maven-release-plugin - 2.2.1 - - forked-path - v@{project.version} - - org.apache.maven.plugins maven-enforcer-plugin From 3373d7f7676217e34c495a77581b20ae36427f6d Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Wed, 19 Mar 2014 20:55:38 -0400 Subject: [PATCH 20/25] [maven-release-plugin] prepare release cas-client-3.3.1 --- cas-client-core/pom.xml | 2 +- cas-client-integration-atlassian/pom.xml | 2 +- cas-client-integration-jboss/pom.xml | 2 +- cas-client-integration-tomcat-common/pom.xml | 2 +- cas-client-integration-tomcat-v6/pom.xml | 2 +- cas-client-integration-tomcat-v7/pom.xml | 2 +- cas-client-support-distributed-ehcache/pom.xml | 2 +- cas-client-support-distributed-memcached/pom.xml | 2 +- pom.xml | 5 +++-- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index 1e6be56..dcb1941 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 cas-client 4.0.0 diff --git a/cas-client-integration-atlassian/pom.xml b/cas-client-integration-atlassian/pom.xml index bb6761c..7747e22 100644 --- a/cas-client-integration-atlassian/pom.xml +++ b/cas-client-integration-atlassian/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 cas-client 4.0.0 diff --git a/cas-client-integration-jboss/pom.xml b/cas-client-integration-jboss/pom.xml index e0eb030..d42ee84 100644 --- a/cas-client-integration-jboss/pom.xml +++ b/cas-client-integration-jboss/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 cas-client 4.0.0 diff --git a/cas-client-integration-tomcat-common/pom.xml b/cas-client-integration-tomcat-common/pom.xml index d3c45ae..43282a1 100644 --- a/cas-client-integration-tomcat-common/pom.xml +++ b/cas-client-integration-tomcat-common/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 4.0.0 diff --git a/cas-client-integration-tomcat-v6/pom.xml b/cas-client-integration-tomcat-v6/pom.xml index fa147c7..4b92c49 100644 --- a/cas-client-integration-tomcat-v6/pom.xml +++ b/cas-client-integration-tomcat-v6/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 4.0.0 diff --git a/cas-client-integration-tomcat-v7/pom.xml b/cas-client-integration-tomcat-v7/pom.xml index 0c6b4f1..80af721 100644 --- a/cas-client-integration-tomcat-v7/pom.xml +++ b/cas-client-integration-tomcat-v7/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 4.0.0 diff --git a/cas-client-support-distributed-ehcache/pom.xml b/cas-client-support-distributed-ehcache/pom.xml index 493656b..818949d 100644 --- a/cas-client-support-distributed-ehcache/pom.xml +++ b/cas-client-support-distributed-ehcache/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 4.0.0 Jasig CAS Client for Java - Distributed Proxy Storage Support: EhCache diff --git a/cas-client-support-distributed-memcached/pom.xml b/cas-client-support-distributed-memcached/pom.xml index 369878a..1b9f65c 100644 --- a/cas-client-support-distributed-memcached/pom.xml +++ b/cas-client-support-distributed-memcached/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 4.0.0 diff --git a/pom.xml b/pom.xml index 4272a51..2246a74 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.jasig.cas.client - 3.3.1-SNAPSHOT + 3.3.1 cas-client pom @@ -26,7 +26,8 @@ scm:git:git@github.com:Jasig/java-cas-client.git scm:git:git@github.com:Jasig/java-cas-client.git https://github.com/Jasig/java-cas-client - + cas-client-3.3.1 + 2006 From 80f7f3647efa1e0242b33dffccdd2952c2084d86 Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Wed, 19 Mar 2014 20:55:47 -0400 Subject: [PATCH 21/25] [maven-release-plugin] prepare for next development iteration --- cas-client-core/pom.xml | 2 +- cas-client-integration-atlassian/pom.xml | 2 +- cas-client-integration-jboss/pom.xml | 2 +- cas-client-integration-tomcat-common/pom.xml | 2 +- cas-client-integration-tomcat-v6/pom.xml | 2 +- cas-client-integration-tomcat-v7/pom.xml | 2 +- cas-client-support-distributed-ehcache/pom.xml | 2 +- cas-client-support-distributed-memcached/pom.xml | 2 +- pom.xml | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index dcb1941..1482cc6 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT cas-client 4.0.0 diff --git a/cas-client-integration-atlassian/pom.xml b/cas-client-integration-atlassian/pom.xml index 7747e22..83c039b 100644 --- a/cas-client-integration-atlassian/pom.xml +++ b/cas-client-integration-atlassian/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT cas-client 4.0.0 diff --git a/cas-client-integration-jboss/pom.xml b/cas-client-integration-jboss/pom.xml index d42ee84..6c5c96b 100644 --- a/cas-client-integration-jboss/pom.xml +++ b/cas-client-integration-jboss/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT cas-client 4.0.0 diff --git a/cas-client-integration-tomcat-common/pom.xml b/cas-client-integration-tomcat-common/pom.xml index 43282a1..72be093 100644 --- a/cas-client-integration-tomcat-common/pom.xml +++ b/cas-client-integration-tomcat-common/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT 4.0.0 diff --git a/cas-client-integration-tomcat-v6/pom.xml b/cas-client-integration-tomcat-v6/pom.xml index 4b92c49..3ce7551 100644 --- a/cas-client-integration-tomcat-v6/pom.xml +++ b/cas-client-integration-tomcat-v6/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT 4.0.0 diff --git a/cas-client-integration-tomcat-v7/pom.xml b/cas-client-integration-tomcat-v7/pom.xml index 80af721..486d932 100644 --- a/cas-client-integration-tomcat-v7/pom.xml +++ b/cas-client-integration-tomcat-v7/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT 4.0.0 diff --git a/cas-client-support-distributed-ehcache/pom.xml b/cas-client-support-distributed-ehcache/pom.xml index 818949d..e6ee05f 100644 --- a/cas-client-support-distributed-ehcache/pom.xml +++ b/cas-client-support-distributed-ehcache/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT 4.0.0 Jasig CAS Client for Java - Distributed Proxy Storage Support: EhCache diff --git a/cas-client-support-distributed-memcached/pom.xml b/cas-client-support-distributed-memcached/pom.xml index 1b9f65c..e20df13 100644 --- a/cas-client-support-distributed-memcached/pom.xml +++ b/cas-client-support-distributed-memcached/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 2246a74..8b08eb4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.jasig.cas.client - 3.3.1 + 3.3.2-SNAPSHOT cas-client pom @@ -26,7 +26,7 @@ scm:git:git@github.com:Jasig/java-cas-client.git scm:git:git@github.com:Jasig/java-cas-client.git https://github.com/Jasig/java-cas-client - cas-client-3.3.1 + HEAD 2006 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 22/25] 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."); } } From 7069a4f6fb2b816f77a18396095fb22123f1032e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LELEU?= Date: Thu, 27 Mar 2014 21:02:30 +0100 Subject: [PATCH 23/25] CASC-220: Support front channel SLO logout Update after Scott's code review --- .../client/session/SingleSignOutHandler.java | 68 +++---- .../session/LogoutMessageGenerator.java | 6 +- .../session/SingleSignOutHandlerTests.java | 191 +++++++++--------- 3 files changed, 122 insertions(+), 143 deletions(-) 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()); } } From 4a0e960c32519a351b845e8efecbaf76979b1a12 Mon Sep 17 00:00:00 2001 From: LELEU Jerome Date: Tue, 1 Apr 2014 16:46:19 +0200 Subject: [PATCH 24/25] CASC-220: Support front channel SLO logout Updates after Marvin's code review --- .../client/session/SingleSignOutHandlerTests.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 afdf7bd..365a25e 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 @@ -62,7 +62,7 @@ public final class SingleSignOutHandlerTests { } @Test - public void tokenRequestKOIfNoSession() { + public void tokenRequestFailsIfNoSession() { handler.setEagerlyCreateSessions(false); request.setSession(null); request.setParameter(ARTIFACT_PARAMETER_NAME, TICKET); @@ -73,7 +73,7 @@ public final class SingleSignOutHandlerTests { } @Test - public void tokenRequestKOBadParameter() { + public void tokenRequestFailsIfBadParameter() { final MockHttpSession session = new MockHttpSession(); request.setSession(session); request.setParameter(ANOTHER_PARAMETER, TICKET); @@ -95,7 +95,7 @@ public final class SingleSignOutHandlerTests { } @Test - public void backChannelLogoutKOMultipart() { + public void backChannelLogoutFailsIfMultipart() { final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET); request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage); request.setMethod("POST"); @@ -107,7 +107,7 @@ public final class SingleSignOutHandlerTests { } @Test - public void backChannelLogoutKONoSessionIndex() { + public void backChannelLogoutFailsIfNoSessionIndex() { final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(""); request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage); request.setMethod("POST"); @@ -129,7 +129,7 @@ public final class SingleSignOutHandlerTests { } @Test - public void frontChannelLogoutKOBadParameter() { + public void frontChannelLogoutFailsIfBadParameter() { final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(TICKET); request.setParameter(ANOTHER_PARAMETER, logoutMessage); request.setMethod("GET"); @@ -141,7 +141,7 @@ public final class SingleSignOutHandlerTests { } @Test - public void frontChannelLogoutKONoSessionIndex() { + public void frontChannelLogoutFailsIfNoSessionIndex() { final String logoutMessage = LogoutMessageGenerator.generateFrontChannelLogoutMessage(""); request.setParameter(FRONT_LOGOUT_PARAMETER_NAME, logoutMessage); request.setQueryString(FRONT_LOGOUT_PARAMETER_NAME + "=" + logoutMessage); From c4c3223140ff4e3fc3e5011647119f0c6d12cfe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LELEU?= Date: Mon, 7 Apr 2014 18:54:40 +0200 Subject: [PATCH 25/25] CASC-220: Support front channel SLO logout Update after new Misagh's comments --- .../org/jasig/cas/client/session/SingleSignOutHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 8026ed1..c93d9b3 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 @@ -214,7 +214,7 @@ public final class SingleSignOutHandler { return false; } else { - logger.trace("Ignoring URI {}", request.getRequestURI()); + logger.trace("Ignoring URI for logout: {}", request.getRequestURI()); return true; } } @@ -264,7 +264,7 @@ public final class SingleSignOutHandler { // decode the bytes into a String return new String(result, 0, resultLength, "UTF-8"); - } catch (Exception e) { + } catch (final Exception e) { logger.error("Unable to decompress logout message", e); throw new RuntimeException(e); } finally { @@ -282,7 +282,7 @@ public final class SingleSignOutHandler { 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())) { + if (isFrontChannelLogoutRequest(request)) { logoutMessage = uncompressLogoutMessage(CommonUtils.safeGetParameter(request, this.frontLogoutParameterName)); } else {