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:
parent
1474e73b11
commit
b37d2ed978
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -70,24 +70,30 @@ public class AclPermissionEvaluator implements PermissionEvaluator {
|
|||
List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
|
||||
List<Permission> requiredPermission = resolvePermission(permission);
|
||||
|
||||
final boolean debug = logger.isDebugEnabled();
|
||||
|
||||
if (debug) {
|
||||
logger.debug("Checking permission '" + permission + "' for object '" + oid + "'");
|
||||
}
|
||||
|
||||
try {
|
||||
// Lookup only ACLs for SIDs we're interested in
|
||||
Acl acl = aclService.readAclById(oid, sids);
|
||||
|
||||
if (acl.isGranted(requiredPermission, sids, false)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Access is granted");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal");
|
||||
}
|
||||
|
||||
} catch (NotFoundException nfe) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Returning false - no ACLs apply for this principal");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -2,6 +2,7 @@ package org.springframework.security.access.expression.method;
|
|||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -14,6 +15,7 @@ import org.springframework.expression.EvaluationContext;
|
|||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.security.access.PermissionCacheOptimizer;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.ExpressionUtils;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
|
@ -35,6 +37,7 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
|
||||
private PermissionCacheOptimizer permissionCacheOptimizer = null;
|
||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
private ExpressionParser expressionParser = new SpelExpressionParser();
|
||||
private RoleHierarchy roleHierarchy;
|
||||
|
@ -57,12 +60,20 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
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")
|
||||
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
|
||||
MethodSecurityExpressionRoot rootObject = (MethodSecurityExpressionRoot) ctx.getRootObject().getValue();
|
||||
final boolean debug = logger.isDebugEnabled();
|
||||
List retainList;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Filtering with expression: " + filterExpression.getExpressionString());
|
||||
}
|
||||
|
||||
|
@ -70,9 +81,14 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
Collection collection = (Collection)filterTarget;
|
||||
retainList = new ArrayList(collection.size());
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Filtering collection with " + collection.size() + " elements");
|
||||
}
|
||||
|
||||
if (permissionCacheOptimizer != null) {
|
||||
permissionCacheOptimizer.cachePermissionsFor(rootObject.getAuthentication(), collection);
|
||||
}
|
||||
|
||||
for (Object filterObject : (Collection)filterTarget) {
|
||||
rootObject.setFilterObject(filterObject);
|
||||
|
||||
|
@ -81,7 +97,7 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Retaining elements: " + retainList);
|
||||
}
|
||||
|
||||
|
@ -95,8 +111,12 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
Object[] array = (Object[])filterTarget;
|
||||
retainList = new ArrayList(array.length);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Filtering collection with " + array.length + " elements");
|
||||
if (debug) {
|
||||
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++) {
|
||||
|
@ -107,7 +127,7 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (debug) {
|
||||
logger.debug("Retaining elements: " + retainList);
|
||||
}
|
||||
|
||||
|
@ -135,6 +155,10 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
|
|||
this.permissionEvaluator = permissionEvaluator;
|
||||
}
|
||||
|
||||
public void setPermissionCacheOptimizer(PermissionCacheOptimizer permissionCacheOptimizer) {
|
||||
this.permissionCacheOptimizer = permissionCacheOptimizer;
|
||||
}
|
||||
|
||||
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
|
||||
this.trustResolver = trustResolver;
|
||||
}
|
||||
|
|
|
@ -52,9 +52,15 @@
|
|||
|
||||
<b:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
|
||||
<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 id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
|
||||
<b:constructor-arg ref="aclService"/>
|
||||
</b:bean>
|
||||
|
||||
</b:beans>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Global logging configuration
|
||||
log4j.rootLogger=DEBUG, stdout, fileout
|
||||
log4j.rootLogger=INFO, stdout, fileout
|
||||
|
||||
log4j.logger.sample.contact=DEBUG
|
||||
log4j.logger.org.springframework.web.*=DEBUG
|
||||
|
|
Loading…
Reference in New Issue