Initial checkin of user security context switching (see SEC-15). This is the first cut of the SwitchUserProcessingFilter that handles switching to a target uesr and exiting back to the original user. Note: This is going to be used for the common use-case of an Administrator 'switching' to another user (i.e. ROLE_ADMIN -> ROLE_USER). This is the initial cut of a Unix 'su' for Acegi managed web applications.

This commit is contained in:
Mark St. Godard 2005-08-04 05:49:12 +00:00
parent 27a57410c1
commit ec5e39c2e8
4 changed files with 969 additions and 0 deletions

View File

@ -0,0 +1,59 @@
/* Copyright 2004, 2005 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 net.sf.acegisecurity.ui.switchuser;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.GrantedAuthorityImpl;
/**
* Custom <code>GrantedAuthority</code> used by {@link
* net.sf.acegisecurity.ui.switchuser.SwitchUserProcessingFilter}
*
* <p>
* Stores the <code>Authentication</code> object of the original user to be
* used later when 'exiting' from a user switch.
* </p>
*
* @author Mark St.Godard
* @version $Id$
*
* @see net.sf.acegisecurity.ui.switchuser.SwitchUserProcessingFilter
*/
public class SwitchUserGrantedAuthority extends GrantedAuthorityImpl {
//~ Instance fields ========================================================
private Authentication source;
//~ Constructors ===========================================================
public SwitchUserGrantedAuthority(String role, Authentication source) {
super(role);
this.source = source;
}
//~ Methods ================================================================
/**
* Returns the original user associated with a successful user switch.
*
* @return The original <code>Authentication</code> object of the switched
* user.
*/
public Authentication getSource() {
return source;
}
}

View File

