diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java index 83bee24bb6..4877c0b5bf 100644 --- a/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java +++ b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java @@ -25,11 +25,14 @@ import net.sf.acegisecurity.acl.AclManager; import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry; import net.sf.acegisecurity.acl.basic.SimpleAclEntry; +import org.apache.commons.collections.iterators.ArrayIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; +import java.lang.reflect.Array; + import java.util.Collection; import java.util.HashSet; import java.util.Iterator; @@ -168,25 +171,22 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider return null; } - if (!(returnedObject instanceof Collection)) { + Filterer filterer = null; + + if (returnedObject instanceof Collection) { + Collection collection = (Collection) returnedObject; + filterer = new CollectionFilterer(collection); + } else if (returnedObject.getClass().isArray()) { + Object[] array = (Object[]) returnedObject; + filterer = new ArrayFilterer(array); + } else { throw new AuthorizationServiceException( - "A Collection (or null) was required as the returnedObject, but the returnedObject was: " + "A Collection or an array (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(); + Iterator collectionIter = filterer.iterator(); while (collectionIter.hasNext()) { Object domainObject = collectionIter.next(); @@ -228,7 +228,7 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider } if (!hasPermission) { - removeList.add(domainObject); + filterer.remove(domainObject); if (logger.isDebugEnabled()) { logger.debug( @@ -238,22 +238,7 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider } } - // Now the Iterator has ended, remove Objects from Collection - Iterator removeIter = removeList.iterator(); - - int originalSize = collection.size(); - - while (removeIter.hasNext()) { - collection.remove(removeIter.next()); - } - - if (logger.isDebugEnabled()) { - logger.debug("Original collection contained " - + originalSize + " elements; now contains " - + collection.size() + " elements"); - } - - return collection; + return filterer.getFilteredObject(); } } @@ -281,3 +266,170 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider return true; } } + + +/** + * Filter strategy interface. + */ +interface Filterer { + //~ Methods ================================================================ + + /** + * Gets the filtered collection or array. + * + * @return the filtered collection or array + */ + public Object getFilteredObject(); + + /** + * Returns an iterator over the filtered collection or array. + * + * @return an Iterator + */ + public Iterator iterator(); + + /** + * Removes the the given object from the resulting list. + * + * @param object the object to be removed + */ + public void remove(Object object); +} + + +/** + * A filter used to filter Collections. + */ +class CollectionFilterer implements Filterer { + //~ Static fields/initializers ============================================= + + protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class); + + //~ Instance fields ======================================================== + + private Collection collection; + private Set removeList; + + //~ Constructors =========================================================== + + CollectionFilterer(Collection collection) { + this.collection = collection; + + // 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) + removeList = new HashSet(); + } + + //~ Methods ================================================================ + + /** + * @see net.sf.acegisecurity.afterinvocation.Filterer#getFilteredObject() + */ + public Object getFilteredObject() { + // Now the Iterator has ended, remove Objects from Collection + Iterator removeIter = removeList.iterator(); + + int originalSize = collection.size(); + + while (removeIter.hasNext()) { + collection.remove(removeIter.next()); + } + + if (logger.isDebugEnabled()) { + logger.debug("Original collection contained " + originalSize + + " elements; now contains " + collection.size() + " elements"); + } + + return collection; + } + + /** + * @see net.sf.acegisecurity.afterinvocation.Filterer#iterator() + */ + public Iterator iterator() { + return collection.iterator(); + } + + /** + * @see net.sf.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object) + */ + public void remove(Object object) { + removeList.add(object); + } +} + + +/** + * A filter used to filter arrays. + */ +class ArrayFilterer implements Filterer { + //~ Static fields/initializers ============================================= + + protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class); + + //~ Instance fields ======================================================== + + private Set removeList; + private Object[] list; + + //~ Constructors =========================================================== + + ArrayFilterer(Object[] list) { + this.list = list; + + // Collect the removed objects to a HashSet so that + // it is fast to lookup them when a filtered array + // is constructed. + removeList = new HashSet(); + } + + //~ Methods ================================================================ + + /** + * @see net.sf.acegisecurity.afterinvocation.Filterer#getFilteredObject() + */ + public Object getFilteredObject() { + // Recreate an array of same type and filter the removed objects. + int originalSize = list.length; + int sizeOfResultingList = originalSize - removeList.size(); + Object[] filtered = (Object[]) Array.newInstance(list.getClass() + .getComponentType(), + sizeOfResultingList); + + for (int i = 0, j = 0; i < list.length; i++) { + Object object = list[i]; + + if (!removeList.contains(object)) { + filtered[j] = object; + j++; + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Original array contained " + originalSize + + " elements; now contains " + sizeOfResultingList + + " elements"); + } + + return filtered; + } + + /** + * @see net.sf.acegisecurity.afterinvocation.Filterer#iterator() + */ + public Iterator iterator() { + return new ArrayIterator(list); + } + + /** + * @see net.sf.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object) + */ + public void remove(Object object) { + removeList.add(object); + } +} diff --git a/core/src/test/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProviderTests.java b/core/src/test/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProviderTests.java index 9d4bc3e160..339d527af6 100644 --- a/core/src/test/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProviderTests.java +++ b/core/src/test/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProviderTests.java @@ -167,6 +167,44 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProviderTests assertEquals("belmont", filteredList.get(0)); } + public void testCorrectOperationWhenReturnedObjectIsArray() + throws Exception { + // Create an AclManager + AclManager aclManager = new MockAclManager("belmont", "marissa", + new AclEntry[] {new MockAclEntry(), new SimpleAclEntry( + "marissa", new MockAclObjectIdentity(), null, + SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry( + "marissa", new MockAclObjectIdentity(), null, + SimpleAclEntry.READ), new SimpleAclEntry("marissa", + new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)}); + + BasicAclEntryAfterInvocationCollectionFilteringProvider provider = new BasicAclEntryAfterInvocationCollectionFilteringProvider(); + provider.setAclManager(aclManager); + assertEquals(aclManager, provider.getAclManager()); + provider.afterPropertiesSet(); + + // Create a Collection containing many items, which only "belmont" + // should remain in after filtering by provider + String[] list = new String[4]; + list[0] = "sydney"; + list[1] = "melbourne"; + list[2] = "belmont"; + list[3] = "brisbane"; + + // Create the Authentication and Config Attribs we'll be presenting + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("marissa", + "NOT_USED"); + ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); + attr.addConfigAttribute(new SecurityConfig("AFTER_ACL_COLLECTION_READ")); + + // Filter + String[] filteredList = (String[]) provider.decide(auth, + new MockMethodInvocation(), attr, list); + + assertEquals(1, filteredList.length); + assertEquals("belmont", filteredList[0]); + } + public void testDetectsIfReturnedObjectIsNotACollection() throws Exception { // Create an AclManager diff --git a/project.xml b/project.xml index b866fca1e7..06cb790e3d 100644 --- a/project.xml +++ b/project.xml @@ -129,6 +129,9 @@ Orlando Garcia Carmona + + Joni Suominen + @@ -151,16 +154,6 @@ true - - jetty - org.mortbay.jetty - 4.2.22 - jar - http://jetty.mortbay.org - - true - - retroweaver retroweaver @@ -258,6 +251,16 @@ 3.8.1 jar + + jetty + org.mortbay.jetty + 4.2.22 + jar + http://jetty.mortbay.org + + true + + springframework spring-core