Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 553   Methods: 17
NCLOC: 255   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
SwitchUserProcessingFilter.java 72.2% 84.4% 76.5% 80.4%
coverage coverage
 1    /* Copyright 2004, 2005 Acegi Technology Pty Limited
 2    *
 3    * Licensed under the Apache License, Version 2.0 (the "License");
 4    * you may not use this file except in compliance with the License.
 5    * You may obtain a copy of the License at
 6    *
 7    * http://www.apache.org/licenses/LICENSE-2.0
 8    *
 9    * Unless required by applicable law or agreed to in writing, software
 10    * distributed under the License is distributed on an "AS IS" BASIS,
 11    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    * See the License for the specific language governing permissions and
 13    * limitations under the License.
 14    */
 15   
 16    package org.acegisecurity.ui.switchuser;
 17   
 18    import java.io.IOException;
 19    import java.util.ArrayList;
 20    import java.util.Arrays;
 21    import java.util.List;
 22   
 23    import javax.servlet.Filter;
 24    import javax.servlet.FilterChain;
 25    import javax.servlet.FilterConfig;
 26    import javax.servlet.ServletException;
 27    import javax.servlet.ServletRequest;
 28    import javax.servlet.ServletResponse;
 29    import javax.servlet.http.HttpServletRequest;
 30    import javax.servlet.http.HttpServletResponse;
 31   
 32    import org.acegisecurity.AccountExpiredException;
 33    import org.acegisecurity.AcegiMessageSource;
 34    import org.acegisecurity.Authentication;
 35    import org.acegisecurity.AuthenticationCredentialsNotFoundException;
 36    import org.acegisecurity.AuthenticationException;
 37    import org.acegisecurity.CredentialsExpiredException;
 38    import org.acegisecurity.DisabledException;
 39    import org.acegisecurity.GrantedAuthority;
 40    import org.acegisecurity.LockedException;
 41    import org.acegisecurity.context.SecurityContextHolder;
 42    import org.acegisecurity.event.authentication.AuthenticationSwitchUserEvent;
 43    import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 44    import org.acegisecurity.ui.WebAuthenticationDetails;
 45    import org.acegisecurity.userdetails.UserDetails;
 46    import org.acegisecurity.userdetails.UserDetailsService;
 47    import org.acegisecurity.userdetails.UsernameNotFoundException;
 48    import org.apache.commons.logging.Log;
 49    import org.apache.commons.logging.LogFactory;
 50    import org.springframework.beans.BeansException;
 51    import org.springframework.beans.factory.InitializingBean;
 52    import org.springframework.context.ApplicationEventPublisher;
 53    import org.springframework.context.ApplicationEventPublisherAware;
 54    import org.springframework.context.MessageSource;
 55    import org.springframework.context.MessageSourceAware;
 56    import org.springframework.context.support.MessageSourceAccessor;
 57    import org.springframework.util.Assert;
 58   
 59   
 60    /**
 61    * Switch User processing filter responsible for user context switching.
 62    *
 63    * <p>
 64    * This filter is similar to Unix 'su' however for Acegi-managed web
 65    * applications. A common use-case for this feature is the ability to allow
 66    * higher-authority users (i.e. ROLE_ADMIN) to switch to a regular user (i.e.
 67    * ROLE_USER).
 68    * </p>
 69    *
 70    * <p>
 71    * This filter assumes that the user performing the switch will be required to
 72    * be logged in as normal (i.e. ROLE_ADMIN user). The user will then access a
 73    * page/controller that enables the administrator to specify who they wish to
 74    * become (see <code>switchUserUrl</code>). <br>
 75    * <b>Note: This URL will be required to have to appropriate security
 76    * contraints configured so that only users of that role can access (i.e.
 77    * ROLE_ADMIN).</b>
 78    * </p>
 79    *
 80    * <p>
 81    * On successful switch, the user's <code>SecurityContextHolder</code> will be
 82    * updated to reflect the specified user and will also contain an additinal
 83    * {@link org.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority } which
 84    * contains the original user.
 85    * </p>
 86    *
 87    * <p>
 88    * To 'exit' from a user context, the user will then need to access a URL (see
 89    * <code>exitUserUrl</code>) that will switch back to the original user as
 90    * identified by the <code>SWITCH_USER_GRANTED_AUTHORITY</code>.
 91    * </p>
 92    *
 93    * <p>
 94    * To configure the Switch User Processing Filter, create a bean definition for
 95    * the Switch User processing filter and add to the filterChainProxy. <br>
 96    * Example:
 97    * <pre>
 98    * &lt;bean id="switchUserProcessingFilter" class="org.acegisecurity.ui.switchuser.SwitchUserProcessingFilter">
 99    * &lt;property name="authenticationDao" ref="jdbcDaoImpl" />
 100    * &lt;property name="switchUserUrl">&lt;value>/j_acegi_switch_user&lt;/value>&lt;/property>
 101    * &lt;property name="exitUserUrl">&lt;value>/j_acegi_exit_user&lt;/value>&lt;/property>
 102    * &lt;property name="targetUrl">&lt;value>/index.jsp&lt;/value>&lt;/property>
 103    * &lt;/bean>
 104    * </pre>
 105    * </p>
 106    *
 107    * @see org.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority
 108    */
 109    public class SwitchUserProcessingFilter implements Filter, InitializingBean,
 110    ApplicationEventPublisherAware, MessageSourceAware {
 111    //~ Static fields/initializers =============================================
 112   
 113    private static final Log logger = LogFactory.getLog(SwitchUserProcessingFilter.class);
 114   
 115    // ~ Static fields/initializers
 116    // =============================================
 117    public static final String ACEGI_SECURITY_SWITCH_USERNAME_KEY = "j_username";
 118    public static final String ROLE_PREVIOUS_ADMINISTRATOR = "ROLE_PREVIOUS_ADMINISTRATOR";
 119   
 120    //~ Instance fields ========================================================
 121   
 122    private ApplicationEventPublisher eventPublisher;
 123   
 124    // ~ Instance fields
 125    // ========================================================
 126    private UserDetailsService userDetailsService;
 127    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
 128    private String exitUserUrl = "/j_acegi_exit_user";
 129    private String switchUserUrl = "/j_acegi_switch_user";
 130    private String targetUrl;
 131   
 132    //~ Methods ================================================================
 133   
 134  2 public void afterPropertiesSet() throws Exception {
 135  2 Assert.hasLength(switchUserUrl, "switchUserUrl must be specified");
 136  2 Assert.hasLength(exitUserUrl, "exitUserUrl must be specified");
 137  2 Assert.hasLength(targetUrl, "targetUrl must be specified");
 138  1 Assert.notNull(userDetailsService, "authenticationDao must be specified");
 139  0 Assert.notNull(messages, "A message source must be set");
 140    }
 141   
 142    /**
 143    * Attempt to exit from an already switched user.
 144    *
 145    * @param request The http servlet request
 146    *
 147    * @return The original <code>Authentication</code> object or
 148    * <code>null</code> otherwise.
 149    *
 150    * @throws AuthenticationCredentialsNotFoundException If no
 151    * <code>Authentication</code> associated with this request.
 152    */
 153  2 protected Authentication attemptExitUser(HttpServletRequest request)
 154    throws AuthenticationCredentialsNotFoundException {
 155    // need to check to see if the current user has a SwitchUserGrantedAuthority
 156  2 Authentication current = SecurityContextHolder.getContext()
 157    .getAuthentication();
 158   
 159  2 if (null == current) {
 160  1 throw new AuthenticationCredentialsNotFoundException(messages
 161    .getMessage("SwitchUserProcessingFilter.noCurrentUser",
 162    "No current user associated with this request"));
 163    }
 164   
 165    // check to see if the current user did actual switch to another user
 166    // if so, get the original source user so we can switch back
 167  1 Authentication original = getSourceAuthentication(current);
 168   
 169  1 if (original == null) {
 170  0 logger.error(
 171    "Could not find original user Authentication object!");
 172  0 throw new AuthenticationCredentialsNotFoundException(messages
 173    .getMessage(
 174    "SwitchUserProcessingFilter.noOriginalAuthentication",
 175    "Could not find original Authentication object"));
 176    }
 177   
 178    // get the source user details
 179  1 UserDetails originalUser = null;
 180  1 Object obj = original.getPrincipal();
 181   
 182  1 if ((obj != null) && obj instanceof UserDetails) {
 183  0 originalUser = (UserDetails) obj;
 184    }
 185   
 186    // publish event
 187  1 if (this.eventPublisher != null) {
 188  0 eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
 189    current, originalUser));
 190    }
 191   
 192  1 return original;
 193    }
 194   
 195    /**
 196    * Attempt to switch to another user. If the user does not exist or
 197    * is not active, return null.
 198    *
 199    * @param request The http request
 200    *
 201    * @return The new <code>Authentication</code> request if
 202    * successfully switched to another user,
 203    * <code>null</code> otherwise.
 204    *
 205    * @throws AuthenticationException
 206    * @throws UsernameNotFoundException If the target user is not
 207    * found.
 208    * @throws LockedException DOCUMENT ME!
 209    * @throws DisabledException If the target user is disabled.
 210    * @throws AccountExpiredException If the target user account is
 211    * expired.
 212    * @throws CredentialsExpiredException If the target user
 213    * credentials are expired.
 214    */
 215  7 protected Authentication attemptSwitchUser(
 216    HttpServletRequest request) throws AuthenticationException {
 217  7 UsernamePasswordAuthenticationToken targetUserRequest = null;
 218   
 219  7 String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY);
 220   
 221  7 if (username == null) {
 222  0 username = "";
 223    }
 224   
 225  7 if (logger.isDebugEnabled()) {
 226  0 logger.debug("Attempt to switch to user [" + username + "]");
 227    }
 228   
 229    // load the user by name
 230  7 UserDetails targetUser = this.userDetailsService
 231    .loadUserByUsername(username);
 232   
 233    // user not found
 234  6 if (targetUser == null) {
 235  0 throw new UsernameNotFoundException(messages.getMessage(
 236    "SwitchUserProcessingFilter.usernameNotFound",
 237    new Object[] {username},
 238    "Username {0} not found"));
 239    }
 240   
 241    // account is expired
 242  6 if (!targetUser.isAccountNonLocked()) {
 243  0 throw new LockedException(messages.getMessage(
 244    "SwitchUserProcessingFilter.locked",
 245    "User account is locked"));
 246    }
 247   
 248    // user is disabled
 249  6 if (!targetUser.isEnabled()) {
 250  1 throw new DisabledException(messages.getMessage(
 251    "SwitchUserProcessingFilter.disabled",
 252    "User is disabled"));
 253    }
 254   
 255    // account is expired
 256  5 if (!targetUser.isAccountNonExpired()) {
 257  1 throw new AccountExpiredException(messages.getMessage(
 258    "SwitchUserProcessingFilter.expired",
 259    "User account has expired"));
 260    }
 261   
 262    // credentials expired
 263  4 if (!targetUser.isCredentialsNonExpired()) {
 264  1 throw new CredentialsExpiredException(messages
 265    .getMessage(
 266    "SwitchUserProcessingFilter.credentialsExpired",
 267    "User credentials have expired"));
 268    }
 269   
 270    // ok, create the switch user token
 271  3 targetUserRequest = createSwitchUserToken(request,
 272    username, targetUser);
 273   
 274  3 if (logger.isDebugEnabled()) {
 275  0 logger.debug("Switch User Token ["
 276    + targetUserRequest + "]");
 277    }
 278   
 279    // publish event
 280  3 if (this.eventPublisher != null) {
 281  0 eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
 282    SecurityContextHolder.getContext()
 283    .getAuthentication(),
 284    targetUser));
 285    }
 286   
 287  3 return targetUserRequest;
 288    }
 289   
 290    /**
 291    * Create a switch user token that contains an additional
 292    * <tt>GrantedAuthority</tt> that contains the original
 293    * <code>Authentication</code> object.
 294    *
 295    * @param request The http servlet request.
 296    * @param username The username of target user
 297    * @param targetUser The target user
 298    *
 299    * @return The authentication token
 300    *
 301    * @see SwitchUserGrantedAuthority
 302    */
 303  3 private UsernamePasswordAuthenticationToken createSwitchUserToken(
 304    HttpServletRequest request, String username,
 305    UserDetails targetUser) {
 306  3 UsernamePasswordAuthenticationToken targetUserRequest;
 307   
 308    // grant an additional authority that contains the original Authentication object
 309    // which will be used to 'exit' from the current switched user.
 310  3 Authentication currentAuth = SecurityContextHolder.getContext()
 311    .getAuthentication();
 312  3 GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(ROLE_PREVIOUS_ADMINISTRATOR,
 313    currentAuth);
 314   
 315    // get the original authorities
 316  3 List orig = Arrays.asList(targetUser.getAuthorities());
 317   
 318    // add the new switch user authority
 319  3 List newAuths = new ArrayList(orig);
 320  3 newAuths.add(switchAuthority);
 321   
 322  3 GrantedAuthority[] authorities = {};
 323  3 authorities = (GrantedAuthority[]) newAuths.toArray(authorities);
 324   
 325    // create the new authentication token
 326  3 targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
 327    targetUser.getPassword(), authorities);
 328   
 329    // set details
 330  3 targetUserRequest.setDetails(new WebAuthenticationDetails(
 331    request));
 332   
 333  3 return targetUserRequest;
 334    }
 335   
 336  0 public void destroy() {}
 337   
 338    /**
 339    * @see javax.servlet.Filter#doFilter
 340    */
 341  4 public void doFilter(ServletRequest request,
 342    ServletResponse response, FilterChain chain)
 343    throws IOException, ServletException {
 344  4 Assert.isInstanceOf(HttpServletRequest.class, request);
 345  4 Assert.isInstanceOf(HttpServletResponse.class, response);
 346   
 347  4 HttpServletRequest httpRequest = (HttpServletRequest) request;
 348  4 HttpServletResponse httpResponse = (HttpServletResponse) response;
 349   
 350    // check for switch or exit request
 351  4 if (requiresSwitchUser(httpRequest)) {
 352    // if set, attempt switch and store original
 353  2 Authentication targetUser = attemptSwitchUser(httpRequest);
 354   
 355    // update the current context to the new target user
 356  2 SecurityContextHolder.getContext()
 357    .setAuthentication(targetUser);
 358   
 359    // redirect to target url
 360  2 httpResponse.sendRedirect(httpResponse
 361    .encodeRedirectURL(httpRequest
 362    .getContextPath() + targetUrl));
 363   
 364  2 return;
 365  2 } else if (requiresExitUser(httpRequest)) {
 366    // get the original authentication object (if exists)
 367  2 Authentication originalUser = attemptExitUser(httpRequest);
 368   
 369    // update the current context back to the original user
 370  1 SecurityContextHolder.getContext()
 371    .setAuthentication(originalUser);
 372   
 373    // redirect to target url
 374  1 httpResponse.sendRedirect(httpResponse
 375    .encodeRedirectURL(httpRequest
 376    .getContextPath()
 377    + targetUrl));
 378   
 379  1 return;
 380    }
 381   
 382  0 chain.doFilter(request, response);
 383    }
 384   
 385    /**
 386    * Find the original
 387    * <code>Authentication</code> object from
 388    * the current user's granted authorities.
 389    * A successfully switched user should
 390    * have a
 391    * <code>SwitchUserGrantedAuthority</code>
 392    * that contains the original source user
 393    * <code>Authentication</code> object.
 394    *
 395    * @param current The current
 396    * <code>Authentication</code>
 397    * object
 398    *
 399    * @return The source user
 400    * <code>Authentication</code>
 401    * object or <code>null</code>
 402    * otherwise.
 403    */
 404  1 private Authentication getSourceAuthentication(
 405    Authentication current) {
 406  1 Authentication original = null;
 407   
 408    // iterate over granted authorities and find the 'switch user' authority
 409  1 GrantedAuthority[] authorities = current
 410    .getAuthorities();
 411   
 412  1 for (int i = 0; i < authorities.length;
 413    i++) {
 414    // check for switch user type of authority
 415  3 if (authorities[i] instanceof SwitchUserGrantedAuthority) {
 416  1 original = ((SwitchUserGrantedAuthority) authorities[i])
 417    .getSource();
 418  1 logger.debug(
 419    "Found original switch user granted authority ["
 420    + original + "]");
 421    }
 422    }
 423   
 424  1 return original;
 425    }
 426   
 427  0 public void init(FilterConfig ignored)
 428    throws ServletException {}
 429   
 430    /**
 431    * Checks the request URI for the presence
 432    * of <tt>exitUserUrl</tt>.
 433    *
 434    * @param request The http servlet request
 435    *
 436    * @return <code>true</code> if the request
 437    * requires a exit user,
 438    * <code>false</code> otherwise.
 439    *
 440    * @see SwitchUserProcessingFilter#exitUserUrl
 441    */
 442  3 protected boolean requiresExitUser(
 443    HttpServletRequest request) {
 444  3 String uri = stripUri(request);
 445   
 446  3 return uri.endsWith(request
 447    .getContextPath() + exitUserUrl);
 448    }
 449   
 450    /**
 451    * Checks the request URI for the
 452    * presence of <tt>switchUserUrl</tt>.
 453    *
 454    * @param request The http servlet
 455    * request
 456    *
 457    * @return <code>true</code> if the
 458    * request requires a switch,
 459    * <code>false</code>
 460    * otherwise.
 461    *
 462    * @see SwitchUserProcessingFilter#switchUserUrl
 463    */
 464  6 protected boolean requiresSwitchUser(
 465    HttpServletRequest request) {
 466  6 String uri = stripUri(request);
 467   
 468  6 return uri.endsWith(request
 469    .getContextPath()
 470    + switchUserUrl);
 471    }
 472   
 473  0 public void setApplicationEventPublisher(
 474    ApplicationEventPublisher eventPublisher)
 475    throws BeansException {
 476  0 this.eventPublisher = eventPublisher;
 477    }
 478   
 479    /**
 480    * Sets the authentication data
 481    * access object.
 482    *
 483    * @param authenticationDao The
 484    * authentication dao
 485    */
 486  10 public void setUserDetailsService(
 487    UserDetailsService authenticationDao) {
 488  10 this.userDetailsService = authenticationDao;
 489    }
 490   
 491    /**
 492    * Set the URL to respond to exit
 493    * user processing.
 494    *
 495    * @param exitUserUrl The exit user
 496    * URL.
 497    */
 498  5 public void setExitUserUrl(
 499    String exitUserUrl) {
 500  5 this.exitUserUrl = exitUserUrl;
 501    }
 502   
 503  0 public void setMessageSource(
 504    MessageSource messageSource) {
 505  0 this.messages = new MessageSourceAccessor(messageSource);
 506    }
 507   
 508    /**
 509    * Set the URL to respond to switch
 510    * user processing.
 511    *
 512    * @param switchUserUrl The switch
 513    * user URL.
 514    */
 515  6 public void setSwitchUserUrl(
 516    String switchUserUrl) {
 517  6 this.switchUserUrl = switchUserUrl;
 518    }
 519   
 520    /**
 521    * Sets the URL to go to after a
 522    * successful switch / exit user
 523    * request.
 524    *
 525    * @param targetUrl The target url.
 526    */
 527  2 public void setTargetUrl(
 528    String targetUrl) {
 529  2 this.targetUrl = targetUrl;
 530    }
 531   
 532    /**
 533    * Strips any content after the ';'
 534    * in the request URI
 535    *
 536    * @param request The http request
 537    *
 538    * @return The stripped uri
 539    */
 540  9 private static String stripUri(
 541    HttpServletRequest request) {
 542  9 String uri = request
 543    .getRequestURI();
 544  9 int idx = uri.indexOf(';');
 545   
 546  9 if (idx > 0) {
 547  1 uri = uri.substring(0,
 548    idx);
 549    }
 550   
 551  9 return uri;
 552    }
 553    }