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: 310   Methods: 9
NCLOC: 163   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
HttpSessionContextIntegrationFilter.java 57.1% 70.8% 77.8% 66.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.context;
 17   
 18    import org.apache.commons.logging.Log;
 19    import org.apache.commons.logging.LogFactory;
 20   
 21    import org.springframework.beans.factory.InitializingBean;
 22   
 23    import java.io.IOException;
 24   
 25    import javax.servlet.Filter;
 26    import javax.servlet.FilterChain;
 27    import javax.servlet.FilterConfig;
 28    import javax.servlet.ServletException;
 29    import javax.servlet.ServletRequest;
 30    import javax.servlet.ServletResponse;
 31    import javax.servlet.http.HttpServletRequest;
 32    import javax.servlet.http.HttpSession;
 33   
 34   
 35    /**
 36    * <p>
 37    * Populates the {@link SecurityContextHolder}</code> with information obtained
 38    * from the <code>HttpSession</code>.
 39    * </p>
 40    *
 41    * <p>
 42    * The <code>HttpSession</code> will be queried to retrieve the
 43    * <code>SecurityContext</code> that should be stored against the
 44    * <code>SecurityContextHolder</code> for the duration of the web request. At
 45    * the end of the web request, any updates made to the
 46    * <code>SecurityContextHolder</code> will be persisted back to the
 47    * <code>HttpSession</code> by this filter.
 48    * </p>
 49    *
 50    * <p>
 51    * If a valid <code>SecurityContext</code> cannot be obtained from the
 52    * <code>HttpSession</code> for whatever reason, a fresh
 53    * <code>SecurityContext</code> will be created and used instead. The created
 54    * object will be of the instance defined by the {@link #setContext(Class)}
 55    * method (which defaults to {@link
 56    * org.acegisecurity.context.SecurityContextImpl}.
 57    * </p>
 58    *
 59    * <p>
 60    * No <code>HttpSession</code> will be created by this filter if one does not
 61    * already exist. If at the end of the web request the
 62    * <code>HttpSession</code> does not exist, a <code>HttpSession</code> will
 63    * <b>only</b> be created if the current contents of the
 64    * <code>SecurityContextHolder</code> are not {@link
 65    * java.lang.Object#equals(java.lang.Object)} to a <code>new</code> instance
 66    * of {@link #setContext(Class)}. This avoids needless
 67    * <code>HttpSession</code> creation, but automates the storage of changes
 68    * made to the <code>SecurityContextHolder</code>.
 69    * </p>
 70    *
 71    * <p>
 72    * This filter will only execute once per request, to resolve servlet container
 73    * (specifically Weblogic) incompatibilities.
 74    * </p>
 75    *
 76    * <p>
 77    * If for whatever reason no <code>HttpSession</code> should <b>ever</b> be
 78    * created (eg this filter is only being used with Basic authentication or
 79    * similar clients that will never present the same <code>jsessionid</code>
 80    * etc), the {@link #setAllowSessionCreation(boolean)} should be set to
 81    * <code>false</code>. Only do this if you really need to conserve server
 82    * memory and ensure all classes using the <code>SecurityContextHolder</code> are
 83    * designed to have no persistence of the <code>SecurityContext</code> between web
 84    * requests.
 85    * </p>
 86    *
 87    * <p>
 88    * This filter MUST be executed BEFORE any authentication procesing mechanisms.
 89    * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
 90    * expect the <code>SecurityContextHolder</code> to contain a valid
 91    * <code>SecurityContext</code> by the time they execute.
 92    * </p>
 93    *
 94    * @author Ben Alex
 95    * @author Patrick Burleson
 96    * @version $Id: HttpSessionContextIntegrationFilter.java,v 1.12 2005/11/17 00:55:49 benalex Exp $
 97    */
 98    public class HttpSessionContextIntegrationFilter implements InitializingBean,
 99    Filter {
 100    //~ Static fields/initializers =============================================
 101   
 102    protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class);
 103    private static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied";
 104    public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";
 105   
 106    //~ Instance fields ========================================================
 107   
 108    private Class context = SecurityContextImpl.class;
 109    private Object contextObject;
 110   
 111    /**
 112    * Indicates if this filter can create a <code>HttpSession</code> if needed
 113    * (sessions are always created sparingly, but setting this value to false
 114    * will prohibit sessions from ever being created). Defaults to true.
 115    */
 116    private boolean allowSessionCreation = true;
 117   
 118    //~ Methods ================================================================
 119   
 120  0 public void setAllowSessionCreation(boolean allowSessionCreation) {
 121  0 this.allowSessionCreation = allowSessionCreation;
 122    }
 123   
 124  0 public boolean isAllowSessionCreation() {
 125  0 return allowSessionCreation;
 126    }
 127   
 128  7 public void setContext(Class secureContext) {
 129  7 this.context = secureContext;
 130    }
 131   
 132  1 public Class getContext() {
 133  1 return context;
 134    }
 135   
 136  7 public void afterPropertiesSet() throws Exception {
 137  7 if ((this.context == null)
 138    || (!SecurityContext.class.isAssignableFrom(this.context))) {
 139  2 throw new IllegalArgumentException(
 140    "context must be defined and implement SecurityContext (typically use org.acegisecurity.context.SecurityContextImpl; existing class is "
 141    + this.context + ")");
 142    }
 143   
 144  5 this.contextObject = generateNewContext();
 145    }
 146   
 147    /**
 148    * Does nothing. We use IoC container lifecycle services instead.
 149    */
 150  4 public void destroy() {}
 151   
 152  5 public void doFilter(ServletRequest request, ServletResponse response,
 153    FilterChain chain) throws IOException, ServletException {
 154  5 if ((request != null) && (request.getAttribute(FILTER_APPLIED) != null)) {
 155    // ensure that filter is only applied once per request
 156  0 chain.doFilter(request, response);
 157    } else {
 158  5 if (request != null) {
 159  5 request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
 160    }
 161   
 162  5 HttpSession httpSession = null;
 163  5 boolean httpSessionExistedAtStartOfRequest = false;
 164   
 165  5 try {
 166  5 httpSession = ((HttpServletRequest) request).getSession(false);
 167    } catch (IllegalStateException ignored) {}
 168   
 169  5 if (httpSession != null) {
 170  3 httpSessionExistedAtStartOfRequest = true;
 171   
 172  3 Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);
 173   
 174  3 if (contextFromSessionObject != null) {
 175  3 if (contextFromSessionObject instanceof SecurityContext) {
 176  2 if (logger.isDebugEnabled()) {
 177  0 logger.debug(
 178    "Obtained from ACEGI_SECURITY_CONTEXT a valid SecurityContext and set to SecurityContextHolder: '"
 179    + contextFromSessionObject + "'");
 180    }
 181   
 182  2 SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject);
 183    } else {
 184  1 if (logger.isWarnEnabled()) {
 185  1 logger.warn(
 186    "ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
 187    + contextFromSessionObject
 188    + "'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class? - new SecurityContext instance associated with SecurityContextHolder");
 189    }
 190   
 191  1 SecurityContextHolder.setContext(generateNewContext());
 192    }
 193    } else {
 194  0 if (logger.isDebugEnabled()) {
 195  0 logger.debug(
 196    "HttpSession returned null object for ACEGI_SECURITY_CONTEXT - new SecurityContext instance associated with SecurityContextHolder");
 197    }
 198   
 199  0 SecurityContextHolder.setContext(generateNewContext());
 200    }
 201    } else {
 202  2 if (logger.isDebugEnabled()) {
 203  0 logger.debug(
 204    "No HttpSession currently exists - new SecurityContext instance associated with SecurityContextHolder");
 205    }
 206   
 207  2 SecurityContextHolder.setContext(generateNewContext());
 208    }
 209   
 210    // Make the HttpSession null, as we want to ensure we don't keep
 211    // a reference to the HttpSession laying around in case the
 212    // chain.doFilter() invalidates it.
 213  5 httpSession = null;
 214   
 215    // Proceed with chain
 216  5 int contextWhenChainProceeded = SecurityContextHolder.getContext()
 217    .hashCode();
 218   
 219  5 try {
 220  5 chain.doFilter(request, response);
 221    } catch (IOException ioe) {
 222  1 throw ioe;
 223    } catch (ServletException se) {
 224  0 throw se;
 225    } finally {
 226    // do clean up, even if there was an exception
 227    // Store context back to HttpSession
 228  5 try {
 229  5 httpSession = ((HttpServletRequest) request).getSession(false);
 230    } catch (IllegalStateException ignored) {}
 231   
 232  5 if ((httpSession == null) && httpSessionExistedAtStartOfRequest) {
 233  0 if (logger.isDebugEnabled()) {
 234  0 logger.debug(
 235    "HttpSession is now null, but was not null at start of request; session was invalidated, so do not create a new session");
 236    }
 237    }
 238   
 239    // Generate a HttpSession only if we need to
 240  5 if ((httpSession == null)
 241    && !httpSessionExistedAtStartOfRequest) {
 242  2 if (!allowSessionCreation) {
 243  0 if (logger.isDebugEnabled()) {
 244  0 logger.debug(
 245    "The HttpSession is currently null, and the HttpSessionContextIntegrationFilter is prohibited from creating a HttpSession (because the allowSessionCreation property is false) - SecurityContext thus not stored for next request");
 246    }
 247  2 } else if (!contextObject.equals(
 248    SecurityContextHolder.getContext())) {
 249  1 if (logger.isDebugEnabled()) {
 250  0 logger.debug(
 251    "HttpSession being created as SecurityContextHolder contents are non-default");
 252    }
 253   
 254  1 try {
 255  1 httpSession = ((HttpServletRequest) request)
 256    .getSession(true);
 257    } catch (IllegalStateException ignored) {}
 258    } else {
 259  1 if (logger.isDebugEnabled()) {
 260  0 logger.debug(
 261    "HttpSession is null, but SecurityContextHolder has not changed from default: ' "
 262    + SecurityContextHolder.getContext()
 263    + "'; not creating HttpSession or storing SecurityContextHolder contents");
 264    }
 265    }
 266    }
 267   
 268    // If HttpSession exists, store current SecurityContextHolder contents
 269    // but only if SecurityContext has actually changed (see JIRA SEC-37)
 270  5 if ((httpSession != null)
 271    && (SecurityContextHolder.getContext().hashCode() != contextWhenChainProceeded)) {
 272  3 httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY,
 273    SecurityContextHolder.getContext());
 274   
 275  3 if (logger.isDebugEnabled()) {
 276  0 logger.debug("SecurityContext stored to HttpSession: '"
 277    + SecurityContextHolder.getContext() + "'");
 278    }
 279    }
 280   
 281    // Remove SecurityContextHolder contents
 282  5 SecurityContextHolder.setContext(generateNewContext());
 283   
 284  5 if (logger.isDebugEnabled()) {
 285  0 logger.debug(
 286    "SecurityContextHolder set to new context, as request processing completed");
 287    }
 288    }
 289    }
 290    }
 291   
 292  13 public SecurityContext generateNewContext() throws ServletException {
 293  13 try {
 294  13 return (SecurityContext) this.context.newInstance();
 295    } catch (InstantiationException ie) {
 296  0 throw new ServletException(ie);
 297    } catch (IllegalAccessException iae) {
 298  0 throw new ServletException(iae);
 299    }
 300    }
 301   
 302    /**
 303    * Does nothing. We use IoC container lifecycle services instead.
 304    *
 305    * @param filterConfig ignored
 306    *
 307    * @throws ServletException ignored
 308    */
 309  5 public void init(FilterConfig filterConfig) throws ServletException {}
 310    }