Ben Alex 21 лет назад
Родитель
Сommit
738fd2161d
25 измененных файлов с 2572 добавлено и 0 удалено
  1. 366 0
      core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java
  2. 75 0
      core/src/main/java/org/acegisecurity/intercept/ObjectDefinitionSource.java
  3. 50 0
      core/src/main/java/org/acegisecurity/intercept/SecurityInterceptorCallback.java
  4. 73 0
      core/src/main/java/org/acegisecurity/intercept/method/AbstractMethodDefinitionSource.java
  5. 162 0
      core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionAttributes.java
  6. 187 0
      core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java
  7. 29 0
      core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSource.java
  8. 82 0
      core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditor.java
  9. 92 0
      core/src/main/java/org/acegisecurity/intercept/method/MethodSecurityInterceptor.java
  10. 6 0
      core/src/main/java/org/acegisecurity/intercept/method/package.html
  11. 31 0
      core/src/main/java/org/acegisecurity/intercept/package.html
  12. 71 0
      core/src/main/java/org/acegisecurity/intercept/web/AbstractFilterInvocationDefinitionSource.java
  13. 109 0
      core/src/main/java/org/acegisecurity/intercept/web/FilterInvocation.java
  14. 180 0
      core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionMap.java
  15. 28 0
      core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSource.java
  16. 120 0
      core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditor.java
  17. 91 0
      core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java
  18. 178 0
      core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java
  19. 5 0
      core/src/main/java/org/acegisecurity/intercept/web/package.html
  20. 161 0
      core/src/main/java/org/acegisecurity/ui/AbstractIntegrationFilter.java
  21. 120 0
      core/src/main/java/org/acegisecurity/ui/AutoIntegrationFilter.java
  22. 6 0
      core/src/main/java/org/acegisecurity/ui/package.html
  23. 263 0
      core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java
  24. 82 0
      core/src/main/java/org/acegisecurity/ui/webapp/HttpSessionIntegrationFilter.java
  25. 5 0
      core/src/main/java/org/acegisecurity/ui/webapp/package.html

+ 366 - 0
core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java

@@ -0,0 +1,366 @@
+/* Copyright 2004 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.intercept;
+
+import net.sf.acegisecurity.AccessDecisionManager;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
+import net.sf.acegisecurity.AuthenticationManager;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.RunAsManager;
+import net.sf.acegisecurity.context.Context;
+import net.sf.acegisecurity.context.ContextHolder;
+import net.sf.acegisecurity.context.SecureContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+
+/**
+ * Abstract class that implements security interception for secure objects.
+ * 
+ * <P>
+ * The <code>AbstractSecurityInterceptor</code> will ensure the proper startup
+ * configuration of the security interceptor. It will also implement the
+ * proper handling of secure object invocations, being:
+ * 
+ * <ol>
+ * <li>
+ * Extract the {@link SecureContext} from the {@link ContextHolder}, handling
+ * any errors such as invalid or <code>null</code> objects.
+ * </li>
+ * <li>
+ * Obtain the {@link Authentication} object from the extracted
+ * <code>SecureContext</code>.
+ * </li>
+ * <li>
+ * Determine if the request relates to a secured or public invocation by
+ * looking up the secure object request against the {@link
+ * ObjectDefinitionSource}.
+ * </li>
+ * <li>
+ * For an invocation that is secured (there is a
+ * <code>ConfigAttributeDefinition</code> for the secure object invocation):
+ * 
+ * <ol>
+ * <li>
+ * Authenticate the request against the configured {@link
+ * AuthenticationManager}, replacing the <code>Authentication</code> object on
+ * the <code>ContextHolder</code> with the returned value.
+ * </li>
+ * <li>
+ * Authorize the request against the configured {@link AccessDecisionManager}.
+ * </li>
+ * <li>
+ * Perform any run-as replacement via the configured {@link RunAsManager}.
+ * </li>
+ * <li>
+ * Perform a callback to the {@link SecurityInterceptorCallback}, which will
+ * actually proceed with executing the object.
+ * </li>
+ * <li>
+ * If the <code>RunAsManager</code> replaced the <code>Authentication</code>
+ * object, return the <code>ContextHolder</code> to the object that existed
+ * after the call to <code>AuthenticationManager</code>.
+ * </li>
+ * </ol>
+ * 
+ * </li>
+ * <li>
+ * For an invocation that is public (there is no
+ * <code>ConfigAttributeDefinition</code> for the secure object invocation):
+ * 
+ * <ol>
+ * <li>
+ * If the <code>ContextHolder</code> contains a <code>SecureContext</code>, set
+ * the <code>isAuthenticated</code> flag on the <code>Authentication</code>
+ * object to false.
+ * </li>
+ * <li>
+ * Perform a callback to the {@link SecurityInterceptorCallback}, which will
+ * actually proceed with the invocation.
+ * </li>
+ * </ol>
+ * 
+ * </li>
+ * <li>
+ * Return the result from the <code>SecurityInterceptorCallback</code> to the
+ * method that called {@link AbstractSecurityInterceptor#interceptor(Object,
+ * SecurityInterceptorCallback)}. This is almost always a concrete subclass of
+ * the <code>AbstractSecurityInterceptor</code>.
+ * </li>
+ * </ol>
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class AbstractSecurityInterceptor implements InitializingBean {
+    //~ Static fields/initializers =============================================
+
+    protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
+
+    //~ Instance fields ========================================================
+
+    private AccessDecisionManager accessDecisionManager;
+    private AuthenticationManager authenticationManager;
+    private RunAsManager runAsManager;
+    private boolean validateConfigAttributes = true;
+
+    //~ Methods ================================================================
+
+    public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
+
+    public void setAccessDecisionManager(
+        AccessDecisionManager accessDecisionManager) {
+        this.accessDecisionManager = accessDecisionManager;
+    }
+
+    public AccessDecisionManager getAccessDecisionManager() {
+        return accessDecisionManager;
+    }
+
+    public void setAuthenticationManager(AuthenticationManager newManager) {
+        this.authenticationManager = newManager;
+    }
+
+    public AuthenticationManager getAuthenticationManager() {
+        return this.authenticationManager;
+    }
+
+    public void setRunAsManager(RunAsManager runAsManager) {
+        this.runAsManager = runAsManager;
+    }
+
+    public RunAsManager getRunAsManager() {
+        return runAsManager;
+    }
+
+    public void setValidateConfigAttributes(boolean validateConfigAttributes) {
+        this.validateConfigAttributes = validateConfigAttributes;
+    }
+
+    public boolean isValidateConfigAttributes() {
+        return validateConfigAttributes;
+    }
+
+    public void afterPropertiesSet() {
+        if (this.authenticationManager == null) {
+            throw new IllegalArgumentException(
+                "An AuthenticationManager is required");
+        }
+
+        if (this.accessDecisionManager == null) {
+            throw new IllegalArgumentException(
+                "An AccessDecisionManager is required");
+        }
+
+        if (this.runAsManager == null) {
+            throw new IllegalArgumentException("A RunAsManager is required");
+        }
+
+        if (this.obtainObjectDefinitionSource() == null) {
+            throw new IllegalArgumentException(
+                "An ObjectDefinitionSource is required");
+        }
+
+        if (this.validateConfigAttributes) {
+            Iterator iter = this.obtainObjectDefinitionSource()
+                                .getConfigAttributeDefinitions();
+
+            if (iter == null) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn(
+                        "Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
+                }
+
+                return;
+            }
+
+            Set set = new HashSet();
+
+            while (iter.hasNext()) {
+                ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
+                    .next();
+                Iterator attributes = def.getConfigAttributes();
+
+                while (attributes.hasNext()) {
+                    ConfigAttribute attr = (ConfigAttribute) attributes.next();
+
+                    if (!this.runAsManager.supports(attr)
+                        && !this.accessDecisionManager.supports(attr)) {
+                        set.add(attr);
+                    }
+                }
+            }
+
+            if (set.size() == 0) {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Validated configuration attributes");
+                }
+            } else {
+                throw new IllegalArgumentException(
+                    "Unsupported configuration attributes: " + set.toString());
+            }
+        }
+    }
+
+    /**
+     * Does the work of authenticating and authorizing the request.
+     * 
+     * <P>
+     * Throws {@link net.sf.acegisecurity.AcegiSecurityException} and its
+     * subclasses.
+     * </p>
+     *
+     * @param object details of a secure object invocation
+     * @param callback the object that will complete the target secure object
+     *        invocation
+     *
+     * @return The value that was returned by the
+     *         <code>SecurityInterceptorCallback</code>
+     *
+     * @throws Throwable if any error occurs during the
+     *         <code>SecurityInterceptorCallback</code>
+     * @throws IllegalArgumentException if a required argument was missing or
+     *         invalid
+     * @throws AuthenticationCredentialsNotFoundException if the
+     *         <code>ContextHolder</code> is not populated with a valid
+     *         <code>SecureContext</code>
+     */
+    public Object interceptor(Object object,
+        SecurityInterceptorCallback callback) throws Throwable {
+        if (object == null) {
+            throw new IllegalArgumentException("Object was null");
+        }
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback was null");
+        }
+
+        if (!this.obtainObjectDefinitionSource().supports(object.getClass())) {
+            throw new IllegalArgumentException(
+                "ObjectDefinitionSource does not support objects of type "
+                + object.getClass());
+        }
+
+        ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource()
+                                             .getAttributes(object);
+
+        if (attr != null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Secure object: " + object.toString()
+                    + "; ConfigAttributes: " + attr.toString());
+            }
+
+            // Ensure ContextHolder presents a populated SecureContext
+            if ((ContextHolder.getContext() == null)
+                || !(ContextHolder.getContext() instanceof SecureContext)) {
+                throw new AuthenticationCredentialsNotFoundException(
+                    "A valid SecureContext was not provided in the RequestContext");
+            }
+
+            SecureContext context = (SecureContext) ContextHolder.getContext();
+
+            // We check for just the property we're interested in (we do
+            // not call Context.validate() like the ContextInterceptor)
+            if (context.getAuthentication() == null) {
+                throw new AuthenticationCredentialsNotFoundException(
+                    "Authentication credentials were not found in the SecureContext");
+            }
+
+            // Attempt authentication
+            Authentication authenticated = this.authenticationManager
+                .authenticate(context.getAuthentication());
+            authenticated.setAuthenticated(true);
+            logger.debug("Authenticated: " + authenticated.toString());
+            context.setAuthentication(authenticated);
+            ContextHolder.setContext((Context) context);
+
+            // Attempt authorization
+            this.accessDecisionManager.decide(authenticated, object, attr);
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Authorization successful");
+            }
+
+            // Attempt to run as a different user
+            Authentication runAs = this.runAsManager.buildRunAs(authenticated,
+                    object, attr);
+
+            if (runAs == null) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug(
+                        "RunAsManager did not change Authentication object");
+                }
+
+                return callback.proceedWithObject(object);
+            } else {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Switching to RunAs Authentication: "
+                        + runAs.toString());
+                }
+
+                context.setAuthentication(runAs);
+                ContextHolder.setContext((Context) context);
+
+                Object ret = callback.proceedWithObject(object);
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Reverting to original Authentication: "
+                        + authenticated.toString());
+                }
+
+                context.setAuthentication(authenticated);
+                ContextHolder.setContext((Context) context);
+
+                return ret;
+            }
+        } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Public object - authentication not attempted");
+            }
+
+            // Set Authentication object (if it exists) to be unauthenticated
+            if ((ContextHolder.getContext() != null)
+                && ContextHolder.getContext() instanceof SecureContext) {
+                SecureContext context = (SecureContext) ContextHolder
+                    .getContext();
+
+                if (context.getAuthentication() != null) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug(
+                            "Authentication object detected and tagged as unauthenticated");
+                    }
+
+                    Authentication authenticated = context.getAuthentication();
+                    authenticated.setAuthenticated(false);
+                    context.setAuthentication(authenticated);
+                    ContextHolder.setContext((Context) context);
+                }
+            }
+
+            return callback.proceedWithObject(object);
+        }
+    }
+}

