From d0cad6c08d0047d2e939fa62d98bbe3c187e7c56 Mon Sep 17 00:00:00 2001 From: Michael Remond Date: Thu, 8 Aug 2013 12:15:00 +0200 Subject: [PATCH] SEC-977: Add support for CAS gateway feature The new filter TriggerCasGatewayAuthenticationFilter has been added to call the CasAuthenticationEntryPoint when we want try a silent CAS authentication (typically on a public page). The trigger criteria is done with a requestMatcher instance. The method unsuccessfulAuthentication has been overridden in CasAuthenticationFilter in order to redirect to the saved url if there was no SSO session (no service ticket sent from CAS). To avoid infinite loop, we use the DefaultGatewayResolverImpl from Jasig Cas Client. --- .../TriggerCasGatewayException.java | 52 +++++++++ .../cas/web/CasAuthenticationEntryPoint.java | 28 ++++- .../cas/web/CasAuthenticationFilter.java | 41 ++++++- .../web/DefaultCasGatewayRequestMatcher.java | 103 ++++++++++++++++++ .../cas/web/TriggerCasGatewayFilter.java | 78 +++++++++++++ .../web/CasAuthenticationEntryPointTests.java | 43 ++++++++ .../cas/web/CasAuthenticationFilterTests.java | 29 +++++ .../DefaultCasGatewayRequestMatcherTests.java | 74 +++++++++++++ .../cas/web/TriggerCasGatewayFilterTests.java | 90 +++++++++++++++ cas/template.mf | 1 + 10 files changed, 534 insertions(+), 5 deletions(-) create mode 100644 cas/src/main/java/org/springframework/security/cas/authentication/TriggerCasGatewayException.java create mode 100644 cas/src/main/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcher.java create mode 100644 cas/src/main/java/org/springframework/security/cas/web/TriggerCasGatewayFilter.java create mode 100644 cas/src/test/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcherTests.java create mode 100644 cas/src/test/java/org/springframework/security/cas/web/TriggerCasGatewayFilterTests.java diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/TriggerCasGatewayException.java b/cas/src/main/java/org/springframework/security/cas/authentication/TriggerCasGatewayException.java new file mode 100644 index 00000000000..ba22550e9c6 --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/authentication/TriggerCasGatewayException.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2013 the original author or authors. + * + * Licensed 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 + * + * 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.springframework.security.cas.authentication; + +import org.springframework.security.cas.web.CasAuthenticationEntryPoint; +import org.springframework.security.core.AuthenticationException; + +/** + * Exception used to indicate the {@link CasAuthenticationEntryPoint} to make a CAS gateway authentication request. + * + * @author Michael Remond + * + */ +public class TriggerCasGatewayException extends + AuthenticationException { + + //~ Constructors =================================================================================================== + + /** + * Constructs an {@code InitiateCasGatewayAuthenticationException} with the specified message and no root cause. + * + * @param msg the detail message + */ + public TriggerCasGatewayException(String msg) { + super(msg); + } + + /** + * Constructs an {@code InitiateCasGatewayAuthenticationException} with the specified message and root cause. + * + * @param msg the detail message + * @param t the root cause + */ + public TriggerCasGatewayException(String msg, Throwable t) { + super(msg, t); + } + +} diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java index a06b73dfade..b29ff81ddf2 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java +++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited +/* Copyright 2004, 2005, 2006, 2013 Acegi Technology Pty Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,11 @@ import javax.servlet.http.HttpServletResponse; import org.jasig.cas.client.util.CommonUtils; +import org.springframework.beans.factory.InitializingBean; import org.springframework.security.cas.ServiceProperties; +import org.springframework.security.cas.authentication.TriggerCasGatewayException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; @@ -38,6 +39,9 @@ * redirect to the page indicated by the service property. The service is a HTTP URL * belonging to the current application. The service URL is monitored by the {@link CasAuthenticationFilter}, * which will validate the CAS login was successful. + *

+ * If the raised exception is {@link TriggerCasGatewayException}, the parameter gateway is set + * to true on the redirect url. * * @author Ben Alex * @author Scott Battaglia @@ -72,7 +76,7 @@ public final void commence(final HttpServletRequest servletRequest, final HttpSe final AuthenticationException authenticationException) throws IOException, ServletException { final String urlEncodedService = createServiceUrl(servletRequest, response); - final String redirectUrl = createRedirectUrl(urlEncodedService); + final String redirectUrl = createRedirectUrl(servletRequest, response, authenticationException, urlEncodedService); preCommence(servletRequest, response); @@ -95,8 +99,24 @@ protected String createServiceUrl(final HttpServletRequest request, final HttpSe * @param serviceUrl the service url that should be included. * @return the redirect url. CANNOT be NULL. */ + @Deprecated protected String createRedirectUrl(final String serviceUrl) { - return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, this.serviceProperties.isSendRenew(), false); + return createRedirectUrl(null, null, null, serviceUrl); + } + + /** + * Constructs the Url for Redirection to the CAS server. Default implementation relies on the CAS client to do the bulk of the work. + * Parameter gateway is infered from authenticationException class. + * + * @param request the HttpServletRequest + * @param response the HttpServletResponse + * @param authenticationException the authentication Exception that triggers CasAuthenticationEntryPoint + * @param serviceUrl the service url that should be included. + * @return the redirect url. CANNOT be NULL. + */ + protected String createRedirectUrl(HttpServletRequest request, HttpServletResponse response, final AuthenticationException authenticationException, final String serviceUrl) { + boolean gateway = authenticationException instanceof TriggerCasGatewayException; + return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, this.serviceProperties.isSendRenew(), gateway); } /** diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java index ed2e53126de..8c0fee6e6e8 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java +++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java @@ -1,4 +1,4 @@ -/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited +/* Copyright 2004, 2005, 2006, 2013 Acegi Technology Pty Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,16 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy tickets. @@ -183,6 +189,10 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler(); + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + + private RequestCache requestCache = new HttpSessionRequestCache(); + //~ Constructors =================================================================================================== public CasAuthenticationFilter() { @@ -216,6 +226,25 @@ protected final void successfulAuthentication(HttpServletRequest request, chain.doFilter(request, response); } + /** + * We override this method because we don't want to clear the rememberMe in case of an unsuccessfull CAS gateway request. + */ + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException, ServletException { + + // if the request had a non empty artifact, we really encounter an unsuccessful authentication + // else it is probably an CAS gateway request with no SSO session, we redirect to the saved url + if (StringUtils.hasText(obtainArtifact(request))) { + super.unsuccessfulAuthentication(request, response, failed); + } else { + SavedRequest savedRequest = requestCache.getRequest(request, response); + if (savedRequest != null) { + redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl()); + } + } + } + @Override public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException { @@ -299,6 +328,16 @@ public final void setServiceProperties(final ServiceProperties serviceProperties this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts(); } + public final void setRedirectStrategy(RedirectStrategy redirectStrategy) { + Assert.notNull(redirectStrategy,"redirectStrategy cannot be null"); + this.redirectStrategy = redirectStrategy; + } + + public final void setRequestCache(RequestCache requestCache) { + Assert.notNull(requestCache,"requestCache cannot be null"); + this.requestCache = requestCache; + } + /** * Indicates if the request is elgible to process a service ticket. This method exists for readability. * @param request diff --git a/cas/src/main/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcher.java b/cas/src/main/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcher.java new file mode 100644 index 00000000000..ebc07f3337a --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcher.java @@ -0,0 +1,103 @@ +package org.springframework.security.cas.web; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl; +import org.jasig.cas.client.authentication.GatewayResolver; +import org.springframework.security.cas.ServiceProperties; +import org.springframework.security.cas.authentication.CasAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.util.RequestMatcher; +import org.springframework.util.Assert; + +/** + * Default RequestMatcher implementation for the {@link TriggerCasGatewayFilter}. + * + * This RequestMatcher returns true iff : + *

+ * + * Implementors can override this class to customize the authentication check and the gateway criteria. + *

+ * The request is marked as "gatewayed" using the configured {@link GatewayResolver} to avoid infinite loop. + * + * @author Michael Remond + * + */ +public class DefaultCasGatewayRequestMatcher implements RequestMatcher { + + // ~ Instance fields + // ================================================================================================ + + private ServiceProperties serviceProperties; + + private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl(); + + // ~ Constructors + // =================================================================================================== + + public DefaultCasGatewayRequestMatcher(ServiceProperties serviceProperties) { + Assert.notNull(serviceProperties, "serviceProperties cannot be null"); + this.serviceProperties = serviceProperties; + } + + public final boolean matches(HttpServletRequest request) { + + // Test if we are already authenticated + if (isAuthenticated(request)) { + return false; + } + + // Test if the request was already gatewayed to avoid infinite loop + final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceProperties.getService()); + + if (wasGatewayed) { + return false; + } + + // If request matches gateway criteria, we mark the request as gatewayed and return true to trigger a CAS + // gateway authentication + if (performGatewayAuthentication(request)) { + gatewayStorage.storeGatewayInformation(request, serviceProperties.getService()); + return true; + } else { + return false; + } + } + + /** + * Test if the user is authenticated in Spring Security. Default implementation test if the user is CAS + * authenticated. + * + * @param request + * @return true if the user is authenticated + */ + protected boolean isAuthenticated(HttpServletRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication instanceof CasAuthenticationToken; + } + + /** + * Method that determines if the current request triggers a CAS gateway authentication. Default implementation + * always returns true. + * + * @param request + * @return true if the request must trigger a CAS gateway authentication + */ + protected boolean performGatewayAuthentication(HttpServletRequest request) { + return true; + } + + public void setGatewayStorage(GatewayResolver gatewayStorage) { + Assert.notNull(gatewayStorage, "gatewayStorage cannot be null"); + this.gatewayStorage = gatewayStorage; + } + +} diff --git a/cas/src/main/java/org/springframework/security/cas/web/TriggerCasGatewayFilter.java b/cas/src/main/java/org/springframework/security/cas/web/TriggerCasGatewayFilter.java new file mode 100644 index 00000000000..b060a3d08c9 --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/web/TriggerCasGatewayFilter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2013 the original author or authors. + * + * Licensed 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 + * + * 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.springframework.security.cas.web; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.cas.authentication.TriggerCasGatewayException; +import org.springframework.security.web.util.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.GenericFilterBean; + +/** + * Triggers a CAS gateway authentication attempt. + *

+ * This filter must be placed after the ExceptionTranslationFilter in the filter chain in order to start + * the authentication process. Throws a {@link TriggerCasGatewayException} when the current request matches against the + * configured {@link RequestMatcher}. + *

+ * The default implementation you can use is {@link DefaultCasGatewayRequestMatcher}. + * + * @author Michael Remond + */ +public class TriggerCasGatewayFilter extends GenericFilterBean { + + // ~ Instance fields + // ================================================================================================ + + private RequestMatcher requestMatcher; + + // ~ Constructors + // =================================================================================================== + + public TriggerCasGatewayFilter(RequestMatcher requestMatcher) { + Assert.notNull(requestMatcher, "requestMatcher cannot be null"); + this.requestMatcher = requestMatcher; + } + + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, + ServletException { + + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + if (requestMatcher.matches(request)) { + throw new TriggerCasGatewayException("Try a CAS gateway authentication"); + } else { + // Continue in the chain + chain.doFilter(request, response); + } + + } + + public void setRequestMatcher(RequestMatcher requestMatcher) { + this.requestMatcher = requestMatcher; + } + +} diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java index 5349fc4e944..153efcf6208 100644 --- a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java +++ b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java @@ -20,6 +20,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.cas.ServiceProperties; +import org.springframework.security.cas.authentication.TriggerCasGatewayException; import org.springframework.security.cas.web.CasAuthenticationEntryPoint; import java.net.URLEncoder; @@ -108,4 +109,46 @@ public void testNormalOperationWithRenewTrue() throws Exception { + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + "&renew=true", response.getRedirectedUrl()); } + + public void testNormalOperationWithRenewFalseAndGateway() throws Exception { + ServiceProperties sp = new ServiceProperties(); + sp.setSendRenew(false); + sp.setService("https://mycompany.com/bigWebApp/j_spring_cas_security_check"); + + CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint(); + ep.setLoginUrl("https://cas/login"); + ep.setServiceProperties(sp); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/some_path"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + ep.afterPropertiesSet(); + ep.commence(request, response, new TriggerCasGatewayException("")); + assertEquals("https://cas/login?service=" + + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + "&gateway=true", + response.getRedirectedUrl()); + } + + public void testNormalOperationWithRenewTrueAndGateway() throws Exception { + ServiceProperties sp = new ServiceProperties(); + sp.setSendRenew(true); + sp.setService("https://mycompany.com/bigWebApp/j_spring_cas_security_check"); + + CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint(); + ep.setLoginUrl("https://cas/login"); + ep.setServiceProperties(sp); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/some_path"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + ep.afterPropertiesSet(); + ep.commence(request, response, new TriggerCasGatewayException("")); + assertEquals("https://cas/login?service=" + + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + "&renew=true&gateway=true", + response.getRedirectedUrl()); + } } diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java index f5967b33c9e..6fee2f43bba 100644 --- a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java +++ b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java @@ -19,9 +19,11 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import java.io.IOException; import java.lang.reflect.Method; import javax.servlet.FilterChain; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -43,6 +45,7 @@ import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.NullRememberMeServices; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.util.ReflectionUtils; @@ -211,4 +214,30 @@ public void testChainNotInvokedForProxyReceptor() throws Exception { filter.doFilter(request,response,chain); verifyZeroInteractions(chain); } + + // SEC-977 + @Test + public void testCasGatewayWithNoSSOSession() throws IOException, ServletException { + HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); + MockHttpServletRequest initialRequest = new MockHttpServletRequest("GET", "/some_path"); + requestCache.saveRequest(initialRequest, null); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/j_spring_cas_security_check"); + request.setSession(initialRequest.getSession()); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = mock(FilterChain.class); + + CasAuthenticationFilter filter = new CasAuthenticationFilter(); + filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class)); + filter.setAuthenticationManager(new AuthenticationManager() { + public Authentication authenticate(Authentication a) { + throw new BadCredentialsException("No ticket found"); + } + }); + + filter.doFilter(request,response,chain); + verify(chain, never()).doFilter(request, response); + assertEquals("Should redirect to saved url", "http://localhost/some_path", response.getRedirectedUrl()); + + } } \ No newline at end of file diff --git a/cas/src/test/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcherTests.java b/cas/src/test/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcherTests.java new file mode 100644 index 00000000000..5422f15dba2 --- /dev/null +++ b/cas/src/test/java/org/springframework/security/cas/web/DefaultCasGatewayRequestMatcherTests.java @@ -0,0 +1,74 @@ +package org.springframework.security.cas.web; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import junit.framework.Assert; + +import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.cas.ServiceProperties; +import org.springframework.security.cas.authentication.CasAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; + +public class DefaultCasGatewayRequestMatcherTests { + + @Test + public void testNullServiceProperties() throws Exception { + try { + new DefaultCasGatewayRequestMatcher(null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + Assert.assertEquals("serviceProperties cannot be null", expected.getMessage()); + } + } + + @Test + public void testNormalOperationWithNoSSOSession() throws IOException, ServletException { + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + DefaultCasGatewayRequestMatcher rm = new DefaultCasGatewayRequestMatcher(serviceProperties); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + + // First request + Assert.assertTrue(rm.matches(request)); + Assert.assertNotNull(request.getSession(false).getAttribute(DefaultGatewayResolverImpl.CONST_CAS_GATEWAY)); + // Second request + Assert.assertFalse(rm.matches(request)); + Assert.assertNull(request.getSession(false).getAttribute(DefaultGatewayResolverImpl.CONST_CAS_GATEWAY)); + } + + @Test + public void testGatewayWhenAlreadyCasAuthenticated() throws IOException, ServletException { + SecurityContextHolder.getContext().setAuthentication(mock(CasAuthenticationToken.class)); + + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + DefaultCasGatewayRequestMatcher rm = new DefaultCasGatewayRequestMatcher(serviceProperties); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + + Assert.assertFalse(rm.matches(request)); + } + + @Test + public void testGatewayWithNoMatchingRequest() throws IOException, ServletException { + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + DefaultCasGatewayRequestMatcher rm = new DefaultCasGatewayRequestMatcher(serviceProperties) { + @Override + protected boolean performGatewayAuthentication(HttpServletRequest request) { + return false; + } + }; + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + + Assert.assertFalse(rm.matches(request)); + } + +} diff --git a/cas/src/test/java/org/springframework/security/cas/web/TriggerCasGatewayFilterTests.java b/cas/src/test/java/org/springframework/security/cas/web/TriggerCasGatewayFilterTests.java new file mode 100644 index 00000000000..2720ae5e1f2 --- /dev/null +++ b/cas/src/test/java/org/springframework/security/cas/web/TriggerCasGatewayFilterTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013-2013 the original author or authors. + * + * Licensed 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 + * + * 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.springframework.security.cas.web; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.cas.authentication.TriggerCasGatewayException; +import org.springframework.security.web.util.AnyRequestMatcher; +import org.springframework.security.web.util.RequestMatcher; + +/** + * Tests {@link TriggerCasGatewayFilter} + * + * @author Michael Remond + * + */ +public class TriggerCasGatewayFilterTests { + + @Test + public void testNullRequestMatcher() throws Exception { + try { + new TriggerCasGatewayFilter(null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + Assert.assertEquals("requestMatcher cannot be null", expected.getMessage()); + } + } + + @Test + public void testGatewayWithMatchingRequest() throws IOException, ServletException { + TriggerCasGatewayFilter filter = new TriggerCasGatewayFilter(new AnyRequestMatcher()); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = mock(FilterChain.class); + + try { + filter.doFilter(request, response, chain); + fail("should have throw an AuthenticationException"); + } catch (TriggerCasGatewayException expected) { + Assert.assertEquals("Try a CAS gateway authentication", expected.getMessage()); + } + verifyZeroInteractions(chain); + } + + @Test + public void testGatewayWithNoMatchingRequest() throws IOException, ServletException { + TriggerCasGatewayFilter filter = new TriggerCasGatewayFilter(new RequestMatcher() { + public boolean matches(HttpServletRequest request) { + return false; + } + }); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = mock(FilterChain.class); + + filter.doFilter(request, response, chain); + verify(chain).doFilter(request, response); + } + +} diff --git a/cas/template.mf b/cas/template.mf index 0b20a5b55c3..80f2444238c 100644 --- a/cas/template.mf +++ b/cas/template.mf @@ -14,6 +14,7 @@ Import-Template: org.springframework.security.core.*;version="${secRange}", org.springframework.security.authentication.*;version="${secRange}", org.springframework.security.web.*;version="${secRange}", + org.springframework.web.filter.*;version="${springRange}", org.springframework.beans.factory;version="${springRange}", org.springframework.cache.*;version="${springRange}";resolution:=optional, org.springframework.context.*;version="${springRange}",