From 30a83ad354a33102fa113a28fd7dc7092bc110f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Garc=C3=ADa?= Date: Tue, 8 Jun 2021 15:02:06 +0200 Subject: [PATCH] Added support for the CAS gateway feature. --- .../TriggerCasGatewayException.java | 54 ++++++++ .../cas/web/CasAuthenticationEntryPoint.java | 25 +++- .../cas/web/CasAuthenticationFilter.java | 40 +++++- .../web/CasCookieGatewayRequestMatcher.java | 131 ++++++++++++++++++ .../cas/web/TriggerCasGatewayFilter.java | 78 +++++++++++ .../web/CasAuthenticationEntryPointTests.java | 50 ++++++- .../cas/web/CasAuthenticationFilterTests.java | 53 +++++-- .../CasCookieGatewayRequestMatcherTests.java | 97 +++++++++++++ 8 files changed, 510 insertions(+), 18 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/CasCookieGatewayRequestMatcher.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/CasCookieGatewayRequestMatcherTests.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..e0dccbc3ef5 --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/authentication/TriggerCasGatewayException.java @@ -0,0 +1,54 @@ +/* + * 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 { + + + private static final long serialVersionUID = 4864856111227081697L; + + //~ 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); + } + +} \ No newline at end of file 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 25221addf8c..5052b867c50 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 @@ -25,6 +25,7 @@ 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.util.Assert; @@ -39,6 +40,9 @@ * 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 @@ -71,7 +75,7 @@ public void afterPropertiesSet() { public final void commence(final HttpServletRequest servletRequest, HttpServletResponse response, AuthenticationException authenticationException) throws IOException { String urlEncodedService = createServiceUrl(servletRequest, response); - String redirectUrl = createRedirectUrl(urlEncodedService); + String redirectUrl = createRedirectUrl(servletRequest, response, authenticationException, urlEncodedService); preCommence(servletRequest, response); response.sendRedirect(redirectUrl); } @@ -94,11 +98,26 @@ protected String createServiceUrl(HttpServletRequest request, HttpServletRespons * @param serviceUrl the service url that should be included. * @return the redirect url. CANNOT be NULL. */ + @Deprecated protected String createRedirectUrl(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); + } + /** * Template method for you to do your own pre-processing before the redirect occurs. * @param request the HttpServletRequest 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 42339c0c9db..e233dfd990e 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 @@ -26,7 +26,6 @@ import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; import org.jasig.cas.client.util.CommonUtils; import org.jasig.cas.client.validation.TicketValidator; - import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; @@ -38,12 +37,18 @@ 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.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy @@ -204,6 +209,10 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler(); + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + + private RequestCache requestCache = new HttpSessionRequestCache(); + public CasAuthenticationFilter() { super("/login/cas"); setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()); @@ -226,6 +235,25 @@ protected final void successfulAuthentication(HttpServletRequest request, HttpSe chain.doFilter(request, response); } + /** + * We override this method because we don't want to clear the rememberMe in case of an unsuccessful 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(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException { @@ -302,6 +330,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. diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasCookieGatewayRequestMatcher.java b/cas/src/main/java/org/springframework/security/cas/web/CasCookieGatewayRequestMatcher.java new file mode 100644 index 00000000000..443c21110d6 --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/web/CasCookieGatewayRequestMatcher.java @@ -0,0 +1,131 @@ +package org.springframework.security.cas.web; + +import javax.servlet.http.Cookie; +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.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * 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 CasCookieGatewayRequestMatcher implements RequestMatcher { + + // ~ Instance fields + // ================================================================================================ + + private ServiceProperties serviceProperties; + + private String cookieName; + + private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl(); + + // ~ Constructors + // =================================================================================================== + + public CasCookieGatewayRequestMatcher(ServiceProperties serviceProperties, final String cookieName) { + Assert.notNull(serviceProperties, "serviceProperties cannot be null"); + this.serviceProperties = serviceProperties; + this.cookieName = cookieName; + } + + 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. + * This implementation returns true only if a {@link Cookie} with the configured + * name is present at the request + * + * @param request + * @return true if the request must trigger a CAS gateway authentication + */ + protected boolean performGatewayAuthentication(HttpServletRequest request) { + if (!StringUtils.hasText(this.cookieName)) { + return true; + } + + Cookie[] cookies = request.getCookies(); + if (cookies == null || cookies.length == 0) { + return false; + } + + for (Cookie cookie: cookies) { + // Check the cookie name. If it matches the configured cookie name, return true + if (this.cookieName.equalsIgnoreCase(cookie.getName())) { + return true; + } + } + return false; + } + + public void setGatewayStorage(GatewayResolver gatewayStorage) { + Assert.notNull(gatewayStorage, "gatewayStorage cannot be null"); + this.gatewayStorage = gatewayStorage; + } + + public String getCookieName() { + return cookieName; + } + + public void setCookieName(String cookieName) { + this.cookieName = cookieName; + } +} \ No newline at end of file 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..4e459426df6 --- /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.matcher.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; + } + +} \ No newline at end of file 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 c61d372944d..d91dda0fb5b 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 @@ -16,16 +16,16 @@ package org.springframework.security.cas.web; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + import java.net.URLEncoder; import org.junit.Test; - import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.cas.ServiceProperties; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import org.springframework.security.cas.authentication.TriggerCasGatewayException; /** * Tests {@link CasAuthenticationEntryPoint}. @@ -95,4 +95,46 @@ public void testNormalOperationWithRenewTrue() throws Exception { .isEqualTo(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("")); + assertThat("https://cas/login?service=" + + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + + "&gateway=true").isEqualTo(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("")); + assertThat("https://cas/login?service=" + + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + + "&renew=true&gateway=true").isEqualTo(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 f161e98473b..c1deb2b2672 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 @@ -16,12 +16,25 @@ package org.springframework.security.cas.web; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.io.IOException; + import javax.servlet.FilterChain; +import javax.servlet.ServletException; import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; import org.junit.After; import org.junit.Test; - import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -34,15 +47,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; /** * Tests {@link CasAuthenticationFilter}. @@ -196,4 +201,32 @@ public void testChainNotInvokedForProxyReceptor() throws Exception { 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(); + request.setMethod("GET"); + request.setServletPath("/login/cas"); + 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()); + + } + } diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasCookieGatewayRequestMatcherTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasCookieGatewayRequestMatcherTests.java new file mode 100644 index 00000000000..06fabe610ca --- /dev/null +++ b/cas/src/test/java/org/springframework/security/cas/web/CasCookieGatewayRequestMatcherTests.java @@ -0,0 +1,97 @@ +package org.springframework.security.cas.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +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 CasCookieGatewayRequestMatcherTests { + + @Test + public void testNullServiceProperties() throws Exception { + try { + new CasCookieGatewayRequestMatcher(null, null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("serviceProperties cannot be null", expected.getMessage()); + } + } + + @Test + public void testNormalOperationWithNoSSOSession() throws IOException, ServletException { + SecurityContextHolder.getContext().setAuthentication(null); + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + CasCookieGatewayRequestMatcher rm = new CasCookieGatewayRequestMatcher(serviceProperties, null); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + + // First request + assertTrue(rm.matches(request)); + assertNotNull(request.getSession(false).getAttribute(DefaultGatewayResolverImpl.CONST_CAS_GATEWAY)); + // Second request + assertFalse(rm.matches(request)); + assertNotNull(request.getSession(false).getAttribute(DefaultGatewayResolverImpl.CONST_CAS_GATEWAY)); + } + + + @Test + public void testGatewayWhenCasAuthenticated() throws IOException, ServletException { + SecurityContextHolder.getContext().setAuthentication(null); + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + CasCookieGatewayRequestMatcher rm = new CasCookieGatewayRequestMatcher(serviceProperties, "CAS_TGT_COOKIE_TEST_NAME"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + request.setCookies(new Cookie("CAS_TGT_COOKIE_TEST_NAME", "casTGCookieValue")); + + assertTrue(rm.matches(request)); + + MockHttpServletRequest requestWithoutCasCookie = new MockHttpServletRequest("GET", "/some_path"); + requestWithoutCasCookie.setCookies(new Cookie("WRONG_CAS_TGT_COOKIE_TEST_NAME", "casTGCookieValue")); + + assertFalse(rm.matches(requestWithoutCasCookie)); + } + + @Test + public void testGatewayWhenAlreadySessionCreated() throws IOException, ServletException { + SecurityContextHolder.getContext().setAuthentication(mock(CasAuthenticationToken.class)); + + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + CasCookieGatewayRequestMatcher rm = new CasCookieGatewayRequestMatcher(serviceProperties, "CAS_TGT_COOKIE_TEST_NAME"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + assertFalse(rm.matches(request)); + } + + + @Test + public void testGatewayWithNoMatchingRequest() throws IOException, ServletException { + SecurityContextHolder.getContext().setAuthentication(null); + ServiceProperties serviceProperties = new ServiceProperties(); + serviceProperties.setService("http://localhost/j_spring_cas_security_check"); + CasCookieGatewayRequestMatcher rm = new CasCookieGatewayRequestMatcher(serviceProperties, "CAS_TGT_COOKIE_TEST_NAME") { + @Override + protected boolean performGatewayAuthentication(HttpServletRequest request) { + return false; + } + }; + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/some_path"); + + assertFalse(rm.matches(request)); + } + +} \ No newline at end of file