diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java
new file mode 100644
index 0000000000..143426aa17
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java
@@ -0,0 +1,68 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+
+/**
+ * Indicates a class is responsible for participating in an {@link
+ * AfterInvocationProviderManager} decision.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AfterInvocationProvider {
+ //~ Methods ================================================================
+
+ public Object decide(Authentication authentication, Object object,
+ ConfigAttributeDefinition config, Object returnedObject)
+ throws AccessDeniedException;
+
+ /**
+ * Indicates whether this AfterInvocationProvider is able to
+ * participate in a decision involving the passed
+ * ConfigAttribute.
+ *
+ *
+ * This allows the AbstractSecurityInterceptor to check every
+ * configuration attribute can be consumed by the configured
+ * AccessDecisionManager and/or RunAsManager
+ * and/or AccessDecisionManager.
+ *
AbstractSecurityInterceptor
+ *
+ * @return true if this AfterInvocationProvider can support
+ * the passed configuration attribute
+ */
+ public boolean supports(ConfigAttribute attribute);
+
+ /**
+ * Indicates whether the AfterInvocationProvider is able to
+ * provide "after invocation" processing for the indicated secured object
+ * type.
+ *
+ * @param clazz the class of secure object 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/afterinvocation/AfterInvocationProviderManager.java b/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProviderManager.java
new file mode 100644
index 0000000000..c7bc8de911
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProviderManager.java
@@ -0,0 +1,167 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.AfterInvocationManager;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Provider-based implementation of {@link AfterInvocationManager}.
+ *
+ * + * Handles configuration of a bean context defined list of {@link + * AfterInvocationProvider}s. + *
+ * + *
+ * Every AfterInvocationProvider will be polled when the {@link
+ * #decide(Authentication, Object, ConfigAttributeDefinition, Object)} method
+ * is called. The Object returned from each provider will be
+ * presented to the successive provider for processing. This means each
+ * provider must ensure they return the Object, even if
+ * they are not interested in the "after invocation" decision (perhaps as the
+ * secure object invocation did not include a configuration attribute a given
+ * provider is configured to respond to).
+ *
AfterInvocationProviders and ensures
+ * each can support the presented class.
+ *
+ *
+ * If one or more providers cannot support the presented class,
+ * false is returned.
+ *
AfterInvocationProviderManager can support
+ * the secure object class, which requires every one of its
+ * AfterInvocationProviders to support the secure
+ * object class
+ */
+ public boolean supports(Class clazz) {
+ Iterator iter = this.providers.iterator();
+
+ while (iter.hasNext()) {
+ AfterInvocationProvider provider = (AfterInvocationProvider) iter
+ .next();
+
+ if (!provider.supports(clazz)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void checkIfValidList(List listToCheck) {
+ if ((listToCheck == null) || (listToCheck.size() == 0)) {
+ throw new IllegalArgumentException(
+ "A list of AfterInvocationProviders is required");
+ }
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java
new file mode 100644
index 0000000000..d8c2f8809b
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java
@@ -0,0 +1,250 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthorizationServiceException;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.AclManager;
+import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+
+/**
+ *
+ * Given a Collection of domain object instances returned from a
+ * secure object invocation, remove any Collection elements the
+ * principal does not have appropriate permission to access as defined by the
+ * {@link AclManager}.
+ *
+ * The AclManager is used to retrieve the access control list
+ * (ACL) permissions associated with each Collection domain
+ * object instance element for the current Authentication object.
+ * This class is designed to process {@link AclEntry}s that are subclasses of
+ * {@link net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry} only.
+ * Generally these are obtained by using the {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
+ *
+ * This after invocation provider will fire if any {@link
+ * ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then lookup the ACLs from the
+ * AclManager and ensure the principal is {@link
+ * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry#isPermitted(int)} for
+ * at least one of the {@link #requirePermission}s for each
+ * Collection element. If the principal does not have at least
+ * one of the permissions, that element will not be included in the returned
+ * Collection.
+ *
+ * Often users will setup a BasicAclEntryAfterInvocationProvider
+ * with a {@link #processConfigAttribute} of
+ * AFTER_ACL_COLLECTION_READ and a {@link #requirePermission} of
+ * SimpleAclEntry.READ. These are also the defaults.
+ *
+ * The AclManager is allowed to return any implementations of
+ * AclEntry it wishes. However, this provider will only be able
+ * to validate against AbstractBasicAclEntrys, and thus a
+ * Collection element will be filtered from the resulting
+ * Collection if no AclEntry is of type
+ * AbstractBasicAclEntry.
+ *
+ * If the provided returnObject is null, a
+ * nullCollection will be returned. If the provided
+ * returnObject is not a Collection, an {@link
+ * AuthorizationServiceException} will be thrown.
+ *
+ * All comparisons and prefixes are case sensitive. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class BasicAclEntryAfterInvocationCollectionFilteringProvider + implements AfterInvocationProvider, InitializingBean { + //~ Instance fields ======================================================== + + private AclManager aclManager; + private String processConfigAttribute = "AFTER_ACL_COLLECTION_READ"; + private int[] requirePermission = {SimpleAclEntry.READ}; + + //~ Methods ================================================================ + + public void setAclManager(AclManager aclManager) { + this.aclManager = aclManager; + } + + public AclManager getAclManager() { + return aclManager; + } + + public void setProcessConfigAttribute(String processConfigAttribute) { + this.processConfigAttribute = processConfigAttribute; + } + + public String getProcessConfigAttribute() { + return processConfigAttribute; + } + + public void setRequirePermission(int[] requirePermission) { + this.requirePermission = requirePermission; + } + + public int[] getRequirePermission() { + return requirePermission; + } + + public void afterPropertiesSet() throws Exception { + if (processConfigAttribute == null) { + throw new IllegalArgumentException( + "A processConfigAttribute is mandatory"); + } + + if ((requirePermission == null) || (requirePermission.length == 0)) { + throw new IllegalArgumentException( + "One or more requirePermission entries is mandatory"); + } + + if (aclManager == null) { + throw new IllegalArgumentException("An aclManager is mandatory"); + } + } + + public Object decide(Authentication authentication, Object object, + ConfigAttributeDefinition config, Object returnedObject) + throws AccessDeniedException { + Iterator iter = config.getConfigAttributes(); + + while (iter.hasNext()) { + ConfigAttribute attr = (ConfigAttribute) iter.next(); + + if (this.supports(attr)) { + // Need to process the Collection for this invocation + if (returnedObject == null) { + return null; + } + + if (!(returnedObject instanceof Collection)) { + throw new AuthorizationServiceException( + "A Collection (or null) was required as the returnedObject, but the returnedObject was: " + + returnedObject); + } + + Collection collection = (Collection) returnedObject; + + // We create a Set of objects to be removed from the Collection, + // as ConcurrentModificationException prevents removal during + // iteration, and making a new Collection to be returned is + // problematic as the original Collection implementation passed + // to the method may not necessarily be re-constructable (as + // the Collection(collection) constructor is not guaranteed and + // manually adding may lose sort order or other capabilities) + Set removeList = new HashSet(); + + // Locate unauthorised Collection elements + Iterator collectionIter = collection.iterator(); + + while (collectionIter.hasNext()) { + Object domainObject = collectionIter.next(); + + boolean hasPermission = false; + + AclEntry[] acls = null; + + if (domainObject == null) { + hasPermission = true; + } else { + acls = aclManager.getAcls(domainObject, authentication); + } + + if ((acls != null) && (acls.length != 0)) { + for (int i = 0; i < acls.length; i++) { + // Locate processable AclEntrys + if (acls[i] instanceof AbstractBasicAclEntry) { + AbstractBasicAclEntry processableAcl = (AbstractBasicAclEntry) acls[i]; + + // See if principal has any of the required permissions + for (int y = 0; y < requirePermission.length; + y++) { + if (processableAcl.isPermitted( + requirePermission[y])) { + hasPermission = true; + } + } + } + } + } + + if (!hasPermission) { + removeList.add(domainObject); + } + } + + // Now the Iterator has ended, remove Objects from Collection + Iterator removeIter = removeList.iterator(); + + while (removeIter.hasNext()) { + collection.remove(removeIter.next()); + } + + return collection; + } + } + + return returnedObject; + } + + public boolean supports(ConfigAttribute attribute) { + if ((attribute.getAttribute() != null) + && attribute.getAttribute().equals(getProcessConfigAttribute())) { + return true; + } else { + return false; + } + } + + /** + * This implementation supports any type of class, because it does not + * query the presented secure object. + * + * @param clazz the secure object + * + * @return alwaystrue
+ */
+ public boolean supports(Class clazz) {
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java
new file mode 100644
index 0000000000..73f555a1d2
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java
@@ -0,0 +1,211 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.AclManager;
+import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Iterator;
+
+
+/**
+ * + * Given a domain object instance returned from a secure object invocation, + * ensures the principal has appropriate permission as defined by the {@link + * AclManager}. + *
+ * + *
+ * The AclManager is used to retrieve the access control list
+ * (ACL) permissions associated with a domain object instance for the current
+ * Authentication object. This class is designed to process
+ * {@link AclEntry}s that are subclasses of {@link
+ * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry} only. Generally these
+ * are obtained by using the {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
+ *
+ * This after invocation provider will fire if any {@link
+ * ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then lookup the ACLs from the
+ * AclManager and ensure the principal is {@link
+ * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry#isPermitted(int)} for
+ * at least one of the {@link #requirePermission}s.
+ *
+ * Often users will setup a BasicAclEntryAfterInvocationProvider
+ * with a {@link #processConfigAttribute} of AFTER_ACL_READ and a
+ * {@link #requirePermission} of SimpleAclEntry.READ. These are
+ * also the defaults.
+ *
+ * If the principal does not have sufficient permissions, an
+ * AccessDeniedException will be thrown.
+ *
+ * The AclManager is allowed to return any implementations of
+ * AclEntry it wishes. However, this provider will only be able
+ * to validate against AbstractBasicAclEntrys, and thus access
+ * will be denied if no AclEntry is of type
+ * AbstractBasicAclEntry.
+ *
+ * If the provided returnObject is null, permission
+ * will always be granted and null will be returned.
+ *
+ * All comparisons and prefixes are case sensitive. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class BasicAclEntryAfterInvocationProvider + implements AfterInvocationProvider, InitializingBean { + //~ Instance fields ======================================================== + + private AclManager aclManager; + private String processConfigAttribute = "AFTER_ACL_READ"; + private int[] requirePermission = {SimpleAclEntry.READ}; + + //~ Methods ================================================================ + + public void setAclManager(AclManager aclManager) { + this.aclManager = aclManager; + } + + public AclManager getAclManager() { + return aclManager; + } + + public void setProcessConfigAttribute(String processConfigAttribute) { + this.processConfigAttribute = processConfigAttribute; + } + + public String getProcessConfigAttribute() { + return processConfigAttribute; + } + + public void setRequirePermission(int[] requirePermission) { + this.requirePermission = requirePermission; + } + + public int[] getRequirePermission() { + return requirePermission; + } + + public void afterPropertiesSet() throws Exception { + if (processConfigAttribute == null) { + throw new IllegalArgumentException( + "A processConfigAttribute is mandatory"); + } + + if ((requirePermission == null) || (requirePermission.length == 0)) { + throw new IllegalArgumentException( + "One or more requirePermission entries is mandatory"); + } + + if (aclManager == null) { + throw new IllegalArgumentException("An aclManager is mandatory"); + } + } + + public Object decide(Authentication authentication, Object object, + ConfigAttributeDefinition config, Object returnedObject) + throws AccessDeniedException { + Iterator iter = config.getConfigAttributes(); + + while (iter.hasNext()) { + ConfigAttribute attr = (ConfigAttribute) iter.next(); + + if (this.supports(attr)) { + // Need to make an access decision on this invocation + if (returnedObject == null) { + // AclManager interface contract prohibits nulls + // As they have permission to null/nothing, grant access + return null; + } + + AclEntry[] acls = aclManager.getAcls(returnedObject, + authentication); + + if ((acls == null) || (acls.length == 0)) { + throw new AccessDeniedException("Authentication: " + + authentication.toString() + + " has NO permissions at all to the domain object: " + + returnedObject); + } + + for (int i = 0; i < acls.length; i++) { + // Locate processable AclEntrys + if (acls[i] instanceof AbstractBasicAclEntry) { + AbstractBasicAclEntry processableAcl = (AbstractBasicAclEntry) acls[i]; + + // See if principal has any of the required permissions + for (int y = 0; y < requirePermission.length; y++) { + if (processableAcl.isPermitted(requirePermission[y])) { + return returnedObject; + } + } + } + } + + // No permissions match + throw new AccessDeniedException("Authentication: " + + authentication.toString() + + " has ACL permissions to the domain object, but not the required ACL permission to the domain object: " + + returnedObject); + } + } + + return returnedObject; + } + + public boolean supports(ConfigAttribute attribute) { + if ((attribute.getAttribute() != null) + && attribute.getAttribute().equals(getProcessConfigAttribute())) { + return true; + } else { + return false; + } + } + + /** + * This implementation supports any type of class, because it does not + * query the presented secure object. + * + * @param clazz the secure object + * + * @return alwaystrue
+ */
+ public boolean supports(Class clazz) {
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/package.html b/core/src/main/java/org/acegisecurity/afterinvocation/package.html
new file mode 100644
index 0000000000..9446663d6b
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/afterinvocation/package.html
@@ -0,0 +1,5 @@
+
+
+Used for post-processing of an object returned from a secure object invocation.
+
+