View Javadoc

    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     public void setAclManager(AclManager aclManager) {
    120         this.aclManager = aclManager;
    121     }
    122
    123     public AclManager getAclManager() {
    124         return aclManager;
    125     }
    126
    127     public void setProcessConfigAttribute(String processConfigAttribute) {
    128         this.processConfigAttribute = processConfigAttribute;
    129     }
    130
    131     public String getProcessConfigAttribute() {
    132         return processConfigAttribute;
    133     }
    134
    135     public void setRequirePermission(int[] requirePermission) {
    136         this.requirePermission = requirePermission;
    137     }
    138
    139     public int[] getRequirePermission() {
    140         return requirePermission;
    141     }
    142
    143     public void afterPropertiesSet() throws Exception {
    144         Assert.notNull(processConfigAttribute,
    145             "A processConfigAttribute is mandatory");
    146         Assert.notNull(aclManager, "An aclManager is mandatory");
    147
    148         if ((requirePermission == null) || (requirePermission.length == 0)) {
    149             throw new IllegalArgumentException(
    150                 "One or more requirePermission entries is mandatory");
    151         }
    152     }
    153
    154     public Object decide(Authentication authentication, Object object,
    155         ConfigAttributeDefinition config, Object returnedObject)
    156         throws AccessDeniedException {
    157         Iterator iter = config.getConfigAttributes();
    158
    159         while (iter.hasNext()) {
    160             ConfigAttribute attr = (ConfigAttribute) iter.next();
    161
    162             if (this.supports(attr)) {
    163                 // Need to process the Collection for this invocation
    164                 if (returnedObject == null) {
    165                     if (logger.isDebugEnabled()) {
    166                         logger.debug("Return object is null, skipping");
    167                     }
    168
    169                     return null;
    170                 }
    171
    172                 Filterer filterer = null;
    173
    174                 if (returnedObject instanceof Collection) {
    175                     Collection collection = (Collection) returnedObject;
    176                     filterer = new CollectionFilterer(collection);
    177                 } else if (returnedObject.getClass().isArray()) {
    178                     Object[] array = (Object[]) returnedObject;
    179                     filterer = new ArrayFilterer(array);
    180                 } else {
    181                     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                 Iterator collectionIter = filterer.iterator();
    188
    189                 while (collectionIter.hasNext()) {
    190                     Object domainObject = collectionIter.next();
    191
    192                     boolean hasPermission = false;
    193
    194                     AclEntry[] acls = null;
    195
    196                     if (domainObject == null) {
    197                         hasPermission = true;
    198                     } else {
    199                         acls = aclManager.getAcls(domainObject, authentication);
    200                     }
    201
    202                     if ((acls != null) && (acls.length != 0)) {
    203                         for (int i = 0; i < acls.length; i++) {
    204                             // Locate processable AclEntrys
    205                             if (acls[i] instanceof BasicAclEntry) {
    206                             	BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
    207
    208                                 // See if principal has any of the required permissions
    209                                 for (int y = 0; y < requirePermission.length;
    210                                     y++) {
    211                                     if (processableAcl.isPermitted(
    212                                             requirePermission[y])) {
    213                                         hasPermission = true;
    214
    215                                         if (logger.isDebugEnabled()) {
    216                                             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                     if (!hasPermission) {
    229                         filterer.remove(domainObject);
    230
    231                         if (logger.isDebugEnabled()) {
    232                             logger.debug(
    233                                 "Principal is NOT authorised for element: "
    234                                 + domainObject);
    235                         }
    236                     }
    237                 }
    238
    239                 return filterer.getFilteredObject();
    240             }
    241         }
    242
    243         return returnedObject;
    244     }
    245
    246     public boolean supports(ConfigAttribute attribute) {
    247         if ((attribute.getAttribute() != null)
    248             && attribute.getAttribute().equals(getProcessConfigAttribute())) {
    249             return true;
    250         } else {
    251             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     public boolean supports(Class clazz) {
    264         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     CollectionFilterer(Collection collection) {
    318         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         removeList = new HashSet();
    328     }
    329
    330     //~ Methods ================================================================
    331
    332     /***
    333      * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
    334      */
    335     public Object getFilteredObject() {
    336         // Now the Iterator has ended, remove Objects from Collection
    337         Iterator removeIter = removeList.iterator();
    338
    339         int originalSize = collection.size();
    340
    341         while (removeIter.hasNext()) {
    342             collection.remove(removeIter.next());
    343         }
    344
    345         if (logger.isDebugEnabled()) {
    346             logger.debug("Original collection contained " + originalSize
    347                 + " elements; now contains " + collection.size() + " elements");
    348         }
    349
    350         return collection;
    351     }
    352
    353     /***
    354      * @see org.acegisecurity.afterinvocation.Filterer#iterator()
    355      */
    356     public Iterator iterator() {
    357         collectionIter = collection.iterator();
    358
    359         return collectionIter;
    360     }
    361
    362     /***
    363      * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
    364      */
    365     public void remove(Object object) {
    366         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     ArrayFilterer(Object[] list) {
    387         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         removeList = new HashSet();
    393     }
    394
    395     //~ Methods ================================================================
    396
    397     /***
    398      * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
    399      */
    400     public Object getFilteredObject() {
    401         // Recreate an array of same type and filter the removed objects.
    402         int originalSize = list.length;
    403         int sizeOfResultingList = originalSize - removeList.size();
    404         Object[] filtered = (Object[]) Array.newInstance(list.getClass()
    405                                                              .getComponentType(),
    406                 sizeOfResultingList);
    407
    408         for (int i = 0, j = 0; i < list.length; i++) {
    409             Object object = list[i];
    410
    411             if (!removeList.contains(object)) {
    412                 filtered[j] = object;
    413                 j++;
    414             }
    415         }
    416
    417         if (logger.isDebugEnabled()) {
    418             logger.debug("Original array contained " + originalSize
    419                 + " elements; now contains " + sizeOfResultingList
    420                 + " elements");
    421         }
    422
    423         return filtered;
    424     }
    425
    426     /***
    427      * @see org.acegisecurity.afterinvocation.Filterer#iterator()
    428      */
    429     public Iterator iterator() {
    430         return new ArrayIterator(list);
    431     }
    432
    433     /***
    434      * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
    435      */
    436     public void remove(Object object) {
    437         removeList.add(object);
    438     }
    439 }