+ 75 - 0
core/src/main/java/org/acegisecurity/intercept/ObjectDefinitionSource.java

@@ -0,0 +1,75 @@
+/* Copyright 2004 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.intercept;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import java.util.Iterator;
+
+
+/**
+ * Implemented by classes that store and can identify the {@link
+ * ConfigAttributeDefinition} that applies to a given secure object
+ * invocation.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface ObjectDefinitionSource {
+    //~ Methods ================================================================
+
+    /**
+     * Accesses the <code>ConfigAttributeDefinition</code> that applies to a
+     * given secure object.
+     *
+     * @param object the object being secured
+     *
+     * @return the <code>ConfigAttributeDefinition</code> that applies to the
+     *         passed object
+     *
+     * @throws IllegalArgumentException if the passed object is not of a type
+     *         supported by the <code>ObjectDefinitionSource</code>
+     *         implementation
+     */
+    public ConfigAttributeDefinition getAttributes(Object object)
+        throws IllegalArgumentException;
+
+    /**
+     * If available, all of the <code>ConfigAttributeDefinition</code>s defined
+     * by the implementing class.
+     * 
+     * <P>
+     * This is used by the {@link AbstractSecurityInterceptor} to perform
+     * startup time validation of each <code>ConfigAttribute</code> configured
+     * against it.
+     * </p>
+     *
+     * @return an iterator over all the <code>ConfigAttributeDefinition</code>s
+     *         or <code>null</code> if unsupported
+     */
+    public Iterator getConfigAttributeDefinitions();
+
+    /**
+     * Indicates whether the <code>ObjectDefinitionSource</code> implementation
+     * is able to provide <code>ConfigAttributeDefinition</code>s for the
+     * indicated secure object type.
+     *
+     * @param clazz the class that is being queried
+     *
+     * @return true if the implementation can process the indicated class
+     */
+    public boolean supports(Class clazz);
+}

+ 50 - 0
core/src/main/java/org/acegisecurity/intercept/SecurityInterceptorCallback.java

@@ -0,0 +1,50 @@
+/* Copyright 2004 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.intercept;
+
+/**
+ * Allows the {@link AbstractSecurityInterceptor} to continue the secure object
+ * invocation at the appropriate time.
+ * 
+ * <P>
+ * Concrete <code>AbstractSecurityInterceptor</code> subclasses are required to
+ * provide a <code>SecurityInterceptorCallback</code>. This is called by the
+ * <code>AbstractSecurityInterceptor</code> at the exact time the secure
+ * object should have its processing continued. The exact way processing is
+ * continued is specific to the type of secure object. For example, it may
+ * involve proceeding with a method invocation, servicing a request, or
+ * continuing a filter chain.
+ * </p>
+ * 
+ * <P>
+ * The result from processing the secure object should be returned to the
+ * <code>AbstractSecurityInterceptor</code>, which in turn will ultimately
+ * return it to the calling class.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface SecurityInterceptorCallback {
+    //~ Methods ================================================================
+
+    /**
+     * Continues to process the secured object.
+     *
+     * @return the result (if any) from calling the secured object
+     */
+    public Object proceedWithObject(Object object) throws Throwable;
+}

+ 73 - 0
core/src/main/java/org/acegisecurity/intercept/method/AbstractMethodDefinitionSource.java

@@ -0,0 +1,73 @@
+/* Copyright 2004 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.intercept.method;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Abstract implementation of <Code>MethodDefinitionSource</code>.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class AbstractMethodDefinitionSource
+    implements MethodDefinitionSource {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(AbstractMethodDefinitionSource.class);
+
+    //~ Methods ================================================================
+
+    public ConfigAttributeDefinition getAttributes(Object object)
+        throws IllegalArgumentException {
+        if ((object == null) || !this.supports(object.getClass())) {
+            throw new IllegalArgumentException(
+                "Object must be a MethodInvocation");
+        }
+
+        return this.lookupAttributes((MethodInvocation) object);
+    }
+
+    public boolean supports(Class clazz) {
+        if (MethodInvocation.class.isAssignableFrom(clazz)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Performs the actual lookup of the relevant
+     * <code>ConfigAttributeDefinition</code> for the specified
+     * <code>MethodInvocation</code>.
+     * 
+     * <P>
+     * Provided so subclasses need only to provide one basic method to properly
+     * interface with the <code>MethodDefinitionSource</code>.
+     * </p>
+     *
+     * @return the <code>ConfigAttributeDefinition</code> that applies to the
+     *         specified <code>MethodInvocation</code>
+     */
+    protected abstract ConfigAttributeDefinition lookupAttributes(
+        MethodInvocation mi);
+}

+ 162 - 0
core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionAttributes.java

