diff --git a/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserGrantedAuthority.java b/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserGrantedAuthority.java
new file mode 100644
index 0000000000..5295d196e7
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserGrantedAuthority.java
@@ -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 GrantedAuthority used by {@link
+ * net.sf.acegisecurity.ui.switchuser.SwitchUserProcessingFilter}
+ *
+ *
+ * Stores the Authentication object of the original user to be
+ * used later when 'exiting' from a user switch.
+ *
Authentication object of the switched
+ * user.
+ */
+ public Authentication getSource() {
+ return source;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java
new file mode 100644
index 0000000000..8c1dfb452e
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java
@@ -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.
+ *
+ * + * 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). + *
+ * + *
+ * 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 switchUserUrl).
+ * 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).
+ *
+ * On successful switch, the user's SecureContextHolder 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.
+ *
+ * To 'exit' from a user context, the user will then need to access a URL (see
+ * exitUserUrl) that will switch back to the original user as
+ * identified by the SWITCH_USER_GRANTED_AUTHORITY.
+ *
+ * To configure the Switch User Processing Filter, create a bean definition for
+ * the Switch User processing filter and add to the filterChainProxy.
+ * Example:
+ *
+ * <bean id="switchUserProcessingFilter" class="net.sf.acegisecurity.ui.switchuser.SwitchUserProcessingFilter"> + * <property name="authenticationDao" ref="jdbcDaoImpl" /> + * <property name="switchUserUrl"><value>/j_acegi_switch_user</value></property> + * <property name="exitUserUrl"><value>/j_acegi_exit_user</value></property> + * <property name="targetUrl"><value>/index.jsp</value></property> + * </bean> + *+ * + * + * @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
/j_acegi_exit_user.
+ *
+ * @return the default exit user url
+ */
+ public String getDefaultExitUserUrl() {
+ return "/j_acegi_exit_user";
+ }
+
+ // ~ Methods
+ // ================================================================
+
+ /**
+ * This filter by default responds to /j_acegi_switch_user.
+ *
+ * @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 Authentication object or
+ * null otherwise.
+ *
+ * @throws AuthenticationCredentialsNotFoundException If no
+ * Authentication 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 Authentication request if successfully
+ * switched to another user, null 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 exitUserUrl.
+ *
+ * @param request The http servlet request
+ *
+ * @return true if the request requires a exit user,
+ * false 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 switchUserUrl.
+ *
+ * @param request The http servlet request
+ *
+ * @return true if the request requires a switch,
+ * false 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 Authentication object from the current
+ * user's granted authorities. A successfully switched user should have a
+ * SwitchUserGrantedAuthority that contains the original
+ * source user Authentication object.
+ *
+ * @param current The current Authentication object
+ *
+ * @return The source user Authentication object or
+ * null 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
+ * GrantedAuthority that contains the original
+ * Authentication 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;
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilterTests.java
new file mode 100644
index 0000000000..11bc2b14e6
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilterTests.java
@@ -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);
+ }
+ }
+ }
+}
diff --git a/doc/xdocs/changes.xml b/doc/xdocs/changes.xml
index 20b4c8da63..3d8f8dae61 100644
--- a/doc/xdocs/changes.xml
+++ b/doc/xdocs/changes.xml
@@ -26,6 +26,7 @@