From 8df9f0272b272fffe2fcb771157b79b8be95fb0f Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Tue, 18 Jun 2013 21:56:58 -0400 Subject: [PATCH 1/4] Added redirect strategy to support Faces. --- .../authentication/AuthenticationFilter.java | 27 +++++++++---- .../AuthenticationRedirectStrategy.java | 14 +++++++ ...DefaultAuthenticationRedirectStrategy.java | 18 +++++++++ ...patibleAuthenticationRedirectStrategy.java | 34 ++++++++++++++++ ...ltAuthenticationRedirectStrategyTests.java | 28 +++++++++++++ ...leAuthenticationRedirectStrategyTests.java | 39 +++++++++++++++++++ 6 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java create mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategyTests.java create mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategyTests.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 3ff1896..3807969 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 @@ -68,6 +68,8 @@ public class AuthenticationFilter extends AbstractCasFilter { private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl(); + private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy(); + protected void initInternal(final FilterConfig filterConfig) throws ServletException { if (!isIgnoreInitConfiguration()) { super.initInternal(filterConfig); @@ -81,13 +83,23 @@ public class AuthenticationFilter extends AbstractCasFilter { final String gatewayStorageClass = getPropertyFromInitParams(filterConfig, "gatewayStorageClass", null); if (gatewayStorageClass != null) { - try { - this.gatewayStorage = (GatewayResolver) Class.forName(gatewayStorageClass).newInstance(); - } catch (final Exception e) { - logger.error(e.getMessage(),e); - throw new ServletException(e); - } + this.gatewayStorage = classNameToClass(gatewayStorageClass); } + + final String authenticationRedirectStrategyClass = getPropertyFromInitParams(filterConfig, "authenticationRedirectStrategyClass", null); + + if (authenticationRedirectStrategyClass != null) { + this.authenticationRedirectStrategy = classNameToClass(authenticationRedirectStrategyClass); + } + } + } + + private T classNameToClass(final String className) throws ServletException { + try { + return (T) Class.forName(className).newInstance(); + } catch (final Exception e) { + logger.error(e.getMessage(),e); + throw new ServletException(e); } } @@ -131,8 +143,7 @@ public class AuthenticationFilter extends AbstractCasFilter { final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway); logger.debug("redirecting to \"{}\"", urlToRedirectTo); - - response.sendRedirect(urlToRedirectTo); + this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo); } public final void setRenew(final boolean renew) { diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java new file mode 100644 index 0000000..701d852 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java @@ -0,0 +1,14 @@ +package org.jasig.cas.client.authentication; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by battags on 6/18/13. + */ +public interface AuthenticationRedirectStrategy { + + void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl) throws IOException; + +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java new file mode 100644 index 0000000..0d34e4c --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java @@ -0,0 +1,18 @@ +package org.jasig.cas.client.authentication; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Implementation of the {@link AuthenticationRedirectStrategy} class that preserves the original behavior that existed prior to 3.3.0. + * + * @author Scott Battaglia + * @since 3.3.0 + */ +public final class DefaultAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy { + + public void redirect(final HttpServletRequest request, final HttpServletResponse response, final String potentialRedirectUrl) throws IOException { + response.sendRedirect(potentialRedirectUrl); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java new file mode 100644 index 0000000..13ff374 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java @@ -0,0 +1,34 @@ +package org.jasig.cas.client.authentication; + +import org.jasig.cas.client.util.CommonUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Implementation of the redirect strategy that can handle a Faces Ajax request in addition to the standard redirect style. + * + * @author Scott Battaglia + * @since 3.3.0 + */ +public final class FacesCompatibleAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy { + + private static final String FACES_PARTIAL_AJAX_PARAMETER = "javax.faces.partial.ajax"; + + public void redirect(final HttpServletRequest request, final HttpServletResponse response, final String potentialRedirectUrl) throws IOException { + + if (CommonUtils.isNotBlank(request.getParameter(FACES_PARTIAL_AJAX_PARAMETER))) { + // this is an ajax request - redirect ajaxly + response.setContentType("text/xml"); + response.setStatus(200); + + final PrintWriter writer = response.getWriter(); + writer.write(""); + writer.write(String.format("", potentialRedirectUrl)); + } else { + response.sendRedirect(potentialRedirectUrl); + } + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategyTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategyTests.java new file mode 100644 index 0000000..0b1389e --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategyTests.java @@ -0,0 +1,28 @@ +package org.jasig.cas.client.authentication; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +public class DefaultAuthenticationRedirectStrategyTests { + + private DefaultAuthenticationRedirectStrategy strategy; + + @Before + public void setUp() throws Exception { + this.strategy = new DefaultAuthenticationRedirectStrategy(); + } + + @Test + public void didWeRedirect() throws Exception { + final String redirectUrl = "http://www.jasig.org"; + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + this.strategy.redirect(request, response, redirectUrl); + assertEquals(redirectUrl, response.getRedirectedUrl()); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategyTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategyTests.java new file mode 100644 index 0000000..920cf7f --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategyTests.java @@ -0,0 +1,39 @@ +package org.jasig.cas.client.authentication; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.junit.Assert.*; + +public class FacesCompatibleAuthenticationRedirectStrategyTests { + + private FacesCompatibleAuthenticationRedirectStrategy strategy; + + @Before + public void setUp() throws Exception { + this.strategy = new FacesCompatibleAuthenticationRedirectStrategy(); + } + + @Test + public void didWeRedirect() throws Exception { + final String redirectUrl = "http://www.jasig.org"; + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + this.strategy.redirect(request, response, redirectUrl); + assertEquals(redirectUrl, response.getRedirectedUrl()); + } + + @Test + public void facesPartialResponse() throws Exception { + final String redirectUrl = "http://www.jasig.org"; + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + request.setParameter("javax.faces.partial.ajax", "true"); + this.strategy.redirect(request, response, redirectUrl); + assertNull(response.getRedirectedUrl()); + assertTrue(response.getContentAsString().contains(redirectUrl)); + } +} From 73e36bf6d239e49846527bddb20a19130d9d9bcf Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Tue, 18 Jun 2013 21:57:22 -0400 Subject: [PATCH 2/4] Fixed failing test case that was missing an import statement. --- .../cas/client/validation/Cas20ServiceTicketValidatorTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java index ed599c4..83e6c00 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import static org.junit.Assert.*; import java.io.UnsupportedEncodingException; +import java.util.List; /** * Test cases for the {@link Cas20ServiceTicketValidator}. From 8643e85b1e3272394ee5523dee0359ce6ea5769a Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Tue, 18 Jun 2013 22:07:50 -0400 Subject: [PATCH 3/4] Added missing javadoc --- .../AuthenticationRedirectStrategy.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java index 701d852..cb3e2e8 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java @@ -5,10 +5,23 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** - * Created by battags on 6/18/13. + * Interface to abstract the authentication strategy for redirecting. The traditional method was to always just redirect, + * but due to AJAX, etc. we may need to support other strategies. This interface is designed to hold that logic such that + * authentication filter class does not get crazily complex. + * + * @author Scott Battaglia + * @since 3.3.0 */ public interface AuthenticationRedirectStrategy { + /** + * Method name is a bit of a misnomer. This method handles "redirection" for a localized version of redirection (i.e. AJAX might mean an XML fragment that contains the url to go to). + * + * @param request the original HttpServletRequest. MAY NOT BE NULL. + * @param response the original HttpServletResponse. MAY NOT BE NULL. + * @param potentialRedirectUrl the url that might be used (there are no guarantees of course!) + * @throws IOException the exception to throw if there is some type of error. This will bubble up through the filter. + */ void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl) throws IOException; } From 9674ad8a45f2c8edf24d4dd98641cf8b2968e22e Mon Sep 17 00:00:00 2001 From: Scott Battaglia Date: Wed, 19 Jun 2013 22:26:09 -0400 Subject: [PATCH 4/4] Swithed to existing Reflection utils. Added supporting unit test. --- .../authentication/AuthenticationFilter.java | 14 ++------- .../AuthenticationFilterTests.java | 31 ++++++++++++++++--- 2 files changed, 30 insertions(+), 15 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 3807969..38d16f4 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 @@ -20,6 +20,7 @@ package org.jasig.cas.client.authentication; import org.jasig.cas.client.util.AbstractCasFilter; import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.util.ReflectUtils; import org.jasig.cas.client.validation.Assertion; import javax.servlet.FilterChain; @@ -83,26 +84,17 @@ public class AuthenticationFilter extends AbstractCasFilter { final String gatewayStorageClass = getPropertyFromInitParams(filterConfig, "gatewayStorageClass", null); if (gatewayStorageClass != null) { - this.gatewayStorage = classNameToClass(gatewayStorageClass); + this.gatewayStorage = ReflectUtils.newInstance(gatewayStorageClass); } final String authenticationRedirectStrategyClass = getPropertyFromInitParams(filterConfig, "authenticationRedirectStrategyClass", null); if (authenticationRedirectStrategyClass != null) { - this.authenticationRedirectStrategy = classNameToClass(authenticationRedirectStrategyClass); + this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass); } } } - private T classNameToClass(final String className) throws ServletException { - try { - return (T) Class.forName(className).newInstance(); - } catch (final Exception e) { - logger.error(e.getMessage(),e); - throw new ServletException(e); - } - } - public void init() { super.init(); CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null."); 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 b2a520b..2a615ab 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 @@ -27,15 +27,19 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import junit.framework.TestCase; import org.jasig.cas.client.util.AbstractCasFilter; import org.jasig.cas.client.validation.AssertionImpl; +import org.junit.After; +import org.junit.Before; +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; +import static org.junit.Assert.*; + /** * Tests for the AuthenticationFilter. * @@ -43,7 +47,7 @@ import org.springframework.mock.web.MockServletContext; * @version $Revision: 11753 $ $Date: 2007-01-03 13:37:26 -0500 (Wed, 03 Jan 2007) $ * @since 3.0 */ -public final class AuthenticationFilterTests extends TestCase { +public final class AuthenticationFilterTests { private static final String CAS_SERVICE_URL = "https://localhost:8443/service"; @@ -51,7 +55,8 @@ public final class AuthenticationFilterTests extends TestCase { private AuthenticationFilter filter; - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { // TODO CAS_SERVICE_URL, false, CAS_LOGIN_URL this.filter = new AuthenticationFilter(); final MockFilterConfig config = new MockFilterConfig(); @@ -60,10 +65,12 @@ public final class AuthenticationFilterTests extends TestCase { this.filter.init(config); } - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { this.filter.destroy(); } + @Test public void testRedirect() throws Exception { final MockHttpSession session = new MockHttpSession(); final MockHttpServletRequest request = new MockHttpServletRequest(); @@ -84,6 +91,7 @@ public final class AuthenticationFilterTests extends TestCase { .getRedirectedUrl()); } + @Test public void testRedirectWithQueryString() throws Exception { final MockHttpSession session = new MockHttpSession(); final MockHttpServletRequest request = new MockHttpServletRequest(); @@ -116,6 +124,7 @@ public final class AuthenticationFilterTests extends TestCase { "UTF-8"), response.getRedirectedUrl()); } + @Test public void testAssertion() throws Exception { final MockHttpSession session = new MockHttpSession(); final MockHttpServletRequest request = new MockHttpServletRequest(); @@ -136,6 +145,7 @@ public final class AuthenticationFilterTests extends TestCase { assertNull(response.getRedirectedUrl()); } + @Test public void testRenew() throws Exception { final MockHttpSession session = new MockHttpSession(); final MockHttpServletRequest request = new MockHttpServletRequest(); @@ -156,6 +166,7 @@ public final class AuthenticationFilterTests extends TestCase { assertTrue(response.getRedirectedUrl().indexOf("renew=true") != -1); } + @Test public void testGateway() throws Exception { final MockHttpSession session = new MockHttpSession(); final MockHttpServletRequest request = new MockHttpServletRequest(); @@ -181,6 +192,7 @@ public final class AuthenticationFilterTests extends TestCase { assertNull(response2.getRedirectedUrl()); } + @Test public void testRenewInitParamThrows() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); final MockFilterConfig config = new MockFilterConfig(); @@ -195,6 +207,7 @@ public final class AuthenticationFilterTests extends TestCase { } } + @Test public void testAllowsRenewContextParam() throws Exception { final AuthenticationFilter f = new AuthenticationFilter(); final MockServletContext context = new MockServletContext(); @@ -206,4 +219,14 @@ public final class AuthenticationFilterTests extends TestCase { renewField.setAccessible(true); assertTrue((Boolean) renewField.get(f)); } + + @Test + 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("authenticationRedirectStrategyClass", "org.jasig.cas.client.authentication.FacesCompatibleAuthenticationRedirectStrategy"); + f.init(new MockFilterConfig(context)); + } }