@@ -0,0 +1,162 @@
+/* Copyright 2004 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.intercept.method;
+
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.metadata.Attributes;
+
+import java.lang.reflect.Method;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+
+/**
+ * Stores a {@link ConfigAttributeDefinition} for each method signature defined
+ * by Commons Attributes.
+ * 
+ * <P>
+ * This class will only detect those attributes which are defined for:
+ * 
+ * <ul>
+ * <li>
+ * The class-wide attributes defined for the intercepted class.
+ * </li>
+ * <li>
+ * The class-wide attributes defined for interfaces explicitly implemented by
+ * the intercepted class.
+ * </li>
+ * <li>
+ * The method-specific attributes defined for the intercepted method of the
+ * intercepted class.
+ * </li>
+ * <li>
+ * The method-specific attributes defined by any explicitly implemented
+ * interface if that interface contains a method signature matching that of
+ * the intercepted method.
+ * </li>
+ * </ul>
+ * </p>
+ * 
+ * <P>
+ * Note that attributes defined against parent classes (either for their
+ * methods or interfaces) are not detected. The attributes must be defined
+ * against an explicit method or interface on the intercepted class.
+ * </p>
+ * 
+ * <p>
+ * Attributes detected that do not implement {@link ConfigAttribute} will be
+ * ignored.
+ * </p>
+ *
+ * @author Cameron Braid
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class MethodDefinitionAttributes extends AbstractMethodDefinitionSource {
+    //~ Instance fields ========================================================
+
+    private Attributes attributes;
+
+    //~ Methods ================================================================
+
+    public void setAttributes(Attributes attributes) {
+        this.attributes = attributes;
+    }
+
+    public Iterator getConfigAttributeDefinitions() {
+        return null;
+    }
+
+    protected ConfigAttributeDefinition lookupAttributes(
+        MethodInvocation invocation) {
+        ConfigAttributeDefinition definition = new ConfigAttributeDefinition();
+
+        Class interceptedClass = invocation.getMethod().getDeclaringClass();
+
+        // add the class level attributes for the implementing class
+        addClassAttributes(definition, interceptedClass);
+
+        // add the class level attributes for the implemented interfaces
+        addClassAttributes(definition, interceptedClass.getInterfaces());
+
+        // add the method level attributes for the implemented method
+        addMethodAttributes(definition, invocation.getMethod());
+
+        // add the method level attributes for the implemented intreface methods
+        addInterfaceMethodAttributes(definition, invocation.getMethod());
+
+        return definition;
+    }
+
+    private void add(ConfigAttributeDefinition definition, Collection attribs) {
+        for (Iterator iter = attribs.iterator(); iter.hasNext();) {
+            Object o = (Object) iter.next();
+
+            if (o instanceof ConfigAttribute) {
+                definition.addConfigAttribute((ConfigAttribute) o);
+            }
+        }
+    }
+
+    private void addClassAttributes(ConfigAttributeDefinition definition,
+        Class clazz) {
+        addClassAttributes(definition, new Class[] {clazz});
+    }
+
+    private void addClassAttributes(ConfigAttributeDefinition definition,
+        Class[] clazz) {
+        for (int i = 0; i < clazz.length; i++) {
+            Collection classAttributes = attributes.getAttributes(clazz[i]);
+
+            if (classAttributes != null) {
+                add(definition, classAttributes);
+            }
+        }
+    }
+
+    private void addInterfaceMethodAttributes(
+        ConfigAttributeDefinition definition, Method method) {
+        Class[] interfaces = method.getDeclaringClass().getInterfaces();
+
+        for (int i = 0; i < interfaces.length; i++) {
+            Class clazz = interfaces[i];
+
+            try {
+                Method m = clazz.getDeclaredMethod(method.getName(),
+                        method.getParameterTypes());
+                addMethodAttributes(definition, m);
+            } catch (Exception e) {
+                // this won't happen since we are getting a method from an interface that 
+                // the declaring class implements
+            }
+        }
+    }
+
+    private void addMethodAttributes(ConfigAttributeDefinition definition,
+        Method method) {
+        // add the method level attributes
+        Collection methodAttributes = attributes.getAttributes(method);
+
+        if (methodAttributes != null) {
+            add(definition, methodAttributes);
+        }
+    }
+}

+ 187 - 0
core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java

@@ -0,0 +1,187 @@
+/* Copyright 2004 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.intercept.method;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.lang.reflect.Method;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Stores a {@link ConfigAttributeDefinition} for each method signature defined
+ * in a bean context.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(MethodDefinitionMap.class);
+
+    //~ Instance fields ========================================================
+
+    /** Map from Method to ApplicationDefinition */
+    protected Map methodMap = new HashMap();
+
+    /** Map from Method to name pattern used for registration */
+    private Map nameMap = new HashMap();
+
+    //~ Methods ================================================================
+
+    public Iterator getConfigAttributeDefinitions() {
+        return methodMap.values().iterator();
+    }
+
+    public int getMethodMapSize() {
+        return this.methodMap.size();
+    }
+
+    /**
+     * Add configuration attributes for a secure method. Method names can end
+     * or start with <code>&#42</code> for matching multiple methods.
+     *
+     * @param method the method to be secured
+     * @param attr required authorities associated with the method
+     */
+    public void addSecureMethod(Method method, ConfigAttributeDefinition attr) {
+        logger.info("Adding secure method [" + method + "] with attributes ["
+            + attr + "]");
+        this.methodMap.put(method, attr);
+    }
+
+    /**
+     * Add configuration attributes for a secure method. Method names can end
+     * or start with <code>&#42</code> for matching multiple methods.
+     *
+     * @param name class and method name, separated by a dot
+     * @param attr required authorities associated with the method
+     *
+     * @throws IllegalArgumentException DOCUMENT ME!
+     */
+    public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
+        int lastDotIndex = name.lastIndexOf(".");
+
+        if (lastDotIndex == -1) {
+            throw new IllegalArgumentException("'" + name
+                + "' is not a valid method name: format is FQN.methodName");
+        }
+
+        String className = name.substring(0, lastDotIndex);
+        String methodName = name.substring(lastDotIndex + 1);
+
+        try {
+            Class clazz = Class.forName(className, true,
+                    Thread.currentThread().getContextClassLoader());
+            addSecureMethod(clazz, methodName, attr);
+        } catch (ClassNotFoundException ex) {
+            throw new IllegalArgumentException("Class '" + className
+                + "' not found");
+        }
+    }
+
+    /**
+     * Add configuration attributes for a secure method. Method names can end
+     * or start with <code>&#42</code> for matching multiple methods.
+     *
+     * @param clazz target interface or class
+     * @param mappedName mapped method name
+     * @param attr required authorities associated with the method
+     *
+     * @throws IllegalArgumentException DOCUMENT ME!
+     */
+    public void addSecureMethod(Class clazz, String mappedName,
+        ConfigAttributeDefinition attr) {
+        String name = clazz.getName() + '.' + mappedName;
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Adding secure method [" + name
+                + "] with attributes [" + attr + "]");
+        }
+
+        Method[] methods = clazz.getDeclaredMethods();
+        List matchingMethods = new ArrayList();
+
+        for (int i = 0; i < methods.length; i++) {
+            if (methods[i].getName().equals(mappedName)
+                || isMatch(methods[i].getName(), mappedName)) {
+                matchingMethods.add(methods[i]);
+            }
+        }
+
+        if (matchingMethods.isEmpty()) {
+            throw new IllegalArgumentException("Couldn't find method '"
+                + mappedName + "' on " + clazz);
+        }
+
+        // register all matching methods
+        for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
+            Method method = (Method) it.next();
+            String regMethodName = (String) this.nameMap.get(method);
+
+            if ((regMethodName == null)
+                || (!regMethodName.equals(name)
+                && (regMethodName.length() <= name.length()))) {
+                // no already registered method name, or more specific
+                // method name specification now -> (re-)register method
+                if (regMethodName != null) {
+                    logger.debug("Replacing attributes for secure method ["
+                        + method + "]: current name [" + name
+                        + "] is more specific than [" + regMethodName + "]");
+                }
+
+                this.nameMap.put(method, name);
+                addSecureMethod(method, attr);
+            } else {
+                logger.debug("Keeping attributes for secure method [" + method
+                    + "]: current name [" + name
+                    + "] is not more specific than [" + regMethodName + "]");
+            }
+        }
+    }
+
+    protected ConfigAttributeDefinition lookupAttributes(MethodInvocation mi) {
+        return (ConfigAttributeDefinition) this.methodMap.get(mi.getMethod());
+    }
+
+    /**
+     * Return if the given method name matches the mapped name. The default
+     * implementation checks for "xxx" and "xxx" matches.
+     *
+     * @param methodName the method name of the class
+     * @param mappedName the name in the descriptor
+     *
+     * @return if the names match
+     */
+    private boolean isMatch(String methodName, String mappedName) {
+        return (mappedName.endsWith("*")
+        && methodName.startsWith(mappedName.substring(0, mappedName.length()
+                - 1)))
+        || (mappedName.startsWith("*")
+        && methodName.endsWith(mappedName.substring(1, mappedName.length())));
+    }
+}

