Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 439   Methods: 18
NCLOC: 204   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
BasicAclEntryAfterInvocationCollectionFilteringProvider.java 84.8% 91.3% 100% 90.3%
coverage coverage
 1    /* Copyright 2004, 2005 Acegi Technology Pty Limited
 2    *
 3    * Licensed under the Apache License, Version 2.0 (the "License");
 4    * you may not use this file except in compliance with the License.
 5    * You may obtain a copy of the License at
 6    *
 7    * http://www.apache.org/licenses/LICENSE-2.0
 8    *
 9    * Unless required by applicable law or agreed to in writing, software
 10    * distributed under the License is distributed on an "AS IS" BASIS,
 11    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    * See the License for the specific language governing permissions and
 13    * limitations under the License.
 14    */
 15   
 16    package org.acegisecurity.afterinvocation;
 17   
 18    import org.acegisecurity.AccessDeniedException;
 19    import org.acegisecurity.Authentication;
 20    import org.acegisecurity.AuthorizationServiceException;
 21    import org.acegisecurity.ConfigAttribute;
 22    import org.acegisecurity.ConfigAttributeDefinition;
 23    import org.acegisecurity.acl.AclEntry;
 24    import org.acegisecurity.acl.AclManager;
 25    import org.acegisecurity.acl.basic.BasicAclEntry;
 26    import org.acegisecurity.acl.basic.SimpleAclEntry;
 27   
 28    import org.apache.commons.collections.iterators.ArrayIterator;
 29    import org.apache.commons.logging.Log;
 30    import org.apache.commons.logging.LogFactory;
 31   
 32    import org.springframework.beans.factory.InitializingBean;
 33   
 34    import org.springframework.util.Assert;
 35   
 36    import java.lang.reflect.Array;
 37   
 38    import java.util.Collection;
 39    import java.util.HashSet;
 40    import java.util.Iterator;
 41    import java.util.Set;
 42   
 43   
 44    /**
 45    * <p>
 46    * Given a <code>Collection</code> of domain object instances returned from a
 47    * secure object invocation, remove any <code>Collection</code> elements the
 48    * principal does not have appropriate permission to access as defined by the
 49    * {@link AclManager}.
 50    * </p>
 51    *
 52    * <p>
 53    * The <code>AclManager</code> is used to retrieve the access control list
 54    * (ACL) permissions associated with each <code>Collection</code> domain
 55    * object instance element for the current <code>Authentication</code> object.
 56    * This class is designed to process {@link AclEntry}s that are subclasses of
 57    * {@link org.acegisecurity.acl.basic.BasicAclEntry} only.
 58    * Generally these are obtained by using the {@link
 59    * org.acegisecurity.acl.basic.BasicAclProvider}.
 60    * </p>
 61    *
 62    * <p>
 63    * This after invocation provider will fire if any {@link
 64    * ConfigAttribute#getAttribute()} matches the {@link
 65    * #processConfigAttribute}. The provider will then lookup the ACLs from the
 66    * <code>AclManager</code> and ensure the principal is {@link
 67    * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for
 68    * at least one of the {@link #requirePermission}s for each
 69    * <code>Collection</code> element. If the principal does not have at least
 70    * one of the permissions, that element will not be included in the returned
 71    * <code>Collection</code>.
 72    * </p>
 73    *
 74    * <p>
 75    * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code>
 76    * with a {@link #processConfigAttribute} of
 77    * <code>AFTER_ACL_COLLECTION_READ</code> and a {@link #requirePermission} of
 78    * <code>SimpleAclEntry.READ</code>. These are also the defaults.
 79    * </p>
 80    *
 81    * <p>
 82    * The <code>AclManager</code> is allowed to return any implementations of
 83    * <code>AclEntry</code> it wishes. However, this provider will only be able
 84    * to validate against <code>BasicAclEntry</code>s, and thus a
 85    * <code>Collection</code> element will be filtered from the resulting
 86    * <code>Collection</code> if no <code>AclEntry</code> is of type
 87    * <code>BasicAclEntry</code>.
 88    * </p>
 89    *
 90    * <p>
 91    * If the provided <code>returnObject</code> is <code>null</code>, a
 92    * <code>null</code><code>Collection</code> will be returned. If the provided
 93    * <code>returnObject</code> is not a <code>Collection</code>, an {@link
 94    * AuthorizationServiceException} will be thrown.
 95    * </p>
 96    *
 97    * <p>
 98    * All comparisons and prefixes are case sensitive.
 99    * </p>
 100    *
 101    * @author Ben Alex
 102    * @author Paulo Neves
 103    * @version $Id: BasicAclEntryAfterInvocationCollectionFilteringProvider.java,v 1.7 2005/11/17 00:55:56 benalex Exp $
 104    */
 105    public class BasicAclEntryAfterInvocationCollectionFilteringProvider
 106    implements AfterInvocationProvider, InitializingBean {
 107    //~ Static fields/initializers =============================================
 108   
 109    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
 110   
 111    //~ Instance fields ========================================================
 112   
 113    private AclManager aclManager;
 114    private String processConfigAttribute = "AFTER_ACL_COLLECTION_READ";
 115    private int[] requirePermission = {SimpleAclEntry.READ};
 116   
 117    //~ Methods ================================================================
 118   
 119  10 public void setAclManager(AclManager aclManager) {
 120  10 this.aclManager = aclManager;
 121    }
 122   
 123  2 public AclManager getAclManager() {
 124  2 return aclManager;
 125    }
 126   
 127  2 public void setProcessConfigAttribute(String processConfigAttribute) {
 128  2 this.processConfigAttribute = processConfigAttribute;
 129    }
 130   
 131  12 public String getProcessConfigAttribute() {
 132  12 return processConfigAttribute;
 133    }
 134   
 135  2 public void setRequirePermission(int[] requirePermission) {
 136  2 this.requirePermission = requirePermission;
 137    }
 138   
 139  2 public int[] getRequirePermission() {
 140  2 return requirePermission;
 141    }
 142   
 143  11 public void afterPropertiesSet() throws Exception {
 144  11 Assert.notNull(processConfigAttribute,
 145    "A processConfigAttribute is mandatory");
 146  10 Assert.notNull(aclManager, "An aclManager is mandatory");
 147   
 148  9 if ((requirePermission == null) || (requirePermission.length == 0)) {
 149  1 throw new IllegalArgumentException(
 150    "One or more requirePermission entries is mandatory");
 151    }
 152    }
 153   
 154  9 public Object decide(Authentication authentication, Object object,
 155    ConfigAttributeDefinition config, Object returnedObject)
 156    throws AccessDeniedException {
 157  9 Iterator iter = config.getConfigAttributes();
 158   
 159  9 while (iter.hasNext()) {
 160  10 ConfigAttribute attr = (ConfigAttribute) iter.next();
 161   
 162  10 if (this.supports(attr)) {
 163    // Need to process the Collection for this invocation
 164  8 if (returnedObject == null) {
 165  1 if (logger.isDebugEnabled()) {
 166  0 logger.debug("Return object is null, skipping");
 167    }
 168   
 169  1 return null;
 170    }
 171   
 172  7 Filterer filterer = null;
 173   
 174  7 if (returnedObject instanceof Collection) {
 175  5 Collection collection = (Collection) returnedObject;
 176  5 filterer = new CollectionFilterer(collection);
 177  2 } else if (returnedObject.getClass().isArray()) {
 178  1 Object[] array = (Object[]) returnedObject;
 179  1 filterer = new ArrayFilterer(array);
 180    } else {
 181  1 throw new AuthorizationServiceException(
 182    "A Collection or an array (or null) was required as the returnedObject, but the returnedObject was: "
 183    + returnedObject);
 184    }
 185   
 186    // Locate unauthorised Collection elements
 187  6 Iterator collectionIter = filterer.iterator();
 188   
 189  6 while (collectionIter.hasNext()) {
 190  24 Object domainObject = collectionIter.next();
 191   
 192  24 boolean hasPermission = false;
 193   
 194  24 AclEntry[] acls = null;
 195   
 196  24 if (domainObject == null) {
 197  0 hasPermission = true;
 198    } else {
 199  24 acls = aclManager.getAcls(domainObject, authentication);
 200    }
 201   
 202  24 if ((acls != null) && (acls.length != 0)) {
 203  5 for (int i = 0; i < acls.length; i++) {
 204    // Locate processable AclEntrys
 205  13 if (acls[i] instanceof BasicAclEntry) {
 206  9 BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
 207   
 208    // See if principal has any of the required permissions
 209  9 for (int y = 0; y < requirePermission.length;
 210    y++) {
 211  9 if (processableAcl.isPermitted(
 212    requirePermission[y])) {
 213  4 hasPermission = true;
 214   
 215  4 if (logger.isDebugEnabled()) {
 216  0 logger.debug(
 217    "Principal is authorised for element: "
 218    + domainObject
 219    + " due to ACL: "
 220    + processableAcl.toString());
 221    }
 222    }
 223    }
 224    }
 225    }
 226    }
 227   
 228  24 if (!hasPermission) {
 229  20 filterer.remove(domainObject);
 230   
 231  20 if (logger.isDebugEnabled()) {
 232  0 logger.debug(
 233    "Principal is NOT authorised for element: "
 234    + domainObject);
 235    }
 236    }
 237    }
 238   
 239  6 return filterer.getFilteredObject();
 240    }
 241    }
 242   
 243  1 return returnedObject;
 244    }
 245   
 246  10 public boolean supports(ConfigAttribute attribute) {
 247  10 if ((attribute.getAttribute() != null)
 248    && attribute.getAttribute().equals(getProcessConfigAttribute())) {
 249  8 return true;
 250    } else {
 251  2 return false;
 252    }
 253    }
 254   
 255    /**
 256    * This implementation supports any type of class, because it does not
 257    * query the presented secure object.
 258    *
 259    * @param clazz the secure object
 260    *
 261    * @return always <code>true</code>
 262    */
 263  1 public boolean supports(Class clazz) {
 264  1 return true;
 265    }
 266    }
 267   
 268   
 269    /**
 270    * Filter strategy interface.
 271    */
 272    interface Filterer {
 273    //~ Methods ================================================================
 274   
 275    /**
 276    * Gets the filtered collection or array.
 277    *
 278    * @return the filtered collection or array
 279    */
 280    public Object getFilteredObject();
 281   
 282    /**
 283    * Returns an iterator over the filtered collection or array.
 284    *
 285    * @return an Iterator
 286    */
 287    public Iterator iterator();
 288   
 289    /**
 290    * Removes the the given object from the resulting list.
 291    *
 292    * @param object the object to be removed
 293    */
 294    public void remove(Object object);
 295    }
 296   
 297   
 298    /**
 299    * A filter used to filter Collections.
 300    */
 301    class CollectionFilterer implements Filterer {
 302    //~ Static fields/initializers =============================================
 303   
 304    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
 305   
 306    //~ Instance fields ========================================================
 307   
 308    private Collection collection;
 309   
 310    // collectionIter offers significant performance optimisations (as
 311    // per acegisecurity-developer mailing list conversation 19/5/05)
 312    private Iterator collectionIter;
 313    private Set removeList;
 314   
 315    //~ Constructors ===========================================================
 316   
 317  5 CollectionFilterer(Collection collection) {
 318  5 this.collection = collection;
 319   
 320    // We create a Set of objects to be removed from the Collection,
 321    // as ConcurrentModificationException prevents removal during
 322    // iteration, and making a new Collection to be returned is
 323    // problematic as the original Collection implementation passed
 324    // to the method may not necessarily be re-constructable (as
 325    // the Collection(collection) constructor is not guaranteed and
 326    // manually adding may lose sort order or other capabilities)
 327  5 removeList = new HashSet();
 328    }
 329   
 330    //~ Methods ================================================================
 331   
 332    /**
 333    * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
 334    */
 335  5 public Object getFilteredObject() {
 336    // Now the Iterator has ended, remove Objects from Collection
 337  5 Iterator removeIter = removeList.iterator();
 338   
 339  5 int originalSize = collection.size();
 340   
 341  5 while (removeIter.hasNext()) {
 342  0 collection.remove(removeIter.next());
 343    }
 344   
 345  5 if (logger.isDebugEnabled()) {
 346  0 logger.debug("Original collection contained " + originalSize
 347    + " elements; now contains " + collection.size() + " elements");
 348    }
 349   
 350  5 return collection;
 351    }
 352   
 353    /**
 354    * @see org.acegisecurity.afterinvocation.Filterer#iterator()
 355    */
 356  5 public Iterator iterator() {
 357  5 collectionIter = collection.iterator();
 358   
 359  5 return collectionIter;
 360    }
 361   
 362    /**
 363    * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
 364    */
 365  17 public void remove(Object object) {
 366  17 collectionIter.remove();
 367    }
 368    }
 369   
 370   
 371    /**
 372    * A filter used to filter arrays.
 373    */
 374    class ArrayFilterer implements Filterer {
 375    //~ Static fields/initializers =============================================
 376   
 377    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
 378   
 379    //~ Instance fields ========================================================
 380   
 381    private Set removeList;
 382    private Object[] list;
 383   
 384    //~ Constructors ===========================================================
 385   
 386  1 ArrayFilterer(Object[] list) {
 387  1 this.list = list;
 388   
 389    // Collect the removed objects to a HashSet so that
 390    // it is fast to lookup them when a filtered array
 391    // is constructed.
 392  1 removeList = new HashSet();
 393    }
 394   
 395    //~ Methods ================================================================
 396   
 397    /**
 398    * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
 399    */
 400  1 public Object getFilteredObject() {
 401    // Recreate an array of same type and filter the removed objects.
 402  1 int originalSize = list.length;
 403  1 int sizeOfResultingList = originalSize - removeList.size();
 404  1 Object[] filtered = (Object[]) Array.newInstance(list.getClass()
 405    .getComponentType(),
 406    sizeOfResultingList);
 407   
 408  1 for (int i = 0, j = 0; i < list.length; i++) {
 409  4 Object object = list[i];
 410   
 411  4 if (!removeList.contains(object)) {
 412  1 filtered[j] = object;
 413  1 j++;
 414    }
 415    }
 416   
 417  1 if (logger.isDebugEnabled()) {
 418  0 logger.debug("Original array contained " + originalSize
 419    + " elements; now contains " + sizeOfResultingList
 420    + " elements");
 421    }
 422   
 423  1 return filtered;
 424    }
 425   
 426    /**
 427    * @see org.acegisecurity.afterinvocation.Filterer#iterator()
 428    */
 429  1 public Iterator iterator() {
 430  1 return new ArrayIterator(list);
 431    }
 432   
 433    /**
 434    * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
 435    */
 436  3 public void remove(Object object) {
 437  3 removeList.add(object);
 438    }
 439    }