org.acegisecurity.acl.basic.BasicAclProvider Maven / Gradle / Ivy
/* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.acegisecurity.acl.basic;
import org.acegisecurity.Authentication;
import org.acegisecurity.acl.AclEntry;
import org.acegisecurity.acl.AclProvider;
import org.acegisecurity.acl.basic.cache.NullAclEntryCache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
*
* Retrieves access control lists (ACL) entries for domain object instances
* from a data access object (DAO).
*
*
*
* This implementation will provide ACL lookup services for any object that it
* can determine the {@link AclObjectIdentity} for by calling the {@link
* #obtainIdentity(Object)} method. Subclasses can override this method if
* they only want the BasicAclProvider
responding to particular
* domain object instances.
*
*
*
* BasicAclProvider
will walk an inheritance hierarchy if a
* BasicAclEntry
returned by the DAO indicates it has a parent.
* NB: inheritance occurs at a domain instance object level. It does
* not occur at an ACL recipient level. This means
* allBasicAclEntry
s for a given domain instance object
* must have the same parent identity, or
* allBasicAclEntry
s must have null
as their
* parent identity.
*
*
*
* A cache should be used. This is provided by the {@link BasicAclEntryCache}.
* BasicAclProvider
by default is setup to use the {@link
* NullAclEntryCache}, which performs no caching.
*
*
*
* To implement the {@link #getAcls(Object, Authentication)} method,
* BasicAclProvider
requires a {@link EffectiveAclsResolver} to
* be configured against it. By default the {@link
* GrantedAuthorityEffectiveAclsResolver} is used.
*
*
* @author Ben Alex
* @version $Id: BasicAclProvider.java,v 1.5 2005/11/17 00:55:47 benalex Exp $
*/
public class BasicAclProvider implements AclProvider, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(BasicAclProvider.class);
/**
* Marker added to the cache to indicate an AclObjectIdentity has no
* corresponding BasicAclEntry[]s
*/
private static String RECIPIENT_FOR_CACHE_EMPTY = "RESERVED_RECIPIENT_NOBODY";
//~ Instance fields ========================================================
/**
* Must be set to an appropriate data access object. Defaults to
* null
.
*/
private BasicAclDao basicAclDao;
private BasicAclEntryCache basicAclEntryCache = new NullAclEntryCache();
private Class defaultAclObjectIdentityClass = NamedEntityObjectIdentity.class;
private Class restrictSupportToClass = null;
private EffectiveAclsResolver effectiveAclsResolver = new GrantedAuthorityEffectiveAclsResolver();
//~ Methods ================================================================
public AclEntry[] getAcls(Object domainInstance) {
Map map = new HashMap();
AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
Assert.notNull(aclIdentity, "domainInstance is not supported by this provider");
if (logger.isDebugEnabled()) {
logger.debug("Looking up: " + aclIdentity.toString());
}
BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
// Exit if there is no ACL information or parent for this instance
if (instanceAclEntries == null) {
return null;
}
// Add the leaf objects to the Map, keyed on recipient
for (int i = 0; i < instanceAclEntries.length; i++) {
if (logger.isDebugEnabled()) {
logger.debug("Explicit add: "
+ instanceAclEntries[i].toString());
}
map.put(instanceAclEntries[i].getRecipient(), instanceAclEntries[i]);
}
AclObjectIdentity parent = instanceAclEntries[0]
.getAclObjectParentIdentity();
while (parent != null) {
BasicAclEntry[] parentAclEntries = lookup(parent);
if (logger.isDebugEnabled()) {
logger.debug("Parent lookup: " + parent.toString());
}
// Exit loop if parent couldn't be found (unexpected condition)
if (parentAclEntries == null) {
if (logger.isDebugEnabled()) {
logger.debug("Parent could not be found in ACL repository");
}
break;
}
// Now add each _NEW_ recipient to the list
for (int i = 0; i < parentAclEntries.length; i++) {
if (!map.containsKey(parentAclEntries[i].getRecipient())) {
if (logger.isDebugEnabled()) {
logger.debug("Added parent to map: "
+ parentAclEntries[i].toString());
}
map.put(parentAclEntries[i].getRecipient(),
parentAclEntries[i]);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Did NOT add parent to map: "
+ parentAclEntries[i].toString());
}
}
}
// Prepare for next iteration of while loop
parent = parentAclEntries[0].getAclObjectParentIdentity();
}
Collection collection = map.values();
return (AclEntry[]) collection.toArray(new AclEntry[]{});
}
public AclEntry[] getAcls(Object domainInstance,
Authentication authentication) {
AclEntry[] allAcls = (AclEntry[]) this.getAcls(domainInstance);
return this.effectiveAclsResolver.resolveEffectiveAcls(allAcls,
authentication);
}
public void setBasicAclDao(BasicAclDao basicAclDao) {
this.basicAclDao = basicAclDao;
}
public BasicAclDao getBasicAclDao() {
return basicAclDao;
}
public void setBasicAclEntryCache(BasicAclEntryCache basicAclEntryCache) {
this.basicAclEntryCache = basicAclEntryCache;
}
public BasicAclEntryCache getBasicAclEntryCache() {
return basicAclEntryCache;
}
/**
* Allows selection of the AclObjectIdentity
class that an
* attempt should be made to construct if the passed object does not
* implement AclObjectIdentityAware
.
*
*
* NB: Any defaultAclObjectIdentityClass
must provide a
* public constructor that accepts an Object
. Otherwise it is
* not possible for the BasicAclProvider
to try to create the
* AclObjectIdentity
instance at runtime.
*
*
* @param defaultAclObjectIdentityClass
*/
public void setDefaultAclObjectIdentityClass(
Class defaultAclObjectIdentityClass) {
this.defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
}
public Class getDefaultAclObjectIdentityClass() {
return defaultAclObjectIdentityClass;
}
public void setEffectiveAclsResolver(
EffectiveAclsResolver effectiveAclsResolver) {
this.effectiveAclsResolver = effectiveAclsResolver;
}
public EffectiveAclsResolver getEffectiveAclsResolver() {
return effectiveAclsResolver;
}
/**
* If set to a value other than null
, the {@link
* #supports(Object)} method will only support the indicates class.
* This is useful if you wish to wire multiple
* BasicAclProvider
s in a list of
* AclProviderManager.providers
but only have particular
* instances respond to particular domain object types.
*
* @param restrictSupportToClass the class to restrict this
* BasicAclProvider
to service request for, or
* null
(the default) if the
* BasicAclProvider
should respond to every class
* presented
*/
public void setRestrictSupportToClass(Class restrictSupportToClass) {
this.restrictSupportToClass = restrictSupportToClass;
}
public Class getRestrictSupportToClass() {
return restrictSupportToClass;
}
public void afterPropertiesSet() {
Assert.notNull(basicAclDao, "basicAclDao required");
Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
Assert.notNull(effectiveAclsResolver, "effectiveAclsResolver required");
Assert.notNull(defaultAclObjectIdentityClass, "defaultAclObjectIdentityClass required");
Assert.isTrue(AclObjectIdentity.class.isAssignableFrom(this.defaultAclObjectIdentityClass),
"defaultAclObjectIdentityClass must implement AclObjectIdentity");
try {
Constructor constructor = defaultAclObjectIdentityClass
.getConstructor(new Class[]{Object.class});
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException("defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!");
}
}
/**
* Indicates support for the passed object.
*
*
* An object will only be supported if it (i) is allowed to be supported as
* defined by the {@link #setRestrictSupportToClass(Class)} method,
* and (ii) if an AclObjectIdentity
is returned by
* {@link #obtainIdentity(Object)} for that object.
*
*
* @param domainInstance the instance to check
*
* @return true
if this provider supports the passed object,
* false
otherwise
*/
public boolean supports(Object domainInstance) {
if (domainInstance == null) {
if (logger.isDebugEnabled()) {
logger.debug("domainInstance is null");
}
return false;
}
if ((restrictSupportToClass != null)
&& !restrictSupportToClass.isAssignableFrom(
domainInstance.getClass())) {
if (logger.isDebugEnabled()) {
logger.debug("domainInstance not instance of "
+ restrictSupportToClass);
}
return false;
}
if (obtainIdentity(domainInstance) == null) {
if (logger.isDebugEnabled()) {
logger.debug("obtainIdentity returned null");
}
return false;
} else {
if (logger.isDebugEnabled()) {
logger.debug("obtainIdentity returned "
+ obtainIdentity(domainInstance));
}
return true;
}
}
/**
* This method looks up the AclObjectIdentity
of a passed
* domain object instance.
*
*
* This implementation attempts to obtain the
* AclObjectIdentity
via reflection inspection of the class
* for the {@link AclObjectIdentityAware} interface. If this fails, an
* attempt is made to construct a {@link
* #getDefaultAclObjectIdentityClass()} object by passing the domain
* instance object into its constructor.
*
*
* @param domainInstance the domain object instance (never
* null
)
*
* @return an ACL object identity, or null
if one could not be
* obtained
*/
protected AclObjectIdentity obtainIdentity(Object domainInstance) {
if (domainInstance instanceof AclObjectIdentityAware) {
AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
if (logger.isDebugEnabled()) {
logger.debug("domainInstance: " + domainInstance
+ " cast to AclObjectIdentityAware");
}
return aclObjectIdentityAware.getAclObjectIdentity();
}
try {
Constructor constructor = defaultAclObjectIdentityClass
.getConstructor(new Class[] {Object.class});
if (logger.isDebugEnabled()) {
logger.debug("domainInstance: " + domainInstance
+ " attempting to pass to constructor: " + constructor);
}
return (AclObjectIdentity) constructor.newInstance(new Object[] {domainInstance});
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error attempting construction of "
+ defaultAclObjectIdentityClass + ": " + ex.getMessage(), ex);
if (ex.getCause() != null) {
logger.debug("Cause: " + ex.getCause().getMessage(),
ex.getCause());
}
}
return null;
}
}
private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
BasicAclEntry[] result = basicAclEntryCache.getEntriesFromCache(aclObjectIdentity);
if (result != null) {
if (result[0].getRecipient().equals(RECIPIENT_FOR_CACHE_EMPTY)) {
return null;
} else {
return result;
}
}
result = basicAclDao.getAcls(aclObjectIdentity);
if (result == null) {
SimpleAclEntry[] emptyAclEntries = {new SimpleAclEntry(RECIPIENT_FOR_CACHE_EMPTY,
aclObjectIdentity, null, 0)};
basicAclEntryCache.putEntriesInCache(emptyAclEntries);
return null;
}
basicAclEntryCache.putEntriesInCache(result);
return result;
}
}