/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.switchuser;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.*;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
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.security.util.FieldUtils;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.FilterChain;
import java.util.*;
/**
* Tests {@link org.springframework.security.web.authentication.switchuser.SwitchUserFilter}.
*
* @author Mark St.Godard
* @author Luke Taylor
*/
public class SwitchUserFilterTests {
private final static List<GrantedAuthority> ROLES_12 = AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO");
@Before
public void authenticateCurrentUser() {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
}
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
private MockHttpServletRequest createMockSwitchRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("http");
request.setServerName("localhost");
request.setRequestURI("/j_spring_security_switch_user");
return request;
}
private Authentication switchToUser(String name) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("myUsernameParameter", name);
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUsernameParameter("myUsernameParameter");
filter.setUserDetailsService(new MockUserDetailsService());
return filter.attemptSwitchUser(request);
}
@Test
public void requiresExitUserMatchesCorrectly() {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setExitUserUrl("/j_spring_security_my_exit_user");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/j_spring_security_my_exit_user");
assertTrue(filter.requiresExitUser(request));
}
@Test
public void requiresSwitchMatchesCorrectly() {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/j_spring_security_my_switch_user");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/j_spring_security_my_switch_user");
assertTrue(filter.requiresSwitchUser(request));
}
@Test(expected=UsernameNotFoundException.class)
public void attemptSwitchToUnknownUserFails() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "user-that-doesnt-exist");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.attemptSwitchUser(request);
}
@Test(expected=DisabledException.class)
public void attemptSwitchToUserThatIsDisabledFails() throws Exception {
switchToUser("mcgarrett");
}
@Test(expected=AccountExpiredException.class)
public void attemptSwitchToUserWithAccountExpiredFails() throws Exception {
switchToUser("wofat");
}
@Test(expected=CredentialsExpiredException.class)
public void attemptSwitchToUserWithExpiredCredentialsFails() throws Exception {
switchToUser("steve");
}
@Test(expected=UsernameNotFoundException.class)
public void switchUserWithNullUsernameThrowsException() throws Exception {
switchToUser(null);
}
@Test
public void attemptSwitchUserIsSuccessfulWithValidUser() throws Exception {
assertNotNull(switchToUser("jacklord"));
}
@Test
public void switchToLockedAccountCausesRedirectToSwitchFailureUrl() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/j_spring_security_switch_user");
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "mcgarrett");
MockHttpServletResponse response = new MockHttpServletResponse();
SwitchUserFilter filter = new SwitchUserFilter();
filter.setTargetUrl("/target");
filter.setUserDetailsService(new MockUserDetailsService());
filter.afterPropertiesSet();
// Check it with no url set (should get a text response)
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
assertNotNull(response.getErrorMessage());
// Now check for the redirect
request.setContextPath("/mywebapp");
request.setRequestURI("/mywebapp/j_spring_security_switch_user");
filter = new SwitchUserFilter();
filter.setTargetUrl("/target");
filter.setUserDetailsService(new MockUserDetailsService());
filter.setSwitchFailureUrl("/switchfailed");
filter.afterPropertiesSet();
response = new MockHttpServletResponse();
chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
assertEquals("/mywebapp/switchfailed", response.getRedirectedUrl());
assertEquals("/switchfailed", FieldUtils.getFieldValue(filter, "switchFailureUrl"));
}
@Test(expected=IllegalArgumentException.class)
public void configMissingUserDetailsServiceFails() throws Exception {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/j_spring_security_switch_user");
filter.setExitUserUrl("/j_spring_security_exit_user");
filter.setTargetUrl("/main.jsp");
filter.afterPropertiesSet();
}
@Test(expected=IllegalArgumentException.class)
public void testBadConfigMissingTargetUrl() throws Exception {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setSwitchUserUrl("/j_spring_security_switch_user");
filter.setExitUserUrl("/j_spring_security_exit_user");
filter.afterPropertiesSet();
}
@Test
public void defaultProcessesFilterUrlMatchesUrlWithPathParameter() {
MockHttpServletRequest request = createMockSwitchRequest();
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/j_spring_security_switch_user");
request.setRequestURI("/webapp/j_spring_security_switch_user;jsessionid=8JHDUD723J8");
assertTrue(filter.requiresSwitchUser(request));
}
@Test
public void exitUserJackLordToDanoSucceeds() throws Exception {
// original user
UsernamePasswordAuthenticationToken source = new UsernamePasswordAuthenticationToken("dano", "hawaii50", ROLES_12);
// set current user (Admin)
List<GrantedAuthority> adminAuths = new ArrayList<GrantedAuthority>();
adminAuths.addAll(ROLES_12);
adminAuths.add(new SwitchUserGrantedAuthority("PREVIOUS_ADMINISTRATOR", source));
UsernamePasswordAuthenticationToken admin =
new UsernamePasswordAuthenticationToken("jacklord", "hawaii50", adminAuths);
SecurityContextHolder.getContext().setAuthentication(admin);
MockHttpServletRequest request = createMockSwitchRequest();
request.setRequestURI("/j_spring_security_exit_user");
// setup filter
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setExitUserUrl("/j_spring_security_exit_user");
filter.setSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/webapp/someOtherUrl"));
// run 'exit'
FilterChain chain = mock(FilterChain.class);
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
// check current user, should be back to original user (dano)
Authentication targetAuth = SecurityContextHolder.getContext().getAuthentication();
assertNotNull(targetAuth);
assertEquals("dano", targetAuth.getPrincipal());
}
@Test(expected=AuthenticationException.class)
public void exitUserWithNoCurrentUserFails() throws Exception {
// no current user in secure context
SecurityContextHolder.clearContext();
MockHttpServletRequest request = createMockSwitchRequest();
request.setRequestURI("/j_spring_security_exit_user");
// setup filter
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setExitUserUrl("/j_spring_security_exit_user");
// run 'exit', expect fail due to no current user
FilterChain chain = mock(FilterChain.class);
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
}
@Test
public void redirectToTargetUrlIsCorrect() throws Exception {
MockHttpServletRequest request = createMockSwitchRequest();
request.setContextPath("/webapp");
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/webapp/j_spring_security_switch_user");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/j_spring_security_switch_user");
filter.setSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/someOtherUrl"));
filter.setUserDetailsService(new MockUserDetailsService());
FilterChain chain = mock(FilterChain.class);
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
assertEquals("/webapp/someOtherUrl", response.getRedirectedUrl());
}
@Test
public void redirectOmitsContextPathIfUseRelativeContextSet() throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = createMockSwitchRequest();
request.setContextPath("/webapp");
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/webapp/j_spring_security_switch_user");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/j_spring_security_switch_user");
SimpleUrlAuthenticationSuccessHandler switchSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler("/someOtherUrl");
DefaultRedirectStrategy contextRelativeRedirector = new DefaultRedirectStrategy();
contextRelativeRedirector.setContextRelative(true);
switchSuccessHandler.setRedirectStrategy(contextRelativeRedirector);
filter.setSuccessHandler(switchSuccessHandler);
filter.setUserDetailsService(new MockUserDetailsService());
FilterChain chain = mock(FilterChain.class);
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
assertEquals("/someOtherUrl", response.getRedirectedUrl());
}
@Test
public void testSwitchRequestFromDanoToJackLord() throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
// http request
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/webapp/j_spring_security_switch_user");
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
// http response
MockHttpServletResponse response = new MockHttpServletResponse();
// setup filter
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setSwitchUserUrl("/j_spring_security_switch_user");
filter.setSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/webapp/someOtherUrl"));
FilterChain chain = mock(FilterChain.class);
// test updates user token and context
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(request, response);
// check current user
Authentication targetAuth = SecurityContextHolder.getContext().getAuthentication();
assertNotNull(targetAuth);
assertTrue(targetAuth.getPrincipal() instanceof UserDetails);
assertEquals("jacklord", ((User) targetAuth.getPrincipal()).getUsername());
}
@Test
public void modificationOfAuthoritiesWorks() {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setSwitchUserAuthorityChanger(new SwitchUserAuthorityChanger() {
public Collection<GrantedAuthority> modifyGrantedAuthorities(UserDetails targetUser, Authentication currentAuthentication, Collection<? extends GrantedAuthority> authoritiesToBeGranted) {
List <GrantedAuthority>auths = new ArrayList<GrantedAuthority>();
auths.add(new SimpleGrantedAuthority("ROLE_NEW"));
return auths;
}
});
Authentication result = filter.attemptSwitchUser(request);
assertTrue(result != null);
assertEquals(2, result.getAuthorities().size());
assertTrue(AuthorityUtils.authorityListToSet(result.getAuthorities()).contains("ROLE_NEW"));
}
// SEC-1763
@Test
public void nestedSwitchesAreNotAllowed() throws Exception {
// original user
UsernamePasswordAuthenticationToken source = new UsernamePasswordAuthenticationToken("orig", "hawaii50", ROLES_12);
SecurityContextHolder.getContext().setAuthentication(source);
SecurityContextHolder.getContext().setAuthentication(switchToUser("jacklord"));
Authentication switched = switchToUser("dano");
SwitchUserGrantedAuthority switchedFrom = null;
for (GrantedAuthority ga: switched.getAuthorities()) {
if (ga instanceof SwitchUserGrantedAuthority) {
switchedFrom = (SwitchUserGrantedAuthority)ga;
break;
}
}
assertSame(source, switchedFrom.getSource());
}
//~ Inner Classes ==================================================================================================
private class MockUserDetailsService implements UserDetailsService {
private String password = "hawaii50";
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// jacklord, dano (active)
// mcgarrett (disabled)
// wofat (account expired)
// steve (credentials expired)
if ("jacklord".equals(username) || "dano".equals(username)) {
return new User(username, password, true, true, true, true, ROLES_12);
} else if ("mcgarrett".equals(username)) {
return new User(username, password, false, true, true, true, ROLES_12);
} else if ("wofat".equals(username)) {
return new User(username, password, true, false, true, true, ROLES_12);
} else if ("steve".equals(username)) {
return new User(username, password, true, true, false, true, ROLES_12);
} else {
throw new UsernameNotFoundException("Could not find: " + username);
}
}
}
}