Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 300   Methods: 11
NCLOC: 132   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
BasicAclEntryVoter.java 73.3% 79.6% 100% 80%
coverage coverage
 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  10 public void setAclManager(AclManager aclManager) {
 143  10 this.aclManager = aclManager;
 144    }
 145   
 146  1 public AclManager getAclManager() {
 147  1 return aclManager;
 148    }
 149   
 150  4 public void setInternalMethod(String internalMethod) {
 151  4 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  1 public String getInternalMethod() {
 168  1 return internalMethod;
 169    }
 170   
 171  11 public void setProcessConfigAttribute(String processConfigAttribute) {
 172  11 this.processConfigAttribute = processConfigAttribute;
 173    }
 174   
 175  10 public String getProcessConfigAttribute() {
 176  10 return processConfigAttribute;
 177    }
 178   
 179  10 public void setRequirePermission(int[] requirePermission) {
 180  10 this.requirePermission = requirePermission;
 181    }
 182   
 183  1 public int[] getRequirePermission() {
 184  1 return requirePermission;
 185    }
 186   
 187  11 public void afterPropertiesSet() throws Exception {
 188  11 Assert.notNull(processConfigAttribute,
 189    "A processConfigAttribute is mandatory");
 190  10 Assert.notNull(aclManager, "An aclManager is mandatory");
 191   
 192  9 if ((requirePermission == null) || (requirePermission.length == 0)) {
 193  1 throw new IllegalArgumentException(
 194    "One or more requirePermission entries is mandatory");
 195    }
 196    }
 197   
 198  9 public boolean supports(ConfigAttribute attribute) {
 199  9 if ((attribute.getAttribute() != null)
 200    && attribute.getAttribute().startsWith(getProcessConfigAttribute())) {
 201  8 return true;
 202    } else {
 203  1 return false;
 204    }
 205    }
 206   
 207  8 public int vote(Authentication authentication, Object object,
 208    ConfigAttributeDefinition config) {
 209  8 Iterator iter = config.getConfigAttributes();
 210   
 211  8 while (iter.hasNext()) {
 212  8 ConfigAttribute attr = (ConfigAttribute) iter.next();
 213   
 214  8 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  7 Object domainObject = getDomainObjectInstance(object);
 218   
 219    // If domain object is null, vote to abstain
 220  6 if (domainObject == null) {
 221  1 return AccessDecisionVoter.ACCESS_ABSTAIN;
 222    }
 223   
 224    // Evaluate if we are required to use an inner domain object
 225  5 if ((internalMethod != null) && !"".equals(internalMethod)) {
 226  4 try {
 227  4 Class clazz = domainObject.getClass();
 228  4 Method method = clazz.getMethod(internalMethod,
 229    new Class[] {});
 230  3 domainObject = method.invoke(domainObject,
 231    new Object[] {});
 232    } catch (NoSuchMethodException nsme) {
 233  1 throw new AuthorizationServiceException(
 234    "Object of class '" + domainObject.getClass()
 235    + "' does not provide the requested internalMethod: "
 236    + internalMethod);
 237    } catch (IllegalAccessException iae) {
 238  0 if (logger.isDebugEnabled()) {
 239  0 logger.debug("IllegalAccessException", iae);
 240   
 241  0 if (iae.getCause() != null) {
 242  0 logger.debug("Cause: "
 243    + iae.getCause().getMessage(),
 244    iae.getCause());
 245    }
 246    }
 247   
 248  0 throw new AuthorizationServiceException(
 249    "Problem invoking internalMethod: "
 250    + internalMethod + " for object: " + domainObject);
 251    } catch (InvocationTargetException ite) {
 252  0 if (logger.isDebugEnabled()) {
 253  0 logger.debug("InvocationTargetException", ite);
 254   
 255  0 if (ite.getCause() != null) {
 256  0 logger.debug("Cause: "
 257    + ite.getCause().getMessage(),
 258    ite.getCause());
 259    }
 260    }
 261   
 262  0 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  4 AclEntry[] acls = aclManager.getAcls(domainObject,
 270    authentication);
 271   
 272    // If principal has no permissions for domain object, deny
 273  4 if ((acls == null) || (acls.length == 0)) {
 274  1 return AccessDecisionVoter.ACCESS_DENIED;
 275    }
 276   
 277    // Principal has some permissions for domain object, check them
 278  3 for (int i = 0; i < acls.length; i++) {
 279    // Locate processable AclEntrys
 280  6 if (acls[i] instanceof BasicAclEntry) {
 281  3 BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
 282   
 283    // See if principal has any of the required permissions
 284  3 for (int y = 0; y < requirePermission.length; y++) {
 285  4 if (processableAcl.isPermitted(requirePermission[y])) {
 286  2 return AccessDecisionVoter.ACCESS_GRANTED;
 287    }
 288    }
 289    }
 290    }
 291   
 292    // No permissions match
 293  1 return AccessDecisionVoter.ACCESS_DENIED;
 294    }
 295    }
 296   
 297    // No configuration attribute matched, so abstain
 298  1 return AccessDecisionVoter.ACCESS_ABSTAIN;
 299    }
 300    }