1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.afterinvocation;
17
18 import org.acegisecurity.AccessDeniedException;
19 import org.acegisecurity.Authentication;
20 import org.acegisecurity.AuthorizationServiceException;
21 import org.acegisecurity.ConfigAttribute;
22 import org.acegisecurity.ConfigAttributeDefinition;
23 import org.acegisecurity.acl.AclEntry;
24 import org.acegisecurity.acl.AclManager;
25 import org.acegisecurity.acl.basic.BasicAclEntry;
26 import org.acegisecurity.acl.basic.SimpleAclEntry;
27
28 import org.apache.commons.collections.iterators.ArrayIterator;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 import org.springframework.beans.factory.InitializingBean;
33
34 import org.springframework.util.Assert;
35
36 import java.lang.reflect.Array;
37
38 import java.util.Collection;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.Set;
42
43
44 /***
45 * <p>
46 * Given a <code>Collection</code> of domain object instances returned from a
47 * secure object invocation, remove any <code>Collection</code> elements the
48 * principal does not have appropriate permission to access as defined by the
49 * {@link AclManager}.
50 * </p>
51 *
52 * <p>
53 * The <code>AclManager</code> is used to retrieve the access control list
54 * (ACL) permissions associated with each <code>Collection</code> domain
55 * object instance element for the current <code>Authentication</code> object.
56 * This class is designed to process {@link AclEntry}s that are subclasses of
57 * {@link org.acegisecurity.acl.basic.BasicAclEntry} only.
58 * Generally these are obtained by using the {@link
59 * org.acegisecurity.acl.basic.BasicAclProvider}.
60 * </p>
61 *
62 * <p>
63 * This after invocation provider will fire if any {@link
64 * ConfigAttribute#getAttribute()} matches the {@link
65 * #processConfigAttribute}. The provider will then lookup the ACLs from the
66 * <code>AclManager</code> and ensure the principal is {@link
67 * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for
68 * at least one of the {@link #requirePermission}s for each
69 * <code>Collection</code> element. If the principal does not have at least
70 * one of the permissions, that element will not be included in the returned
71 * <code>Collection</code>.
72 * </p>
73 *
74 * <p>
75 * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code>
76 * with a {@link #processConfigAttribute} of
77 * <code>AFTER_ACL_COLLECTION_READ</code> and a {@link #requirePermission} of
78 * <code>SimpleAclEntry.READ</code>. These are also the defaults.
79 * </p>
80 *
81 * <p>
82 * The <code>AclManager</code> is allowed to return any implementations of
83 * <code>AclEntry</code> it wishes. However, this provider will only be able
84 * to validate against <code>BasicAclEntry</code>s, and thus a
85 * <code>Collection</code> element will be filtered from the resulting
86 * <code>Collection</code> if no <code>AclEntry</code> is of type
87 * <code>BasicAclEntry</code>.
88 * </p>
89 *
90 * <p>
91 * If the provided <code>returnObject</code> is <code>null</code>, a
92 * <code>null</code><code>Collection</code> will be returned. If the provided
93 * <code>returnObject</code> is not a <code>Collection</code>, an {@link
94 * AuthorizationServiceException} will be thrown.
95 * </p>
96 *
97 * <p>
98 * All comparisons and prefixes are case sensitive.
99 * </p>
100 *
101 * @author Ben Alex
102 * @author Paulo Neves
103 * @version $Id: BasicAclEntryAfterInvocationCollectionFilteringProvider.java,v 1.7 2005/11/17 00:55:56 benalex Exp $
104 */
105 public class BasicAclEntryAfterInvocationCollectionFilteringProvider
106 implements AfterInvocationProvider, InitializingBean {
107
108
109 protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
110
111
112
113 private AclManager aclManager;
114 private String processConfigAttribute = "AFTER_ACL_COLLECTION_READ";
115 private int[] requirePermission = {SimpleAclEntry.READ};
116
117
118
119 public void setAclManager(AclManager aclManager) {
120 this.aclManager = aclManager;
121 }
122
123 public AclManager getAclManager() {
124 return aclManager;
125 }
126
127 public void setProcessConfigAttribute(String processConfigAttribute) {
128 this.processConfigAttribute = processConfigAttribute;
129 }
130
131 public String getProcessConfigAttribute() {
132 return processConfigAttribute;
133 }
134
135 public void setRequirePermission(int[] requirePermission) {
136 this.requirePermission = requirePermission;
137 }
138
139 public int[] getRequirePermission() {
140 return requirePermission;
141 }
142
143 public void afterPropertiesSet() throws Exception {
144 Assert.notNull(processConfigAttribute,
145 "A processConfigAttribute is mandatory");
146 Assert.notNull(aclManager, "An aclManager is mandatory");
147
148 if ((requirePermission == null) || (requirePermission.length == 0)) {
149 throw new IllegalArgumentException(
150 "One or more requirePermission entries is mandatory");
151 }
152 }
153
154 public Object decide(Authentication authentication, Object object,
155 ConfigAttributeDefinition config, Object returnedObject)
156 throws AccessDeniedException {
157 Iterator iter = config.getConfigAttributes();
158
159 while (iter.hasNext()) {
160 ConfigAttribute attr = (ConfigAttribute) iter.next();
161
162 if (this.supports(attr)) {
163
164 if (returnedObject == null) {
165 if (logger.isDebugEnabled()) {
166 logger.debug("Return object is null, skipping");
167 }
168
169 return null;
170 }
171
172 Filterer filterer = null;
173
174 if (returnedObject instanceof Collection) {
175 Collection collection = (Collection) returnedObject;
176 filterer = new CollectionFilterer(collection);
177 } else if (returnedObject.getClass().isArray()) {
178 Object[] array = (Object[]) returnedObject;
179 filterer = new ArrayFilterer(array);
180 } else {
181 throw new AuthorizationServiceException(
182 "A Collection or an array (or null) was required as the returnedObject, but the returnedObject was: "
183 + returnedObject);
184 }
185
186
187 Iterator collectionIter = filterer.iterator();
188
189 while (collectionIter.hasNext()) {
190 Object domainObject = collectionIter.next();
191
192 boolean hasPermission = false;
193
194 AclEntry[] acls = null;
195
196 if (domainObject == null) {
197 hasPermission = true;
198 } else {
199 acls = aclManager.getAcls(domainObject, authentication);
200 }
201
202 if ((acls != null) && (acls.length != 0)) {
203 for (int i = 0; i < acls.length; i++) {
204
205 if (acls[i] instanceof BasicAclEntry) {
206 BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
207
208
209 for (int y = 0; y < requirePermission.length;
210 y++) {
211 if (processableAcl.isPermitted(
212 requirePermission[y])) {
213 hasPermission = true;
214
215 if (logger.isDebugEnabled()) {
216 logger.debug(
217 "Principal is authorised for element: "
218 + domainObject
219 + " due to ACL: "
220 + processableAcl.toString());
221 }
222 }
223 }
224 }
225 }
226 }
227
228 if (!hasPermission) {
229 filterer.remove(domainObject);
230
231 if (logger.isDebugEnabled()) {
232 logger.debug(
233 "Principal is NOT authorised for element: "
234 + domainObject);
235 }
236 }
237 }
238
239 return filterer.getFilteredObject();
240 }
241 }
242
243 return returnedObject;
244 }
245
246 public boolean supports(ConfigAttribute attribute) {
247 if ((attribute.getAttribute() != null)
248 && attribute.getAttribute().equals(getProcessConfigAttribute())) {
249 return true;
250 } else {
251 return false;
252 }
253 }
254
255 /***
256 * This implementation supports any type of class, because it does not
257 * query the presented secure object.
258 *
259 * @param clazz the secure object
260 *
261 * @return always <code>true</code>
262 */
263 public boolean supports(Class clazz) {
264 return true;
265 }
266 }
267
268
269 /***
270 * Filter strategy interface.
271 */
272 interface Filterer {
273
274
275 /***
276 * Gets the filtered collection or array.
277 *
278 * @return the filtered collection or array
279 */
280 public Object getFilteredObject();
281
282 /***
283 * Returns an iterator over the filtered collection or array.
284 *
285 * @return an Iterator
286 */
287 public Iterator iterator();
288
289 /***
290 * Removes the the given object from the resulting list.
291 *
292 * @param object the object to be removed
293 */
294 public void remove(Object object);
295 }
296
297
298 /***
299 * A filter used to filter Collections.
300 */
301 class CollectionFilterer implements Filterer {
302
303
304 protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
305
306
307
308 private Collection collection;
309
310
311
312 private Iterator collectionIter;
313 private Set removeList;
314
315
316
317 CollectionFilterer(Collection collection) {
318 this.collection = collection;
319
320
321
322
323
324
325
326
327 removeList = new HashSet();
328 }
329
330
331
332 /***
333 * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
334 */
335 public Object getFilteredObject() {
336
337 Iterator removeIter = removeList.iterator();
338
339 int originalSize = collection.size();
340
341 while (removeIter.hasNext()) {
342 collection.remove(removeIter.next());
343 }
344
345 if (logger.isDebugEnabled()) {
346 logger.debug("Original collection contained " + originalSize
347 + " elements; now contains " + collection.size() + " elements");
348 }
349
350 return collection;
351 }
352
353 /***
354 * @see org.acegisecurity.afterinvocation.Filterer#iterator()
355 */
356 public Iterator iterator() {
357 collectionIter = collection.iterator();
358
359 return collectionIter;
360 }
361
362 /***
363 * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
364 */
365 public void remove(Object object) {
366 collectionIter.remove();
367 }
368 }
369
370
371 /***
372 * A filter used to filter arrays.
373 */
374 class ArrayFilterer implements Filterer {
375
376
377 protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
378
379
380
381 private Set removeList;
382 private Object[] list;
383
384
385
386 ArrayFilterer(Object[] list) {
387 this.list = list;
388
389
390
391
392 removeList = new HashSet();
393 }
394
395
396
397 /***
398 * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
399 */
400 public Object getFilteredObject() {
401
402 int originalSize = list.length;
403 int sizeOfResultingList = originalSize - removeList.size();
404 Object[] filtered = (Object[]) Array.newInstance(list.getClass()
405 .getComponentType(),
406 sizeOfResultingList);
407
408 for (int i = 0, j = 0; i < list.length; i++) {
409 Object object = list[i];
410
411 if (!removeList.contains(object)) {
412 filtered[j] = object;
413 j++;
414 }
415 }
416
417 if (logger.isDebugEnabled()) {
418 logger.debug("Original array contained " + originalSize
419 + " elements; now contains " + sizeOfResultingList
420 + " elements");
421 }
422
423 return filtered;
424 }
425
426 /***
427 * @see org.acegisecurity.afterinvocation.Filterer#iterator()
428 */
429 public Iterator iterator() {
430 return new ArrayIterator(list);
431 }
432
433 /***
434 * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
435 */
436 public void remove(Object object) {
437 removeList.add(object);
438 }
439 }