@ -0,0 +1,462 @@
/* Copyright 2004, 2005 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 net.sf.acegisecurity.ui.switchuser;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.context.SecurityContextHolder;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.AuthenticationDao;
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
import net.sf.acegisecurity.ui.WebAuthenticationDetails;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Switch User processing filter responsible for user context switching.
*
* <p>
* This filter is similar to Unix 'su' however for Acegi-managed web
* applications. A common use-case for this feature is the ability to allow
* higher-authority users (i.e. ROLE_ADMIN) to switch to a regular user (i.e.
* ROLE_USER).
* </p>
*
* <p>
* This filter assumes that the user performing the switch will be required to
* be logged in as normal (i.e. ROLE_ADMIN user). The user will then access a
* page/controller that enables the administrator to specify who they wish to
* become (see <code>switchUserUrl</code>). <br>
* <b>Note: This URL will be required to have to appropriate security
* contraints configured so that only users of that role can access (i.e.
* ROLE_ADMIN).</b>
* </p>
*
* <p>
* On successful switch, the user's <code>SecureContextHolder</code> will be
* updated to reflect the specified user and will also contain an additinal
* {@link net.sf.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority }
* which contains the original user.
* </p>
*
* <p>
* To 'exit' from a user context, the user will then need to access a URL (see
* <code>exitUserUrl</code>) that will switch back to the original user as
* identified by the <code>SWITCH_USER_GRANTED_AUTHORITY</code>.
* </p>
*
* <p>
* To configure the Switch User Processing Filter, create a bean definition for
* the Switch User processing filter and add to the filterChainProxy. <br>
* Example:
* <pre>
* &lt;bean id="switchUserProcessingFilter" class="net.sf.acegisecurity.ui.switchuser.SwitchUserProcessingFilter">
* &lt;property name="authenticationDao" ref="jdbcDaoImpl" />
* &lt;property name="switchUserUrl">&lt;value>/j_acegi_switch_user&lt;/value>&lt;/property>
* &lt;property name="exitUserUrl">&lt;value>/j_acegi_exit_user&lt;/value>&lt;/property>
* &lt;property name="targetUrl">&lt;value>/index.jsp&lt;/value>&lt;/property>
* &lt;/bean>
* </pre>
* </p>
*
* @author Mark St.Godard
* @version $Id$
*
* @see net.sf.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority
*/
public class SwitchUserProcessingFilter implements InitializingBean, Filter {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(SwitchUserProcessingFilter.class);
// ~ Static fields/initializers
// =============================================
public static final String ACEGI_SECURITY_SWITCH_USERNAME_KEY = "j_username";
public static final String SWITCH_USER_GRANTED_AUTHORITY = "PREVIOUS_ADMINISTRATOR";
//~ Instance fields ========================================================
// ~ Instance fields
// ========================================================
private AuthenticationDao authenticationDao;
private String exitUserUrl;
private String switchUserUrl;
private String targetUrl;
//~ Methods ================================================================
/**
* Sets the authentication data access object.
*
* @param authenticationDao The authentication dao
*/
public void setAuthenticationDao(AuthenticationDao authenticationDao) {
this.authenticationDao = authenticationDao;
}
/**
* This filter by default responds to <code>/j_acegi_exit_user</code>.
*
* @return the default exit user url
*/
public String getDefaultExitUserUrl() {
return "/j_acegi_exit_user";
}
// ~ Methods
// ================================================================
/**
* This filter by default responds to <code>/j_acegi_switch_user</code>.
*
* @return the default switch user url
*/
public String getDefaultSwitchUserUrl() {
return "/j_acegi_switch_user";
}
/**
* Set the URL to respond to exit user processing.
*
* @param exitUserUrl The exit user URL.
*/
public void setExitUserUrl(String exitUserUrl) {
this.exitUserUrl = exitUserUrl;
}
/**
* Set the URL to respond to switch user processing.
*
* @param switchUserUrl The switch user URL.
*/
public void setSwitchUserUrl(String switchUserUrl) {
this.switchUserUrl = switchUserUrl;
}
/**
* Sets the URL to go to after a successful switch / exit user request.
*
* @param targetUrl The target url.
*/
public void setTargetUrl(String targetUrl) {
this.targetUrl = targetUrl;
}
public void afterPropertiesSet() throws Exception {
Assert.hasLength(switchUserUrl, "switchUserUrl must be specified");
Assert.hasLength(exitUserUrl, "exitUserUrl must be specified");
Assert.hasLength(targetUrl, "targetUrl must be specified");
Assert.notNull(authenticationDao, "authenticationDao must be specified");
}
public void destroy() {}
/**
* @see javax.servlet.Filter#doFilter
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("Can only process HttpServletRequest");
}
if (!(response instanceof HttpServletResponse)) {
throw new ServletException("Can only process HttpServletResponse");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// check for switch or exit request
if (requiresSwitchUser(httpRequest)) {
// if set, attempt switch and store original
Authentication targetUser = attemptSwitchUser(httpRequest);
// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);
// redirect to target url
httpResponse.sendRedirect(httpResponse.encodeRedirectURL(targetUrl));
return;
} else if (requiresExitUser(httpRequest)) {
// get the original authentication object (if exists)
Authentication originalUser = attemptExitUser(httpRequest);
// update the current context back to the original user
SecurityContextHolder.getContext().setAuthentication(originalUser);
// redirect to target url
httpResponse.sendRedirect(httpResponse.encodeRedirectURL(targetUrl));
return;
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {}
/**
* Attempt to exit from an already switched user.
*
* @param request The http servlet request
*
* @return The original <code>Authentication</code> object or
* <code>null</code> otherwise.
*
* @throws AuthenticationCredentialsNotFoundException If no
* <code>Authentication</code> associated with this request.
*/
protected Authentication attemptExitUser(HttpServletRequest request)
throws AuthenticationCredentialsNotFoundException {
// need to check to see if the current user has a SwitchUserGrantedAuthority
Authentication current = SecurityContextHolder.getContext()
.getAuthentication();
if (null == current) {
throw new AuthenticationCredentialsNotFoundException(
"No current user associated with this request!");
}
// check to see if the current user did actual switch to another user
// if so, get the original source user so we can switch back
Authentication original = getSourceAuthentication(current);
if (original == null) {
logger.error("Could not find original user Authentication object!");
throw new AuthenticationCredentialsNotFoundException(
"Could not find original Authentication object!");
}
return original;
}
/**
* Attempt to switch to another user. If the user does not exist or is not
* active, return null.
*
* @param request The http request
*
* @return The new <code>Authentication</code> request if successfully
* switched to another user, <code>null</code> otherwise.
*
* @throws AuthenticationException
* @throws UsernameNotFoundException If the target user is not found.
* @throws DisabledException If the target user is disabled.
* @throws AccountExpiredException If the target user account is expired.
* @throws CredentialsExpiredException If the target user credentials are
* expired.
*/
protected Authentication attemptSwitchUser(HttpServletRequest request)
throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest = null;
String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY);
if (username == null) {
username = "";
}
if (logger.isDebugEnabled()) {
logger.debug("Attempt to switch to user [" + username + "]");
}
// load the user by name
UserDetails targetUser = this.authenticationDao.loadUserByUsername(username);
// user not found
if (targetUser == null) {
throw new UsernameNotFoundException("User [" + username
+ "] cannot be found!");
}
// user is disabled
if (!targetUser.isEnabled()) {
throw new DisabledException("User is disabled");
}
// account is expired
if (!targetUser.isAccountNonExpired()) {
throw new AccountExpiredException("User account has expired");
}
// credentials expired
if (!targetUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("User credentials expired");
}
// ok, create the switch user token
targetUserRequest = createSwitchUserToken(request, username, targetUser);
if (logger.isDebugEnabled()) {
logger.debug("Switch User Token [" + targetUserRequest + "]");
}
return targetUserRequest;
}
/**
* Checks the request URI for the presence of <tt>exitUserUrl</tt>.
*
* @param request The http servlet request
*
* @return <code>true</code> if the request requires a exit user,
* <code>false</code> otherwise.
*
* @see SwitchUserProcessingFilter#exitUserUrl
*/
protected boolean requiresExitUser(HttpServletRequest request) {
String uri = stripUri(request);
return uri.endsWith(request.getContextPath() + exitUserUrl);
}
/**
* Checks the request URI for the presence of <tt>switchUserUrl</tt>.
*
* @param request The http servlet request
*
* @return <code>true</code> if the request requires a switch,
* <code>false</code> otherwise.
*
* @see SwitchUserProcessingFilter#switchUserUrl
*/
protected boolean requiresSwitchUser(HttpServletRequest request) {
String uri = stripUri(request);
return uri.endsWith(request.getContextPath() + switchUserUrl);
}
/**
* Strips any content after the ';' in the request URI
*
* @param request The http request
*
* @return The stripped uri
*/
private static String stripUri(HttpServletRequest request) {
String uri = request.getRequestURI();
int idx = uri.indexOf(';');
if (idx > 0) {
uri = uri.substring(0, idx);
}
return uri;
}
/**
* Find the original <code>Authentication</code> object from the current
* user's granted authorities. A successfully switched user should have a
* <code>SwitchUserGrantedAuthority</code> that contains the original
* source user <code>Authentication</code> object.
*
* @param current The current <code>Authentication</code> object
*
* @return The source user <code>Authentication</code> object or
* <code>null</code> otherwise.
*/
private Authentication getSourceAuthentication(Authentication current) {
Authentication original = null;
// iterate over granted authorities and find the 'switch user' authority
GrantedAuthority[] authorities = current.getAuthorities();
for (int i = 0; i < authorities.length; i++) {
// check for switch user type of authority
if (authorities[i] instanceof SwitchUserGrantedAuthority) {
original = ((SwitchUserGrantedAuthority) authorities[i])
.getSource();
logger.debug("Found original switch user granted authority ["
+ original + "]");
}
}
return original;
}
/**
* Create a switch user token that contains an additional
* <tt>GrantedAuthority</tt> that contains the original
* <code>Authentication</code> object.
*
* @param request The http servlet request.
* @param username The username of target user
* @param targetUser The target user
*
* @return The authentication token
*
* @see SwitchUserGrantedAuthority
*/
private UsernamePasswordAuthenticationToken createSwitchUserToken(
HttpServletRequest request, String username, UserDetails targetUser) {
UsernamePasswordAuthenticationToken targetUserRequest;
// grant an additional authority that contains the original Authentication object
// which will be used to 'exit' from the current switched user.
Authentication currentAuth = SecurityContextHolder.getContext()
.getAuthentication();
GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(SWITCH_USER_GRANTED_AUTHORITY,
currentAuth);
// get the original authorities
List orig = Arrays.asList(targetUser.getAuthorities());
// add the new switch user authority
List newAuths = new ArrayList(orig);
newAuths.add(switchAuthority);
GrantedAuthority[] authorities = {};
authorities = (GrantedAuthority[]) newAuths.toArray(authorities);
// create the new authentication token
targetUserRequest = new UsernamePasswordAuthenticationToken(username,
targetUser.getPassword(), authorities);
// set details
targetUserRequest.setDetails(new WebAuthenticationDetails(request));
return targetUserRequest;
}
}

