From 738fd2161d9acd63ff002e80103e8bc72d6d2e5f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 2 Apr 2004 12:02:01 +0000 Subject: [PATCH] Initial commit. --- .../AbstractSecurityInterceptor.java | 366 ++++++++++++++++++ .../intercept/ObjectDefinitionSource.java | 75 ++++ .../SecurityInterceptorCallback.java | 50 +++ .../AbstractMethodDefinitionSource.java | 73 ++++ .../method/MethodDefinitionAttributes.java | 162 ++++++++ .../intercept/method/MethodDefinitionMap.java | 187 +++++++++ .../method/MethodDefinitionSource.java | 29 ++ .../method/MethodDefinitionSourceEditor.java | 82 ++++ .../method/MethodSecurityInterceptor.java | 92 +++++ .../intercept/method/package.html | 6 + .../org/acegisecurity/intercept/package.html | 31 ++ ...tractFilterInvocationDefinitionSource.java | 71 ++++ .../intercept/web/FilterInvocation.java | 109 ++++++ .../web/FilterInvocationDefinitionMap.java | 180 +++++++++ .../web/FilterInvocationDefinitionSource.java | 28 ++ ...ilterInvocationDefinitionSourceEditor.java | 120 ++++++ .../web/FilterSecurityInterceptor.java | 91 +++++ .../web/SecurityEnforcementFilter.java | 178 +++++++++ .../acegisecurity/intercept/web/package.html | 5 + .../ui/AbstractIntegrationFilter.java | 161 ++++++++ .../ui/AutoIntegrationFilter.java | 120 ++++++ .../java/org/acegisecurity/ui/package.html | 6 + .../AuthenticationProcessingFilter.java | 263 +++++++++++++ .../webapp/HttpSessionIntegrationFilter.java | 82 ++++ .../org/acegisecurity/ui/webapp/package.html | 5 + 25 files changed, 2572 insertions(+) create mode 100644 core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/ObjectDefinitionSource.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/SecurityInterceptorCallback.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/AbstractMethodDefinitionSource.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionAttributes.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSource.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditor.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/MethodSecurityInterceptor.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/method/package.html create mode 100644 core/src/main/java/org/acegisecurity/intercept/package.html create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/AbstractFilterInvocationDefinitionSource.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/FilterInvocation.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionMap.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSource.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditor.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java create mode 100644 core/src/main/java/org/acegisecurity/intercept/web/package.html create mode 100644 core/src/main/java/org/acegisecurity/ui/AbstractIntegrationFilter.java create mode 100644 core/src/main/java/org/acegisecurity/ui/AutoIntegrationFilter.java create mode 100644 core/src/main/java/org/acegisecurity/ui/package.html create mode 100644 core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java create mode 100644 core/src/main/java/org/acegisecurity/ui/webapp/HttpSessionIntegrationFilter.java create mode 100644 core/src/main/java/org/acegisecurity/ui/webapp/package.html diff --git a/core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java b/core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java new file mode 100644 index 0000000000..073ddeed67 --- /dev/null +++ b/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. + * + *

