View Javadoc

1   /* Copyright 2004, 2005 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  
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     //~ Static fields/initializers =============================================
108 
109     protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
110 
111     //~ Instance fields ========================================================
112 
113     private AclManager aclManager;
114     private String processConfigAttribute = "AFTER_ACL_COLLECTION_READ";
115     private int[] requirePermission = {SimpleAclEntry.READ};
116 
117     //~ Methods ================================================================
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                 // Need to process the Collection for this invocation
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                 // Locate unauthorised Collection elements
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                             // Locate processable AclEntrys
205                             if (acls[i] instanceof BasicAclEntry) {
206                             	BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
207 
208                                 // See if principal has any of the required permissions
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     //~ Methods ================================================================
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     //~ Static fields/initializers =============================================
303 
304     protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
305 
306     //~ Instance fields ========================================================
307 
308     private Collection collection;
309 
310     // collectionIter offers significant performance optimisations (as
311     // per acegisecurity-developer mailing list conversation 19/5/05)
312     private Iterator collectionIter;
313     private Set removeList;
314 
315     //~ Constructors ===========================================================
316 
317     CollectionFilterer(Collection collection) {
318         this.collection = collection;
319 
320         // We create a Set of objects to be removed from the Collection,
321         // as ConcurrentModificationException prevents removal during
322         // iteration, and making a new Collection to be returned is
323         // problematic as the original Collection implementation passed
324         // to the method may not necessarily be re-constructable (as
325         // the Collection(collection) constructor is not guaranteed and
326         // manually adding may lose sort order or other capabilities)
327         removeList = new HashSet();
328     }
329 
330     //~ Methods ================================================================
331 
332     /***
333      * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
334      */
335     public Object getFilteredObject() {
336         // Now the Iterator has ended, remove Objects from Collection
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     //~ Static fields/initializers =============================================
376 
377     protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
378 
379     //~ Instance fields ========================================================
380 
381     private Set removeList;
382     private Object[] list;
383 
384     //~ Constructors ===========================================================
385 
386     ArrayFilterer(Object[] list) {
387         this.list = list;
388 
389         // Collect the removed objects to a HashSet so that
390         // it is fast to lookup them when a filtered array
391         // is constructed.
392         removeList = new HashSet();
393     }
394 
395     //~ Methods ================================================================
396 
397     /***
398      * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
399      */
400     public Object getFilteredObject() {
401         // Recreate an array of same type and filter the removed objects.
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 }