View File

@ -0,0 +1,447 @@
/* Copyright 2004, 2005 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 net.sf.acegisecurity.ui.switchuser;
import junit.framework.TestCase;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.context.SecurityContextHolder;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.AuthenticationDao;
import net.sf.acegisecurity.providers.dao.User;
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
import net.sf.acegisecurity.util.MockFilterChain;
import org.springframework.dao.DataAccessException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
/**
* Tests {@link net.sf.acegisecurity.ui.switchuser.SwitchUserProcessingFilter}.
*
* @author Mark St.Godard
* @version $Id$
*/
public class SwitchUserProcessingFilterTests extends TestCase {
//~ Constructors ===========================================================
public SwitchUserProcessingFilterTests() {
super();
}
public SwitchUserProcessingFilterTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(SwitchUserProcessingFilterTests.class);
}
public void testAttemptSwitchToUnknownUser() throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano",
"hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"user-that-doesnt-exist");
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
try {
Authentication result = filter.attemptSwitchUser(request);
fail("Should not be able to switch to unknown user");
} catch (UsernameNotFoundException expected) {}
}
public void testAttemptSwitchToUserThatIsDisabled()
throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano",
"hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = new MockHttpServletRequest();
// this user is disabled
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"mcgarrett");
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
try {
Authentication result = filter.attemptSwitchUser(request);
fail("Should not be able to switch to disabled user");
} catch (DisabledException expected) {
// user should be disabled
}
}
public void testAttemptSwitchToUserWithAccountExpired()
throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano",
"hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = new MockHttpServletRequest();
// this user is disabled
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"wofat");
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
try {
Authentication result = filter.attemptSwitchUser(request);
fail("Should not be able to switch to user with expired account");
} catch (AccountExpiredException expected) {
// expected user account expired
}
}
public void testAttemptSwitchToUserWithExpiredCredentials()
throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano",
"hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = new MockHttpServletRequest();
// this user is disabled
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"steve");
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
try {
Authentication result = filter.attemptSwitchUser(request);
fail("Should not be able to switch to user with expired account");
} catch (CredentialsExpiredException expected) {
// user credentials expired
}
}
public void testAttemptSwitchUser() throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano",
"hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"jacklord");
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
Authentication result = filter.attemptSwitchUser(request);
assertTrue(result != null);
}
public void testBadConfigMissingAuthenticationDao() {
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setSwitchUserUrl("/j_acegi_switch_user");
filter.setExitUserUrl("/j_acegi_exit_user");
filter.setTargetUrl("/main.jsp");
try {
filter.afterPropertiesSet();
fail("Expect to fail due to missing 'authenticationDao'");
} catch (Exception expected) {
// expected exception
}
}
public void testBadConfigMissingExitUserUrl() {
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.setSwitchUserUrl("/j_acegi_switch_user");
filter.setTargetUrl("/main.jsp");
try {
filter.afterPropertiesSet();
fail("Expect to fail due to missing 'exitUserUrl'");
} catch (Exception expected) {
// expected exception
}
}
public void testBadConfigMissingSwitchUserUrl() {
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.setExitUserUrl("/j_acegi_exit_user");
filter.setTargetUrl("/main.jsp");
try {
filter.afterPropertiesSet();
fail("Expect to fail due to missing 'switchUserUrl'");
} catch (Exception expected) {
// expected exception
}
}
public void testBadConfigMissingTargetUrl() {
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.setSwitchUserUrl("/j_acegi_switch_user");
filter.setExitUserUrl("/j_acegi_exit_user");
try {
filter.afterPropertiesSet();
fail("Expect to fail due to missing 'targetUrl'");
} catch (Exception expected) {
// expected exception
}
}
public void testDefaultExitProcessUrl() {
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
assertEquals("/j_acegi_exit_user", filter.getDefaultExitUserUrl());
}
public void testDefaultProcessesFilterUrlWithPathParameter() {
MockHttpServletRequest request = createMockSwitchRequest();
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setSwitchUserUrl("/j_acegi_switch_user");
request.setRequestURI(
"/webapp/j_acegi_switch_user;jsessionid=8JHDUD723J8");
assertTrue(filter.requiresSwitchUser(request));
}
public void testDefaultSwitchProcessUrl() {
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
assertEquals("/j_acegi_switch_user", filter.getDefaultSwitchUserUrl());
}
public void testExitRequestUserJackLordToDano() throws Exception {
// original user
GrantedAuthority[] auths = {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")};
UsernamePasswordAuthenticationToken source = new UsernamePasswordAuthenticationToken("dano",
"hawaii50", auths);
// set current user (Admin)
GrantedAuthority[] adminAuths = {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO"), new SwitchUserGrantedAuthority("PREVIOUS_ADMINISTRATOR",
source)};
UsernamePasswordAuthenticationToken admin = new UsernamePasswordAuthenticationToken("jacklord",
"hawaii50", adminAuths);
SecurityContextHolder.getContext().setAuthentication(admin);
// http request
MockHttpServletRequest request = createMockSwitchRequest();
request.setRequestURI("/j_acegi_exit_user");
// http response
MockHttpServletResponse response = new MockHttpServletResponse();
// setup filter
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.setExitUserUrl("/j_acegi_exit_user");
MockFilterChain chain = new MockFilterChain(true);
// run 'exit'
filter.doFilter(request, response, chain);
// check current user, should be back to original user (dano)
Authentication targetAuth = SecurityContextHolder.getContext()
.getAuthentication();
assertNotNull(targetAuth);
assertEquals("dano", targetAuth.getPrincipal());
}
public void testExitUserWithNoCurrentUser() throws Exception {
// no current user in secure context
SecurityContextHolder.getContext().setAuthentication(null);
// http request
MockHttpServletRequest request = createMockSwitchRequest();
request.setRequestURI("/j_acegi_exit_user");
// http response
MockHttpServletResponse response = new MockHttpServletResponse();
// setup filter
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.setExitUserUrl("/j_acegi_exit_user");
MockFilterChain chain = new MockFilterChain(true);
// run 'exit', expect fail due to no current user
try {
filter.doFilter(request, response, chain);
fail("Cannot exit from a user with no current user set!");
} catch (AuthenticationException expected) {}
}
public void testRedirectToTargetUrl() throws Exception {
// set current user
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano",
"hawaii50");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletRequest request = createMockSwitchRequest();
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"jacklord");
request.setRequestURI("/webapp/j_acegi_switch_user");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain(true);
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setSwitchUserUrl("/j_acegi_switch_user");
filter.setTargetUrl("/webapp/someOtherUrl");
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.doFilter(request, response, chain);
assertEquals("/webapp/someOtherUrl", response.getRedirectedUrl());
}
public void testRequiresExitUser() {
// filter
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setExitUserUrl("/j_acegi_exit_user");
// request
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/j_acegi_exit_user");
assertTrue(filter.requiresExitUser(request));
}
public void testRequiresSwitch() {
// filter
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setSwitchUserUrl("/j_acegi_switch_user");
// request
MockHttpServletRequest request = createMockSwitchRequest();
assertTrue(filter.requiresSwitchUser(request));
}
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_acegi_switch_user");
request.addParameter(SwitchUserProcessingFilter.ACEGI_SECURITY_SWITCH_USERNAME_KEY,
"jacklord");
// http response
MockHttpServletResponse response = new MockHttpServletResponse();
// setup filter
SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
filter.setSwitchUserUrl("/j_acegi_switch_user");
MockFilterChain chain = new MockFilterChain(true);
// test updates user token and context
filter.doFilter(request, response, chain);
// check current user
Authentication targetAuth = SecurityContextHolder.getContext()
.getAuthentication();
assertNotNull(targetAuth);
assertEquals("jacklord", targetAuth.getPrincipal());
}
private MockHttpServletRequest createMockSwitchRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("http");
request.setServerName("localhost");
request.setRequestURI("/j_acegi_switch_user");
return request;
}
//~ Inner Classes ==========================================================
private class MockAuthenticationDaoUserJackLord implements AuthenticationDao {
private String password = "hawaii50";
public void setPassword(String password) {
this.password = password;
}
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
// 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,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else if ("mcgarrett".equals(username)) {
return new User(username, password, false, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else if ("wofat".equals(username)) {
return new User(username, password, true, false, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else if ("steve".equals(username)) {
return new User(username, password, true, true, false, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
throw new UsernameNotFoundException("Could not find: "
+ username);
}
}
}
}

View File

@ -26,6 +26,7 @@
</properties>
<body>
<release version="0.9.0" date="In CVS">
<action dev="markstg" type="add">SwitchUserProcessingFilter to provide user security context switching</action>
<action dev="benalex" type="update">JdbcDaoImpl modified to support synthetic primary keys</action>
<action dev="benalex" type="update">Greatly improve BasicAclEntryAfterInvocationCollectionFilteringProvider performance with large collections (if the principal has access to relatively few collection elements)</action>
<action dev="benalex" type="update">Reorder DaoAuthenticationProvider exception logic as per developer list discussion</action>