/* * Copyright 2002-2016 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.web.authentication.rememberme; import static org.assertj.core.api.Assertions.assertThat; import static org.powermock.api.mockito.PowerMockito.spy; import static org.powermock.api.mockito.PowerMockito.when; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AccountStatusUserDetailsChecker; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * @author Luke Taylor */ @SuppressWarnings("unchecked") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest(ReflectionUtils.class) public class AbstractRememberMeServicesTests { static User joe = new User("joe", "password", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_A")); MockUserDetailsService uds; @Before public void setup() { uds = new MockUserDetailsService(joe, false); } @Test(expected = InvalidCookieException.class) public void nonBase64CookieShouldBeDetected() { new MockRememberMeServices(uds).decodeCookie("nonBase64CookieValue%"); } @Test public void setAndGetAreConsistent() throws Exception { MockRememberMeServices services = new MockRememberMeServices(uds); assertThat(services.getCookieName()).isNotNull(); assertThat(services.getParameter()).isNotNull(); assertThat(services.getKey()).isEqualTo("xxxx"); services.setParameter("rm"); assertThat(services.getParameter()).isEqualTo("rm"); services.setCookieName("kookie"); assertThat(services.getCookieName()).isEqualTo("kookie"); services.setTokenValiditySeconds(600); assertThat(services.getTokenValiditySeconds()).isEqualTo(600); assertThat(services.getUserDetailsService()).isSameAs(uds); AuthenticationDetailsSource ads = Mockito.mock(AuthenticationDetailsSource.class); services.setAuthenticationDetailsSource(ads); assertThat(services.getAuthenticationDetailsSource()).isSameAs(ads); services.afterPropertiesSet(); } @Test public void cookieShouldBeCorrectlyEncodedAndDecoded() throws Exception { String[] cookie = new String[] { "name", "cookie", "tokens", "blah" }; MockRememberMeServices services = new MockRememberMeServices(uds); String encoded = services.encodeCookie(cookie); // '=' aren't allowed in version 0 cookies. assertThat(encoded.endsWith("=")).isFalse(); String[] decoded = services.decodeCookie(encoded); assertThat(decoded.length).isEqualTo(4); assertThat(decoded[0]).isEqualTo("name"); assertThat(decoded[1]).isEqualTo("cookie"); assertThat(decoded[2]).isEqualTo("tokens"); assertThat(decoded[3]).isEqualTo("blah"); } @Test public void cookieWithOpenIDidentifierAsNameIsEncodedAndDecoded() throws Exception { String[] cookie = new String[] { "http://id.openid.zz", "cookie", "tokens", "blah" }; MockRememberMeServices services = new MockRememberMeServices(uds); String[] decoded = services.decodeCookie(services.encodeCookie(cookie)); assertThat(decoded.length).isEqualTo(4); assertThat(decoded[0]).isEqualTo("http://id.openid.zz"); // Check https (SEC-1410) cookie[0] = "https://id.openid.zz"; decoded = services.decodeCookie(services.encodeCookie(cookie)); assertThat(decoded.length).isEqualTo(4); assertThat(decoded[0]).isEqualTo("https://id.openid.zz"); } @Test public void autoLoginShouldReturnNullIfNoLoginCookieIsPresented() { MockRememberMeServices services = new MockRememberMeServices(uds); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); assertThat(services.autoLogin(request, response)).isNull(); // shouldn't try to invalidate our cookie assertThat(response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)).isNull(); request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); // set non-login cookie request.setCookies(new Cookie("mycookie", "cookie")); assertThat(services.autoLogin(request, response)).isNull(); assertThat(response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)).isNull(); } @Test public void successfulAutoLoginReturnsExpectedAuthentication() throws Exception { MockRememberMeServices services = new MockRememberMeServices(uds); services.afterPropertiesSet(); assertThat(services.getUserDetailsService()).isNotNull(); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); MockHttpServletResponse response = new MockHttpServletResponse(); Authentication result = services.autoLogin(request, response); assertThat(result).isNotNull(); } @Test public void autoLoginShouldFailIfCookieIsNotBase64() throws Exception { MockRememberMeServices services = new MockRememberMeServices(uds); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); request.setCookies(new Cookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, "ZZZ")); Authentication result = services.autoLogin(request, response); assertThat(result).isNull(); assertCookieCancelled(response); } @Test public void autoLoginShouldFailIfCookieIsEmpty() throws Exception { MockRememberMeServices services = new MockRememberMeServices(uds); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); request.setCookies(new Cookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, "")); Authentication result = services.autoLogin(request, response); assertThat(result).isNull(); assertCookieCancelled(response); } @Test public void autoLoginShouldFailIfInvalidCookieExceptionIsRaised() { MockRememberMeServices services = new MockRememberMeServices( new MockUserDetailsService(joe, true)); MockHttpServletRequest request = new MockHttpServletRequest(); // Wrong number of tokens request.setCookies(createLoginCookie("cookie:1")); MockHttpServletResponse response = new MockHttpServletResponse(); Authentication result = services.autoLogin(request, response); assertThat(result).isNull(); assertCookieCancelled(response); } @Test public void autoLoginShouldFailIfUserNotFound() { uds.setThrowException(true); MockRememberMeServices services = new MockRememberMeServices(uds); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); MockHttpServletResponse response = new MockHttpServletResponse(); Authentication result = services.autoLogin(request, response); assertThat(result).isNull(); assertCookieCancelled(response); } @Test public void autoLoginShouldFailIfUserAccountIsLocked() { MockRememberMeServices services = new MockRememberMeServices(uds); services.setUserDetailsChecker(new AccountStatusUserDetailsChecker()); uds.toReturn = new User("joe", "password", false, true, true, true, joe.getAuthorities()); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); MockHttpServletResponse response = new MockHttpServletResponse(); Authentication result = services.autoLogin(request, response); assertThat(result).isNull(); assertCookieCancelled(response); } @Test public void loginFailShouldCancelCookie() { uds.setThrowException(true); MockRememberMeServices services = new MockRememberMeServices(uds); MockHttpServletRequest request = new MockHttpServletRequest(); request.setContextPath("contextpath"); request.setCookies(createLoginCookie("cookie:1:2")); MockHttpServletResponse response = new MockHttpServletResponse(); services.loginFail(request, response); assertCookieCancelled(response); } @Test public void logoutShouldCancelCookie() throws Exception { MockRememberMeServices services = new MockRememberMeServices(uds); services.setCookieDomain("spring.io"); MockHttpServletRequest request = new MockHttpServletRequest(); request.setContextPath("contextpath"); request.setCookies(createLoginCookie("cookie:1:2")); MockHttpServletResponse response = new MockHttpServletResponse(); services.logout(request, response, Mockito.mock(Authentication.class)); // Try again with null Authentication response = new MockHttpServletResponse(); services.logout(request, response, null); assertCookieCancelled(response); Cookie returnedCookie = response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertThat(returnedCookie.getDomain()).isEqualTo("spring.io"); } @Test(expected = CookieTheftException.class) public void cookieTheftExceptionShouldBeRethrown() { MockRememberMeServices services = new MockRememberMeServices(uds) { protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { throw new CookieTheftException("Pretending cookie was stolen"); } }; MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); MockHttpServletResponse response = new MockHttpServletResponse(); services.autoLogin(request, response); } @Test public void loginSuccessCallsOnLoginSuccessCorrectly() { MockRememberMeServices services = new MockRememberMeServices(uds); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); Authentication auth = new UsernamePasswordAuthenticationToken("joe", "password"); // No parameter set services.loginSuccess(request, response, auth); assertThat(services.loginSuccessCalled).isFalse(); // Parameter set to true services = new MockRememberMeServices(uds); request.setParameter(MockRememberMeServices.DEFAULT_PARAMETER, "true"); services.loginSuccess(request, response, auth); assertThat(services.loginSuccessCalled).isTrue(); // Different parameter name, set to true services = new MockRememberMeServices(uds); services.setParameter("my_parameter"); request.setParameter("my_parameter", "true"); services.loginSuccess(request, response, auth); assertThat(services.loginSuccessCalled).isTrue(); // Parameter set to false services = new MockRememberMeServices(uds); request.setParameter(MockRememberMeServices.DEFAULT_PARAMETER, "false"); services.loginSuccess(request, response, auth); assertThat(services.loginSuccessCalled).isFalse(); // alwaysRemember set to true services = new MockRememberMeServices(uds); services.setAlwaysRemember(true); services.loginSuccess(request, response, auth); assertThat(services.loginSuccessCalled).isTrue(); } @Test public void setCookieUsesCorrectNamePathAndValue() { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); request.setContextPath("contextpath"); MockRememberMeServices services = new MockRememberMeServices(uds) { protected String encodeCookie(String[] cookieTokens) { return cookieTokens[0]; } }; services.setCookieName("mycookiename"); services.setCookie(new String[] { "mycookie" }, 1000, request, response); Cookie cookie = response.getCookie("mycookiename"); assertThat(cookie).isNotNull(); assertThat(cookie.getValue()).isEqualTo("mycookie"); assertThat(cookie.getName()).isEqualTo("mycookiename"); assertThat(cookie.getPath()).isEqualTo("contextpath"); assertThat(cookie.getSecure()).isFalse(); } @Test public void setCookieSetsSecureFlagIfConfigured() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); request.setContextPath("contextpath"); MockRememberMeServices services = new MockRememberMeServices(uds) { protected String encodeCookie(String[] cookieTokens) { return cookieTokens[0]; } }; services.setUseSecureCookie(true); services.setCookie(new String[] { "mycookie" }, 1000, request, response); Cookie cookie = response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertThat(cookie.getSecure()).isTrue(); } @Test public void setHttpOnlyIgnoredForServlet25() throws Exception { spy(ReflectionUtils.class); when(ReflectionUtils.findMethod(Cookie.class, "setHttpOnly", boolean.class)).thenReturn(null); MockRememberMeServices services = new MockRememberMeServices(uds); assertThat(ReflectionTestUtils.getField(services, "setHttpOnlyMethod")).isNull(); services = new MockRememberMeServices("key", new MockUserDetailsService(joe, false)); assertThat(ReflectionTestUtils.getField(services, "setHttpOnlyMethod")).isNull(); } // SEC-2791 @Test public void setCookieMaxAge0VersionSet() { MockRememberMeServices services = new MockRememberMeServices(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); services.setCookie(new String[] { "value" }, 0, request, response); Cookie cookie = response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertThat(cookie.getVersion()).isEqualTo(1); } // SEC-2791 @Test public void setCookieMaxAgeNegativeVersionSet() { MockRememberMeServices services = new MockRememberMeServices(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); services.setCookie(new String[] { "value" }, -1, request, response); Cookie cookie = response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertThat(cookie.getVersion()).isEqualTo(1); } // SEC-2791 @Test public void setCookieMaxAge1VersionSet() { MockRememberMeServices services = new MockRememberMeServices(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); services.setCookie(new String[] { "value" }, 1, request, response); Cookie cookie = response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertThat(cookie.getVersion()).isEqualTo(0); } @Test public void setCookieDomainValue() { MockRememberMeServices services = new MockRememberMeServices(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); services.setCookieName("mycookiename"); services.setCookieDomain("spring.io"); services.setCookie(new String[] { "mycookie" }, 1000, request, response); Cookie cookie = response.getCookie("mycookiename"); assertThat(cookie).isNotNull(); assertThat(cookie.getDomain()).isEqualTo("spring.io"); } private Cookie[] createLoginCookie(String cookieToken) { MockRememberMeServices services = new MockRememberMeServices(uds); Cookie cookie = new Cookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, services.encodeCookie( StringUtils.delimitedListToStringArray(cookieToken, ":"))); return new Cookie[] { cookie }; } private void assertCookieCancelled(MockHttpServletResponse response) { Cookie returnedCookie = response.getCookie( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertThat(returnedCookie).isNotNull(); assertThat(returnedCookie.getMaxAge()).isEqualTo(0); } // ~ Inner Classes // ================================================================================================== static class MockRememberMeServices extends AbstractRememberMeServices { boolean loginSuccessCalled; MockRememberMeServices(String key, UserDetailsService userDetailsService) { super(key, userDetailsService); } MockRememberMeServices(UserDetailsService userDetailsService) { super("xxxx", userDetailsService); } MockRememberMeServices() { this(new MockUserDetailsService(null, false)); } protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { loginSuccessCalled = true; } protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws RememberMeAuthenticationException { if (cookieTokens.length != 3) { throw new InvalidCookieException("deliberate exception"); } UserDetails user = getUserDetailsService().loadUserByUsername("joe"); return user; } } public static class MockUserDetailsService implements UserDetailsService { private UserDetails toReturn; private boolean throwException; public MockUserDetailsService() { this(null, false); } public MockUserDetailsService(UserDetails toReturn, boolean throwException) { this.toReturn = toReturn; this.throwException = throwException; } public UserDetails loadUserByUsername(String username) { if (throwException) { throw new UsernameNotFoundException("as requested by mock"); } return toReturn; } public void setThrowException(boolean value) { this.throwException = value; } } }