View Javadoc

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  public class LabelBasedAclVoter extends AbstractAclVoter {
54      //~ Static fields/initializers =====================================================================================
55  
56      private static final Log logger = LogFactory.getLog(LabelBasedAclVoter.class);
57  
58      //~ Instance fields ================================================================================================
59  
60      private Map labelMap = null;
61      private String attributeIndicatingLabeledOperation = null;
62      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          this.allowAccessIfNoAttributesAreLabeled = allowAccessIfNoAttributesAreLabeled;
75      }
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          this.attributeIndicatingLabeledOperation = attributeIndicatingLabeledOperation;
86      }
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         this.labelMap = labelMap;
141     }
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         if (attribute.getAttribute().equals(attributeIndicatingLabeledOperation)) {
156             logger.debug(attribute + " is supported.");
157 
158             return true;
159         }
160 
161         if (logger.isDebugEnabled()) {
162             logger.debug(attribute + " is unsupported.");
163         }
164 
165         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         int result = ACCESS_ABSTAIN;
180 
181         if (logger.isDebugEnabled()) {
182             logger.debug("==========================================================");
183         }
184 
185         if (this.supports((ConfigAttribute) config.getConfigAttributes().next())) {
186             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             List userLabels = new Vector();
192 
193             for (int i = 0; i < authentication.getAuthorities().length; i++) {
194                 if (labelMap.containsKey(authentication.getAuthorities()[i].getAuthority())) {
195                     String userLabel = authentication.getAuthorities()[i].getAuthority();
196                     userLabels.add(userLabel);
197                     logger.debug("Adding " + userLabel + " to <<<" + authentication.getName()
198                         + "'s>>> authorized label list");
199                 }
200             }
201 
202             MethodInvocation invocation = (MethodInvocation) object;
203 
204             int matches = 0;
205             int misses = 0;
206             int labeledArguments = 0;
207 
208             for (int j = 0; j < invocation.getArguments().length; j++) {
209                 if (invocation.getArguments()[j] instanceof LabeledData) {
210                     labeledArguments++;
211 
212                     boolean matched = false;
213 
214                     String argumentDataLabel = ((LabeledData) invocation.getArguments()[j]).getLabel();
215                     logger.debug("Argument[" + j + "/" + invocation.getArguments()[j].getClass().getName()
216                         + "] has a data label of " + argumentDataLabel);
217 
218                     List validDataLabels = new Vector();
219 
220                     for (int i = 0; i < userLabels.size(); i++) {
221                         validDataLabels.addAll((List) labelMap.get(userLabels.get(i)));
222                     }
223 
224                     logger.debug("The valid labels for user label " + userLabels + " are " + validDataLabels);
225 
226                     Iterator dataLabelIter = validDataLabels.iterator();
227 
228                     while (dataLabelIter.hasNext()) {
229                         String validDataLabel = (String) dataLabelIter.next();
230 
231                         if (argumentDataLabel.equals(validDataLabel)) {
232                             logger.debug(userLabels + " maps to " + validDataLabel + " which matches the argument");
233                             matched = true;
234                         }
235                     }
236 
237                     if (matched) {
238                         logger.debug("We have a match!");
239                         matches++;
240                     } else {
241                         logger.debug("We have a miss!");
242                         misses++;
243                     }
244                 }
245             }
246             Assert.isTrue((matches + misses) == labeledArguments,
247                 "The matches (" + matches + ") and misses (" + misses + " ) don't add up (" + labeledArguments + ")");
248 
249             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             if ((matches > 0) && (misses == 0)) {
257                 result = ACCESS_GRANTED;
258             } else if (labeledArguments == 0) {
259                 if (allowAccessIfNoAttributesAreLabeled) {
260                     result = ACCESS_GRANTED;
261                 } else {
262                     result = ACCESS_DENIED;
263                 }
264             }
265         }
266 
267         if (logger.isDebugEnabled()) {
268             switch (result) {
269             case ACCESS_GRANTED:
270 
271                 if (logger.isDebugEnabled()) {
272                     logger.debug("===== Access is granted =====");
273                 }
274 
275                 break;
276 
277             case ACCESS_DENIED:
278 
279                 if (logger.isDebugEnabled()) {
280                     logger.debug("===== Access is denied =====");
281                 }
282 
283                 break;
284 
285             case ACCESS_ABSTAIN:
286 
287                 if (logger.isDebugEnabled()) {
288                     logger.debug("===== Abstaining =====");
289                 }
290 
291                 break;
292             }
293         }
294 
295         return result;
296     }
297 }