+ 29 - 0
core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSource.java

@@ -0,0 +1,29 @@
+/* Copyright 2004 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.intercept.method;
+
+import net.sf.acegisecurity.intercept.ObjectDefinitionSource;
+
+
+/**
+ * Marker interface for <code>ObjectDefinitionSource</code> implementations
+ * that are designed to perform lookups keyed on
+ * <code>MethodInvocation</code>s.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface MethodDefinitionSource extends ObjectDefinitionSource {}

+ 82 - 0
core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditor.java

@@ -0,0 +1,82 @@
+/* Copyright 2004 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.intercept.method;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.ConfigAttributeEditor;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.propertyeditors.PropertiesEditor;
+
+import java.beans.PropertyEditorSupport;
+
+import java.util.Iterator;
+import java.util.Properties;
+
+
+/**
+ * Property editor to assist with the setup of a {@link
+ * MethodDefinitionSource}.
+ * 
+ * <p>
+ * The class creates and populates a {@link MethodDefinitionMap}.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class MethodDefinitionSourceEditor extends PropertyEditorSupport {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(MethodDefinitionSourceEditor.class);
+
+    //~ Methods ================================================================
+
+    public void setAsText(String s) throws IllegalArgumentException {
+        MethodDefinitionMap source = new MethodDefinitionMap();
+
+        if ((s == null) || "".equals(s)) {
+            // Leave value in property editor null
+        } else {
+            // Use properties editor to tokenize the string
+            PropertiesEditor propertiesEditor = new PropertiesEditor();
+            propertiesEditor.setAsText(s);
+
+            Properties props = (Properties) propertiesEditor.getValue();
+
+            // Now we have properties, process each one individually
+            ConfigAttributeEditor configAttribEd = new ConfigAttributeEditor();
+
+            for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
+                String name = (String) iter.next();
+                String value = props.getProperty(name);
+
+                // Convert value to series of security configuration attributes
+                configAttribEd.setAsText(value);
+
+                ConfigAttributeDefinition attr = (ConfigAttributeDefinition) configAttribEd
+                    .getValue();
+
+                // Register name and attribute
+                source.addSecureMethod(name, attr);
+            }
+        }
+
+        setValue(source);
+    }
+}

+ 92 - 0
core/src/main/java/org/acegisecurity/intercept/method/MethodSecurityInterceptor.java

@@ -0,0 +1,92 @@
+/* Copyright 2004 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.intercept.method;
+
+import net.sf.acegisecurity.intercept.AbstractSecurityInterceptor;
+import net.sf.acegisecurity.intercept.ObjectDefinitionSource;
+import net.sf.acegisecurity.intercept.SecurityInterceptorCallback;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+
+/**
+ * Provides security interception of method invocations.
+ * 
+ * <p>
+ * The <code>ObjectDefinitionSource</code> required by this security
+ * interceptor is of type {@link MethodDefinitionSource}.
+ * </p>
+ * 
+ * <P>
+ * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class MethodSecurityInterceptor extends AbstractSecurityInterceptor
+    implements MethodInterceptor, SecurityInterceptorCallback {
+    //~ Instance fields ========================================================
+
+    private MethodDefinitionSource objectDefinitionSource;
+
+    //~ Methods ================================================================
+
+    public void setObjectDefinitionSource(MethodDefinitionSource newSource) {
+        this.objectDefinitionSource = newSource;
+    }
+
+    public MethodDefinitionSource getObjectDefinitionSource() {
+        return this.objectDefinitionSource;
+    }
+
+    public void afterPropertiesSet() {
+        super.afterPropertiesSet();
+
+        if (!this.getAccessDecisionManager().supports(MethodInvocation.class)) {
+            throw new IllegalArgumentException(
+                "AccessDecisionManager does not support MethodInvocation");
+        }
+
+        if (!this.getRunAsManager().supports(MethodInvocation.class)) {
+            throw new IllegalArgumentException(
+                "RunAsManager does not support MethodInvocation");
+        }
+    }
+
+    /**
+     * This method should be used to enforce security on a
+     * <code>MethodInvocation</code>.
+     *
+     * @param mi The method being invoked which requires a security decision
+     *
+     * @return The returned value from the method invocation
+     *
+     * @throws Throwable if any error occurs
+     */
+    public Object invoke(MethodInvocation mi) throws Throwable {
+        return super.interceptor(mi, this);
+    }
+
+    public ObjectDefinitionSource obtainObjectDefinitionSource() {
+        return this.objectDefinitionSource;
+    }
+
+    public Object proceedWithObject(Object object) throws Throwable {
+        return ((MethodInvocation) object).proceed();
+    }
+}

+ 6 - 0
core/src/main/java/org/acegisecurity/intercept/method/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+Enforces security for <code>MethodInvocation</code>s, such as via
+Spring AOP.
+</body>
+</html>

+ 31 - 0
core/src/main/java/org/acegisecurity/intercept/package.html

@@ -0,0 +1,31 @@
+<html>
+<body>
+Actually enforces the security and ties the whole security system together.
+<P>
+A <i>secure object</i> is a term frequently used throughout the security
+system. It does <b>not</b> refer to a business object that is being
+secured, but instead refers to some infrastructure object that can have
+security facilities provided for it by the Acegi Security System for
+Spring. For example, one secure object would be
+<code>MethodInvocation</code>, whilst another would be HTTP {@link
+net.sf.acegisecurity.intercept.web.FilterInvocation}. Note these are
+infrastructure objects and their design allows them to represent a large
+variety of actual resources that might need to be secured, such as business
+objects or HTTP request URLs.
+</p>
+ 
+<P>Each secure object typically has its 
+own <code>net.sf.acegisecurity.intercept</code> package.
+Each package usually includes a concrete security interceptor (which
+subclasses {@link net.sf.acegisecurity.intercept.AbstractSecurityInterceptor},
+an appropriate {@link net.sf.acegisecurity.intercept.ObjectDefinitionSource}
+for the type of resources the secure object represents, and a property editor
+to populate the <code>ObjectDefinitionSource</code>.
+
+<P>It is simple to create new secure object types, given the
+<code>AbstractSecurityInterceptor</code> provides the majority of the logic
+and other specialised packages provide the authentication, authorization,
+run-as replacement management and <code>ContextHolder</code> population.
+
+</body>
+</html>

+ 71 - 0
core/src/main/java/org/acegisecurity/intercept/web/AbstractFilterInvocationDefinitionSource.java

@@ -0,0 +1,71 @@
+/* Copyright 2004 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.intercept.web;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Abstract implementation of <Code>FilterInvocationDefinitionSource</code>.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class AbstractFilterInvocationDefinitionSource
+    implements FilterInvocationDefinitionSource {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(AbstractFilterInvocationDefinitionSource.class);
+
+    //~ Methods ================================================================
+
+    public ConfigAttributeDefinition getAttributes(Object object)
+        throws IllegalArgumentException {
+        if ((object == null) || !this.supports(object.getClass())) {
+            throw new IllegalArgumentException(
+                "Object must be a FilterInvocation");
+        }
+
+        return this.lookupAttributes((FilterInvocation) object);
+    }
+
+    public boolean supports(Class clazz) {
+        if (FilterInvocation.class.isAssignableFrom(clazz)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Performs the actual lookup of the relevant
+     * <code>ConfigAttributeDefinition</code> for the specified
+     * <code>FilterInvocation</code>.
+     * 
+     * <P>
+     * Provided so subclasses need only to provide one basic method to properly
+     * interface with the <code>FilterInvocationDefinitionSource</code>.
+     * </p>
+     *
+     * @return the <code>ConfigAttributeDefinition</code> that applies to the
+     *         specified <code>FilterInvocation</code>
+     */
+    protected abstract ConfigAttributeDefinition lookupAttributes(
+        FilterInvocation filterInvocation);
+}

