From 1a6c782ff34fc72d89146a66c398bff365520f22 Mon Sep 17 00:00:00 2001 From: LELEU Jerome Date: Fri, 6 Jul 2012 20:47:51 +0200 Subject: [PATCH 1/2] SEC-1987: Create RememberMeAware interface --- .../AuthenticationTrustResolverImpl.java | 14 ++++++++-- .../RememberMeAuthenticationToken.java | 10 ++++++- .../authentication/RememberMeAware.java | 27 +++++++++++++++++++ .../TestingAuthenticationToken.java | 17 ++++++++++-- .../AuthenticationTrustResolverImplTests.java | 4 ++- 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/authentication/RememberMeAware.java diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java index 8dc03cdfbfc..3f5955baa94 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java @@ -21,12 +21,14 @@ /** * Basic implementation of {@link AuthenticationTrustResolver}. *

- * Makes trust decisions based on whether the passed Authentication is an instance of a defined class. + * Makes trust decisions based on whether the passed Authentication is an instance of a defined class or + * implements the {@link RememberMeAware} interface and its isRemember() method returns true. *

* If {@link #anonymousClass} or {@link #rememberMeClass} is null, the corresponding method will * always return false. * * @author Ben Alex + * @see RememberMeAware#isRememberMe() */ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResolver { //~ Instance fields ================================================================================================ @@ -57,7 +59,15 @@ public boolean isRememberMe(Authentication authentication) { return false; } - return rememberMeClass.isAssignableFrom(authentication.getClass()); + if (rememberMeClass.isAssignableFrom(authentication.getClass())) { + return true; + } + + if (authentication instanceof RememberMeAware) { + return ((RememberMeAware) authentication).isRememberMe(); + } + + return false; } public void setAnonymousClass(Class anonymousClass) { diff --git a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java index 2315f0fab89..a54e6ad62a9 100644 --- a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java @@ -30,7 +30,7 @@ * @author Ben Alex * @author Luke Taylor */ -public class RememberMeAuthenticationToken extends AbstractAuthenticationToken { +public class RememberMeAuthenticationToken extends AbstractAuthenticationToken implements RememberMeAware { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; @@ -99,4 +99,12 @@ public boolean equals(Object obj) { return false; } + /** + * This method always returns true as a RememberMeAuthenticationToken is always considered as remember-me. + * + * @return true + */ + public final boolean isRememberMe() { + return true; + } } diff --git a/core/src/main/java/org/springframework/security/authentication/RememberMeAware.java b/core/src/main/java/org/springframework/security/authentication/RememberMeAware.java new file mode 100644 index 00000000000..70baa1d5412 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/RememberMeAware.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012 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.authentication; + +/** + * This interface represents the ability for an authentication token to be considered as remember-me. + * + * @author Jerome Leleu + * @since 3.2.0 + */ +public interface RememberMeAware { + + public boolean isRememberMe(); +} diff --git a/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java index fe2f0b476d0..f0bf53f5684 100644 --- a/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java @@ -15,7 +15,6 @@ package org.springframework.security.authentication; -import java.util.Arrays; import java.util.List; import org.springframework.security.core.GrantedAuthority; @@ -29,12 +28,13 @@ * * @author Ben Alex */ -public class TestingAuthenticationToken extends AbstractAuthenticationToken { +public class TestingAuthenticationToken extends AbstractAuthenticationToken implements RememberMeAware { //~ Instance fields ================================================================================================ private static final long serialVersionUID = 1L; private final Object credentials; private final Object principal; + private final boolean isRemember; //~ Constructors =================================================================================================== @@ -42,6 +42,7 @@ public TestingAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; + this.isRemember = false; } public TestingAuthenticationToken(Object principal, Object credentials, String... authorities) { @@ -53,6 +54,14 @@ public TestingAuthenticationToken(Object principal, Object credentials, List authorities, boolean isRemember) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + this.isRemember = isRemember; } //~ Methods ======================================================================================================== @@ -64,4 +73,8 @@ public Object getCredentials() { public Object getPrincipal() { return this.principal; } + + public boolean isRememberMe() { + return isRemember; + } } diff --git a/core/src/test/java/org/springframework/security/authentication/AuthenticationTrustResolverImplTests.java b/core/src/test/java/org/springframework/security/authentication/AuthenticationTrustResolverImplTests.java index 9acd77f1825..b74c661f450 100644 --- a/core/src/test/java/org/springframework/security/authentication/AuthenticationTrustResolverImplTests.java +++ b/core/src/test/java/org/springframework/security/authentication/AuthenticationTrustResolverImplTests.java @@ -45,7 +45,9 @@ public void testCorrectOperationIsRememberMe() { AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl(); assertTrue(trustResolver.isRememberMe( new RememberMeAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")))); - assertFalse(trustResolver.isAnonymous( + assertTrue(trustResolver.isRememberMe( + new TestingAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored"), true))); + assertFalse(trustResolver.isRememberMe( new TestingAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")))); } From fa2a1f3839625b23909755284eee4b4745cf1e26 Mon Sep 17 00:00:00 2001 From: LELEU Jerome Date: Fri, 6 Jul 2012 20:55:54 +0200 Subject: [PATCH 2/2] SEC-1986: Add remember-me support for CAS SEC-2004: Upgrade CAS server to last release version : 3.5.0 --- .../CasAuthenticationProvider.java | 12 +- .../CasAuthenticationToken.java | 18 +- .../cas/web/CasAuthenticationEntryPoint.java | 27 ++- .../web/CasRememberMeAccessDeniedHandler.java | 86 +++++++ .../AbstractStatelessTicketCacheTests.java | 2 +- .../CasAuthenticationProviderTests.java | 91 ++++++-- .../CasAuthenticationTokenTests.java | 63 +++-- .../web/CasAuthenticationEntryPointTests.java | 21 ++ .../cas/CasSampleRememberMeTests.groovy | 61 +++++ .../cas/pages/IsAuthenticatedPage.groovy | 34 +++ .../cas/pages/IsFullyAuthenticatedPage.groovy | 34 +++ .../samples/cas/pages/LoginPage.groovy | 3 +- .../WEB-INF/applicationContext-security.xml | 13 +- .../cas/sample/src/main/webapp/auth/index.jsp | 6 + .../sample/src/main/webapp/fully/index.jsp | 7 + samples/cas/sample/src/main/webapp/index.jsp | 2 + samples/cas/server/casserver.gradle | 2 +- .../webapp/WEB-INF/deployerConfigContext.xml | 72 ++++++ .../src/main/webapp/WEB-INF/login-webflow.xml | 217 ++++++++++++++++++ .../ticketExpirationPolicies.xml | 35 +++ .../view/jsp/default/ui/casLoginView.jsp | 143 ++++++++++++ .../2.0/casServiceValidationSuccess.jsp | 37 +++ 22 files changed, 937 insertions(+), 49 deletions(-) create mode 100644 cas/src/main/java/org/springframework/security/cas/web/CasRememberMeAccessDeniedHandler.java create mode 100644 samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleRememberMeTests.groovy create mode 100644 samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsAuthenticatedPage.groovy create mode 100644 samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsFullyAuthenticatedPage.groovy create mode 100644 samples/cas/sample/src/main/webapp/auth/index.jsp create mode 100644 samples/cas/sample/src/main/webapp/fully/index.jsp create mode 100644 samples/cas/server/src/main/webapp/WEB-INF/deployerConfigContext.xml create mode 100644 samples/cas/server/src/main/webapp/WEB-INF/login-webflow.xml create mode 100644 samples/cas/server/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml create mode 100644 samples/cas/server/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp create mode 100644 samples/cas/server/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java index b35199c5828..92f7c6e517e 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java @@ -56,6 +56,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia //~ Static fields/initializers ===================================================================================== private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class); + public static final String DEFAULT_REMEMBERME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed"; //~ Instance fields ================================================================================================ @@ -68,6 +69,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia private TicketValidator ticketValidator; private ServiceProperties serviceProperties; private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + private String rememberMeAttributeName = DEFAULT_REMEMBERME_ATTRIBUTE_NAME; //~ Methods ======================================================================================================== @@ -141,7 +143,7 @@ private CasAuthenticationToken authenticateNow(final Authentication authenticati final UserDetails userDetails = loadUserByAssertion(assertion); userDetailsChecker.check(userDetails); return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), - authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion); + authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion, rememberMeAttributeName); } catch (final TicketValidationException e) { throw new BadCredentialsException(e.getMessage(), e); } @@ -234,6 +236,14 @@ public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { this.authoritiesMapper = authoritiesMapper; } + public String getRememberMeAttributeName() { + return rememberMeAttributeName; + } + + public void setRememberMeAttributeName(String rememberMeAttributeName) { + this.rememberMeAttributeName = rememberMeAttributeName; + } + public boolean supports(final Class authentication) { return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) || (CasAuthenticationToken.class.isAssignableFrom(authentication)) || diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java index ffa78978096..ee0c972e90b 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java @@ -20,6 +20,7 @@ import org.jasig.cas.client.validation.Assertion; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.RememberMeAware; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.userdetails.UserDetails; @@ -30,7 +31,7 @@ * @author Ben Alex * @author Scott Battaglia */ -public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable { +public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable, RememberMeAware { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; @@ -40,6 +41,7 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen private final UserDetails userDetails; private final int keyHash; private final Assertion assertion; + private final boolean rememberMe; //~ Constructors =================================================================================================== @@ -57,15 +59,18 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen * org.springframework.security.core.userdetails.UserDetailsService}) (cannot be null) * @param assertion the assertion returned from the CAS servers. It contains the principal and how to obtain a * proxy ticket for the user. + * @param rememberMeAttributeName the CAS remember-me attribute name * * @throws IllegalArgumentException if a null was passed */ public CasAuthenticationToken(final String key, final Object principal, final Object credentials, - final Collection authorities, final UserDetails userDetails, final Assertion assertion) { + final Collection authorities, final UserDetails userDetails, final Assertion assertion, + String rememberMeAttributeName) { super(authorities); if ((key == null) || ("".equals(key)) || (principal == null) || "".equals(principal) || (credentials == null) - || "".equals(credentials) || (authorities == null) || (userDetails == null) || (assertion == null)) { + || "".equals(credentials) || (authorities == null) || (userDetails == null) || (assertion == null) || + (rememberMeAttributeName == null)) { throw new IllegalArgumentException("Cannot pass null or empty values to constructor"); } @@ -74,6 +79,8 @@ public CasAuthenticationToken(final String key, final Object principal, final Ob this.credentials = credentials; this.userDetails = userDetails; this.assertion = assertion; + String rememberMeStringValue = (String) assertion.getPrincipal().getAttributes().get(rememberMeAttributeName); + this.rememberMe = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue); setAuthenticated(true); } @@ -121,9 +128,14 @@ public UserDetails getUserDetails() { return userDetails; } + public boolean isRememberMe() { + return rememberMe; + } + public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()); + sb.append(" RememberMe: ").append(this.rememberMe); sb.append(" Assertion: ").append(this.assertion); sb.append(" Credentials (Service/Proxy Ticket): ").append(this.credentials); 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..bd8e9fe4e05 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 @@ -36,8 +36,9 @@ * The user's browser will be redirected to the JA-SIG CAS enterprise-wide login page. * This page is specified by the loginUrl property. Once login is complete, the CAS login page will * 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. + * belonging to the current application. The renew parameter can be forced to true when redirecting + * to the CAS server. The service URL is monitored by the {@link CasAuthenticationFilter}, which will + * validate the CAS login was successful. * * @author Ben Alex * @author Scott Battaglia @@ -68,11 +69,18 @@ public void afterPropertiesSet() throws Exception { Assert.notNull(this.serviceProperties.getService(),"serviceProperties.getService() cannot be null."); } + + public final void commence(final HttpServletRequest servletRequest, final HttpServletResponse response, + final AuthenticationException authenticationException) throws IOException, ServletException { + // by default, don't force the renew parameter to true + commence(servletRequest, response, authenticationException, false); + } + public final void commence(final HttpServletRequest servletRequest, final HttpServletResponse response, - final AuthenticationException authenticationException) throws IOException, ServletException { + final AuthenticationException authenticationException, boolean forceRenew) throws IOException, ServletException { final String urlEncodedService = createServiceUrl(servletRequest, response); - final String redirectUrl = createRedirectUrl(urlEncodedService); + final String redirectUrl = createRedirectUrl(urlEncodedService, forceRenew); preCommence(servletRequest, response); @@ -91,12 +99,19 @@ protected String createServiceUrl(final HttpServletRequest request, final HttpSe /** * Constructs the Url for Redirection to the CAS server. Default implementation relies on the CAS client to do the bulk of the work. + * The renew parameter can be forced to true if the forceRenew parameter is true. * * @param serviceUrl the service url that should be included. + * @param forceRenew if the renew parameter should be forced to true * @return the redirect url. CANNOT be NULL. */ - protected String createRedirectUrl(final String serviceUrl) { - return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, this.serviceProperties.isSendRenew(), false); + protected String createRedirectUrl(final String serviceUrl, final boolean forceRenew) { + boolean renew = this.serviceProperties.isSendRenew(); + // if forceRenew is true, the renew parameter is forced to true + if (forceRenew) { + renew = true; + } + return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, renew, false); } /** diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasRememberMeAccessDeniedHandler.java b/cas/src/main/java/org/springframework/security/cas/web/CasRememberMeAccessDeniedHandler.java new file mode 100644 index 00000000000..f0ce8c69483 --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/web/CasRememberMeAccessDeniedHandler.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012 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.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.InsufficientAuthenticationException; +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.access.AccessDeniedHandlerImpl; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.util.Assert; + +/** + * This class represents the AccessDeniedHandlerImpl dedicated to CAS authentication with remember-me support. + * + * @author Jerome Leleu + * @since 3.2.0 + */ +public class CasRememberMeAccessDeniedHandler extends AccessDeniedHandlerImpl implements InitializingBean { + + private RequestCache requestCache = new HttpSessionRequestCache(); + + private CasAuthenticationEntryPoint casAuthenticationEntryPoint; + + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) + throws IOException, ServletException { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + // if CAS authentication token + if (authentication != null && authentication instanceof CasAuthenticationToken) { + CasAuthenticationToken casAuthenticationToken = (CasAuthenticationToken) authentication; + // in remember-me mode + if (casAuthenticationToken.isRememberMe()) { + requestCache.saveRequest(request, response); + logger.debug("Calling Authentication entry point with renew=true."); + casAuthenticationEntryPoint.commence(request, response, + new InsufficientAuthenticationException("Full CAS authentication is required to access this resource"), true); + return; + } + } + + super.handle(request, response, accessDeniedException); + } + + public RequestCache getRequestCache() { + return requestCache; + } + + public void setRequestCache(RequestCache requestCache) { + this.requestCache = requestCache; + } + + public CasAuthenticationEntryPoint getCasAuthenticationEntryPoint() { + return casAuthenticationEntryPoint; + } + + public void setCasAuthenticationEntryPoint(CasAuthenticationEntryPoint casAuthenticationEntryPoint) { + this.casAuthenticationEntryPoint = casAuthenticationEntryPoint; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(this.casAuthenticationEntryPoint, "casAuthenticationEntryPoint must be specified"); + } +} diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/AbstractStatelessTicketCacheTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/AbstractStatelessTicketCacheTests.java index a62d3f60494..c08bb7186dc 100644 --- a/cas/src/test/java/org/springframework/security/cas/authentication/AbstractStatelessTicketCacheTests.java +++ b/cas/src/test/java/org/springframework/security/cas/authentication/AbstractStatelessTicketCacheTests.java @@ -25,7 +25,7 @@ protected CasAuthenticationToken getToken() { final Assertion assertion = new AssertionImpl("rod"); return new CasAuthenticationToken("key", user, "ST-0-ER94xMJmn6pha35CQRoZ", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), user, assertion); + AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), user, assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); } } diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java index 495ded74d83..5c0ab2b03a9 100644 --- a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java +++ b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java @@ -18,6 +18,8 @@ import static org.mockito.Mockito.*; import static org.junit.Assert.*; +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; import org.jasig.cas.client.validation.Assertion; import org.jasig.cas.client.validation.AssertionImpl; import org.jasig.cas.client.validation.TicketValidationException; @@ -50,6 +52,7 @@ */ @SuppressWarnings("unchecked") public class CasAuthenticationProviderTests { + public static final String TEST_REMEMBERME_ATTRIBUTE_NAME = "testRememberMeName"; //~ Methods ======================================================================================================== private UserDetails makeUserDetails() { @@ -80,7 +83,7 @@ public void statefulAuthenticationIsSuccessful() throws Exception { cap.setStatelessTicketCache(cache); cap.setServiceProperties(makeServiceProperties()); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.afterPropertiesSet(); UsernamePasswordAuthenticationToken token = @@ -103,15 +106,61 @@ public void statefulAuthenticationIsSuccessful() throws Exception { assertTrue(casResult.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_B"))); assertEquals(cap.getKey().hashCode(), casResult.getKeyHash()); assertEquals("details", casResult.getDetails()); + assertFalse(casResult.isRememberMe()); // Now confirm the CasAuthenticationToken is automatically re-accepted. // To ensure TicketValidator not called again, set it to deliver an exception... - cap.setTicketValidator(new MockTicketValidator(false)); + cap.setTicketValidator(new MockTicketValidator(false, null)); Authentication laterResult = cap.authenticate(result); assertEquals(result, laterResult); } + @Test + public void testRememberMeDefaultAttributeName() throws Exception { + CasAuthenticationProvider cap = new CasAuthenticationProvider(); + cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); + cap.setKey("qwerty"); + + StatelessTicketCache cache = new MockStatelessTicketCache(); + cap.setStatelessTicketCache(cache); + cap.setServiceProperties(makeServiceProperties()); + + cap.setTicketValidator(new MockTicketValidator(true, CasAuthenticationProvider.DEFAULT_REMEMBERME_ATTRIBUTE_NAME)); + cap.afterPropertiesSet(); + + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-124"); + + Authentication result = cap.authenticate(token); + + CasAuthenticationToken casResult = (CasAuthenticationToken) result; + assertTrue(casResult.isRememberMe()); + } + + @Test + public void testRememberMeNewAttributeName() throws Exception { + CasAuthenticationProvider cap = new CasAuthenticationProvider(); + cap.setRememberMeAttributeName(TEST_REMEMBERME_ATTRIBUTE_NAME); + cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); + cap.setKey("qwerty"); + + StatelessTicketCache cache = new MockStatelessTicketCache(); + cap.setStatelessTicketCache(cache); + cap.setServiceProperties(makeServiceProperties()); + + cap.setTicketValidator(new MockTicketValidator(true, TEST_REMEMBERME_ATTRIBUTE_NAME)); + cap.afterPropertiesSet(); + + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-125"); + + Authentication result = cap.authenticate(token); + + CasAuthenticationToken casResult = (CasAuthenticationToken) result; + assertTrue(casResult.isRememberMe()); + } + @Test public void statelessAuthenticationIsSuccessful() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); @@ -120,7 +169,7 @@ public void statelessAuthenticationIsSuccessful() throws Exception { StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); @@ -143,7 +192,7 @@ public void statelessAuthenticationIsSuccessful() throws Exception { // Now try to authenticate again. To ensure TicketValidator not // called again, set it to deliver an exception... - cap.setTicketValidator(new MockTicketValidator(false)); + cap.setTicketValidator(new MockTicketValidator(false, null)); // Previously created UsernamePasswordAuthenticationToken is OK Authentication newResult = cap.authenticate(token); @@ -240,7 +289,7 @@ public void missingTicketIdIsDetected() throws Exception { StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); @@ -259,12 +308,12 @@ public void invalidKeyIsDetected() throws Exception { StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY", makeUserDetails(), "credentials", - AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion); + AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion, TEST_REMEMBERME_ATTRIBUTE_NAME); cap.authenticate(token); } @@ -274,7 +323,7 @@ public void detectsMissingAuthoritiesPopulator() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @@ -284,7 +333,7 @@ public void detectsMissingKey() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setStatelessTicketCache(new MockStatelessTicketCache()); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @@ -296,7 +345,7 @@ public void detectsMissingStatelessTicketCache() throws Exception { cap.setStatelessTicketCache(null); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @@ -317,7 +366,7 @@ public void gettersAndSettersMatch() throws Exception { cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); @@ -334,7 +383,7 @@ public void ignoresClassesItDoesNotSupport() throws Exception { cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); @@ -351,7 +400,7 @@ public void ignoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPr cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); - cap.setTicketValidator(new MockTicketValidator(true)); + cap.setTicketValidator(new MockTicketValidator(true, null)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); @@ -398,15 +447,27 @@ public void removeTicketFromCache(String serviceTicket) { private class MockTicketValidator implements TicketValidator { private boolean returnTicket; + private String rememberMeAttributeName; - public MockTicketValidator(boolean returnTicket) { + public MockTicketValidator(boolean returnTicket, String rememberMeAttributeName) { this.returnTicket = returnTicket; + this.rememberMeAttributeName = rememberMeAttributeName; } + @SuppressWarnings("rawtypes") public Assertion validate(final String ticket, final String service) throws TicketValidationException { if (returnTicket) { - return new AssertionImpl("rod"); + Assertion assertion; + if (rememberMeAttributeName != null) { + Map attributes = new HashMap(); + attributes.put(rememberMeAttributeName, "true"); + final AttributePrincipal principal = new AttributePrincipalImpl("rod", attributes); + assertion = new AssertionImpl(principal); + } else { + assertion = new AssertionImpl("rod"); + } + return assertion; } throw new BadCredentialsException("As requested from mock"); } diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java index 16dc11f1d67..c9ee389cffa 100644 --- a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java +++ b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java @@ -16,6 +16,9 @@ package org.springframework.security.cas.authentication; import junit.framework.TestCase; + +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; import org.jasig.cas.client.validation.Assertion; import org.jasig.cas.client.validation.AssertionImpl; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -50,51 +53,59 @@ public final void setUp() throws Exception { public void testConstructorRejectsNulls() { final Assertion assertion = new AssertionImpl("test"); try { - new CasAuthenticationToken(null, makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion); + new CasAuthenticationToken(null, makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { } try { - new CasAuthenticationToken("key", null, "Password", ROLES, makeUserDetails(), assertion); + new CasAuthenticationToken("key", null, "Password", ROLES, makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { } try { - new CasAuthenticationToken("key", makeUserDetails(), null, ROLES, makeUserDetails(), assertion); + new CasAuthenticationToken("key", makeUserDetails(), null, ROLES, makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { } try { - new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, makeUserDetails(), null); + new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, makeUserDetails(), null, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { } try { - new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, null, assertion); + new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, null, assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { } try { - new CasAuthenticationToken("key", makeUserDetails(), "Password", AuthorityUtils.createAuthorityList("ROLE_1", null), makeUserDetails(), assertion); + new CasAuthenticationToken("key", makeUserDetails(), "Password", AuthorityUtils.createAuthorityList("ROLE_1", null), + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException expected) { assertTrue(true); } - } + + try { + new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion, null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } +} public void testEqualsWhenEqual() { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); assertEquals(token1, token2); } @@ -103,7 +114,7 @@ public void testGetters() { // Build the proxy list returned in the ticket from CAS final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); assertEquals("key".hashCode(), token.getKeyHash()); assertEquals(makeUserDetails(), token.getPrincipal()); assertEquals("Password", token.getCredentials()); @@ -111,6 +122,7 @@ public void testGetters() { assertTrue(token.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_TWO"))); assertEquals(assertion, token.getAssertion()); assertEquals(makeUserDetails().getUsername(), token.getUserDetails().getUsername()); + assertFalse(token.isRememberMe()); } public void testNoArgConstructorDoesntExist() { @@ -126,10 +138,10 @@ public void testNotEqualsDueToAbstractParentEqualsCheck() { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails("OTHER_NAME"), "Password", - ROLES, makeUserDetails(), assertion); + ROLES, makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); assertTrue(!token1.equals(token2)); } @@ -138,7 +150,7 @@ public void testNotEqualsDueToDifferentAuthenticationClass() { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken("Test", "Password", ROLES); assertTrue(!token1.equals(token2)); @@ -148,10 +160,10 @@ public void testNotEqualsDueToKey() { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); CasAuthenticationToken token2 = new CasAuthenticationToken("DIFFERENT_KEY", makeUserDetails(), "Password", - ROLES, makeUserDetails(), assertion); + ROLES, makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); assertTrue(!token1.equals(token2)); } @@ -161,10 +173,10 @@ public void testNotEqualsDueToAssertion() { final Assertion assertion2 = new AssertionImpl("test"); CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion2); + makeUserDetails(), assertion2, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); assertTrue(!token1.equals(token2)); } @@ -172,16 +184,29 @@ public void testNotEqualsDueToAssertion() { public void testSetAuthenticated() { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); assertTrue(token.isAuthenticated()); token.setAuthenticated(false); assertTrue(!token.isAuthenticated()); } + @SuppressWarnings({ + "rawtypes", "unchecked" + }) + public void testIsRemember() { + Map attributes = new HashMap(); + attributes.put(CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME, "true"); + final AttributePrincipal principal = new AttributePrincipalImpl("test", attributes); + final Assertion assertion = new AssertionImpl(principal); + CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); + assertTrue(token.isRememberMe()); + } + public void testToString() { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password",ROLES, - makeUserDetails(), assertion); + makeUserDetails(), assertion, CasAuthenticationProviderTests.TEST_REMEMBERME_ATTRIBUTE_NAME); String result = token.toString(); assertTrue(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1); } 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..449de1a241d 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 @@ -108,4 +108,25 @@ public void testNormalOperationWithRenewTrue() throws Exception { + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + "&renew=true", response.getRedirectedUrl()); } + + public void testNormalOperationWithForceRenewTrue() 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, null, true); + assertEquals("https://cas/login?service=" + + URLEncoder.encode("https://mycompany.com/bigWebApp/j_spring_cas_security_check", "UTF-8") + "&renew=true", + response.getRedirectedUrl()); + } } diff --git a/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleRememberMeTests.groovy b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleRememberMeTests.groovy new file mode 100644 index 00000000000..533c5e50748 --- /dev/null +++ b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/CasSampleRememberMeTests.groovy @@ -0,0 +1,61 @@ +/* + * Copyright 2012 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.samples.cas + +import geb.spock.* + +import org.junit.runner.RunWith; +import org.spockframework.runtime.Sputnik; +import org.springframework.security.samples.cas.pages.* + +import spock.lang.Shared; +import spock.lang.Stepwise; + +/** + * Tests the remember-me feature in the CAS sample application. + * + * @author Jerome Leleu + * @since 3.2.0 + */ +@Stepwise +class CasSampleRememberMeTests extends AbstractCasTests { + + def 'access isFullyAuthenticated page, authenticate with rme, logout from application'() { + when: 'Unauthenticated user accesses the isFullyAuthenticated page' + to IsFullyAuthenticatedPage + then: 'The login page is displayed' + at LoginPage + when: 'login with ROLE_USER after requesting the isFullyAuthenticated page' + login 'scott', true + then: 'the isFullyAuthenticated page is displayed' + at IsFullyAuthenticatedPage + navModule.logout.click() + } + + def 'request isFullyAuthenticated page and be redirected to login page'() { + when: 'Unauthenticated user accesses the isFullyAuthenticated page' + to IsFullyAuthenticatedPage + then: 'The login page is displayed' + at LoginPage + } + + def 'request isAuthenticated page and access to it'() { + when: 'Unauthenticated user accesses the isAuthenticated page' + to IsAuthenticatedPage + then: 'The isAuthenticated page is displayed' + at IsAuthenticatedPage + } +} diff --git a/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsAuthenticatedPage.groovy b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsAuthenticatedPage.groovy new file mode 100644 index 00000000000..28cc728dc80 --- /dev/null +++ b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsAuthenticatedPage.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2012 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.samples.cas.pages; + +import geb.* +import org.springframework.security.samples.cas.modules.* + + +/** + * Represents the "isAuthenticated" page within the CAS Sample application. + * + * @author Jerome Leleu + * @since 3.2.0 + */ +class IsAuthenticatedPage extends Page { + static url = "auth/" + static at = { assert $('h1').text() == 'isAuthenticated() Page'; true} + static content = { + navModule { module NavModule } + } +} \ No newline at end of file diff --git a/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsFullyAuthenticatedPage.groovy b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsFullyAuthenticatedPage.groovy new file mode 100644 index 00000000000..8ee64691e72 --- /dev/null +++ b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/IsFullyAuthenticatedPage.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2012 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.samples.cas.pages; + +import geb.* +import org.springframework.security.samples.cas.modules.* + + +/** + * Represents the "isFullyAuthenticated" page within the CAS Sample application. + * + * @author Jerome Leleu + * @since 3.2.0 + */ +class IsFullyAuthenticatedPage extends Page { + static url = "fully/" + static at = { assert $('h1').text() == 'isFullyAuthenticated() Page'; true} + static content = { + navModule { module NavModule } + } +} \ No newline at end of file diff --git a/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy index 5f7793e84ed..54a61cbb63f 100644 --- a/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy +++ b/samples/cas/sample/src/integration-test/groovy/org/springframework/security/samples/cas/pages/LoginPage.groovy @@ -26,9 +26,10 @@ class LoginPage extends Page { static url = loginUrl() static at = { assert driver.currentUrl.startsWith(loginUrl()); true} static content = { - login(required:false) { user, password=user -> + login(required:false) { user, rme=false, password=user -> loginForm.username = user loginForm.password = password + loginForm.rememberMe = rme submit.click() } loginForm { $('#login') } diff --git a/samples/cas/sample/src/main/webapp/WEB-INF/applicationContext-security.xml b/samples/cas/sample/src/main/webapp/WEB-INF/applicationContext-security.xml index 1efc9e0f56f..f82262ca006 100644 --- a/samples/cas/sample/src/main/webapp/WEB-INF/applicationContext-security.xml +++ b/samples/cas/sample/src/main/webapp/WEB-INF/applicationContext-security.xml @@ -16,8 +16,13 @@ - + + + + + + + @@ -26,6 +31,10 @@ + + + diff --git a/samples/cas/sample/src/main/webapp/auth/index.jsp b/samples/cas/sample/src/main/webapp/auth/index.jsp new file mode 100644 index 00000000000..326745b7448 --- /dev/null +++ b/samples/cas/sample/src/main/webapp/auth/index.jsp @@ -0,0 +1,6 @@ + + +

isAuthenticated() Page

+

This is a protected page. You can get to me if you've been authenticated.

+ + \ No newline at end of file diff --git a/samples/cas/sample/src/main/webapp/fully/index.jsp b/samples/cas/sample/src/main/webapp/fully/index.jsp new file mode 100644 index 00000000000..c3018c72c81 --- /dev/null +++ b/samples/cas/sample/src/main/webapp/fully/index.jsp @@ -0,0 +1,7 @@ + + +

isFullyAuthenticated() Page

+

This is a protected page. You can get to me if you've authenticated this session.

+Logout + + \ No newline at end of file diff --git a/samples/cas/sample/src/main/webapp/index.jsp b/samples/cas/sample/src/main/webapp/index.jsp index 546d5996d76..e3594578cbc 100644 --- a/samples/cas/sample/src/main/webapp/index.jsp +++ b/samples/cas/sample/src/main/webapp/index.jsp @@ -6,6 +6,8 @@

Your principal object is....: <%= request.getUserPrincipal() %>

Secure page

+

isFullyAuthenticated() page

+

isAuthenticated() page

Proxy Ticket Sample page

Extremely secure page

diff --git a/samples/cas/server/casserver.gradle b/samples/cas/server/casserver.gradle index d3d98dc825f..50d5e63f8da 100644 --- a/samples/cas/server/casserver.gradle +++ b/samples/cas/server/casserver.gradle @@ -9,7 +9,7 @@ configurations { casServer } dependencies { - casServer "org.jasig.cas:cas-server-webapp:3.4.3.1@war" + casServer "org.jasig.cas:cas-server-webapp:3.5.0@war" } task casServerOverlay(type: Sync) { diff --git a/samples/cas/server/src/main/webapp/WEB-INF/deployerConfigContext.xml b/samples/cas/server/src/main/webapp/WEB-INF/deployerConfigContext.xml new file mode 100644 index 00000000000..61d5a9753d2 --- /dev/null +++ b/samples/cas/server/src/main/webapp/WEB-INF/deployerConfigContext.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/cas/server/src/main/webapp/WEB-INF/login-webflow.xml b/samples/cas/server/src/main/webapp/WEB-INF/login-webflow.xml new file mode 100644 index 00000000000..c766186d6d1 --- /dev/null +++ b/samples/cas/server/src/main/webapp/WEB-INF/login-webflow.xml @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/cas/server/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml b/samples/cas/server/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml new file mode 100644 index 00000000000..4b93d221eda --- /dev/null +++ b/samples/cas/server/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml @@ -0,0 +1,35 @@ + + + + + + Assignment of expiration policies for the different tickets generated by CAS including ticket granting ticket + (TGT), service ticket (ST), proxy granting ticket (PGT), and proxy ticket (PT). + These expiration policies determine how long the ticket they are assigned + to can be used and even how often they + can be used before becoming expired / invalid. + + + + + + + + + + + + + + + + + + + diff --git a/samples/cas/server/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp b/samples/cas/server/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp new file mode 100644 index 00000000000..4d916f7117c --- /dev/null +++ b/samples/cas/server/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp @@ -0,0 +1,143 @@ +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +<%-- + ~ 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. + --%> + +<%@ page contentType="text/html; charset=UTF-8" %> + + + +
+

Non-secure Connection

+

You are currently accessing CAS over a non-secure connection. Single Sign On WILL NOT WORK. In order to have single sign on work, you MUST log in over HTTPS.

+
+
+ +
+ + + +

+
+ + + ${sessionScope.openIdLocalId} + + + + + + + +
+
+ + <%-- + NOTE: Certain browsers will offer the option of caching passwords for a user. There is a non-standard attribute, + "autocomplete" that when set to "off" will tell certain browsers not to prompt to cache credentials. For more + information, see the following web page: + http://www.geocities.com/technofundo/tech/web/ie_autocomplete.html + --%> + + +
+
+ " type="checkbox" /> + +
+
+ + +
+
+ + + + + " tabindex="4" type="submit" /> + " tabindex="5" type="reset" /> +
+
+
+ + \ No newline at end of file diff --git a/samples/cas/server/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp b/samples/cas/server/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp new file mode 100644 index 00000000000..18302653b56 --- /dev/null +++ b/samples/cas/server/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp @@ -0,0 +1,37 @@ +<%@ page session="false" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %><%-- + ~ 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. + --%> + + + + ${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)} + + true + + + ${pgtIou} + + + + + ${fn:escapeXml(proxy.principal.id)} + + + + + \ No newline at end of file