View Javadoc

    1   /* Copyright 2004 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.acl.basic;
    17
    18  import org.acegisecurity.Authentication;
    19  import org.acegisecurity.acl.AclEntry;
    20  import org.acegisecurity.acl.AclProvider;
    21  import org.acegisecurity.acl.basic.cache.NullAclEntryCache;
    22
    23  import org.apache.commons.logging.Log;
    24  import org.apache.commons.logging.LogFactory;
    25
    26  import org.springframework.beans.factory.InitializingBean;
    27  import org.springframework.util.Assert;
    28
    29  import java.lang.reflect.Constructor;
    30
    31  import java.util.Collection;
    32  import java.util.HashMap;
    33  import java.util.Map;
    34
    35
    36  /***
    37   * <P>
    38   * Retrieves access control lists (ACL) entries for domain object instances
    39   * from a data access object (DAO).
    40   * </p>
    41   *
    42   * <P>
    43   * This implementation will provide ACL lookup services for any object that it
    44   * can determine the {@link AclObjectIdentity} for by calling the {@link
    45   * #obtainIdentity(Object)} method. Subclasses can override this method if
    46   * they only want the <code>BasicAclProvider</code> responding to particular
    47   * domain object instances.
    48   * </p>
    49   *
    50   * <P>
    51   * <code>BasicAclProvider</code> will walk an inheritance hierarchy if a
    52   * <code>BasicAclEntry</code> returned by the DAO indicates it has a parent.
    53   * NB: inheritance occurs at a <I>domain instance object</I> level. It does
    54   * not occur at an ACL recipient level. This means
    55   * <B>all</B><code>BasicAclEntry</code>s for a given domain instance object
    56   * <B>must</B> have the <B>same</B> parent identity, or
    57   * <B>all</B><code>BasicAclEntry</code>s must have <code>null</code> as their
    58   * parent identity.
    59   * </p>
    60   *
    61   * <P>
    62   * A cache should be used. This is provided by the {@link BasicAclEntryCache}.
    63   * <code>BasicAclProvider</code> by default is setup to use the {@link
    64   * NullAclEntryCache}, which performs no caching.
    65   * </p>
    66   *
    67   * <P>
    68   * To implement the {@link #getAcls(Object, Authentication)} method,
    69   * <code>BasicAclProvider</code> requires a {@link EffectiveAclsResolver} to
    70   * be configured against it. By default the {@link
    71   * GrantedAuthorityEffectiveAclsResolver} is used.
    72   * </p>
    73   *
    74   * @author Ben Alex
    75   * @version $Id: BasicAclProvider.java,v 1.5 2005/11/17 00:55:47 benalex Exp $
    76   */
    77  public class BasicAclProvider implements AclProvider, InitializingBean {
    78      //~ Static fields/initializers =============================================
    79
    80      private static final Log logger = LogFactory.getLog(BasicAclProvider.class);
    81
    82      /***
    83       * Marker added to the cache to indicate an AclObjectIdentity has no
    84       * corresponding BasicAclEntry[]s
    85       */
    86      private static String RECIPIENT_FOR_CACHE_EMPTY = "RESERVED_RECIPIENT_NOBODY";
    87
    88      //~ Instance fields ========================================================
    89
    90      /***
    91       * Must be set to an appropriate data access object. Defaults to
    92       * <code>null</code>.
    93       */
    94      private BasicAclDao basicAclDao;
    95      private BasicAclEntryCache basicAclEntryCache = new NullAclEntryCache();
    96      private Class defaultAclObjectIdentityClass = NamedEntityObjectIdentity.class;
    97      private Class restrictSupportToClass = null;
    98      private EffectiveAclsResolver effectiveAclsResolver = new GrantedAuthorityEffectiveAclsResolver();
    99
    100     //~ Methods ================================================================
    101
    102     public AclEntry[] getAcls(Object domainInstance) {
    103         Map map = new HashMap();
    104
    105         AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
    106
    107         Assert.notNull(aclIdentity, "domainInstance is not supported by this provider");
    108
    109         if (logger.isDebugEnabled()) {
    110             logger.debug("Looking up: " + aclIdentity.toString());
    111         }
    112
    113         BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
    114
    115         // Exit if there is no ACL information or parent for this instance
    116         if (instanceAclEntries == null) {
    117             return null;
    118         }
    119
    120         // Add the leaf objects to the Map, keyed on recipient
    121         for (int i = 0; i < instanceAclEntries.length; i++) {
    122             if (logger.isDebugEnabled()) {
    123                 logger.debug("Explicit add: "
    124                         + instanceAclEntries[i].toString());
    125             }
    126
    127             map.put(instanceAclEntries[i].getRecipient(), instanceAclEntries[i]);
    128         }
    129
    130         AclObjectIdentity parent = instanceAclEntries[0]
    131                 .getAclObjectParentIdentity();
    132
    133         while (parent != null) {
    134             BasicAclEntry[] parentAclEntries = lookup(parent);
    135
    136             if (logger.isDebugEnabled()) {
    137                 logger.debug("Parent lookup: " + parent.toString());
    138             }
    139
    140             // Exit loop if parent couldn't be found (unexpected condition)
    141             if (parentAclEntries == null) {
    142                 if (logger.isDebugEnabled()) {
    143                     logger.debug("Parent could not be found in ACL repository");
    144                 }
    145
    146                 break;
    147             }
    148
    149             // Now add each _NEW_ recipient to the list
    150             for (int i = 0; i < parentAclEntries.length; i++) {
    151                 if (!map.containsKey(parentAclEntries[i].getRecipient())) {
    152                     if (logger.isDebugEnabled()) {
    153                         logger.debug("Added parent to map: "
    154                                 + parentAclEntries[i].toString());
    155                     }
    156
    157                     map.put(parentAclEntries[i].getRecipient(),
    158                             parentAclEntries[i]);
    159                 } else {
    160                     if (logger.isDebugEnabled()) {
    161                         logger.debug("Did NOT add parent to map: "
    162                                 + parentAclEntries[i].toString());
    163                     }
    164                 }
    165             }
    166
    167             // Prepare for next iteration of while loop
    168             parent = parentAclEntries[0].getAclObjectParentIdentity();
    169         }
    170
    171         Collection collection = map.values();
    172
    173         return (AclEntry[]) collection.toArray(new AclEntry[]{});
    174     }
    175
    176     public AclEntry[] getAcls(Object domainInstance,
    177         Authentication authentication) {
    178         AclEntry[] allAcls = (AclEntry[]) this.getAcls(domainInstance);
    179
    180         return this.effectiveAclsResolver.resolveEffectiveAcls(allAcls,
    181             authentication);
    182     }
    183
    184     public void setBasicAclDao(BasicAclDao basicAclDao) {
    185         this.basicAclDao = basicAclDao;
    186     }
    187
    188     public BasicAclDao getBasicAclDao() {
    189         return basicAclDao;
    190     }
    191
    192     public void setBasicAclEntryCache(BasicAclEntryCache basicAclEntryCache) {
    193         this.basicAclEntryCache = basicAclEntryCache;
    194     }
    195
    196     public BasicAclEntryCache getBasicAclEntryCache() {
    197         return basicAclEntryCache;
    198     }
    199
    200     /***
    201      * Allows selection of the <code>AclObjectIdentity</code> class that an
    202      * attempt should be made to construct if the passed object does not
    203      * implement <code>AclObjectIdentityAware</code>.
    204      *
    205      * <P>
    206      * NB: Any <code>defaultAclObjectIdentityClass</code><b>must</b> provide a
    207      * public constructor that accepts an <code>Object</code>. Otherwise it is
    208      * not possible for the <code>BasicAclProvider</code> to try to create the
    209      * <code>AclObjectIdentity</code> instance at runtime.
    210      * </p>
    211      *
    212      * @param defaultAclObjectIdentityClass
    213      */
    214     public void setDefaultAclObjectIdentityClass(
    215         Class defaultAclObjectIdentityClass) {
    216         this.defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
    217     }
    218
    219     public Class getDefaultAclObjectIdentityClass() {
    220         return defaultAclObjectIdentityClass;
    221     }
    222
    223     public void setEffectiveAclsResolver(
    224         EffectiveAclsResolver effectiveAclsResolver) {
    225         this.effectiveAclsResolver = effectiveAclsResolver;
    226     }
    227
    228     public EffectiveAclsResolver getEffectiveAclsResolver() {
    229         return effectiveAclsResolver;
    230     }
    231
    232     /***
    233      * If set to a value other than <code>null</code>, the {@link
    234      * #supports(Object)} method will <b>only</b> support the indicates class.
    235      * This is useful if you wish to wire multiple
    236      * <code>BasicAclProvider</code>s in a list of
    237      * <code>AclProviderManager.providers</code> but only have particular
    238      * instances respond to particular domain object types.
    239      *
    240      * @param restrictSupportToClass the class to restrict this
    241      *        <code>BasicAclProvider</code> to service request for, or
    242      *        <code>null</code> (the default) if the
    243      *        <code>BasicAclProvider</code> should respond to every class
    244      *        presented
    245      */
    246     public void setRestrictSupportToClass(Class restrictSupportToClass) {
    247         this.restrictSupportToClass = restrictSupportToClass;
    248     }
    249
    250     public Class getRestrictSupportToClass() {
    251         return restrictSupportToClass;
    252     }
    253
    254     public void afterPropertiesSet() {
    255         Assert.notNull(basicAclDao, "basicAclDao required");
    256         Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
    257         Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
    258         Assert.notNull(effectiveAclsResolver, "effectiveAclsResolver required");
    259         Assert.notNull(defaultAclObjectIdentityClass, "defaultAclObjectIdentityClass required");
    260         Assert.isTrue(AclObjectIdentity.class.isAssignableFrom(this.defaultAclObjectIdentityClass),
    261                 "defaultAclObjectIdentityClass must implement AclObjectIdentity");
    262
    263         try {
    264             Constructor constructor = defaultAclObjectIdentityClass
    265                     .getConstructor(new Class[]{Object.class});
    266         } catch (NoSuchMethodException nsme) {
    267             throw new IllegalArgumentException("defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!");
    268         }
    269     }
    270
    271     /***
    272      * Indicates support for the passed object.
    273      *
    274      * <p>
    275      * An object will only be supported if it (i) is allowed to be supported as
    276      * defined by the {@link #setRestrictSupportToClass(Class)} method,
    277      * <b>and</b> (ii) if an <code>AclObjectIdentity</code> is returned by
    278      * {@link #obtainIdentity(Object)} for that object.
    279      * </p>
    280      *
    281      * @param domainInstance the instance to check
    282      *
    283      * @return <code>true</code> if this provider supports the passed object,
    284      *         <code>false</code> otherwise
    285      */
    286     public boolean supports(Object domainInstance) {
    287         if (domainInstance == null) {
    288             if (logger.isDebugEnabled()) {
    289                 logger.debug("domainInstance is null");
    290             }
    291
    292             return false;
    293         }
    294
    295         if ((restrictSupportToClass != null)
    296             && !restrictSupportToClass.isAssignableFrom(
    297                 domainInstance.getClass())) {
    298             if (logger.isDebugEnabled()) {
    299                 logger.debug("domainInstance not instance of "
    300                     + restrictSupportToClass);
    301             }
    302
    303             return false;
    304         }
    305
    306         if (obtainIdentity(domainInstance) == null) {
    307             if (logger.isDebugEnabled()) {
    308                 logger.debug("obtainIdentity returned null");
    309             }
    310
    311             return false;
    312         } else {
    313             if (logger.isDebugEnabled()) {
    314                 logger.debug("obtainIdentity returned "
    315                     + obtainIdentity(domainInstance));
    316             }
    317
    318             return true;
    319         }
    320     }
    321
    322     /***
    323      * This method looks up the <code>AclObjectIdentity</code> of a passed
    324      * domain object instance.
    325      *
    326      * <P>
    327      * This implementation attempts to obtain the
    328      * <code>AclObjectIdentity</code> via reflection inspection of the class
    329      * for the {@link AclObjectIdentityAware} interface. If this fails, an
    330      * attempt is made to construct a {@link
    331      * #getDefaultAclObjectIdentityClass()} object by passing the domain
    332      * instance object into its constructor.
    333      * </p>
    334      *
    335      * @param domainInstance the domain object instance (never
    336      *        <code>null</code>)
    337      *
    338      * @return an ACL object identity, or <code>null</code> if one could not be
    339      *         obtained
    340      */
    341     protected AclObjectIdentity obtainIdentity(Object domainInstance) {
    342         if (domainInstance instanceof AclObjectIdentityAware) {
    343             AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
    344
    345             if (logger.isDebugEnabled()) {
    346                 logger.debug("domainInstance: " + domainInstance
    347                     + " cast to AclObjectIdentityAware");
    348             }
    349
    350             return aclObjectIdentityAware.getAclObjectIdentity();
    351         }
    352
    353         try {
    354             Constructor constructor = defaultAclObjectIdentityClass
    355                 .getConstructor(new Class[] {Object.class});
    356
    357             if (logger.isDebugEnabled()) {
    358                 logger.debug("domainInstance: " + domainInstance
    359                     + " attempting to pass to constructor: " + constructor);
    360             }
    361
    362             return (AclObjectIdentity) constructor.newInstance(new Object[] {domainInstance});
    363         } catch (Exception ex) {
    364             if (logger.isDebugEnabled()) {
    365                 logger.debug("Error attempting construction of "
    366                     + defaultAclObjectIdentityClass + ": " + ex.getMessage(), ex);
    367
    368                 if (ex.getCause() != null) {
    369                     logger.debug("Cause: " + ex.getCause().getMessage(),
    370                         ex.getCause());
    371                 }
    372             }
    373
    374             return null;
    375         }
    376     }
    377
    378     private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
    379         BasicAclEntry[] result = basicAclEntryCache.getEntriesFromCache(aclObjectIdentity);
    380
    381         if (result != null) {
    382             if (result[0].getRecipient().equals(RECIPIENT_FOR_CACHE_EMPTY)) {
    383                 return null;
    384             } else {
    385                 return result;
    386             }
    387         }
    388
    389         result = basicAclDao.getAcls(aclObjectIdentity);
    390
    391         if (result == null) {
    392             SimpleAclEntry[] emptyAclEntries = {new SimpleAclEntry(RECIPIENT_FOR_CACHE_EMPTY,
    393                         aclObjectIdentity, null, 0)};
    394             basicAclEntryCache.putEntriesInCache(emptyAclEntries);
    395
    396             return null;
    397         }
    398
    399         basicAclEntryCache.putEntriesInCache(result);
    400
    401         return result;
    402     }
    403 }