+ 109 - 0
core/src/main/java/org/acegisecurity/intercept/web/FilterInvocation.java

@@ -0,0 +1,109 @@
+/* Copyright 2004 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.intercept.web;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Holds objects associated with a HTTP filter.
+ * 
+ * <P>
+ * Guarantees the request and response are instances of
+ * <code>HttpServletRequest</code> and <code>HttpServletResponse</code>, and
+ * that there are no <code>null</code> objects.
+ * </p>
+ * 
+ * <P>
+ * Required so that security system classes can obtain access to the filter
+ * environment, as well as the request and response.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class FilterInvocation {
+    //~ Instance fields ========================================================
+
+    private FilterChain chain;
+    private ServletRequest request;
+    private ServletResponse response;
+
+    //~ Constructors ===========================================================
+
+    public FilterInvocation(ServletRequest request, ServletResponse response,
+        FilterChain chain) {
+        if ((request == null) || (response == null) || (chain == null)) {
+            throw new IllegalArgumentException(
+                "Cannot pass null values to constructor");
+        }
+
+        if (!(request instanceof HttpServletRequest)) {
+            throw new IllegalArgumentException(
+                "Can only process HttpServletRequest");
+        }
+
+        if (!(response instanceof HttpServletResponse)) {
+            throw new IllegalArgumentException(
+                "Can only process HttpServletResponse");
+        }
+
+        this.request = request;
+        this.response = response;
+        this.chain = chain;
+    }
+
+    protected FilterInvocation() {
+        throw new IllegalArgumentException("Cannot use default constructor");
+    }
+
+    //~ Methods ================================================================
+
+    public FilterChain getChain() {
+        return chain;
+    }
+
+    public HttpServletRequest getHttpRequest() {
+        return (HttpServletRequest) request;
+    }
+
+    public HttpServletResponse getHttpResponse() {
+        return (HttpServletResponse) response;
+    }
+
+    public ServletRequest getRequest() {
+        return request;
+    }
+
+    public String getRequestUrl() {
+        return getHttpRequest().getServletPath()
+        + ((getHttpRequest().getQueryString() == null) ? ""
+                                                       : ("?"
+        + getHttpRequest().getQueryString()));
+    }
+
+    public ServletResponse getResponse() {
+        return response;
+    }
+
+    public String toString() {
+        return "FilterInvocation: URL: " + getRequestUrl();
+    }
+}

+ 180 - 0
core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionMap.java

@@ -0,0 +1,180 @@
+/* Copyright 2004 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.intercept.web;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.PatternMatcher;
+import org.apache.oro.text.regex.Perl5Compiler;
+import org.apache.oro.text.regex.Perl5Matcher;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+
+/**
+ * Maintains a <Code>List</code> of <code>ConfigAttributeDefinition</code>s
+ * associated with different HTTP request URL patterns.
+ * 
+ * <P>
+ * Regular expressions are used to match a HTTP request URL against a
+ * <code>ConfigAttributeDefinition</code>.
+ * </p>
+ * 
+ * <p>
+ * The order of registering the regular expressions using the {@link
+ * #addSecureUrl(String, ConfigAttributeDefinition)} is very important. The
+ * system will identify the <B>first</B>  matching regular expression for a
+ * given HTTP URL. It will not proceed to evaluate later regular expressions
+ * if a match has already been found. Accordingly, the most specific regular
+ * expressions should be registered first, with the most general regular
+ * expressions registered last.
+ * </p>
+ * 
+ * <P>
+ * If no registered regular expressions match the HTTP URL, <code>null</code>
+ * is returned.
+ * </p>
+ */
+public class FilterInvocationDefinitionMap
+    extends AbstractFilterInvocationDefinitionSource {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionMap.class);
+
+    //~ Instance fields ========================================================
+
+    private List requestMap = new Vector();
+    private boolean convertUrlToLowercaseBeforeComparison = false;
+
+    //~ Methods ================================================================
+
+    public Iterator getConfigAttributeDefinitions() {
+        Set set = new HashSet();
+        Iterator iter = requestMap.iterator();
+
+        while (iter.hasNext()) {
+            EntryHolder entryHolder = (EntryHolder) iter.next();
+            set.add(entryHolder.getConfigAttributeDefinition());
+        }
+
+        return set.iterator();
+    }
+
+    public void setConvertUrlToLowercaseBeforeComparison(
+        boolean convertUrlToLowercaseBeforeComparison) {
+        this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
+    }
+
+    public boolean isConvertUrlToLowercaseBeforeComparison() {
+        return convertUrlToLowercaseBeforeComparison;
+    }
+
+    public int getMapSize() {
+        return this.requestMap.size();
+    }
+
+    public void addSecureUrl(String perl5RegExp, ConfigAttributeDefinition attr) {
+        Pattern compiledPattern;
+        Perl5Compiler compiler = new Perl5Compiler();
+
+        try {
+            compiledPattern = compiler.compile(perl5RegExp,
+                    Perl5Compiler.READ_ONLY_MASK);
+        } catch (MalformedPatternException mpe) {
+            throw new IllegalArgumentException("Malformed regular expression: "
+                + perl5RegExp);
+        }
+
+        requestMap.add(new EntryHolder(compiledPattern, attr));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Added regular expression: "
+                + compiledPattern.getPattern().toString() + "; attributes: "
+                + attr.toString());
+        }
+    }
+
+    protected ConfigAttributeDefinition lookupAttributes(
+        FilterInvocation filterInvocation) {
+        PatternMatcher matcher = new Perl5Matcher();
+
+        Iterator iter = requestMap.iterator();
+
+        String url = filterInvocation.getRequestUrl();
+
+        if (convertUrlToLowercaseBeforeComparison) {
+            url = url.toLowerCase();
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Converted URL to lowercase, from: '"
+                    + filterInvocation.getRequest() + "'; to: '" + url + "'");
+            }
+        }
+
+        while (iter.hasNext()) {
+            EntryHolder entryHolder = (EntryHolder) iter.next();
+
+            boolean matched = matcher.matches(url,
+                    entryHolder.getCompiledPattern());
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Candidate is: '" + url + "'; pattern is "
+                    + entryHolder.getCompiledPattern().getPattern()
+                    + "; matched=" + matched);
+            }
+
+            if (matched) {
+                return entryHolder.getConfigAttributeDefinition();
+            }
+        }
+
+        return null;
+    }
+
+    //~ Inner Classes ==========================================================
+
+    protected class EntryHolder {
+        private ConfigAttributeDefinition configAttributeDefinition;
+        private Pattern compiledPattern;
+
+        public EntryHolder(Pattern compiledPattern,
+            ConfigAttributeDefinition attr) {
+            this.compiledPattern = compiledPattern;
+            this.configAttributeDefinition = attr;
+        }
+
+        protected EntryHolder() {
+            throw new IllegalArgumentException("Cannot use default constructor");
+        }
+
+        public Pattern getCompiledPattern() {
+            return compiledPattern;
+        }
+
+        public ConfigAttributeDefinition getConfigAttributeDefinition() {
+            return configAttributeDefinition;
+        }
+    }
+}

+ 28 - 0
core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSource.java