+ * The AbstractSecurityInterceptor will ensure the proper startup + * configuration of the security interceptor. It will also implement the + * proper handling of secure object invocations, being: + * + *

    + *
  1. + * Extract the {@link SecureContext} from the {@link ContextHolder}, handling + * any errors such as invalid or null objects. + *
  2. + *
  3. + * Obtain the {@link Authentication} object from the extracted + * SecureContext. + *
  4. + *
  5. + * Determine if the request relates to a secured or public invocation by + * looking up the secure object request against the {@link + * ObjectDefinitionSource}. + *
  6. + *
  7. + * For an invocation that is secured (there is a + * ConfigAttributeDefinition for the secure object invocation): + * + *
      + *
    1. + * Authenticate the request against the configured {@link + * AuthenticationManager}, replacing the Authentication object on + * the ContextHolder with the returned value. + *
    2. + *
    3. + * Authorize the request against the configured {@link AccessDecisionManager}. + *
    4. + *
    5. + * Perform any run-as replacement via the configured {@link RunAsManager}. + *
    6. + *
    7. + * Perform a callback to the {@link SecurityInterceptorCallback}, which will + * actually proceed with executing the object. + *
    8. + *
    9. + * If the RunAsManager replaced the Authentication + * object, return the ContextHolder to the object that existed + * after the call to AuthenticationManager. + *
    10. + *
    + * + *
  8. + *
  9. + * For an invocation that is public (there is no + * ConfigAttributeDefinition for the secure object invocation): + * + *
      + *
    1. + * If the ContextHolder contains a SecureContext, set + * the isAuthenticated flag on the Authentication + * object to false. + *
    2. + *
    3. + * Perform a callback to the {@link SecurityInterceptorCallback}, which will + * actually proceed with the invocation. + *
    4. + *
    + * + *
  10. + *
  11. + * Return the result from the SecurityInterceptorCallback to the + * method that called {@link AbstractSecurityInterceptor#interceptor(Object, + * SecurityInterceptorCallback)}. This is almost always a concrete subclass of + * the AbstractSecurityInterceptor. + *
  12. + *
+ *

+ * + * @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. + * + *

+ * Throws {@link net.sf.acegisecurity.AcegiSecurityException} and its + * subclasses. + *

+ * + * @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 + * SecurityInterceptorCallback + * + * @throws Throwable if any error occurs during the + * SecurityInterceptorCallback + * @throws IllegalArgumentException if a required argument was missing or + * invalid + * @throws AuthenticationCredentialsNotFoundException if the + * ContextHolder is not populated with a valid + * SecureContext + */ + 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); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/ObjectDefinitionSource.java b/core/src/main/java/org/acegisecurity/intercept/ObjectDefinitionSource.java new file mode 100644 index 0000000000..b97c9b0fbc --- /dev/null +++ b/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 ConfigAttributeDefinition that applies to a + * given secure object. + * + * @param object the object being secured + * + * @return the ConfigAttributeDefinition that applies to the + * passed object + * + * @throws IllegalArgumentException if the passed object is not of a type + * supported by the ObjectDefinitionSource + * implementation + */ + public ConfigAttributeDefinition getAttributes(Object object) + throws IllegalArgumentException; + + /** + * If available, all of the ConfigAttributeDefinitions defined + * by the implementing class. + * + *

+ * This is used by the {@link AbstractSecurityInterceptor} to perform + * startup time validation of each ConfigAttribute configured + * against it. + *

+ * + * @return an iterator over all the ConfigAttributeDefinitions + * or null if unsupported + */ + public Iterator getConfigAttributeDefinitions(); + + /** + * Indicates whether the ObjectDefinitionSource implementation + * is able to provide ConfigAttributeDefinitions 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); +} diff --git a/core/src/main/java/org/acegisecurity/intercept/SecurityInterceptorCallback.java b/core/src/main/java/org/acegisecurity/intercept/SecurityInterceptorCallback.java new file mode 100644 index 0000000000..2e1295e3c4 --- /dev/null +++ b/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. + * + *

+ * Concrete AbstractSecurityInterceptor subclasses are required to + * provide a SecurityInterceptorCallback. This is called by the + * AbstractSecurityInterceptor 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. + *

+ * + *

+ * The result from processing the secure object should be returned to the + * AbstractSecurityInterceptor, which in turn will ultimately + * return it to the calling class. + *

+ * + * @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; +} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/AbstractMethodDefinitionSource.java b/core/src/main/java/org/acegisecurity/intercept/method/AbstractMethodDefinitionSource.java new file mode 100644 index 0000000000..d189040b93 --- /dev/null +++ b/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 MethodDefinitionSource. + * + * @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 + * ConfigAttributeDefinition for the specified + * MethodInvocation. + * + *

+ * Provided so subclasses need only to provide one basic method to properly + * interface with the MethodDefinitionSource. + *

+ * + * @return the ConfigAttributeDefinition that applies to the + * specified MethodInvocation + */ + protected abstract ConfigAttributeDefinition lookupAttributes( + MethodInvocation mi); +} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionAttributes.java b/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionAttributes.java new file mode 100644 index 0000000000..89bdea224f --- /dev/null +++ b/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. + * + *

