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 * <bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
94 * <property name="allowIfAllAbstainDecisions"><value>false</value></property>
95 * <property name="decisionVoters">
96 * <list>
97 * <bean class="org.acegisecurity.vote.RoleVoter"/>
98 * <bean class="org.acegisecurity.vote.LabelBasedAclVoter">
99 * <property name="attributeIndicatingLabeledOperation">
100 * <value>LABELED_OPERATION</value>
101 * </property>
102 * <property name="labelMap">
103 * <map>
104 * <entry key="DATA_LABEL_BLUE">
105 * <list>
106 * <value>blue</value>
107 * <value>indigo</value>
108 * <value>purple</value>
109 * </list>
110 * </entry>
111 * <entry key="LABEL_ORANGE">
112 * <list>
113 * <value>orange</value>
114 * <value>sunshine</value>
115 * <value>amber</value>
116 * </list>
117 * </entry>
118 * <entry key="LABEL_ADMIN">
119 * <list>
120 * <value>blue</value>
121 * <value>indigo</value>
122 * <value>purple</value>
123 * <value>orange</value>
124 * <value>sunshine</value>
125 * <value>amber</value>
126 * </list>
127 * </entry>
128 * </map>
129 * </property>
130 * </bean>
131 * </list>
132 * </property>
133 * </bean>
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 }