@@ -0,0 +1,28 @@
+/* Copyright 2004 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.intercept.web;
+
+import net.sf.acegisecurity.intercept.ObjectDefinitionSource;
+
+
+/**
+ * Marker interface for <code>ObjectDefinitionSource</code> implementations
+ * that are designed to perform lookups keyed on  {@link FilterInvocation}s.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface FilterInvocationDefinitionSource extends ObjectDefinitionSource {}

+ 120 - 0
core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditor.java

@@ -0,0 +1,120 @@
+/* Copyright 2004 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.intercept.web;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.ConfigAttributeEditor;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.StringUtils;
+
+import java.beans.PropertyEditorSupport;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+
+
+/**
+ * Property editor to assist with the setup of  {@link
+ * FilterInvocationDefinitionSource}.
+ * 
+ * <p>
+ * The class creates and populates a {@link FilterInvocationDefinitionMap}.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class FilterInvocationDefinitionSourceEditor
+    extends PropertyEditorSupport {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionSourceEditor.class);
+
+    //~ Methods ================================================================
+
+    public void setAsText(String s) throws IllegalArgumentException {
+        FilterInvocationDefinitionMap source = new FilterInvocationDefinitionMap();
+
+        if ((s == null) || "".equals(s)) {
+            // Leave value in property editor null
+        } else {
+            BufferedReader br = new BufferedReader(new StringReader(s));
+            int counter = 0;
+            String line;
+
+            while (true) {
+                counter++;
+
+                try {
+                    line = br.readLine();
+                } catch (IOException ioe) {
+                    throw new IllegalArgumentException(ioe.getMessage());
+                }
+
+                if (line == null) {
+                    break;
+                }
+
+                line = line.trim();
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Line " + counter + ": " + line);
+                }
+
+                if (line.startsWith("//")) {
+                    continue;
+                }
+
+                if (line.equals("CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON")) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Line " + counter
+                            + ": Instructing mapper to convert URLs to lowercase before comparison");
+                    }
+
+                    source.setConvertUrlToLowercaseBeforeComparison(true);
+
+                    continue;
+                }
+
+                if (line.lastIndexOf('=') == -1) {
+                    continue;
+                }
+
+                // Tokenize the line into its name/value tokens
+                String[] nameValue = StringUtils.delimitedListToStringArray(line,
+                        "=");
+                String name = nameValue[0];
+                String value = nameValue[1];
+
+                // Convert value to series of security configuration attributes
+                ConfigAttributeEditor configAttribEd = new ConfigAttributeEditor();
+                configAttribEd.setAsText(value);
+
+                ConfigAttributeDefinition attr = (ConfigAttributeDefinition) configAttribEd
+                    .getValue();
+
+                // Register the regular expression and its attribute
+                source.addSecureUrl(name, attr);
+            }
+        }
+
+        setValue(source);
+    }
+}

+ 91 - 0
core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java

@@ -0,0 +1,91 @@
+/* Copyright 2004 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.intercept.web;
+
+import net.sf.acegisecurity.intercept.AbstractSecurityInterceptor;
+import net.sf.acegisecurity.intercept.ObjectDefinitionSource;
+import net.sf.acegisecurity.intercept.SecurityInterceptorCallback;
+
+
+/**
+ * Performs security handling of HTTP resources via a filter implementation.
+ * 
+ * <P>
+ * End users should <B>only</B> use this class to configure their HTTP security
+ * configuration in an application context. They should <B>not</B> attempt to
+ * invoke the <code>FilterSecurityInterceptor</code> except as a standard bean
+ * registration in an application context. At runtime, this class will provide
+ * services to web applications via the {@link SecurityEnforcementFilter}.
+ * </p>
+ * 
+ * <p>
+ * The <code>ObjectDefinitionSource</code> required by this security
+ * interceptor is of type {@link FilterInvocationDefinitionSource}.
+ * </p>
+ * 
+ * <P>
+ * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class FilterSecurityInterceptor extends AbstractSecurityInterceptor
+    implements SecurityInterceptorCallback {
+    //~ Instance fields ========================================================
+
+    private FilterInvocationDefinitionSource objectDefinitionSource;
+
+    //~ Methods ================================================================
+
+    public void setObjectDefinitionSource(
+        FilterInvocationDefinitionSource newSource) {
+        this.objectDefinitionSource = newSource;
+    }
+
+    public FilterInvocationDefinitionSource getObjectDefinitionSource() {
+        return this.objectDefinitionSource;
+    }
+
+    public void afterPropertiesSet() {
+        super.afterPropertiesSet();
+
+        if (!this.getAccessDecisionManager().supports(FilterInvocation.class)) {
+            throw new IllegalArgumentException(
+                "AccessDecisionManager does not support FilterInvocation");
+        }
+
+        if (!this.getRunAsManager().supports(FilterInvocation.class)) {
+            throw new IllegalArgumentException(
+                "RunAsManager does not support FilterInvocation");
+        }
+    }
+
+    public void invoke(FilterInvocation fi) throws Throwable {
+        super.interceptor(fi, this);
+    }
+
+    public ObjectDefinitionSource obtainObjectDefinitionSource() {
+        return this.objectDefinitionSource;
+    }
+
+    public Object proceedWithObject(Object object) throws Throwable {
+        FilterInvocation fi = (FilterInvocation) object;
+        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+
+        return null;
+    }
+}

+ 178 - 0
core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java

@@ -0,0 +1,178 @@
+/* Copyright 2004 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.intercept.web;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.AuthenticationException;
+import net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.io.IOException;
+
+import java.util.Map;
+
+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;
+
+
+/**
+ * Wraps requests to the {@link FilterSecurityInterceptor}.
+ * 
+ * <P>
+ * This filter is necessary because it provides an application context
+ * environment for the <code>FilterSecurityInterceptor</code> instance.
+ * </p>
+ * 
+ * <P>
+ * If a {@link AuthenticationException} is detected, the filter will redirect
+ * to the <code>loginFormUrl</code>. This allows common handling of
+ * authentication failures originating from any subclass of {@link
+ * net.sf.acegisecurity.intercept.AbstractSecurityInterceptor}.
+ * </p>
+ * 
+ * <p>
+ * If an {@link AccessDeniedException} is detected, the filter will response
+ * with a <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error). Again,
+ * this allows common access denied handling irrespective of the originating
+ * security interceptor.
+ * </p>
+ * 
+ * <P>
+ * To use this filter, it is necessary to specify the following filter
+ * initialization parameters:
+ * 
+ * <ul>
+ * <li>
+ * <code>appContextLocation</code> indicates the path to an application context
+ * that contains the <code>FilterSecurityInterceptor</code>.
+ * </li>
+ * <li>
+ * <code>loginFormUrl</code> indicates the URL that should be used for
+ * redirection if an <code>AuthenticationException</code> is detected.
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SecurityEnforcementFilter implements Filter {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(SecurityEnforcementFilter.class);
+
+    //~ Instance fields ========================================================
+
+    protected ClassPathXmlApplicationContext ctx;
+    protected FilterSecurityInterceptor securityInterceptor;
+
+    /**
+     * The URL that should be used for redirection if an
+     * <code>AuthenticationException</code> is detected.
+     */
+    protected String loginFormUrl;
+
+    //~ Methods ================================================================
+
+    public void destroy() {
+        ctx.close();
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response,
+        FilterChain chain) throws IOException, ServletException {
+        if (!(request instanceof HttpServletRequest)) {
+            throw new ServletException("HttpServletRequest required");
+        }
+
+        if (!(response instanceof HttpServletResponse)) {
+            throw new ServletException("HttpServletResponse required");
+        }
+
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        try {
+            securityInterceptor.invoke(fi);
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Chain processed normally");
+            }
+        } catch (AuthenticationException authentication) {
+            HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "Authentication failed - adding target URL to Session: "
+                    + fi.getRequestUrl());
+            }
+
+            ((HttpServletRequest) request).getSession().setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
+                fi.getRequestUrl());
+            ((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
+                .getContextPath() + loginFormUrl);
+        } catch (AccessDeniedException accessDenied) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "Access is denied - sending back forbidden response");
+            }
+
+            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); // 403
+        } catch (Throwable otherException) {
+            throw new ServletException(otherException);
+        }
+    }
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+        String appContextLocation = filterConfig.getInitParameter(
+                "appContextLocation");
+
+        if ((appContextLocation == null) || "".equals(appContextLocation)) {
+            throw new ServletException("appContextLocation must be specified");
+        }
+
+        if (Thread.currentThread().getContextClassLoader().getResource(appContextLocation) == null) {
+            throw new ServletException("Cannot locate " + appContextLocation);
+        }
+
+        loginFormUrl = filterConfig.getInitParameter("loginFormUrl");
+
+        if ((loginFormUrl == null) || "".equals(loginFormUrl)) {
+            throw new ServletException("loginFormUrl must be specified");
+        }
+
+        ctx = new ClassPathXmlApplicationContext(appContextLocation);
+
+        Map beans = ctx.getBeansOfType(FilterSecurityInterceptor.class, true,
+                true);
+
+        if (beans.size() == 0) {
+            throw new ServletException(
+                "Bean context must contain at least one bean of type FilterSecurityInterceptor");
+        }
+
+        String beanName = (String) beans.keySet().iterator().next();
+        securityInterceptor = (FilterSecurityInterceptor) beans.get(beanName);
+    }
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/intercept/web/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Enforces security for HTTP requests, typically by the URL requested.
+</body>
+</html>

+ 161 - 0
core/src/main/java/org/acegisecurity/ui/AbstractIntegrationFilter.java