+ * This class will only detect those attributes which are defined for: + * + *

+ *

+ * + *

+ * 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. + *

+ * + *

+ * Attributes detected that do not implement {@link ConfigAttribute} will be + * ignored. + *

+ * + * @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); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java b/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java new file mode 100644 index 0000000000..206139014e --- /dev/null +++ b/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 * 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 * 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 * 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()))); + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSource.java b/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSource.java new file mode 100644 index 0000000000..219a7a91a9 --- /dev/null +++ b/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 ObjectDefinitionSource implementations + * that are designed to perform lookups keyed on + * MethodInvocations. + * + * @author Ben Alex + * @version $Id$ + */ +public interface MethodDefinitionSource extends ObjectDefinitionSource {} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditor.java b/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditor.java new file mode 100644 index 0000000000..7d05d1345a --- /dev/null +++ b/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}. + * + *

+ * The class creates and populates a {@link MethodDefinitionMap}. + *

+ * + * @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); + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/MethodSecurityInterceptor.java b/core/src/main/java/org/acegisecurity/intercept/method/MethodSecurityInterceptor.java new file mode 100644 index 0000000000..779ebd4622 --- /dev/null +++ b/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. + * + *

+ * The ObjectDefinitionSource required by this security + * interceptor is of type {@link MethodDefinitionSource}. + *

+ * + *

+ * Refer to {@link AbstractSecurityInterceptor} for details on the workflow. + *

+ * + * @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 + * MethodInvocation. + * + * @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(); + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/method/package.html b/core/src/main/java/org/acegisecurity/intercept/method/package.html new file mode 100644 index 0000000000..6b7bd51b8b --- /dev/null +++ b/core/src/main/java/org/acegisecurity/intercept/method/package.html @@ -0,0 +1,6 @@ + + +Enforces security for MethodInvocations, such as via +Spring AOP. + + diff --git a/core/src/main/java/org/acegisecurity/intercept/package.html b/core/src/main/java/org/acegisecurity/intercept/package.html new file mode 100644 index 0000000000..53137b0094 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/intercept/package.html @@ -0,0 +1,31 @@ + + +Actually enforces the security and ties the whole security system together. +

+A secure object is a term frequently used throughout the security +system. It does not 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 +MethodInvocation, 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. +

+ +

Each secure object typically has its +own net.sf.acegisecurity.intercept 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 ObjectDefinitionSource. + +

It is simple to create new secure object types, given the +AbstractSecurityInterceptor provides the majority of the logic +and other specialised packages provide the authentication, authorization, +run-as replacement management and ContextHolder population. + + + diff --git a/core/src/main/java/org/acegisecurity/intercept/web/AbstractFilterInvocationDefinitionSource.java b/core/src/main/java/org/acegisecurity/intercept/web/AbstractFilterInvocationDefinitionSource.java new file mode 100644 index 0000000000..cf552e0f89 --- /dev/null +++ b/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 FilterInvocationDefinitionSource. + * + * @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 + * ConfigAttributeDefinition for the specified + * FilterInvocation. + * + *

+ * Provided so subclasses need only to provide one basic method to properly + * interface with the FilterInvocationDefinitionSource. + *

+ * + * @return the ConfigAttributeDefinition that applies to the + * specified FilterInvocation + */ + protected abstract ConfigAttributeDefinition lookupAttributes( + FilterInvocation filterInvocation); +} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocation.java b/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocation.java new file mode 100644 index 0000000000..08cff383b3 --- /dev/null +++ b/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. + * + *

+ * Guarantees the request and response are instances of + * HttpServletRequest and HttpServletResponse, and + * that there are no null objects. + *

+ * + *

+ * Required so that security system classes can obtain access to the filter + * environment, as well as the request and response. + *

+ * + * @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(); + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionMap.java b/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionMap.java new file mode 100644 index 0000000000..f02394144a --- /dev/null +++ b/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 List of ConfigAttributeDefinitions + * associated with different HTTP request URL patterns. + * + *

+ * Regular expressions are used to match a HTTP request URL against a + * ConfigAttributeDefinition. + *

