From 738fd2161d9acd63ff002e80103e8bc72d6d2e5f Mon Sep 17 00:00:00 2001
From: Ben Alex
+ * The AbstractSecurityInterceptor
will ensure the proper startup
+ * configuration of the security interceptor. It will also implement the
+ * proper handling of secure object invocations, being:
+ *
+ *
+ *
+ * null
objects.
+ * SecureContext
.
+ * ConfigAttributeDefinition
for the secure object invocation):
+ *
+ *
+ *
+ *
+ * Authentication
object on
+ * the ContextHolder
with the returned value.
+ * RunAsManager
replaced the Authentication
+ * object, return the ContextHolder
to the object that existed
+ * after the call to AuthenticationManager
.
+ * ConfigAttributeDefinition
for the secure object invocation):
+ *
+ *
+ *
+ *
+ * ContextHolder
contains a SecureContext
, set
+ * the isAuthenticated
flag on the Authentication
+ * object to false.
+ * SecurityInterceptorCallback
to the
+ * method that called {@link AbstractSecurityInterceptor#interceptor(Object,
+ * SecurityInterceptorCallback)}. This is almost always a concrete subclass of
+ * the AbstractSecurityInterceptor
.
+ *
+ * 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 ConfigAttributeDefinition
s defined
+ * by the implementing class.
+ *
+ *
+ * This is used by the {@link AbstractSecurityInterceptor} to perform
+ * startup time validation of each ConfigAttribute
configured
+ * against it.
+ *
ConfigAttributeDefinition
s
+ * or null
if unsupported
+ */
+ public Iterator getConfigAttributeDefinitions();
+
+ /**
+ * Indicates whether the ObjectDefinitionSource
implementation
+ * is able to provide ConfigAttributeDefinition
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);
+}
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.
+ *
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
.
+ *
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
+ * MethodInvocation
s.
+ *
+ * @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 MethodInvocation
s, 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
.
+ *
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 aList
of ConfigAttributeDefinition
s
+ * 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.
+ *
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: + * + *
appContextLocation
indicates the path to an application context
+ * that contains the FilterSecurityInterceptor
.
+ * loginFormUrl
indicates the URL that should be used for
+ * redirection if an AuthenticationException
is detected.
+ * 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.
+ *
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
).
+ *
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
.
+ *
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: + * + *
appContextLocation
indicates the path to an application context
+ * that contains an {@link AuthenticationManager} that should be used to
+ * process each authentication request.
+ * defaultTargetUrl
indicates the URL that should be used for
+ * redirection if the HttpSession
attribute named {@link
+ * #ACEGI_SECURITY_TARGET_URL_KEY} does not indicate the target URL once
+ * authentication is completed successfully. eg: /
.
+ * authenticationFailureUrl
indicates the URL that should be used
+ * for redirection if the authentication request fails. eg:
+ * /login.jsp?login_error=1
.
+ * filterProcessesUrl
indicates the URL that this filter will
+ * respond to. This parameter is optional, and defaults to
+ * /j_acegi_security_check
.
+ * 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 AbstractIntegrationFilter
s, 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 andHttpSession
.
+
+