@@ -0,0 +1,161 @@
+/* Copyright 2004 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;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.context.Context;
+import net.sf.acegisecurity.context.ContextHolder;
+import net.sf.acegisecurity.context.SecureContext;
+import net.sf.acegisecurity.context.SecureContextImpl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Automatically populates a {@link net.sf.acegisecurity.context.SecureContext}
+ * from a subclass-provided <code>Authentication</code> object.
+ * 
+ * <p>
+ * The container hosting the Acegi Security System for Spring secured
+ * application is expected to expose an {@link Authentication} object in a
+ * well-known location. The <code>Authentication</code> object will have been
+ * created by the Acegi Security System for Spring and placed into the
+ * well-known location via approaches such as container adapters or container
+ * sessions.
+ * </p>
+ * 
+ * <P>
+ * Once the <code>Authentication</code> object has been extracted from the
+ * well-known location, the <code>AbstractIntegrationFilter</code> handles
+ * putting it into the {@link ContextHolder}. It then removes it once the
+ * filter chain has completed.
+ * </p>
+ * 
+ * <p>
+ * This filter will not abort if an <code>Authentication</code> object cannot
+ * be obtained from the well-known location. It will simply continue the
+ * filter chain as normal.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class AbstractIntegrationFilter implements Filter {
+    //~ Static fields/initializers =============================================
+
+    protected static final Log logger = LogFactory.getLog(AbstractIntegrationFilter.class);
+
+    //~ Methods ================================================================
+
+    public void destroy() {}
+
+    public void doFilter(ServletRequest request, ServletResponse response,
+        FilterChain chain) throws IOException, ServletException {
+        // Populate authentication information
+        Object extracted = this.extractFromContainer(request);
+
+        if (extracted instanceof Authentication) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "Authentication added to ContextHolder from container");
+            }
+
+            Authentication auth = (Authentication) extracted;
+
+            // Get or create existing SecureContext
+            SecureContext secureContext = null;
+
+            if ((ContextHolder.getContext() == null)
+                || !(ContextHolder.getContext() instanceof SecureContext)) {
+                secureContext = new SecureContextImpl();
+            } else {
+                secureContext = (SecureContext) ContextHolder.getContext();
+            }
+
+            // Add Authentication to SecureContext, and save
+            secureContext.setAuthentication(auth);
+            ContextHolder.setContext((Context) secureContext);
+        } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "Authentication not added to ContextHolder (could not extract an authentication object from the container which is an instance of Authentication)");
+            }
+        }
+
+        // Proceed with chain
+        chain.doFilter(request, response);
+
+        // Remove authentication information
+        if ((ContextHolder.getContext() != null)
+            && ContextHolder.getContext() instanceof SecureContext) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Removing Authentication from ContextHolder");
+            }
+
+            // Get context holder and remove authentication information
+            SecureContext secureContext = (SecureContext) ContextHolder
+                .getContext();
+            secureContext.setAuthentication(null);
+            ContextHolder.setContext((Context) secureContext);
+        } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "ContextHolder does not contain any authentication information");
+            }
+        }
+    }
+
+    /**
+     * Subclasses must override this method to provide the <code>Object</code>
+     * that contains the <code>Authentication</code> interface.
+     * 
+     * <p>
+     * For convenience we have allowed any <code>Object</code> to be returned
+     * by subclasses, as the abstract class will ensure class casting safety
+     * and ignore objects that do not implement <code>Authentication</code>.
+     * </p>
+     * 
+     * <p>
+     * If no <code>Authentication</code> object is available, subclasses should
+     * return <code>null</code>.
+     * </p>
+     * 
+     * <p>
+     * If the subclass can locate multiple authentication objects, they should
+     * return the object that was created by the Acegi Security System for
+     * Spring (ie the object that implements <code>Authentication</code>).
+     * </p>
+     *
+     * @param request the request, which may be of use in extracting the
+     *        authentication object
+     *
+     * @return <code>null</code> or an object that implements
+     *         <code>Authentication</code>
+     */
+    public abstract Object extractFromContainer(ServletRequest request);
+
+    public void init(FilterConfig filterConfig) throws ServletException {}
+}

+ 120 - 0
core/src/main/java/org/acegisecurity/ui/AutoIntegrationFilter.java

