SEC-593: Added PermissionCacheOptimizer strategy interface and implementation in Acl module.

This is used by DefaultMethodSecurityExpressionHandler to allow permissions to be cached before repeatedly evaluating an expression for a collection of domain objects.
This commit is contained in:
Luke Taylor 2010-02-16 23:43:42 +00:00
parent 1474e73b11
commit b37d2ed978
6 changed files with 131 additions and 10 deletions

View File

@ -0,0 +1,62 @@
package org.springframework.security.acls;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.PermissionCacheOptimizer;
import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.domain.SidRetrievalStrategyImpl;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.acls.model.SidRetrievalStrategy;
import org.springframework.security.core.Authentication;
/**
* Batch loads ACLs for collections of objects to allow optimised filtering.
*
* @author Luke Taylor
* @since 3.1
*/
public class AclPermissionCacheOptimizer implements PermissionCacheOptimizer {
private final Log logger = LogFactory.getLog(getClass());
private final AclService aclService;
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
private ObjectIdentityRetrievalStrategy oidRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
public AclPermissionCacheOptimizer(AclService aclService) {
this.aclService = aclService;
}
public void cachePermissionsFor(Authentication authentication, Collection<?> objects) {
List<ObjectIdentity> oidsToCache = new ArrayList<ObjectIdentity>(objects.size());
for (Object domainObject : objects) {
if (domainObject == null) {
continue;
}
ObjectIdentity oid = oidRetrievalStrategy.getObjectIdentity(domainObject);
oidsToCache.add(oid);
}
List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
if (logger.isDebugEnabled()) {
logger.debug("Eagerly loading Acls for " + oidsToCache.size() + " objects");
}
aclService.readAclsById(oidsToCache, sids);
}
public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
this.oidRetrievalStrategy = objectIdentityRetrievalStrategy;
}
public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
this.sidRetrievalStrategy = sidRetrievalStrategy;
}
}

View File

@ -70,24 +70,30 @@ public class AclPermissionEvaluator implements PermissionEvaluator {
List<Sid> sids = sidRetrievalStrategy.getSids(authentication); List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
List<Permission> requiredPermission = resolvePermission(permission); List<Permission> requiredPermission = resolvePermission(permission);
final boolean debug = logger.isDebugEnabled();
if (debug) {
logger.debug("Checking permission '" + permission + "' for object '" + oid + "'");
}
try { try {
// Lookup only ACLs for SIDs we're interested in // Lookup only ACLs for SIDs we're interested in
Acl acl = aclService.readAclById(oid, sids); Acl acl = aclService.readAclById(oid, sids);
if (acl.isGranted(requiredPermission, sids, false)) { if (acl.isGranted(requiredPermission, sids, false)) {
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Access is granted"); logger.debug("Access is granted");
} }
return true; return true;
} }
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal"); logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal");
} }
} catch (NotFoundException nfe) { } catch (NotFoundException nfe) {
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Returning false - no ACLs apply for this principal"); logger.debug("Returning false - no ACLs apply for this principal");
} }
} }

View File

@ -0,0 +1,23 @@
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.security.core.Authentication;
/**
* Allows permissions to be pre-cached when using pre or post filtering with expressions
*
* @author Luke Taylor
* @since 3.1
*/
public interface PermissionCacheOptimizer extends AopInfrastructureBean {
/**
* Optimises the permission cache for anticipated operation on the supplied collection of objects.
* Usually this will entail batch loading of permissions for the objects in the collection.
*
* @param a the user for whom permissions should be obtained.
* @param objects the (non-null) collection of domain objects for which permissions should be retrieved.
*/
void cachePermissionsFor(Authentication a, Collection<?> objects);
}

View File

