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 java.util.Iterator;
    19
    20  import org.acegisecurity.AccessDeniedException;
    21  import org.acegisecurity.AcegiMessageSource;
    22  import org.acegisecurity.Authentication;
    23  import org.acegisecurity.ConfigAttribute;
    24  import org.acegisecurity.ConfigAttributeDefinition;
    25  import org.acegisecurity.acl.AclEntry;
    26  import org.acegisecurity.acl.AclManager;
    27  import org.acegisecurity.acl.basic.BasicAclEntry;
    28  import org.acegisecurity.acl.basic.SimpleAclEntry;
    29  import org.apache.commons.logging.Log;
    30  import org.apache.commons.logging.LogFactory;
    31  import org.springframework.beans.factory.InitializingBean;
    32  import org.springframework.context.MessageSource;
    33  import org.springframework.context.MessageSourceAware;
    34  import org.springframework.context.support.MessageSourceAccessor;
    35  import org.springframework.util.Assert;
    36
    37
    38  /***
    39   * <p>
    40   * Given a domain object instance returned from a secure object invocation,
    41   * ensures the principal has appropriate permission as defined by the {@link
    42   * 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 org.acegisecurity.acl.basic.BasicAclProvider}.
    52   * </p>
    53   *
    54   * <p>
    55   * This after invocation provider will fire if any  {@link
    56   * ConfigAttribute#getAttribute()} matches the {@link
    57   * #processConfigAttribute}. The provider will then lookup the ACLs from the
    58   * <code>AclManager</code> and ensure the principal is {@link
    59   * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least
    60   * one of the {@link #requirePermission}s.
    61   * </p>
    62   *
    63   * <p>
    64   * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code>
    65   * with a {@link #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a
    66   * {@link #requirePermission} of  <code>SimpleAclEntry.READ</code>. These are
    67   * also the defaults.
    68   * </p>
    69   *
    70   * <p>
    71   * If the principal does not have sufficient permissions, an
    72   * <code>AccessDeniedException</code> will be thrown.
    73   * </p>
    74   *
    75   * <p>
    76   * The <code>AclManager</code> is allowed to return any implementations of
    77   * <code>AclEntry</code> it wishes. However, this provider will only be able
    78   * to validate against <code>BasicAclEntry</code>s, and thus access will be
    79   * denied if no <code>AclEntry</code> is of type <code>BasicAclEntry</code>.
    80   * </p>
    81   *
    82   * <p>
    83   * If the provided <code>returnObject</code> is <code>null</code>, permission
    84   * will always be granted and <code>null</code> will be returned.
    85   * </p>
    86   *
    87   * <p>
    88   * All comparisons and prefixes are case sensitive.
    89   * </p>
    90   */
    91  public class BasicAclEntryAfterInvocationProvider
    92      implements AfterInvocationProvider, InitializingBean, MessageSourceAware {
    93      //~ Static fields/initializers =============================================
    94
    95      protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationProvider.class);
    96
    97      //~ Instance fields ========================================================
    98
    99      private AclManager aclManager;
    100     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
    101     private String processConfigAttribute = "AFTER_ACL_READ";
    102     private int[] requirePermission = {SimpleAclEntry.READ};
    103
    104     //~ Methods ================================================================
    105
    106     public void afterPropertiesSet() throws Exception {
    107         Assert.notNull(processConfigAttribute,
    108             "A processConfigAttribute is mandatory");
    109         Assert.notNull(aclManager, "An aclManager is mandatory");
    110         Assert.notNull(messages, "A message source must be set");
    111
    112         if ((requirePermission == null) || (requirePermission.length == 0)) {
    113             throw new IllegalArgumentException(
    114                 "One or more requirePermission entries is mandatory");
    115         }
    116     }
    117
    118     public Object decide(Authentication authentication, Object object,
    119         ConfigAttributeDefinition config, Object returnedObject)
    120         throws AccessDeniedException {
    121         Iterator iter = config.getConfigAttributes();
    122
    123         while (iter.hasNext()) {
    124             ConfigAttribute attr = (ConfigAttribute) iter.next();
    125
    126             if (this.supports(attr)) {
    127                 // Need to make an access decision on this invocation
    128                 if (returnedObject == null) {
    129                     // AclManager interface contract prohibits nulls
    130                     // As they have permission to null/nothing, grant access
    131                     if (logger.isDebugEnabled()) {
    132                         logger.debug("Return object is null, skipping");
    133                     }
    134
    135                     return null;
    136                 }
    137
    138                 AclEntry[] acls = aclManager.getAcls(returnedObject,
    139                         authentication);
    140
    141                 if ((acls == null) || (acls.length == 0)) {
    142                     throw new AccessDeniedException(messages.getMessage(
    143                             "BasicAclEntryAfterInvocationProvider.noPermission",
    144                             new Object[] {authentication.getName(), returnedObject},
    145                             "Authentication {0} has NO permissions at all to the domain object {1}"));
    146                 }
    147
    148                 for (int i = 0; i < acls.length; i++) {
    149                     // Locate processable AclEntrys
    150                     if (acls[i] instanceof BasicAclEntry) {
    151                         BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
    152
    153                         // See if principal has any of the required permissions
    154                         for (int y = 0; y < requirePermission.length; y++) {
    155                             if (processableAcl.isPermitted(requirePermission[y])) {
    156                                 if (logger.isDebugEnabled()) {
    157                                     logger.debug(
    158                                         "Principal DOES have permission to return object: "
    159                                         + returnedObject + " due to ACL: "
    160                                         + processableAcl.toString());
    161                                 }
    162
    163                                 return returnedObject;
    164                             }
    165                         }
    166                     }
    167                 }
    168
    169                 // No permissions match
    170                 throw new AccessDeniedException(messages.getMessage(
    171                         "BasicAclEntryAfterInvocationProvider.insufficientPermission",
    172                         new Object[] {authentication.getName(), returnedObject},
    173                         "Authentication {0} has ACL permissions to the domain object, but not the required ACL permission to the domain object {1}"));
    174             }
    175         }
    176
    177         return returnedObject;
    178     }
    179
    180     public AclManager getAclManager() {
    181         return aclManager;
    182     }
    183
    184     public String getProcessConfigAttribute() {
    185         return processConfigAttribute;
    186     }
    187
    188     public int[] getRequirePermission() {
    189         return requirePermission;
    190     }
    191
    192     public void setAclManager(AclManager aclManager) {
    193         this.aclManager = aclManager;
    194     }
    195
    196     public void setMessageSource(MessageSource messageSource) {
    197         this.messages = new MessageSourceAccessor(messageSource);
    198     }
    199
    200     public void setProcessConfigAttribute(String processConfigAttribute) {
    201         this.processConfigAttribute = processConfigAttribute;
    202     }
    203
    204     public void setRequirePermission(int[] requirePermission) {
    205         this.requirePermission = requirePermission;
    206     }
    207
    208     public boolean supports(ConfigAttribute attribute) {
    209         if ((attribute.getAttribute() != null)
    210             && attribute.getAttribute().equals(getProcessConfigAttribute())) {
    211             return true;
    212         } else {
    213             return false;
    214         }
    215     }
    216
    217     /***
    218      * This implementation supports any type of class, because it does not
    219      * query the presented secure object.
    220      *
    221      * @param clazz the secure object
    222      *
    223      * @return always <code>true</code>
    224      */
    225     public boolean supports(Class clazz) {
    226         return true;
    227     }
    228 }