1
2
3
4
5
6
7
8
9
10
11
12
13
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
130
131 private static final Log logger = LogFactory.getLog(BasicAclEntryVoter.class);
132
133
134
135 private AclManager aclManager;
136 private String internalMethod;
137 private String processConfigAttribute;
138 private int[] requirePermission;
139
140
141
142 public void setAclManager(AclManager aclManager) {
143 this.aclManager = aclManager;
144 }
145
146 public AclManager getAclManager() {
147 return aclManager;
148 }
149
150 public void setInternalMethod(String internalMethod) {
151 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 public String getInternalMethod() {
168 return internalMethod;
169 }
170
171 public void setProcessConfigAttribute(String processConfigAttribute) {
172 this.processConfigAttribute = processConfigAttribute;
173 }
174
175 public String getProcessConfigAttribute() {
176 return processConfigAttribute;
177 }
178
179 public void setRequirePermission(int[] requirePermission) {
180 this.requirePermission = requirePermission;
181 }
182
183 public int[] getRequirePermission() {
184 return requirePermission;
185 }
186
187 public void afterPropertiesSet() throws Exception {
188 Assert.notNull(processConfigAttribute,
189 "A processConfigAttribute is mandatory");
190 Assert.notNull(aclManager, "An aclManager is mandatory");
191
192 if ((requirePermission == null) || (requirePermission.length == 0)) {
193 throw new IllegalArgumentException(
194 "One or more requirePermission entries is mandatory");
195 }
196 }
197
198 public boolean supports(ConfigAttribute attribute) {
199 if ((attribute.getAttribute() != null)
200 && attribute.getAttribute().startsWith(getProcessConfigAttribute())) {
201 return true;
202 } else {
203 return false;
204 }
205 }
206
207 public int vote(Authentication authentication, Object object,
208 ConfigAttributeDefinition config) {
209 Iterator iter = config.getConfigAttributes();
210
211 while (iter.hasNext()) {
212 ConfigAttribute attr = (ConfigAttribute) iter.next();
213
214 if (this.supports(attr)) {
215
216
217 Object domainObject = getDomainObjectInstance(object);
218
219
220 if (domainObject == null) {
221 return AccessDecisionVoter.ACCESS_ABSTAIN;
222 }
223
224
225 if ((internalMethod != null) && !"".equals(internalMethod)) {
226 try {
227 Class clazz = domainObject.getClass();
228 Method method = clazz.getMethod(internalMethod,
229 new Class[] {});
230 domainObject = method.invoke(domainObject,
231 new Object[] {});
232 } catch (NoSuchMethodException nsme) {
233 throw new AuthorizationServiceException(
234 "Object of class '" + domainObject.getClass()
235 + "' does not provide the requested internalMethod: "
236 + internalMethod);
237 } catch (IllegalAccessException iae) {
238 if (logger.isDebugEnabled()) {
239 logger.debug("IllegalAccessException", iae);
240
241 if (iae.getCause() != null) {
242 logger.debug("Cause: "
243 + iae.getCause().getMessage(),
244 iae.getCause());
245 }
246 }
247
248 throw new AuthorizationServiceException(
249 "Problem invoking internalMethod: "
250 + internalMethod + " for object: " + domainObject);
251 } catch (InvocationTargetException ite) {
252 if (logger.isDebugEnabled()) {
253 logger.debug("InvocationTargetException", ite);
254
255 if (ite.getCause() != null) {
256 logger.debug("Cause: "
257 + ite.getCause().getMessage(),
258 ite.getCause());
259 }
260 }
261
262 throw new AuthorizationServiceException(
263 "Problem invoking internalMethod: "
264 + internalMethod + " for object: " + domainObject);
265 }
266 }
267
268
269 AclEntry[] acls = aclManager.getAcls(domainObject,
270 authentication);
271
272
273 if ((acls == null) || (acls.length == 0)) {
274 return AccessDecisionVoter.ACCESS_DENIED;
275 }
276
277
278 for (int i = 0; i < acls.length; i++) {
279
280 if (acls[i] instanceof BasicAclEntry) {
281 BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
282
283
284 for (int y = 0; y < requirePermission.length; y++) {
285 if (processableAcl.isPermitted(requirePermission[y])) {
286 return AccessDecisionVoter.ACCESS_GRANTED;
287 }
288 }
289 }
290 }
291
292
293 return AccessDecisionVoter.ACCESS_DENIED;
294 }
295 }
296
297
298 return AccessDecisionVoter.ACCESS_ABSTAIN;
299 }
300 }