+ * + *

+ * The order of registering the regular expressions using the {@link + * #addSecureUrl(String, ConfigAttributeDefinition)} is very important. The + * system will identify the first 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. + *

+ * + *

+ * If no registered regular expressions match the HTTP URL, null + * is returned. + *

+ */ +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; + } + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSource.java b/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSource.java new file mode 100644 index 0000000000..58729be8d5 --- /dev/null +++ b/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 ObjectDefinitionSource implementations + * that are designed to perform lookups keyed on {@link FilterInvocation}s. + * + * @author Ben Alex + * @version $Id$ + */ +public interface FilterInvocationDefinitionSource extends ObjectDefinitionSource {} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditor.java b/core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditor.java new file mode 100644 index 0000000000..3f4c10ba43 --- /dev/null +++ b/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}. + * + *

+ * The class creates and populates a {@link FilterInvocationDefinitionMap}. + *

+ * + * @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); + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java b/core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java new file mode 100644 index 0000000000..1873cce939 --- /dev/null +++ b/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. + * + *

+ * End users should only use this class to configure their HTTP security + * configuration in an application context. They should not attempt to + * invoke the FilterSecurityInterceptor except as a standard bean + * registration in an application context. At runtime, this class will provide + * services to web applications via the {@link SecurityEnforcementFilter}. + *

+ * + *

+ * The ObjectDefinitionSource required by this security + * interceptor is of type {@link FilterInvocationDefinitionSource}. + *

+ * + *

+ * Refer to {@link AbstractSecurityInterceptor} for details on the workflow. + *

+ * + * @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; + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java b/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java new file mode 100644 index 0000000000..c6d7b24dfc --- /dev/null +++ b/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}. + * + *

+ * This filter is necessary because it provides an application context + * environment for the FilterSecurityInterceptor instance. + *

+ * + *

+ * If a {@link AuthenticationException} is detected, the filter will redirect + * to the loginFormUrl. This allows common handling of + * authentication failures originating from any subclass of {@link + * net.sf.acegisecurity.intercept.AbstractSecurityInterceptor}. + *

+ * + *

+ * If an {@link AccessDeniedException} is detected, the filter will response + * with a HttpServletResponse.SC_FORBIDDEN (403 error). Again, + * this allows common access denied handling irrespective of the originating + * security interceptor. + *

+ * + *

+ * To use this filter, it is necessary to specify the following filter + * initialization parameters: + * + *

+ *

+ * + * @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 + * AuthenticationException 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); + } +} diff --git a/core/src/main/java/org/acegisecurity/intercept/web/package.html b/core/src/main/java/org/acegisecurity/intercept/web/package.html new file mode 100644 index 0000000000..cae66192f1 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/intercept/web/package.html @@ -0,0 +1,5 @@ + + +Enforces security for HTTP requests, typically by the URL requested. + + diff --git a/core/src/main/java/org/acegisecurity/ui/AbstractIntegrationFilter.java b/core/src/main/java/org/acegisecurity/ui/AbstractIntegrationFilter.java new file mode 100644 index 0000000000..ddca1baad3 --- /dev/null +++ b/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 Authentication object. + * + *

+ * 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 Authentication 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. + *

+ * + *

+ * Once the Authentication object has been extracted from the + * well-known location, the AbstractIntegrationFilter handles + * putting it into the {@link ContextHolder}. It then removes it once the + * filter chain has completed. + *

+ * + *

+ * This filter will not abort if an Authentication object cannot + * be obtained from the well-known location. It will simply continue the + * filter chain as normal. + *

+ * + * @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 Object + * that contains the Authentication interface. + * + *

+ * For convenience we have allowed any Object to be returned + * by subclasses, as the abstract class will ensure class casting safety + * and ignore objects that do not implement Authentication. + *

+ * + *

+ * If no Authentication object is available, subclasses should + * return null. + *

+ * + *

+ * 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 Authentication). + *

