1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.acl.basic;
17
18 import org.acegisecurity.Authentication;
19 import org.acegisecurity.acl.AclEntry;
20 import org.acegisecurity.acl.AclProvider;
21 import org.acegisecurity.acl.basic.cache.NullAclEntryCache;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 import org.springframework.beans.factory.InitializingBean;
27 import org.springframework.util.Assert;
28
29 import java.lang.reflect.Constructor;
30
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.Map;
34
35
36 /***
37 * <P>
38 * Retrieves access control lists (ACL) entries for domain object instances
39 * from a data access object (DAO).
40 * </p>
41 *
42 * <P>
43 * This implementation will provide ACL lookup services for any object that it
44 * can determine the {@link AclObjectIdentity} for by calling the {@link
45 * #obtainIdentity(Object)} method. Subclasses can override this method if
46 * they only want the <code>BasicAclProvider</code> responding to particular
47 * domain object instances.
48 * </p>
49 *
50 * <P>
51 * <code>BasicAclProvider</code> will walk an inheritance hierarchy if a
52 * <code>BasicAclEntry</code> returned by the DAO indicates it has a parent.
53 * NB: inheritance occurs at a <I>domain instance object</I> level. It does
54 * not occur at an ACL recipient level. This means
55 * <B>all</B><code>BasicAclEntry</code>s for a given domain instance object
56 * <B>must</B> have the <B>same</B> parent identity, or
57 * <B>all</B><code>BasicAclEntry</code>s must have <code>null</code> as their
58 * parent identity.
59 * </p>
60 *
61 * <P>
62 * A cache should be used. This is provided by the {@link BasicAclEntryCache}.
63 * <code>BasicAclProvider</code> by default is setup to use the {@link
64 * NullAclEntryCache}, which performs no caching.
65 * </p>
66 *
67 * <P>
68 * To implement the {@link #getAcls(Object, Authentication)} method,
69 * <code>BasicAclProvider</code> requires a {@link EffectiveAclsResolver} to
70 * be configured against it. By default the {@link
71 * GrantedAuthorityEffectiveAclsResolver} is used.
72 * </p>
73 *
74 * @author Ben Alex
75 * @version $Id: BasicAclProvider.java,v 1.5 2005/11/17 00:55:47 benalex Exp $
76 */
77 public class BasicAclProvider implements AclProvider, InitializingBean {
78
79
80 private static final Log logger = LogFactory.getLog(BasicAclProvider.class);
81
82 /***
83 * Marker added to the cache to indicate an AclObjectIdentity has no
84 * corresponding BasicAclEntry[]s
85 */
86 private static String RECIPIENT_FOR_CACHE_EMPTY = "RESERVED_RECIPIENT_NOBODY";
87
88
89
90 /***
91 * Must be set to an appropriate data access object. Defaults to
92 * <code>null</code>.
93 */
94 private BasicAclDao basicAclDao;
95 private BasicAclEntryCache basicAclEntryCache = new NullAclEntryCache();
96 private Class defaultAclObjectIdentityClass = NamedEntityObjectIdentity.class;
97 private Class restrictSupportToClass = null;
98 private EffectiveAclsResolver effectiveAclsResolver = new GrantedAuthorityEffectiveAclsResolver();
99
100
101
102 public AclEntry[] getAcls(Object domainInstance) {
103 Map map = new HashMap();
104
105 AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
106
107 Assert.notNull(aclIdentity, "domainInstance is not supported by this provider");
108
109 if (logger.isDebugEnabled()) {
110 logger.debug("Looking up: " + aclIdentity.toString());
111 }
112
113 BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
114
115
116 if (instanceAclEntries == null) {
117 return null;
118 }
119
120
121 for (int i = 0; i < instanceAclEntries.length; i++) {
122 if (logger.isDebugEnabled()) {
123 logger.debug("Explicit add: "
124 + instanceAclEntries[i].toString());
125 }
126
127 map.put(instanceAclEntries[i].getRecipient(), instanceAclEntries[i]);
128 }
129
130 AclObjectIdentity parent = instanceAclEntries[0]
131 .getAclObjectParentIdentity();
132
133 while (parent != null) {
134 BasicAclEntry[] parentAclEntries = lookup(parent);
135
136 if (logger.isDebugEnabled()) {
137 logger.debug("Parent lookup: " + parent.toString());
138 }
139
140
141 if (parentAclEntries == null) {
142 if (logger.isDebugEnabled()) {
143 logger.debug("Parent could not be found in ACL repository");
144 }
145
146 break;
147 }
148
149
150 for (int i = 0; i < parentAclEntries.length; i++) {
151 if (!map.containsKey(parentAclEntries[i].getRecipient())) {
152 if (logger.isDebugEnabled()) {
153 logger.debug("Added parent to map: "
154 + parentAclEntries[i].toString());
155 }
156
157 map.put(parentAclEntries[i].getRecipient(),
158 parentAclEntries[i]);
159 } else {
160 if (logger.isDebugEnabled()) {
161 logger.debug("Did NOT add parent to map: "
162 + parentAclEntries[i].toString());
163 }
164 }
165 }
166
167
168 parent = parentAclEntries[0].getAclObjectParentIdentity();
169 }
170
171 Collection collection = map.values();
172
173 return (AclEntry[]) collection.toArray(new AclEntry[]{});
174 }
175
176 public AclEntry[] getAcls(Object domainInstance,
177 Authentication authentication) {
178 AclEntry[] allAcls = (AclEntry[]) this.getAcls(domainInstance);
179
180 return this.effectiveAclsResolver.resolveEffectiveAcls(allAcls,
181 authentication);
182 }
183
184 public void setBasicAclDao(BasicAclDao basicAclDao) {
185 this.basicAclDao = basicAclDao;
186 }
187
188 public BasicAclDao getBasicAclDao() {
189 return basicAclDao;
190 }
191
192 public void setBasicAclEntryCache(BasicAclEntryCache basicAclEntryCache) {
193 this.basicAclEntryCache = basicAclEntryCache;
194 }
195
196 public BasicAclEntryCache getBasicAclEntryCache() {
197 return basicAclEntryCache;
198 }
199
200 /***
201 * Allows selection of the <code>AclObjectIdentity</code> class that an
202 * attempt should be made to construct if the passed object does not
203 * implement <code>AclObjectIdentityAware</code>.
204 *
205 * <P>
206 * NB: Any <code>defaultAclObjectIdentityClass</code><b>must</b> provide a
207 * public constructor that accepts an <code>Object</code>. Otherwise it is
208 * not possible for the <code>BasicAclProvider</code> to try to create the
209 * <code>AclObjectIdentity</code> instance at runtime.
210 * </p>
211 *
212 * @param defaultAclObjectIdentityClass
213 */
214 public void setDefaultAclObjectIdentityClass(
215 Class defaultAclObjectIdentityClass) {
216 this.defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
217 }
218
219 public Class getDefaultAclObjectIdentityClass() {
220 return defaultAclObjectIdentityClass;
221 }
222
223 public void setEffectiveAclsResolver(
224 EffectiveAclsResolver effectiveAclsResolver) {
225 this.effectiveAclsResolver = effectiveAclsResolver;
226 }
227
228 public EffectiveAclsResolver getEffectiveAclsResolver() {
229 return effectiveAclsResolver;
230 }
231
232 /***
233 * If set to a value other than <code>null</code>, the {@link
234 * #supports(Object)} method will <b>only</b> support the indicates class.
235 * This is useful if you wish to wire multiple
236 * <code>BasicAclProvider</code>s in a list of
237 * <code>AclProviderManager.providers</code> but only have particular
238 * instances respond to particular domain object types.
239 *
240 * @param restrictSupportToClass the class to restrict this
241 * <code>BasicAclProvider</code> to service request for, or
242 * <code>null</code> (the default) if the
243 * <code>BasicAclProvider</code> should respond to every class
244 * presented
245 */
246 public void setRestrictSupportToClass(Class restrictSupportToClass) {
247 this.restrictSupportToClass = restrictSupportToClass;
248 }
249
250 public Class getRestrictSupportToClass() {
251 return restrictSupportToClass;
252 }
253
254 public void afterPropertiesSet() {
255 Assert.notNull(basicAclDao, "basicAclDao required");
256 Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
257 Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
258 Assert.notNull(effectiveAclsResolver, "effectiveAclsResolver required");
259 Assert.notNull(defaultAclObjectIdentityClass, "defaultAclObjectIdentityClass required");
260 Assert.isTrue(AclObjectIdentity.class.isAssignableFrom(this.defaultAclObjectIdentityClass),
261 "defaultAclObjectIdentityClass must implement AclObjectIdentity");
262
263 try {
264 Constructor constructor = defaultAclObjectIdentityClass
265 .getConstructor(new Class[]{Object.class});
266 } catch (NoSuchMethodException nsme) {
267 throw new IllegalArgumentException("defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!");
268 }
269 }
270
271 /***
272 * Indicates support for the passed object.
273 *
274 * <p>
275 * An object will only be supported if it (i) is allowed to be supported as
276 * defined by the {@link #setRestrictSupportToClass(Class)} method,
277 * <b>and</b> (ii) if an <code>AclObjectIdentity</code> is returned by
278 * {@link #obtainIdentity(Object)} for that object.
279 * </p>
280 *
281 * @param domainInstance the instance to check
282 *
283 * @return <code>true</code> if this provider supports the passed object,
284 * <code>false</code> otherwise
285 */
286 public boolean supports(Object domainInstance) {
287 if (domainInstance == null) {
288 if (logger.isDebugEnabled()) {
289 logger.debug("domainInstance is null");
290 }
291
292 return false;
293 }
294
295 if ((restrictSupportToClass != null)
296 && !restrictSupportToClass.isAssignableFrom(
297 domainInstance.getClass())) {
298 if (logger.isDebugEnabled()) {
299 logger.debug("domainInstance not instance of "
300 + restrictSupportToClass);
301 }
302
303 return false;
304 }
305
306 if (obtainIdentity(domainInstance) == null) {
307 if (logger.isDebugEnabled()) {
308 logger.debug("obtainIdentity returned null");
309 }
310
311 return false;
312 } else {
313 if (logger.isDebugEnabled()) {
314 logger.debug("obtainIdentity returned "
315 + obtainIdentity(domainInstance));
316 }
317
318 return true;
319 }
320 }
321
322 /***
323 * This method looks up the <code>AclObjectIdentity</code> of a passed
324 * domain object instance.
325 *
326 * <P>
327 * This implementation attempts to obtain the
328 * <code>AclObjectIdentity</code> via reflection inspection of the class
329 * for the {@link AclObjectIdentityAware} interface. If this fails, an
330 * attempt is made to construct a {@link
331 * #getDefaultAclObjectIdentityClass()} object by passing the domain
332 * instance object into its constructor.
333 * </p>
334 *
335 * @param domainInstance the domain object instance (never
336 * <code>null</code>)
337 *
338 * @return an ACL object identity, or <code>null</code> if one could not be
339 * obtained
340 */
341 protected AclObjectIdentity obtainIdentity(Object domainInstance) {
342 if (domainInstance instanceof AclObjectIdentityAware) {
343 AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
344
345 if (logger.isDebugEnabled()) {
346 logger.debug("domainInstance: " + domainInstance
347 + " cast to AclObjectIdentityAware");
348 }
349
350 return aclObjectIdentityAware.getAclObjectIdentity();
351 }
352
353 try {
354 Constructor constructor = defaultAclObjectIdentityClass
355 .getConstructor(new Class[] {Object.class});
356
357 if (logger.isDebugEnabled()) {
358 logger.debug("domainInstance: " + domainInstance
359 + " attempting to pass to constructor: " + constructor);
360 }
361
362 return (AclObjectIdentity) constructor.newInstance(new Object[] {domainInstance});
363 } catch (Exception ex) {
364 if (logger.isDebugEnabled()) {
365 logger.debug("Error attempting construction of "
366 + defaultAclObjectIdentityClass + ": " + ex.getMessage(), ex);
367
368 if (ex.getCause() != null) {
369 logger.debug("Cause: " + ex.getCause().getMessage(),
370 ex.getCause());
371 }
372 }
373
374 return null;
375 }
376 }
377
378 private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
379 BasicAclEntry[] result = basicAclEntryCache.getEntriesFromCache(aclObjectIdentity);
380
381 if (result != null) {
382 if (result[0].getRecipient().equals(RECIPIENT_FOR_CACHE_EMPTY)) {
383 return null;
384 } else {
385 return result;
386 }
387 }
388
389 result = basicAclDao.getAcls(aclObjectIdentity);
390
391 if (result == null) {
392 SimpleAclEntry[] emptyAclEntries = {new SimpleAclEntry(RECIPIENT_FOR_CACHE_EMPTY,
393 aclObjectIdentity, null, 0)};
394 basicAclEntryCache.putEntriesInCache(emptyAclEntries);
395
396 return null;
397 }
398
399 basicAclEntryCache.putEntriesInCache(result);
400
401 return result;
402 }
403 }