@ -2,6 +2,7 @@ package org.springframework.security.access.expression.method;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -14,6 +15,7 @@ import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser; import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.security.access.PermissionCacheOptimizer;
import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
@ -35,6 +37,7 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private PermissionCacheOptimizer permissionCacheOptimizer = null;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private ExpressionParser expressionParser = new SpelExpressionParser(); private ExpressionParser expressionParser = new SpelExpressionParser();
private RoleHierarchy roleHierarchy; private RoleHierarchy roleHierarchy;
@ -57,12 +60,20 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
return ctx; return ctx;
} }
/**
* Filters the {@code filterTarget} object (which must be either a collection or an array), by evaluating the
* supplied expression.
* <p>
* If a {@Collection} is used, the original instance will be modified to contain the elements for which
* the permission expression evaluates to {@code true}. For an array, a new array instance will be returned.
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) { public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
MethodSecurityExpressionRoot rootObject = (MethodSecurityExpressionRoot) ctx.getRootObject().getValue(); MethodSecurityExpressionRoot rootObject = (MethodSecurityExpressionRoot) ctx.getRootObject().getValue();
final boolean debug = logger.isDebugEnabled();
List retainList; List retainList;
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Filtering with expression: " + filterExpression.getExpressionString()); logger.debug("Filtering with expression: " + filterExpression.getExpressionString());
} }
@ -70,9 +81,14 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
Collection collection = (Collection)filterTarget; Collection collection = (Collection)filterTarget;
retainList = new ArrayList(collection.size()); retainList = new ArrayList(collection.size());
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Filtering collection with " + collection.size() + " elements"); logger.debug("Filtering collection with " + collection.size() + " elements");
} }
if (permissionCacheOptimizer != null) {
permissionCacheOptimizer.cachePermissionsFor(rootObject.getAuthentication(), collection);
}
for (Object filterObject : (Collection)filterTarget) { for (Object filterObject : (Collection)filterTarget) {
rootObject.setFilterObject(filterObject); rootObject.setFilterObject(filterObject);
@ -81,7 +97,7 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
} }
} }
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Retaining elements: " + retainList); logger.debug("Retaining elements: " + retainList);
} }
@ -95,8 +111,12 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
Object[] array = (Object[])filterTarget; Object[] array = (Object[])filterTarget;
retainList = new ArrayList(array.length); retainList = new ArrayList(array.length);
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Filtering collection with " + array.length + " elements"); logger.debug("Filtering array with " + array.length + " elements");
}
if (permissionCacheOptimizer != null) {
permissionCacheOptimizer.cachePermissionsFor(rootObject.getAuthentication(), Arrays.asList(array));
} }
for (int i = 0; i < array.length; i++) { for (int i = 0; i < array.length; i++) {
@ -107,7 +127,7 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
} }
} }
if (logger.isDebugEnabled()) { if (debug) {
logger.debug("Retaining elements: " + retainList); logger.debug("Retaining elements: " + retainList);
} }
@ -135,6 +155,10 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
this.permissionEvaluator = permissionEvaluator; this.permissionEvaluator = permissionEvaluator;
} }
public void setPermissionCacheOptimizer(PermissionCacheOptimizer permissionCacheOptimizer) {
this.permissionCacheOptimizer = permissionCacheOptimizer;
}
public void setTrustResolver(AuthenticationTrustResolver trustResolver) { public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
this.trustResolver = trustResolver; this.trustResolver = trustResolver;
} }

View File

@ -52,9 +52,15 @@
<b:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <b:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<b:property name="permissionEvaluator" ref="permissionEvaluator"/> <b:property name="permissionEvaluator" ref="permissionEvaluator"/>
<b:property name="permissionCacheOptimizer">
<b:bean class="org.springframework.security.acls.AclPermissionCacheOptimizer">
<b:constructor-arg ref="aclService"/>
</b:bean>
</b:property>
</b:bean> </b:bean>
<b:bean id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator"> <b:bean id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
<b:constructor-arg ref="aclService"/> <b:constructor-arg ref="aclService"/>
</b:bean> </b:bean>
</b:beans> </b:beans>

View File

@ -1,5 +1,5 @@
# Global logging configuration # Global logging configuration
log4j.rootLogger=DEBUG, stdout, fileout log4j.rootLogger=INFO, stdout, fileout
log4j.logger.sample.contact=DEBUG log4j.logger.sample.contact=DEBUG
log4j.logger.org.springframework.web.*=DEBUG log4j.logger.org.springframework.web.*=DEBUG