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