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: 464   Methods: 28
NCLOC: 219   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AbstractProcessingFilter.java 71.4% 87% 89.3% 84.2%
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;
 17   
 18    import java.io.IOException;
 19    import java.util.Properties;
 20   
 21    import javax.servlet.Filter;
 22    import javax.servlet.FilterChain;
 23    import javax.servlet.FilterConfig;
 24    import javax.servlet.ServletException;
 25    import javax.servlet.ServletRequest;
 26    import javax.servlet.ServletResponse;
 27    import javax.servlet.http.HttpServletRequest;
 28    import javax.servlet.http.HttpServletResponse;
 29   
 30    import org.acegisecurity.AcegiMessageSource;
 31    import org.acegisecurity.Authentication;
 32    import org.acegisecurity.AuthenticationException;
 33    import org.acegisecurity.AuthenticationManager;
 34    import org.acegisecurity.context.SecurityContextHolder;
 35    import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
 36    import org.acegisecurity.ui.rememberme.NullRememberMeServices;
 37    import org.acegisecurity.ui.rememberme.RememberMeServices;
 38    import org.apache.commons.logging.Log;
 39    import org.apache.commons.logging.LogFactory;
 40    import org.springframework.beans.factory.InitializingBean;
 41    import org.springframework.context.ApplicationEventPublisher;
 42    import org.springframework.context.ApplicationEventPublisherAware;
 43    import org.springframework.context.MessageSource;
 44    import org.springframework.context.MessageSourceAware;
 45    import org.springframework.context.support.MessageSourceAccessor;
 46    import org.springframework.util.Assert;
 47   
 48   
 49    /**
 50    * Abstract processor of browser-based HTTP-based authentication requests.
 51    *
 52    * <p>
 53    * This filter is responsible for processing authentication requests. If
 54    * authentication is successful, the resulting {@link Authentication} object
 55    * will be placed into the <code>SecurityContext</code>, which is guaranteed
 56    * to have already been created by an earlier filter.
 57    * </p>
 58    *
 59    * <p>
 60    * If authentication fails, the <code>AuthenticationException</code> will be
 61    * placed into the <code>HttpSession</code> with the attribute defined by
 62    * {@link #ACEGI_SECURITY_LAST_EXCEPTION_KEY}.
 63    * </p>
 64    *
 65    * <p>
 66    * To use this filter, it is necessary to specify the following properties:
 67    * </p>
 68    *
 69    * <ul>
 70    * <li>
 71    * <code>defaultTargetUrl</code> indicates the URL that should be used for
 72    * redirection if the <code>HttpSession</code> attribute named {@link
 73    * #ACEGI_SECURITY_TARGET_URL_KEY} does not indicate the target URL once
 74    * authentication is completed successfully. eg: <code>/</code>. This will be
 75    * treated as relative to the web-app's context path, and should include the
 76    * leading <code>/</code>.
 77    * </li>
 78    * <li>
 79    * <code>authenticationFailureUrl</code> indicates the URL that should be used
 80    * for redirection if the authentication request fails. eg:
 81    * <code>/login.jsp?login_error=1</code>.
 82    * </li>
 83    * <li>
 84    * <code>filterProcessesUrl</code> indicates the URL that this filter will
 85    * respond to. This parameter varies by subclass.
 86    * </li>
 87    * <li>
 88    * <code>alwaysUseDefaultTargetUrl</code> causes successful authentication to
 89    * always redirect to the <code>defaultTargetUrl</code>, even if the
 90    * <code>HttpSession</code> attribute named {@link
 91    * #ACEGI_SECURITY_TARGET_URL_KEY} defines the intended target URL.
 92    * </li>
 93    * </ul>
 94    *
 95    * <p>
 96    * To configure this filter to redirect to specific pages as the result of
 97    * specific {@link AuthenticationException}s you can do the following.
 98    * Configure the <code>exceptionMappings</code> property in your application
 99    * xml. This property is a java.util.Properties object that maps a
 100    * fully-qualified exception class name to a redirection url target.<br>
 101    * For example:<br>
 102    * <code> &lt;property name="exceptionMappings"&gt;<br>
 103    * &nbsp;&nbsp;&lt;props&gt;<br>
 104    * &nbsp;&nbsp;&nbsp;&nbsp;&lt;prop&gt; key="org.acegisecurity.BadCredentialsException"&gt;/bad_credentials.jsp&lt;/prop&gt;<br>
 105    * &nbsp;&nbsp;&lt;/props&gt;<br>
 106    * &lt;/property&gt;<br>
 107    * </code><br>
 108    * The example above would redirect all {@link
 109    * org.acegisecurity.BadCredentialsException}s thrown, to a page in the
 110    * web-application called /bad_credentials.jsp.
 111    * </p>
 112    *
 113    * <p>
 114    * Any {@link AuthenticationException} thrown that cannot be matched in the
 115    * <code>exceptionMappings</code> will be redirected to the
 116    * <code>authenticationFailureUrl</code>
 117    * </p>
 118    *
 119    * <p>
 120    * If authentication is successful, an {@link
 121    * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent}
 122    * will be published to the application context. No events will be published
 123    * if authentication was unsuccessful, because this would generally be
 124    * recorded via an <code>AuthenticationManager</code>-specific application
 125    * event.
 126    * </p>
 127    */
 128    public abstract class AbstractProcessingFilter implements Filter,
 129    InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {
 130    //~ Static fields/initializers =============================================
 131   
 132    public static final String ACEGI_SECURITY_TARGET_URL_KEY = "ACEGI_SECURITY_TARGET_URL";
 133    public static final String ACEGI_SECURITY_LAST_EXCEPTION_KEY = "ACEGI_SECURITY_LAST_EXCEPTION";
 134    protected static final Log logger = LogFactory.getLog(AbstractProcessingFilter.class);
 135   
 136    //~ Instance fields ========================================================
 137   
 138    private ApplicationEventPublisher eventPublisher;
 139    private AuthenticationManager authenticationManager;
 140    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
 141    private Properties exceptionMappings = new Properties();
 142    private RememberMeServices rememberMeServices = new NullRememberMeServices();
 143   
 144    /** Where to redirect the browser to if authentication fails */
 145    private String authenticationFailureUrl;
 146   
 147    /**
 148    * Where to redirect the browser to if authentication is successful but
 149    * ACEGI_SECURITY_TARGET_URL_KEY is <code>null</code>
 150    */
 151    private String defaultTargetUrl;
 152   
 153    /**
 154    * The URL destination that this filter intercepts and processes (usually
 155    * something like <code>/j_acegi_security_check</code>)
 156    */
 157    private String filterProcessesUrl = getDefaultFilterProcessesUrl();
 158   
 159    /**
 160    * If <code>true</code>, will always redirect to {@link #defaultTargetUrl}
 161    * upon successful authentication, irrespective of the page that caused
 162    * the authentication request (defaults to <code>false</code>).
 163    */
 164    private boolean alwaysUseDefaultTargetUrl = false;
 165   
 166    /**
 167    * Indicates if the filter chain should be continued prior to delegation to
 168    * {@link #successfulAuthentication(HttpServletRequest,
 169    * HttpServletResponse, Authentication)}, which may be useful in certain
 170    * environment (eg Tapestry). Defaults to <code>false</code>.
 171    */
 172    private boolean continueChainBeforeSuccessfulAuthentication = false;
 173   
 174    //~ Methods ================================================================
 175   
 176  5 public void afterPropertiesSet() throws Exception {
 177  5 Assert.hasLength(filterProcessesUrl,
 178    "filterProcessesUrl must be specified");
 179  4 Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
 180  3 Assert.hasLength(authenticationFailureUrl,
 181    "authenticationFailureUrl must be specified");
 182  2 Assert.notNull(authenticationManager,
 183    "authenticationManager must be specified");
 184  1 Assert.notNull(this.rememberMeServices);
 185    }
 186   
 187    /**
 188    * Performs actual authentication.
 189    *
 190    * @param request from which to extract parameters and perform the
 191    * authentication
 192    *
 193    * @return the authenticated user
 194    *
 195    * @throws AuthenticationException if authentication fails
 196    */
 197    public abstract Authentication attemptAuthentication(
 198    HttpServletRequest request) throws AuthenticationException;
 199   
 200    /**
 201    * Does nothing. We use IoC container lifecycle services instead.
 202    */
 203  9 public void destroy() {}
 204   
 205  11 public void doFilter(ServletRequest request, ServletResponse response,
 206    FilterChain chain) throws IOException, ServletException {
 207  11 if (!(request instanceof HttpServletRequest)) {
 208  1 throw new ServletException("Can only process HttpServletRequest");
 209    }
 210   
 211  10 if (!(response instanceof HttpServletResponse)) {
 212  1 throw new ServletException("Can only process HttpServletResponse");
 213    }
 214   
 215  9 HttpServletRequest httpRequest = (HttpServletRequest) request;
 216  9 HttpServletResponse httpResponse = (HttpServletResponse) response;
 217   
 218  9 if (requiresAuthentication(httpRequest, httpResponse)) {
 219  8 if (logger.isDebugEnabled()) {
 220  0 logger.debug("Request is to process authentication");
 221    }
 222   
 223  8 onPreAuthentication(httpRequest, httpResponse);
 224   
 225  8 Authentication authResult;
 226   
 227  8 try {
 228  8 authResult = attemptAuthentication(httpRequest);
 229    } catch (AuthenticationException failed) {
 230    // Authentication failed
 231  3 unsuccessfulAuthentication(httpRequest, httpResponse, failed);
 232   
 233  3 return;
 234    }
 235   
 236    // Authentication success
 237  5 if (continueChainBeforeSuccessfulAuthentication) {
 238  0 chain.doFilter(request, response);
 239    }
 240   
 241  5 successfulAuthentication(httpRequest, httpResponse, authResult);
 242   
 243  5 return;
 244    }
 245   
 246  1 chain.doFilter(request, response);
 247    }
 248   
 249  3 public String getAuthenticationFailureUrl() {
 250  3 return authenticationFailureUrl;
 251    }
 252   
 253  10 public AuthenticationManager getAuthenticationManager() {
 254  10 return authenticationManager;
 255    }
 256   
 257    /**
 258    * Specifies the default <code>filterProcessesUrl</code> for the
 259    * implementation.
 260    *
 261    * @return the default <code>filterProcessesUrl</code>
 262    */
 263    public abstract String getDefaultFilterProcessesUrl();
 264   
 265  2 public String getDefaultTargetUrl() {
 266  2 return defaultTargetUrl;
 267    }
 268   
 269  1 public Properties getExceptionMappings() {
 270  1 return new Properties(exceptionMappings);
 271    }
 272   
 273  2 public String getFilterProcessesUrl() {
 274  2 return filterProcessesUrl;
 275    }
 276   
 277  2 public RememberMeServices getRememberMeServices() {
 278  2 return rememberMeServices;
 279    }
 280   
 281    /**
 282    * Does nothing. We use IoC container lifecycle services instead.
 283    *
 284    * @param arg0 ignored
 285    *
 286    * @throws ServletException ignored
 287    */
 288  0 public void init(FilterConfig arg0) throws ServletException {}
 289   
 290  3 public boolean isAlwaysUseDefaultTargetUrl() {
 291  3 return alwaysUseDefaultTargetUrl;
 292    }
 293   
 294  1 public boolean isContinueChainBeforeSuccessfulAuthentication() {
 295  1 return continueChainBeforeSuccessfulAuthentication;
 296    }
 297   
 298  8 protected void onPreAuthentication(HttpServletRequest request,
 299    HttpServletResponse response) throws IOException {}
 300   
 301  5 protected void onSuccessfulAuthentication(HttpServletRequest request,
 302    HttpServletResponse response, Authentication authResult)
 303    throws IOException {}
 304   
 305  3 protected void onUnsuccessfulAuthentication(HttpServletRequest request,
 306    HttpServletResponse response) throws IOException {}
 307   
 308    /**
 309    * <p>
 310    * Indicates whether this filter should attempt to process a login request
 311    * for the current invocation.
 312    * </p>
 313    *
 314    * <p>
 315    * It strips any parameters from the "path" section of the request URL
 316    * (such as the jsessionid parameter in
 317    * <em>http://host/myapp/index.html;jsessionid=blah</em>) before matching
 318    * against the <code>filterProcessesUrl</code> property.
 319    * </p>
 320    *
 321    * <p>
 322    * Subclasses may override for special requirements, such as Tapestry
 323    * integration.
 324    * </p>
 325    *
 326    * @param request as received from the filter chain
 327    * @param response as received from the filter chain
 328    *
 329    * @return <code>true</code> if the filter should attempt authentication,
 330    * <code>false</code> otherwise
 331    */
 332  10 protected boolean requiresAuthentication(HttpServletRequest request,
 333    HttpServletResponse response) {
 334  10 String uri = request.getRequestURI();
 335  10 int pathParamIndex = uri.indexOf(';');
 336   
 337  10 if (pathParamIndex > 0) {
 338    // strip everything after the first semi-colon
 339  1 uri = uri.substring(0, pathParamIndex);
 340    }
 341   
 342  10 return uri.endsWith(request.getContextPath() + filterProcessesUrl);
 343    }
 344   
 345  2 public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
 346  2 this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
 347    }
 348   
 349  0 public void setApplicationEventPublisher(
 350    ApplicationEventPublisher eventPublisher) {
 351  0 this.eventPublisher = eventPublisher;
 352    }
 353   
 354  10 public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
 355  10 this.authenticationFailureUrl = authenticationFailureUrl;
 356    }
 357   
 358  14 public void setAuthenticationManager(
 359    AuthenticationManager authenticationManager) {
 360  14 this.authenticationManager = authenticationManager;
 361    }
 362   
 363  1 public void setContinueChainBeforeSuccessfulAuthentication(
 364    boolean continueChainBeforeSuccessfulAuthentication) {
 365  1 this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
 366    }
 367   
 368  9 public void setDefaultTargetUrl(String defaultTargetUrl) {
 369  9 this.defaultTargetUrl = defaultTargetUrl;
 370    }
 371   
 372  1 public void setExceptionMappings(Properties exceptionMappings) {
 373  1 this.exceptionMappings = exceptionMappings;
 374    }
 375   
 376  13 public void setFilterProcessesUrl(String filterProcessesUrl) {
 377  13 this.filterProcessesUrl = filterProcessesUrl;
 378    }
 379   
 380  0 public void setMessageSource(MessageSource messageSource) {
 381  0 this.messages = new MessageSourceAccessor(messageSource);
 382    }
 383   
 384  1 public void setRememberMeServices(RememberMeServices rememberMeServices) {
 385  1 this.rememberMeServices = rememberMeServices;
 386    }
 387   
 388  5 protected void successfulAuthentication(HttpServletRequest request,
 389    HttpServletResponse response, Authentication authResult)
 390    throws IOException {
 391  5 if (logger.isDebugEnabled()) {
 392  0 logger.debug("Authentication success: " + authResult.toString());
 393    }
 394   
 395  5 SecurityContextHolder.getContext().setAuthentication(authResult);
 396   
 397  5 if (logger.isDebugEnabled()) {
 398  0 logger.debug(
 399    "Updated SecurityContextHolder to contain the following Authentication: '"
 400    + authResult + "'");
 401    }
 402   
 403  5 String targetUrl = (String) request.getSession()
 404    .getAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
 405  5 request.getSession().removeAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
 406   
 407  5 if (alwaysUseDefaultTargetUrl == true) {
 408  1 targetUrl = null;
 409    }
 410   
 411  5 if (targetUrl == null) {
 412  4 targetUrl = request.getContextPath() + defaultTargetUrl;
 413    }
 414   
 415  5 if (logger.isDebugEnabled()) {
 416  0 logger.debug(
 417    "Redirecting to target URL from HTTP Session (or default): "
 418    + targetUrl);
 419    }
 420   
 421  5 onSuccessfulAuthentication(request, response, authResult);
 422   
 423  5 rememberMeServices.loginSuccess(request, response, authResult);
 424   
 425    // Fire event
 426  5 if (this.eventPublisher != null) {
 427  0 eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
 428    authResult, this.getClass()));
 429    }
 430   
 431  5 response.sendRedirect(response.encodeRedirectURL(targetUrl));
 432    }
 433   
 434  3 protected void unsuccessfulAuthentication(HttpServletRequest request,
 435    HttpServletResponse response, AuthenticationException failed)
 436    throws IOException {
 437  3 SecurityContextHolder.getContext().setAuthentication(null);
 438   
 439  3 if (logger.isDebugEnabled()) {
 440  0 logger.debug(
 441    "Updated SecurityContextHolder to contain null Authentication");
 442    }
 443   
 444  3 String failureUrl = exceptionMappings.getProperty(failed.getClass()
 445    .getName(),
 446    authenticationFailureUrl);
 447   
 448  3 if (logger.isDebugEnabled()) {
 449  0 logger.debug("Authentication request failed: " + failed.toString());
 450    }
 451   
 452  3 try {
 453  3 request.getSession()
 454    .setAttribute(ACEGI_SECURITY_LAST_EXCEPTION_KEY, failed);
 455    } catch (Exception ignored) {}
 456   
 457  3 onUnsuccessfulAuthentication(request, response);
 458   
 459  3 rememberMeServices.loginFail(request, response);
 460   
 461  3 response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
 462    + failureUrl));
 463    }
 464    }