@@ -0,0 +1,120 @@
+/* Copyright 2004 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;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.adapters.HttpRequestIntegrationFilter;
+import net.sf.acegisecurity.adapters.jboss.JbossIntegrationFilter;
+import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * Detects the container and delegates to the appropriate {@link
+ * AbstractIntegrationFilter}.
+ * 
+ * <p>
+ * This eases the creation of portable, secure applications, as the
+ * <code>web.xml</code> will not need to refer to a specific  integration
+ * filter.
+ * </p>
+ * 
+ * <P>
+ * The filter automatically delegates to {@link HttpRequestIntegrationFilter}
+ * if any <code>Authentication</code> object is detected in the
+ * <code>ServletRequest</code>. Failing this, it will delegate to {@link
+ * HttpSessionIntegrationFilter} if the session object contains an
+ * <code>Authentication</code> object. Finally, this filter will delegate to
+ * {@link JbossIntegrationFilter} if the <code>ServletRequest</code> contains
+ * an instance of JBoss' <code>SimplePrincipal</code>.
+ * </p>
+ * 
+ * <P>
+ * If no location can be found containing the <code>Authentication</code>
+ * object, it will return <code>null</code>.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ *
+ * @see AbstractIntegrationFilter
+ */
+public class AutoIntegrationFilter extends AbstractIntegrationFilter {
+    //~ Methods ================================================================
+
+    public Object extractFromContainer(ServletRequest request) {
+        if (request instanceof HttpServletRequest) {
+            HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+            if (httpRequest.getUserPrincipal() instanceof Authentication) {
+                return getHttpServletRequest().extractFromContainer(request);
+            }
+
+            if (getHttpSessionIntegrationFilter().extractFromContainer(request) != null) {
+                return getHttpSessionIntegrationFilter().extractFromContainer(request);
+            }
+
+            try {
+                Class simplePrincipalClass = Class.forName(
+                        "org.jboss.security.SimplePrincipal");
+
+                if (null != httpRequest.getUserPrincipal()) {
+                    if (simplePrincipalClass.isAssignableFrom(
+                            httpRequest.getUserPrincipal().getClass())) {
+                        return getJbossIntegrationFilter().extractFromContainer(request);
+                    }
+                }
+            } catch (ClassNotFoundException e) {
+                // Can't be JBoss principal
+                // Expected, and normal - fall through
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Allows test case to override the source of
+     * <code>HttpRequestIntegrationFilter</code>.
+     *
+     * @return the <code>HttpRequestIntegrationFilter</code> to use
+     */
+    protected HttpRequestIntegrationFilter getHttpServletRequest() {
+        return new HttpRequestIntegrationFilter();
+    }
+
+    /**
+     * Allows test case to override the source of
+     * <code>HttpSessionIntegrationFilter</code>.
+     *
+     * @return the <code>HttpRequestIntegrationFilter</code> to use
+     */
+    protected HttpSessionIntegrationFilter getHttpSessionIntegrationFilter() {
+        return new HttpSessionIntegrationFilter();
+    }
+
+    /**
+     * Allows test case to override the source of
+     * <code>JbossIntegrationFilter</code>.
+     *
+     * @return the <code>JbossIntegrationFilter</code> to use
+     */
+    protected JbossIntegrationFilter getJbossIntegrationFilter() {
+        return new JbossIntegrationFilter();
+    }
+}

+ 6 - 0
core/src/main/java/org/acegisecurity/ui/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+Interfaces the system with various end-user authentication approaches, such
+as container adapters and web applications.
+</body>
+</html>

+ 263 - 0
core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java

@@ -0,0 +1,263 @@
+/* Copyright 2004 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.webapp;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+import net.sf.acegisecurity.AuthenticationManager;
+import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.io.IOException;
+
+import java.util.Map;
+
+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;
+
+
+/**
+ * Processes an authentication form, putting the result into the
+ * <code>HttpSession</code>.
+ * 
+ * <p>
+ * This filter is responsible for processing authentication requests. A user
+ * will typically authenticate once using a login form, and this filter
+ * processes that form. If authentication is successful, the resulting {@link
+ * Authentication} object will be placed into the <code>HttpSession</code>
+ * with the attribute defined by {@link
+ * HttpSessionIntegrationFilter#ACEGI_SECURITY_AUTHENTICATION_KEY}.
+ * </p>
+ * 
+ * <P>
+ * Login forms must present two parameters to this filter: a username and
+ * password. The filter will process the login against the authentication
+ * environment that was configured from a Spring application context defined
+ * in the filter initialization.
+ * </p>
+ * 
+ * <P>
+ * If authentication fails, the <code>AuthenticationException</code> will be
+ * placed into the <code>HttpSession</code> with the attribute defined by
+ * {@link #ACEGI_SECURITY_LAST_EXCEPTION_KEY}.
+ * </p>
+ * 
+ * <P>
+ * To use this filter, it is necessary to specify the following filter
+ * initialization parameters:
+ * 
+ * <ul>
+ * <li>
+ * <code>appContextLocation</code> indicates the path to an application context
+ * that contains an {@link AuthenticationManager} that should be used to
+ * process each authentication request.
+ * </li>
+ * <li>
+ * <code>defaultTargetUrl</code> indicates the URL that should be used for
+ * redirection if the <code>HttpSession</code> attribute named {@link
+ * #ACEGI_SECURITY_TARGET_URL_KEY} does not indicate the target URL once
+ * authentication is completed successfully. eg: <code>/</code>.
+ * </li>
+ * <li>
+ * <code>authenticationFailureUrl</code> indicates the URL that should be used
+ * for redirection if the authentication request fails. eg:
+ * <code>/login.jsp?login_error=1</code>.
+ * </li>
+ * <li>
+ * <code>filterProcessesUrl</code> indicates the URL that this filter will
+ * respond to. This parameter is optional, and defaults to
+ * <code>/j_acegi_security_check</code>.
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AuthenticationProcessingFilter implements Filter {
+    //~ Static fields/initializers =============================================
+
+    public static final String ACEGI_SECURITY_TARGET_URL_KEY = "ACEGI_SECURITY_TARGET_URL";
+    public static final String ACEGI_SECURITY_FORM_USERNAME_KEY = "j_username";
+    public static final String ACEGI_SECURITY_FORM_PASSWORD_KEY = "j_password";
+    public static final String ACEGI_SECURITY_LAST_EXCEPTION_KEY = "ACEGI_SECURITY_LAST_EXCEPTION";
+    private static final Log logger = LogFactory.getLog(AuthenticationProcessingFilter.class);
+
+    //~ Instance fields ========================================================
+
+    private AuthenticationManager authenticationManager;
+    private ClassPathXmlApplicationContext ctx;
+
+    /** Where to redirect the browser to if authentication fails */
+    private String authenticationFailureUrl;
+
+    /**
+     * Where to redirect the browser to if authentication is successful but
+     * ACEGI_SECURITY_TARGET_URL_KEY is <code>null</code>
+     */
+    private String defaultTargetUrl;
+
+    /**
+     * The URL destination that this filter intercepts and processes (usually
+     * <code>/j_acegi_security_check</code>)
+     */
+    private String filterProcessesUrl;
+
+    //~ Methods ================================================================
+
+    public void destroy() {
+        ctx.close();
+    }
+
+    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;
+
+        if (filterProcessesUrl.equals(httpRequest.getServletPath())) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Request is to process Acegi login form");
+            }
+
+            String username = httpRequest.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
+            String password = httpRequest.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY);
+
+            if (username == null) {
+                username = "";
+            }
+
+            if (password == null) {
+                password = "";
+            }
+
+            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
+                    password);
+
+            Authentication authResult;
+
+            try {
+                authResult = authenticationManager.authenticate(authRequest);
+            } catch (AuthenticationException failed) {
+                // Authentication failed
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Authentication request for user: " + username
+                        + " failed: " + failed.toString());
+                }
+
+                httpRequest.getSession().setAttribute(ACEGI_SECURITY_LAST_EXCEPTION_KEY,
+                    failed);
+                httpRequest.getSession().setAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY,
+                    null);
+                httpResponse.sendRedirect(httpRequest.getContextPath()
+                    + authenticationFailureUrl);
+
+                return;
+            }
+
+            // Authentication success
+            if (logger.isDebugEnabled()) {
+                logger.debug("Authentication success: " + authResult.toString());
+            }
+
+            httpRequest.getSession().setAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY,
+                authResult);
+
+            String targetUrl = (String) httpRequest.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY);
+            httpRequest.getSession().setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
+                null);
+
+            if (targetUrl == null) {
+                targetUrl = defaultTargetUrl;
+            }
+
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "Redirecting to target URL from HTTP Session (or default): "
+                    + targetUrl);
+            }
+
+            httpResponse.sendRedirect(httpRequest.getContextPath() + targetUrl);
+
+            return;
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+        String appContextLocation = filterConfig.getInitParameter(
+                "appContextLocation");
+
+        if ((appContextLocation == null) || "".equals(appContextLocation)) {
+            throw new ServletException("appContextLocation must be specified");
+        }
+
+        if (Thread.currentThread().getContextClassLoader().getResource(appContextLocation) == null) {
+            throw new ServletException("Cannot locate " + appContextLocation);
+        }
+
+        defaultTargetUrl = filterConfig.getInitParameter("defaultTargetUrl");
+
+        if ((defaultTargetUrl == null) || "".equals(defaultTargetUrl)) {
+            throw new ServletException("defaultTargetUrl must be specified");
+        }
+
+        authenticationFailureUrl = filterConfig.getInitParameter(
+                "authenticationFailureUrl");
+
+        if ((authenticationFailureUrl == null)
+            || "".equals(authenticationFailureUrl)) {
+            throw new ServletException(
+                "authenticationFailureUrl must be specified");
+        }
+
+        filterProcessesUrl = filterConfig.getInitParameter("filterProcessesUrl");
+
+        if ((filterProcessesUrl == null) || "".equals(filterProcessesUrl)) {
+            filterProcessesUrl = "/j_acegi_security_check";
+        }
+
+        ctx = new ClassPathXmlApplicationContext(appContextLocation);
+
+        Map beans = ctx.getBeansOfType(AuthenticationManager.class, true, true);
+
+        if (beans.size() == 0) {
+            throw new ServletException(
+                "Bean context must contain at least one bean of type AuthenticationManager");
+        }
+
+        String beanName = (String) beans.keySet().iterator().next();
+        authenticationManager = (AuthenticationManager) beans.get(beanName);
+    }
+}

+ 82 - 0
core/src/main/java/org/acegisecurity/ui/webapp/HttpSessionIntegrationFilter.java

@@ -0,0 +1,82 @@
+/* Copyright 2004 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.webapp;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ui.AbstractIntegrationFilter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * Populates a {@link net.sf.acegisecurity.context.SecureContext} from the
+ * <code>HttpSession</code>.
+ * 
+ * <P>
+ * The filter will inspect the <code>HttpSession</code> for an attribute with
+ * the name indicated by {@link #ACEGI_SECURITY_AUTHENTICATION_KEY}. If that
+ * attribute contains an instance of {@link Authentication}, it will be placed
+ * into the <code>ContextHolder</code>.
+ * </p>
+ * 
+ * <P>
+ * This filter is normally used in conjunction with {@link
+ * AuthenticationProcessingFilter}, which populates the
+ * <code>HttpSession</code> with an <code>Authentication</code> object based
+ * on a form login. Alternatively, users may elect to use their own approach
+ * for populating the <code>HttpSession</code>.
+ * </p>
+ * 
+ * <p>
+ * As with other <code>AbstractIntegrationFilter</code>s, this filter will
+ * ensure the <code>ContextHolder</code> is populated with the
+ * <code>Authentication</code> object for the duration of the HTTP request,
+ * and is unbound from the <code>ContextHolder</code> at the completion of the
+ * request.
+ * </p>
+ * 
+ * <p>
+ * See {@link AbstractIntegrationFilter} for further information.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class HttpSessionIntegrationFilter extends AbstractIntegrationFilter {
+    //~ Static fields/initializers =============================================
+
+    public static final String ACEGI_SECURITY_AUTHENTICATION_KEY = "ACEGI_SECURITY_AUTHENTICATION";
+
+    //~ Methods ================================================================
+
+    public Object extractFromContainer(ServletRequest request) {
+        if (request instanceof HttpServletRequest) {
+            HttpSession httpSession = ((HttpServletRequest) request).getSession();
+
+            if (httpSession != null) {
+                Object authObject = httpSession.getAttribute(ACEGI_SECURITY_AUTHENTICATION_KEY);
+
+                if (authObject instanceof Authentication) {
+                    return authObject;
+                }
+            }
+        }
+
+        return null;
+    }
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/ui/webapp/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Authenticates users via a standard web form and <code>HttpSession</code>.
+</body>
+</html>