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.vote;
    17
    18  import org.acegisecurity.Authentication;
    19  import org.acegisecurity.AuthorizationServiceException;
    20  import org.acegisecurity.ConfigAttribute;
    21  import org.acegisecurity.ConfigAttributeDefinition;
    22  import org.acegisecurity.acl.AclEntry;
    23  import org.acegisecurity.acl.AclManager;
    24  import org.acegisecurity.acl.basic.BasicAclEntry;
    25
    26  import org.apache.commons.logging.Log;
    27  import org.apache.commons.logging.LogFactory;
    28
    29  import org.springframework.beans.factory.InitializingBean;
    30
    31  import org.springframework.util.Assert;
    32
    33  import java.lang.reflect.InvocationTargetException;
    34  import java.lang.reflect.Method;
    35
    36  import java.util.Iterator;
    37
    38
    39  /***
    40   * <p>
    41   * Given a domain object instance passed as a method argument, ensures the
    42   * principal has appropriate permission as defined by the {@link AclManager}.
    43   * </p>
    44   *
    45   * <p>
    46   * The <code>AclManager</code> is used to retrieve the access control list
    47   * (ACL) permissions associated with a domain object instance for the current
    48   * <code>Authentication</code> object. This class is designed to process
    49   * {@link AclEntry}s that are subclasses of {@link
    50   * org.acegisecurity.acl.basic.BasicAclEntry} only. Generally these are
    51   * obtained by using the {@link
    52   * org.acegisecurity.acl.basic.BasicAclProvider}.
    53   * </p>
    54   *
    55   * <p>
    56   * The voter will vote if any  {@link ConfigAttribute#getAttribute()} matches
    57   * the {@link #processConfigAttribute}. The provider will then locate the
    58   * first method argument of type {@link #processDomainObjectClass}. Assuming
    59   * that method argument is non-null, the provider will then lookup the ACLs
    60   * from the <code>AclManager</code> and ensure the principal is {@link
    61   * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least
    62   * one of the {@link #requirePermission}s.
    63   * </p>
    64   *
    65   * <p>
    66   * If the method argument is <code>null</code>, the voter will abstain from
    67   * voting. If the method argument could not be found, an {@link
    68   * org.acegisecurity.AuthorizationServiceException} will be thrown.
    69   * </p>
    70   *
    71   * <p>
    72   * In practical terms users will typically setup a number of
    73   * <code>BasicAclEntryVoter</code>s. Each will have a different {@link
    74   * #processDomainObjectClass}, {@link #processConfigAttribute} and {@link
    75   * #requirePermission} combination. For example, a small application might
    76   * employ the following instances of <code>BasicAclEntryVoter</code>:
    77   *
    78   * <ul>
    79   * <li>
    80   * Process domain object class <code>BankAccount</code>, configuration
    81   * attribute <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission
    82   * <code>SimpleAclEntry.READ</code>
    83   * </li>
    84   * <li>
    85   * Process domain object class <code>BankAccount</code>, configuration
    86   * attribute <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list
    87   * <code>SimpleAclEntry.WRITE</code> and <code>SimpleAclEntry.CREATE</code>
    88   * (allowing the principal to have <b>either</b> of these two permissions
    89   * </li>
    90   * <li>
    91   * Process domain object class <code>Customer</code>, configuration attribute
    92   * <code>VOTE_ACL_CUSTOMER_READ</code>, require permission
    93   * <code>SimpleAclEntry.READ</code>
    94   * </li>
    95   * <li>
    96   * Process domain object class <code>Customer</code>, configuration attribute
    97   * <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list
    98   * <code>SimpleAclEntry.WRITE</code> and <code>SimpleAclEntry.CREATE</code>
    99   * </li>
    100  * </ul>
    101  *
    102  * Alternatively, you could have used a common superclass or interface for the
    103  * {@link #processDomainObjectClass} if both <code>BankAccount</code> and
    104  * <code>Customer</code> had common parents.
    105  * </p>
    106  *
    107  * <p>
    108  * If the principal does not have sufficient permissions, the voter will vote
    109  * to deny access.
    110  * </p>
    111  *
    112  * <p>
    113  * The <code>AclManager</code> is allowed to return any implementations of
    114  * <code>AclEntry</code> it wishes. However, this provider will only be able
    115  * to validate against <code>AbstractBasicAclEntry</code>s, and thus a vote to
    116  * deny access will be made if no <code>AclEntry</code> is of type
    117  * <code>AbstractBasicAclEntry</code>.
    118  * </p>
    119  *
    120  * <p>
    121  * All comparisons and prefixes are case sensitive.
    122  * </p>
    123  *
    124  * @author Ben Alex
    125  * @version $Id: BasicAclEntryVoter.java,v 1.8 2005/11/17 00:55:47 benalex Exp $
    126  */
    127 public class BasicAclEntryVoter extends AbstractAclVoter
    128     implements InitializingBean {
    129     //~ Static fields/initializers =============================================
    130
    131     private static final Log logger = LogFactory.getLog(BasicAclEntryVoter.class);
    132
    133     //~ Instance fields ========================================================
    134
    135     private AclManager aclManager;
    136     private String internalMethod;
    137     private String processConfigAttribute;
    138     private int[] requirePermission;
    139
    140     //~ Methods ================================================================
    141
    142     public void setAclManager(AclManager aclManager) {
    143         this.aclManager = aclManager;
    144     }
    145
    146     public AclManager getAclManager() {
    147         return aclManager;
    148     }
    149
    150     public void setInternalMethod(String internalMethod) {
    151         this.internalMethod = internalMethod;
    152     }
    153
    154     /***
    155      * Optionally specifies a method of the domain object that will be used to
    156      * obtain a contained domain object. That contained domain object will be
    157      * used for the ACL evaluation. This is useful if a domain object contains
    158      * a parent that an ACL evaluation should be targeted for, instead of the
    159      * child domain object (which perhaps is being created and as such does
    160      * not yet have any ACL permissions)
    161      *
    162      * @return <code>null</code> to use the domain object, or the name of a
    163      *         method (that requires no arguments) that should be invoked to
    164      *         obtain an <code>Object</code> which will be the domain object
    165      *         used for ACL evaluation
    166      */
    167     public String getInternalMethod() {
    168         return internalMethod;
    169     }
    170
    171     public void setProcessConfigAttribute(String processConfigAttribute) {
    172         this.processConfigAttribute = processConfigAttribute;
    173     }
    174
    175     public String getProcessConfigAttribute() {
    176         return processConfigAttribute;
    177     }
    178
    179     public void setRequirePermission(int[] requirePermission) {
    180         this.requirePermission = requirePermission;
    181     }
    182
    183     public int[] getRequirePermission() {
    184         return requirePermission;
    185     }
    186
    187     public void afterPropertiesSet() throws Exception {
    188         Assert.notNull(processConfigAttribute,
    189             "A processConfigAttribute is mandatory");
    190         Assert.notNull(aclManager, "An aclManager is mandatory");
    191
    192         if ((requirePermission == null) || (requirePermission.length == 0)) {
    193             throw new IllegalArgumentException(
    194                 "One or more requirePermission entries is mandatory");
    195         }
    196     }
    197
    198     public boolean supports(ConfigAttribute attribute) {
    199         if ((attribute.getAttribute() != null)
    200             && attribute.getAttribute().startsWith(getProcessConfigAttribute())) {
    201             return true;
    202         } else {
    203             return false;
    204         }
    205     }
    206
    207     public int vote(Authentication authentication, Object object,
    208         ConfigAttributeDefinition config) {
    209         Iterator iter = config.getConfigAttributes();
    210
    211         while (iter.hasNext()) {
    212             ConfigAttribute attr = (ConfigAttribute) iter.next();
    213
    214             if (this.supports(attr)) {
    215                 // Need to make an access decision on this invocation
    216                 // Attempt to locate the domain object instance to process
    217                 Object domainObject = getDomainObjectInstance(object);
    218
    219                 // If domain object is null, vote to abstain
    220                 if (domainObject == null) {
    221                     return AccessDecisionVoter.ACCESS_ABSTAIN;
    222                 }
    223
    224                 // Evaluate if we are required to use an inner domain object
    225                 if ((internalMethod != null) && !"".equals(internalMethod)) {
    226                     try {
    227                         Class clazz = domainObject.getClass();
    228                         Method method = clazz.getMethod(internalMethod,
    229                                 new Class[] {});
    230                         domainObject = method.invoke(domainObject,
    231                                 new Object[] {});
    232                     } catch (NoSuchMethodException nsme) {
    233                         throw new AuthorizationServiceException(
    234                             "Object of class '" + domainObject.getClass()
    235                             + "' does not provide the requested internalMethod: "
    236                             + internalMethod);
    237                     } catch (IllegalAccessException iae) {
    238                         if (logger.isDebugEnabled()) {
    239                             logger.debug("IllegalAccessException", iae);
    240
    241                             if (iae.getCause() != null) {
    242                                 logger.debug("Cause: "
    243                                     + iae.getCause().getMessage(),
    244                                     iae.getCause());
    245                             }
    246                         }
    247
    248                         throw new AuthorizationServiceException(
    249                             "Problem invoking internalMethod: "
    250                             + internalMethod + " for object: " + domainObject);
    251                     } catch (InvocationTargetException ite) {
    252                         if (logger.isDebugEnabled()) {
    253                             logger.debug("InvocationTargetException", ite);
    254
    255                             if (ite.getCause() != null) {
    256                                 logger.debug("Cause: "
    257                                     + ite.getCause().getMessage(),
    258                                     ite.getCause());
    259                             }
    260                         }
    261
    262                         throw new AuthorizationServiceException(
    263                             "Problem invoking internalMethod: "
    264                             + internalMethod + " for object: " + domainObject);
    265                     }
    266                 }
    267
    268                 // Obtain the ACLs applicable to the domain object
    269                 AclEntry[] acls = aclManager.getAcls(domainObject,
    270                         authentication);
    271
    272                 // If principal has no permissions for domain object, deny
    273                 if ((acls == null) || (acls.length == 0)) {
    274                     return AccessDecisionVoter.ACCESS_DENIED;
    275                 }
    276
    277                 // Principal has some permissions for domain object, check them
    278                 for (int i = 0; i < acls.length; i++) {
    279                     // Locate processable AclEntrys
    280                     if (acls[i] instanceof BasicAclEntry) {
    281                         BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
    282
    283                         // See if principal has any of the required permissions
    284                         for (int y = 0; y < requirePermission.length; y++) {
    285                             if (processableAcl.isPermitted(requirePermission[y])) {
    286                                 return AccessDecisionVoter.ACCESS_GRANTED;
    287                             }
    288                         }
    289                     }
    290                 }
    291
    292                 // No permissions match
    293                 return AccessDecisionVoter.ACCESS_DENIED;
    294             }
    295         }
    296
    297         // No configuration attribute matched, so abstain
    298         return AccessDecisionVoter.ACCESS_ABSTAIN;
    299     }
    300 }