/*
* 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.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.junit.*;
import org.junit.rules.ExpectedException;
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");
@Rule
public ExpectedException thrown = ExpectedException.none();
@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("/login/impersonate");
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);
}
private Authentication switchToUserWithAuthorityRole(String name, String switchAuthorityRole) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, name);
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setSwitchAuthorityRole(switchAuthorityRole);
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");
assertThat(filter.requiresExitUser(request)).isTrue();
}
@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");
assertThat(filter.requiresSwitchUser(request)).isTrue();
}
@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 {
assertThat(switchToUser("jacklord")).isNotNull();
}
@Test
public void switchToLockedAccountCausesRedirectToSwitchFailureUrl() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/login/impersonate");
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);
assertThat(response.getErrorMessage()).isNotNull();
// Now check for the redirect
request.setContextPath("/mywebapp");
request.setRequestURI("/mywebapp/login/impersonate");
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);
assertThat(response.getRedirectedUrl()).isEqualTo("/mywebapp/switchfailed");
assertThat(FieldUtils.getFieldValue(filter, "switchFailureUrl")).isEqualTo("/switchfailed");
}
@Test(expected = IllegalArgumentException.class)
public void configMissingUserDetailsServiceFails() throws Exception {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/login/impersonate");
filter.setExitUserUrl("/logout/impersonate");
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("/login/impersonate");
filter.setExitUserUrl("/logout/impersonate");
filter.afterPropertiesSet();
}
@Test
public void defaultProcessesFilterUrlMatchesUrlWithPathParameter() {
MockHttpServletRequest request = createMockSwitchRequest();
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/login/impersonate");
request.setRequestURI("/webapp/login/impersonate;jsessionid=8JHDUD723J8");
assertThat(filter.requiresSwitchUser(request)).isTrue();
}
@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("/logout/impersonate");
// setup filter
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setExitUserUrl("/logout/impersonate");
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();
assertThat(targetAuth).isNotNull();
assertThat(targetAuth.getPrincipal()).isEqualTo("dano");
}
@Test(expected = AuthenticationException.class)
public void exitUserWithNoCurrentUserFails() throws Exception {
// no current user in secure context
SecurityContextHolder.clearContext();
MockHttpServletRequest request = createMockSwitchRequest();
request.setRequestURI("/logout/impersonate");
// setup filter
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(new MockUserDetailsService());
filter.setExitUserUrl("/logout/impersonate");
// 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/login/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/login/impersonate");
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);
assertThat(response.getRedirectedUrl()).isEqualTo("/webapp/someOtherUrl");
}
@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/login/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/login/impersonate");
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);
assertThat(response.getRedirectedUrl()).isEqualTo("/someOtherUrl");
}
@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/login/impersonate");
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("/login/impersonate");
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();
assertThat(targetAuth).isNotNull();
assertThat(targetAuth.getPrincipal() instanceof UserDetails).isTrue();
assertThat(((User) targetAuth.getPrincipal()).getUsername()).isEqualTo("jacklord");
}
@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);
assertThat(result != null).isTrue();
assertThat(result.getAuthorities()).hasSize(2);
assertThat(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;
}
}
assertThat(switchedFrom).isNotNull();
assertThat(source).isSameAs(switchedFrom.getSource());
}
// gh-3697
@Test
public void switchAuthorityRoleCannotBeNull() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("switchAuthorityRole cannot be null");
switchToUserWithAuthorityRole("dano", null);
}
// gh-3697
@Test
public void switchAuthorityRoleCanBeChanged() throws Exception {
String switchAuthorityRole = "PREVIOUS_ADMINISTRATOR";
// original user
UsernamePasswordAuthenticationToken source = new UsernamePasswordAuthenticationToken(
"orig", "hawaii50", ROLES_12);
SecurityContextHolder.getContext().setAuthentication(source);
SecurityContextHolder.getContext().setAuthentication(switchToUser("jacklord"));
Authentication switched = switchToUserWithAuthorityRole("dano", switchAuthorityRole);
SwitchUserGrantedAuthority switchedFrom = null;
for (GrantedAuthority ga : switched.getAuthorities()) {
if (ga instanceof SwitchUserGrantedAuthority) {
switchedFrom = (SwitchUserGrantedAuthority) ga;
break;
}
}
assertThat(switchedFrom).isNotNull();
assertThat(switchedFrom.getSource()).isSameAs(source);
assertThat(switchAuthorityRole).isEqualTo(switchedFrom.getAuthority());
}
@Test(expected = IllegalArgumentException.class)
public void setSwitchFailureUrlWhenNullThenThrowException() {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchFailureUrl(null);
}
@Test(expected = IllegalArgumentException.class)
public void setSwitchFailureUrlWhenEmptyThenThrowException() {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchFailureUrl("");
}
@Test
public void setSwitchFailureUrlWhenValidThenNoException() {
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchFailureUrl("/foo");
}
// ~ 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);
}
}
}
}