001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015: package org.acegisecurity.vote;
016:
017: import org.acegisecurity.Authentication;
018: import org.acegisecurity.ConfigAttribute;
019: import org.acegisecurity.ConfigAttributeDefinition;
020:
021: import org.aopalliance.intercept.MethodInvocation;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025:
026: import org.springframework.util.Assert;
027:
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Vector;
031: import java.util.Map;
032:
033: /**
034: * <p>This Acl voter will evaluate methods based on labels applied to incoming arguments. It will only check
035: * methods that have been properly tagged in the MethodSecurityInterceptor with the value stored in
036: * <tt>attributeIndicatingLabeledOperation</tt>. If a method has been tagged, then it examines each argument, and if the
037: * argument implements {@link LabeledData}, then it will asses if the user's list of granted authorities matches.
038: * </p>
039: *
040: * <p>By default, if none of the arguments are labeled, then the access will be granted. This can be overridden by
041: * setting <tt>allowAccessIfNoAttributesAreLabeled</tt> to false in the Spring context file.</p>
042: *
043: * <p>In many situations, different values are linked together to define a common label, it is necessary to
044: * define a map in the application context that links user-assigned label access to domain object labels. This is done
045: * by setting up the <tt>labelMap</tt> in the application context.</p>
046: *
047: * @author Greg Turnquist
048: * @version $Id: LabelBasedAclVoter.java 1962 2007-08-27 17:21:16Z luke_t $
049: *
050: * @see org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
051: */
052: public class LabelBasedAclVoter extends AbstractAclVoter {
053: //~ Static fields/initializers =====================================================================================
054:
055: private static final Log logger = LogFactory
056: .getLog(LabelBasedAclVoter.class);
057:
058: //~ Instance fields ================================================================================================
059:
060: private Map labelMap = null;
061: private String attributeIndicatingLabeledOperation = null;
062: private boolean allowAccessIfNoAttributesAreLabeled = true;
063:
064: //~ Methods ========================================================================================================
065:
066: /**
067: * Set whether or not to allow the user to run methods in which none of the incoming arguments are labeled.
068: *
069: * <p>Default value: <b>true, users can run such methods.</b></p>
070: *
071: * @param allowAccessIfNoAttributesAreLabeled boolean
072: */
073: public void setAllowAccessIfNoAttributesAreLabeled(
074: boolean allowAccessIfNoAttributesAreLabeled) {
075: this .allowAccessIfNoAttributesAreLabeled = allowAccessIfNoAttributesAreLabeled;
076: }
077:
078: /**
079: * Each method intended for evaluation by this voter must include this tag name in the definition of the
080: * MethodSecurityInterceptor, indicating if this voter should evaluate the arguments and compare them against the
081: * label map.
082: *
083: * @param attributeIndicatingLabeledOperation string
084: */
085: public void setAttributeIndicatingLabeledOperation(
086: String attributeIndicatingLabeledOperation) {
087: this .attributeIndicatingLabeledOperation = attributeIndicatingLabeledOperation;
088: }
089:
090: /**
091: * Set the map that correlate a user's assigned label against domain object values that are considered data
092: * labels. An example application context configuration of a <tt>labelMap</tt>:
093: *
094: * <pre>
095: * <bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
096: * <property name="allowIfAllAbstainDecisions"><value>false</value></property>
097: * <property name="decisionVoters">
098: * <list>
099: * <bean class="org.acegisecurity.vote.RoleVoter"/>
100: * <bean class="org.acegisecurity.vote.LabelBasedAclVoter">
101: * <property name="attributeIndicatingLabeledOperation">
102: * <value>LABELED_OPERATION</value>
103: * </property>
104: * <property name="labelMap">
105: * <map>
106: * <entry key="DATA_LABEL_BLUE">
107: * <list>
108: * <value>blue</value>
109: * <value>indigo</value>
110: * <value>purple</value>
111: * </list>
112: * </entry>
113: * <entry key="LABEL_ORANGE">
114: * <list>
115: * <value>orange</value>
116: * <value>sunshine</value>
117: * <value>amber</value>
118: * </list>
119: * </entry>
120: * <entry key="LABEL_ADMIN">
121: * <list>
122: * <value>blue</value>
123: * <value>indigo</value>
124: * <value>purple</value>
125: * <value>orange</value>
126: * <value>sunshine</value>
127: * <value>amber</value>
128: * </list>
129: * </entry>
130: * </map>
131: * </property>
132: * </bean>
133: * </list>
134: * </property>
135: * </bean>
136: * </pre>
137: *
138: * @param labelMap a map structured as in the above example.
139: *
140: */
141: public void setLabelMap(Map labelMap) {
142: this .labelMap = labelMap;
143: }
144:
145: /**
146: * This acl voter will only evaluate labeled methods if they are marked in the security interceptor's
147: * configuration with the attribute stored in attributeIndicatingLabeledOperation.
148: *
149: * @param attribute DOCUMENT ME!
150: *
151: * @return DOCUMENT ME!
152: *
153: * @see org.acegisecurity.vote.AbstractAclVoter
154: * @see org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
155: */
156: public boolean supports(ConfigAttribute attribute) {
157: if (attribute.getAttribute().equals(
158: attributeIndicatingLabeledOperation)) {
159: logger.debug(attribute + " is supported.");
160:
161: return true;
162: }
163:
164: if (logger.isDebugEnabled()) {
165: logger.debug(attribute + " is unsupported.");
166: }
167:
168: return false;
169: }
170:
171: /**
172: * Vote on whether or not the user has all the labels necessary to match the method argument's labeled
173: * data.
174: *
175: * @param authentication DOCUMENT ME!
176: * @param object DOCUMENT ME!
177: * @param config DOCUMENT ME!
178: *
179: * @return ACCESS_ABSTAIN, ACCESS_GRANTED, or ACCESS_DENIED.
180: */
181: public int vote(Authentication authentication, Object object,
182: ConfigAttributeDefinition config) {
183: int result = ACCESS_ABSTAIN;
184:
185: if (logger.isDebugEnabled()) {
186: logger
187: .debug("==========================================================");
188: }
189:
190: if (this .supports((ConfigAttribute) config
191: .getConfigAttributes().next())) {
192: result = ACCESS_DENIED;
193:
194: /* Parse out the user's labels by examining the security context, and checking
195: * for matches against the label map.
196: */
197: List userLabels = new Vector();
198:
199: for (int i = 0; i < authentication.getAuthorities().length; i++) {
200: if (labelMap.containsKey(authentication
201: .getAuthorities()[i].getAuthority())) {
202: String userLabel = authentication.getAuthorities()[i]
203: .getAuthority();
204: userLabels.add(userLabel);
205: logger.debug("Adding " + userLabel + " to <<<"
206: + authentication.getName()
207: + "'s>>> authorized label list");
208: }
209: }
210:
211: MethodInvocation invocation = (MethodInvocation) object;
212:
213: int matches = 0;
214: int misses = 0;
215: int labeledArguments = 0;
216:
217: for (int j = 0; j < invocation.getArguments().length; j++) {
218: if (invocation.getArguments()[j] instanceof LabeledData) {
219: labeledArguments++;
220:
221: boolean matched = false;
222:
223: String argumentDataLabel = ((LabeledData) invocation
224: .getArguments()[j]).getLabel();
225: logger.debug("Argument["
226: + j
227: + "/"
228: + invocation.getArguments()[j].getClass()
229: .getName()
230: + "] has a data label of "
231: + argumentDataLabel);
232:
233: List validDataLabels = new Vector();
234:
235: for (int i = 0; i < userLabels.size(); i++) {
236: validDataLabels.addAll((List) labelMap
237: .get(userLabels.get(i)));
238: }
239:
240: logger.debug("The valid labels for user label "
241: + userLabels + " are " + validDataLabels);
242:
243: Iterator dataLabelIter = validDataLabels.iterator();
244:
245: while (dataLabelIter.hasNext()) {
246: String validDataLabel = (String) dataLabelIter
247: .next();
248:
249: if (argumentDataLabel.equals(validDataLabel)) {
250: logger.debug(userLabels + " maps to "
251: + validDataLabel
252: + " which matches the argument");
253: matched = true;
254: }
255: }
256:
257: if (matched) {
258: logger.debug("We have a match!");
259: matches++;
260: } else {
261: logger.debug("We have a miss!");
262: misses++;
263: }
264: }
265: }
266: Assert.isTrue((matches + misses) == labeledArguments,
267: "The matches (" + matches + ") and misses ("
268: + misses + " ) don't add up ("
269: + labeledArguments + ")");
270:
271: logger.debug("We have " + matches + " matches and "
272: + misses + " misses and " + labeledArguments
273: + " labeled arguments.");
274:
275: /* The result has already been set to ACCESS_DENIED. Only if there is a proper match of
276: * labels will this be overturned. However, if none of the attributes are actually labeled,
277: * the result is dependent on allowAccessIfNoAttributesAreLabeled.
278: */
279: if ((matches > 0) && (misses == 0)) {
280: result = ACCESS_GRANTED;
281: } else if (labeledArguments == 0) {
282: if (allowAccessIfNoAttributesAreLabeled) {
283: result = ACCESS_GRANTED;
284: } else {
285: result = ACCESS_DENIED;
286: }
287: }
288: }
289:
290: if (logger.isDebugEnabled()) {
291: switch (result) {
292: case ACCESS_GRANTED:
293:
294: if (logger.isDebugEnabled()) {
295: logger.debug("===== Access is granted =====");
296: }
297:
298: break;
299:
300: case ACCESS_DENIED:
301:
302: if (logger.isDebugEnabled()) {
303: logger.debug("===== Access is denied =====");
304: }
305:
306: break;
307:
308: case ACCESS_ABSTAIN:
309:
310: if (logger.isDebugEnabled()) {
311: logger.debug("===== Abstaining =====");
312: }
313:
314: break;
315: }
316: }
317:
318: return result;
319: }
320: }
|