+ * + * @param request the request, which may be of use in extracting the + * authentication object + * + * @return null or an object that implements + * Authentication + */ + public abstract Object extractFromContainer(ServletRequest request); + + public void init(FilterConfig filterConfig) throws ServletException {} +} diff --git a/core/src/main/java/org/acegisecurity/ui/AutoIntegrationFilter.java b/core/src/main/java/org/acegisecurity/ui/AutoIntegrationFilter.java new file mode 100644 index 0000000000..e5cf18aa58 --- /dev/null +++ b/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}. + * + *

+ * This eases the creation of portable, secure applications, as the + * web.xml will not need to refer to a specific integration + * filter. + *

+ * + *

+ * The filter automatically delegates to {@link HttpRequestIntegrationFilter} + * if any Authentication object is detected in the + * ServletRequest. Failing this, it will delegate to {@link + * HttpSessionIntegrationFilter} if the session object contains an + * Authentication object. Finally, this filter will delegate to + * {@link JbossIntegrationFilter} if the ServletRequest contains + * an instance of JBoss' SimplePrincipal. + *

+ * + *

+ * If no location can be found containing the Authentication + * object, it will return null. + *

+ * + * @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 + * HttpRequestIntegrationFilter. + * + * @return the HttpRequestIntegrationFilter to use + */ + protected HttpRequestIntegrationFilter getHttpServletRequest() { + return new HttpRequestIntegrationFilter(); + } + + /** + * Allows test case to override the source of + * HttpSessionIntegrationFilter. + * + * @return the HttpRequestIntegrationFilter to use + */ + protected HttpSessionIntegrationFilter getHttpSessionIntegrationFilter() { + return new HttpSessionIntegrationFilter(); + } + + /** + * Allows test case to override the source of + * JbossIntegrationFilter. + * + * @return the JbossIntegrationFilter to use + */ + protected JbossIntegrationFilter getJbossIntegrationFilter() { + return new JbossIntegrationFilter(); + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/package.html b/core/src/main/java/org/acegisecurity/ui/package.html new file mode 100644 index 0000000000..6d9b03a0b4 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/package.html @@ -0,0 +1,6 @@ + + +Interfaces the system with various end-user authentication approaches, such +as container adapters and web applications. + + diff --git a/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java new file mode 100644 index 0000000000..c9d4185e59 --- /dev/null +++ b/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 + * HttpSession. + * + *

+ * 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 HttpSession + * with the attribute defined by {@link + * HttpSessionIntegrationFilter#ACEGI_SECURITY_AUTHENTICATION_KEY}. + *

+ * + *

+ * 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. + *

+ * + *

+ * If authentication fails, the AuthenticationException will be + * placed into the HttpSession with the attribute defined by + * {@link #ACEGI_SECURITY_LAST_EXCEPTION_KEY}. + *

+ * + *

+ * To use this filter, it is necessary to specify the following filter + * initialization parameters: + * + *

+ *

+ * + * @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 null + */ + private String defaultTargetUrl; + + /** + * The URL destination that this filter intercepts and processes (usually + * /j_acegi_security_check) + */ + 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); + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/webapp/HttpSessionIntegrationFilter.java b/core/src/main/java/org/acegisecurity/ui/webapp/HttpSessionIntegrationFilter.java new file mode 100644 index 0000000000..d04f5420ea --- /dev/null +++ b/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 + * HttpSession. + * + *

+ * The filter will inspect the HttpSession 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 ContextHolder. + *

+ * + *

+ * This filter is normally used in conjunction with {@link + * AuthenticationProcessingFilter}, which populates the + * HttpSession with an Authentication object based + * on a form login. Alternatively, users may elect to use their own approach + * for populating the HttpSession. + *

+ * + *

+ * As with other AbstractIntegrationFilters, this filter will + * ensure the ContextHolder is populated with the + * Authentication object for the duration of the HTTP request, + * and is unbound from the ContextHolder at the completion of the + * request. + *

+ * + *

+ * See {@link AbstractIntegrationFilter} for further information. + *

+ * + * @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; + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/webapp/package.html b/core/src/main/java/org/acegisecurity/ui/webapp/package.html new file mode 100644 index 0000000000..1fd2d439e8 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/webapp/package.html @@ -0,0 +1,5 @@ + + +Authenticates users via a standard web form and HttpSession. + +