Coverage Report - org.acegisecurity.vote.LabelBasedAclVoter
 
Classes in this File Line Coverage Branch Coverage Complexity
LabelBasedAclVoter
80% 
80% 
5.6
 
 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  
 package org.acegisecurity.vote;
 16  
 
 17  
 import org.acegisecurity.Authentication;
 18  
 import org.acegisecurity.ConfigAttribute;
 19  
 import org.acegisecurity.ConfigAttributeDefinition;
 20  
 
 21  
 import org.aopalliance.intercept.MethodInvocation;
 22  
 
 23  
 import org.apache.commons.logging.Log;
 24  
 import org.apache.commons.logging.LogFactory;
 25  
 
 26  
 import org.springframework.util.Assert;
 27  
 
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 import java.util.Vector;
 31  
 import java.util.Map;
 32  
 
 33  
 
 34  
 /**
 35  
  * <p>This Acl voter will evaluate methods based on labels applied to incoming arguments. It will only check
 36  
  * methods that have been properly tagged in the MethodSecurityInterceptor with the value stored in
 37  
  * <tt>attributeIndicatingLabeledOperation</tt>. If a method has been tagged, then it examines each argument, and if the
 38  
  * argument implements {@link LabeledData}, then it will asses if the user's list of granted authorities matches.
 39  
  * </p>
 40  
  *
 41  
  * <p>By default, if none of the arguments are labeled, then the access will be granted. This can be overridden by
 42  
  * setting <tt>allowAccessIfNoAttributesAreLabeled</tt> to false in the Spring context file.</p>
 43  
  *
 44  
  * <p>In many situations, different values are linked together to define a common label, it is necessary to
 45  
  * define a map in the application context that links user-assigned label access to domain object labels. This is done
 46  
  * by setting up the <tt>labelMap</tt> in the application context.</p>
 47  
  *
 48  
  * @author Greg Turnquist
 49  
  * @version $Id: LabelBasedAclVoter.java 1962 2007-08-27 17:21:16Z luke_t $
 50  
  *
 51  
  * @see org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
 52  
  */
 53  1
 public class LabelBasedAclVoter extends AbstractAclVoter {
 54  
     //~ Static fields/initializers =====================================================================================
 55  
 
 56  2
     private static final Log logger = LogFactory.getLog(LabelBasedAclVoter.class);
 57  
 
 58  
     //~ Instance fields ================================================================================================
 59  
 
 60  1
     private Map labelMap = null;
 61  1
     private String attributeIndicatingLabeledOperation = null;
 62  1
     private boolean allowAccessIfNoAttributesAreLabeled = true;
 63  
 
 64  
     //~ Methods ========================================================================================================
 65  
 
 66  
     /**
 67  
      * Set whether or not to allow the user to run methods in which none of the incoming arguments are labeled.
 68  
      *
 69  
      * <p>Default value: <b>true, users can run such methods.</b></p>
 70  
      *
 71  
      * @param allowAccessIfNoAttributesAreLabeled boolean
 72  
      */
 73  
     public void setAllowAccessIfNoAttributesAreLabeled(boolean allowAccessIfNoAttributesAreLabeled) {
 74  0
         this.allowAccessIfNoAttributesAreLabeled = allowAccessIfNoAttributesAreLabeled;
 75  0
     }
 76  
 
 77  
     /**
 78  
      * Each method intended for evaluation by this voter must include this tag name in the definition of the
 79  
      * MethodSecurityInterceptor, indicating if this voter should evaluate the arguments and compare them against the
 80  
      * label map.
 81  
      *
 82  
      * @param attributeIndicatingLabeledOperation string
 83  
      */
 84  
     public void setAttributeIndicatingLabeledOperation(String attributeIndicatingLabeledOperation) {
 85  1
         this.attributeIndicatingLabeledOperation = attributeIndicatingLabeledOperation;
 86  1
     }
 87  
 
 88  
     /**
 89  
      * Set the map that correlate a user's assigned label against domain object values that are considered data
 90  
      * labels. An example application context configuration of a <tt>labelMap</tt>:
 91  
      *
 92  
      * <pre>
 93  
      * &lt;bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased"&gt;
 94  
      *     &lt;property name="allowIfAllAbstainDecisions"&gt;&lt;value&gt;false&lt;/value&gt;&lt;/property&gt;
 95  
      *     &lt;property name="decisionVoters"&gt;
 96  
      *       &lt;list&gt;
 97  
      *         &lt;bean class="org.acegisecurity.vote.RoleVoter"/&gt;
 98  
      *         &lt;bean class="org.acegisecurity.vote.LabelBasedAclVoter"&gt;
 99  
      *           &lt;property name="attributeIndicatingLabeledOperation"&gt;
 100  
      *             &lt;value&gt;LABELED_OPERATION&lt;/value&gt;
 101  
      *           &lt;/property&gt;
 102  
      *           &lt;property name="labelMap"&gt;
 103  
      *             &lt;map&gt;
 104  
      *               &lt;entry key="DATA_LABEL_BLUE"&gt;
 105  
      *                 &lt;list&gt;
 106  
      *                   &lt;value&gt;blue&lt;/value&gt;
 107  
      *                   &lt;value&gt;indigo&lt;/value&gt;
 108  
      *                   &lt;value&gt;purple&lt;/value&gt;
 109  
      *                 &lt;/list&gt;
 110  
      *               &lt;/entry&gt;
 111  
      *               &lt;entry key="LABEL_ORANGE"&gt;
 112  
      *                 &lt;list&gt;
 113  
      *                   &lt;value&gt;orange&lt;/value&gt;
 114  
      *                   &lt;value&gt;sunshine&lt;/value&gt;
 115  
      *                   &lt;value&gt;amber&lt;/value&gt;
 116  
      *                 &lt;/list&gt;
 117  
      *               &lt;/entry&gt;
 118  
      *               &lt;entry key="LABEL_ADMIN"&gt;
 119  
      *                 &lt;list&gt;
 120  
      *                   &lt;value&gt;blue&lt;/value&gt;
 121  
      *                   &lt;value&gt;indigo&lt;/value&gt;
 122  
      *                   &lt;value&gt;purple&lt;/value&gt;
 123  
      *                   &lt;value&gt;orange&lt;/value&gt;
 124  
      *                   &lt;value&gt;sunshine&lt;/value&gt;
 125  
      *                   &lt;value&gt;amber&lt;/value&gt;
 126  
      *                 &lt;/list&gt;
 127  
      *               &lt;/entry&gt;
 128  
      *             &lt;/map&gt;
 129  
      *           &lt;/property&gt;
 130  
      *         &lt;/bean&gt;
 131  
      *       &lt;/list&gt;
 132  
      *     &lt;/property&gt;
 133  
      *   &lt;/bean&gt;
 134  
      * </pre>
 135  
      *
 136  
      * @param labelMap a map structured as in the above example.
 137  
      *
 138  
      */
 139  
     public void setLabelMap(Map labelMap) {
 140  1
         this.labelMap = labelMap;
 141  1
     }
 142  
 
 143  
     /**
 144  
      * This acl voter will only evaluate labeled methods if they are marked in the security interceptor's
 145  
      * configuration with the attribute stored in attributeIndicatingLabeledOperation.
 146  
      *
 147  
      * @param attribute DOCUMENT ME!
 148  
      *
 149  
      * @return DOCUMENT ME!
 150  
      *
 151  
      * @see org.acegisecurity.vote.AbstractAclVoter
 152  
      * @see org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
 153  
      */
 154  
     public boolean supports(ConfigAttribute attribute) {
 155  44
         if (attribute.getAttribute().equals(attributeIndicatingLabeledOperation)) {
 156  20
             logger.debug(attribute + " is supported.");
 157  
 
 158  20
             return true;
 159  
         }
 160  
 
 161  24
         if (logger.isDebugEnabled()) {
 162  0
             logger.debug(attribute + " is unsupported.");
 163  
         }
 164  
 
 165  24
         return false;
 166  
     }
 167  
 
 168  
     /**
 169  
      * Vote on whether or not the user has all the labels necessary to match the method argument's labeled
 170  
      * data.
 171  
      *
 172  
      * @param authentication DOCUMENT ME!
 173  
      * @param object DOCUMENT ME!
 174  
      * @param config DOCUMENT ME!
 175  
      *
 176  
      * @return ACCESS_ABSTAIN, ACCESS_GRANTED, or ACCESS_DENIED.
 177  
      */
 178  
     public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
 179  44
         int result = ACCESS_ABSTAIN;
 180  
 
 181  44
         if (logger.isDebugEnabled()) {
 182  0
             logger.debug("==========================================================");
 183  
         }
 184  
 
 185  44
         if (this.supports((ConfigAttribute) config.getConfigAttributes().next())) {
 186  20
             result = ACCESS_DENIED;
 187  
 
 188  
             /* Parse out the user's labels by examining the security context, and checking
 189  
              * for matches against the label map.
 190  
              */
 191  20
             List userLabels = new Vector();
 192  
 
 193  65
             for (int i = 0; i < authentication.getAuthorities().length; i++) {
 194  45
                 if (labelMap.containsKey(authentication.getAuthorities()[i].getAuthority())) {
 195  25
                     String userLabel = authentication.getAuthorities()[i].getAuthority();
 196  25
                     userLabels.add(userLabel);
 197  25
                     logger.debug("Adding " + userLabel + " to <<<" + authentication.getName()
 198  
                         + "'s>>> authorized label list");
 199  
                 }
 200  
             }
 201  
 
 202  20
             MethodInvocation invocation = (MethodInvocation) object;
 203  
 
 204  20
             int matches = 0;
 205  20
             int misses = 0;
 206  20
             int labeledArguments = 0;
 207  
 
 208  60
             for (int j = 0; j < invocation.getArguments().length; j++) {
 209  40
                 if (invocation.getArguments()[j] instanceof LabeledData) {
 210  40
                     labeledArguments++;
 211  
 
 212  40
                     boolean matched = false;
 213  
 
 214  40
                     String argumentDataLabel = ((LabeledData) invocation.getArguments()[j]).getLabel();
 215  40
                     logger.debug("Argument[" + j + "/" + invocation.getArguments()[j].getClass().getName()
 216  
                         + "] has a data label of " + argumentDataLabel);
 217  
 
 218  40
                     List validDataLabels = new Vector();
 219  
 
 220  90
                     for (int i = 0; i < userLabels.size(); i++) {
 221  50
                         validDataLabels.addAll((List) labelMap.get(userLabels.get(i)));
 222  
                     }
 223  
 
 224  40
                     logger.debug("The valid labels for user label " + userLabels + " are " + validDataLabels);
 225  
 
 226  40
                     Iterator dataLabelIter = validDataLabels.iterator();
 227  
 
 228  150
                     while (dataLabelIter.hasNext()) {
 229  110
                         String validDataLabel = (String) dataLabelIter.next();
 230  
 
 231  110
                         if (argumentDataLabel.equals(validDataLabel)) {
 232  34
                             logger.debug(userLabels + " maps to " + validDataLabel + " which matches the argument");
 233  34
                             matched = true;
 234  
                         }
 235  110
                     }
 236  
 
 237  40
                     if (matched) {
 238  32
                         logger.debug("We have a match!");
 239  32
                         matches++;
 240  
                     } else {
 241  8
                         logger.debug("We have a miss!");
 242  8
                         misses++;
 243  
                     }
 244  
                 }
 245  
             }
 246  20
             Assert.isTrue((matches + misses) == labeledArguments,
 247  
                 "The matches (" + matches + ") and misses (" + misses + " ) don't add up (" + labeledArguments + ")");
 248  
 
 249  20
             logger.debug("We have " + matches + " matches and " + misses + " misses and " + labeledArguments
 250  
                 + " labeled arguments.");
 251  
 
 252  
             /* The result has already been set to ACCESS_DENIED. Only if there is a proper match of
 253  
              * labels will this be overturned. However, if none of the attributes are actually labeled,
 254  
              * the result is dependent on allowAccessIfNoAttributesAreLabeled.
 255  
              */
 256  20
             if ((matches > 0) && (misses == 0)) {
 257  14
                 result = ACCESS_GRANTED;
 258  6
             } else if (labeledArguments == 0) {
 259  0
                 if (allowAccessIfNoAttributesAreLabeled) {
 260  0
                     result = ACCESS_GRANTED;
 261  
                 } else {
 262  0
                     result = ACCESS_DENIED;
 263  
                 }
 264  
             }
 265  
         }
 266  
 
 267  44
         if (logger.isDebugEnabled()) {
 268  0
             switch (result) {
 269  
             case ACCESS_GRANTED:
 270  
 
 271  0
                 if (logger.isDebugEnabled()) {
 272  0
                     logger.debug("===== Access is granted =====");
 273  
                 }
 274  
 
 275  
                 break;
 276  
 
 277  
             case ACCESS_DENIED:
 278  
 
 279  0
                 if (logger.isDebugEnabled()) {
 280  0
                     logger.debug("===== Access is denied =====");
 281  
                 }
 282  
 
 283  
                 break;
 284  
 
 285  
             case ACCESS_ABSTAIN:
 286  
 
 287  0
                 if (logger.isDebugEnabled()) {
 288  0
                     logger.debug("===== Abstaining =====");
 289  
                 }
 290  
 
 291  
                 break;
 292  
             }
 293  
         }
 294  
 
 295  44
         return result;
 296  
     }
 297  
 }