SEC-999: Moved caching from AbstractFallbackMethodDefinitionSource to DelegatingMethodDefinitionSource, to allow ExpressionBasedMethodDefinitionSource to take advantage of it. The latter no-longer uses the fallback approach as it requires its own strategy to combine annotations which may be defined at method-on-class, class, method-on-interface or interface level.

This commit is contained in:
Luke Taylor 2008-10-28 06:37:04 +00:00
parent f2ec8c978a
commit c7abdadc06
9 changed files with 334 additions and 241 deletions

View File

@ -13,6 +13,13 @@ public class SecurityExpressionRoot {
private Object filterObject;
private Object returnObject;
/** Allows "permitAll" expression */
public final boolean permitAll = true;
/** Allows "denyAll" expression */
public final boolean denyAll = false;
public SecurityExpressionRoot(Authentication a) {
this.authentication = a;
}

View File

@ -1,5 +1,6 @@
package org.springframework.security.expression.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
@ -13,7 +14,8 @@ import org.springframework.security.expression.annotation.PostAuthorize;
import org.springframework.security.expression.annotation.PostFilter;
import org.springframework.security.expression.annotation.PreAuthorize;
import org.springframework.security.expression.annotation.PreFilter;
import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource;
import org.springframework.security.intercept.method.AbstractMethodDefinitionSource;
import org.springframework.util.ClassUtils;
/**
* MethodDefinitionSource which extracts metadata from the @PreFilter and @PreAuthorize annotations
@ -22,6 +24,9 @@ import org.springframework.security.intercept.method.AbstractFallbackMethodDefin
* Annotations may be specified on classes or methods, and method-specific annotations will take precedence.
* If you use any annotation and do not specify a pre-authorization condition, then the method will be
* allowed as if a @PreAuthorize("permitAll") were present.
* <p>
* Since we are handling multiple annotations here, it's possible that we may have to combine annotations defined in
* multiple locations for a single method - they may be defined on the method itself, or at interface or class level.
*
* @see MethodExpressionVoter
*
@ -29,56 +34,71 @@ import org.springframework.security.intercept.method.AbstractFallbackMethodDefin
* @since 2.5
* @version $Id$
*/
public class ExpressionAnnotationMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource {
public class ExpressionAnnotationMethodDefinitionSource extends AbstractMethodDefinitionSource {
@Override
protected List<ConfigAttribute> findAttributes(Method method, Class targetClass) {
PreFilter preFilter = AnnotationUtils.findAnnotation(method, PreFilter.class);
PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
PostFilter postFilter = AnnotationUtils.findAnnotation(method, PostFilter.class);
PostAuthorize postAuthorize = AnnotationUtils.findAnnotation(method, PostAuthorize.class);
public List<ConfigAttribute> getAttributes(Method method, Class targetClass) {
logger.debug("Looking for expression annotations for method '" +
method.getName() + "' on target class '" + targetClass + "'");
PreFilter preFilter = findAnnotation(method, targetClass, PreFilter.class);
PreAuthorize preAuthorize = findAnnotation(method, targetClass, PreAuthorize.class);
PostFilter postFilter = findAnnotation(method, targetClass, PostFilter.class);
// TODO: Can we check for void methods and throw an exception here?
PostAuthorize postAuthorize = findAnnotation(method, targetClass, PostAuthorize.class);
if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null ) {
// There is no method level meta-data so return and allow parent to query at class-level
// There is no meta-data so return
logger.debug("No expression annotations found");
return null;
}
// There is at least one non-null value, so the parent class will not query for class-specific annotations
// and we have to locate them here as appropriate.
if (preAuthorize == null) {
preAuthorize = (PreAuthorize)targetClass.getAnnotation(PreAuthorize.class);
}
if (preFilter == null) {
preFilter = (PreFilter)targetClass.getAnnotation(PreFilter.class);
}
if (postFilter == null) {
// TODO: Can we check for void methods and throw an exception here?
postFilter = (PostFilter)targetClass.getAnnotation(PostFilter.class);
}
if (postAuthorize == null) {
postAuthorize = (PostAuthorize)targetClass.getAnnotation(PostAuthorize.class);
}
return createAttributeList(preFilter, preAuthorize, postFilter, postAuthorize);
}
@Override
protected List<ConfigAttribute> findAttributes(Class targetClass) {
PreFilter preFilter = (PreFilter)targetClass.getAnnotation(PreFilter.class);
PreAuthorize preAuthorize = (PreAuthorize)targetClass.getAnnotation(PreAuthorize.class);
PostFilter postFilter = (PostFilter)targetClass.getAnnotation(PostFilter.class);
PostAuthorize postAuthorize = (PostAuthorize)targetClass.getAnnotation(PostAuthorize.class);
/**
* See {@link org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource#getAttributes(Method, Class)}
* for the logic of this method. The ordering here is slightly different in that we consider method-specific
* annotations on an interface before class-level ones.
*/
private <A extends Annotation> A findAnnotation(Method method, Class targetClass, Class<A> annotationClass) {
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
A annotation = AnnotationUtils.findAnnotation(specificMethod, annotationClass);
if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null ) {
// There is no class level meta-data (and by implication no meta-data at all)
return null;
if (annotation != null) {
logger.debug(annotation + " found on specific method: " + specificMethod);
return annotation;
}
return createAttributeList(preFilter, preAuthorize, postFilter, postAuthorize);
// Check the original (e.g. interface) method
if (specificMethod != method) {
annotation = AnnotationUtils.findAnnotation(method, annotationClass);
if (annotation != null) {
logger.debug(annotation + " found on: " + method);
return annotation;
}
}
// Check the class-level (note declaringClass, not targetClass, which may not actually implement the method)
annotation = specificMethod.getDeclaringClass().getAnnotation(annotationClass);
if (annotation != null) {
logger.debug(annotation + " found on: " + specificMethod.getDeclaringClass().getName());
return annotation;
}
// Check for a possible interface annotation which would not be inherited by the declaring class
if (specificMethod != method) {
annotation = method.getDeclaringClass().getAnnotation(annotationClass);
if (annotation != null) {
logger.debug(annotation + " found on: " + method.getDeclaringClass().getName());
return annotation;
}
}
return null;
}
public Collection<List<? extends ConfigAttribute>> getConfigAttributeDefinitions() {
@ -91,7 +111,7 @@ public class ExpressionAnnotationMethodDefinitionSource extends AbstractFallback
ConfigAttribute post = null;
// TODO: Optimization of permitAll
String preAuthorizeExpression = preAuthorize == null ? "permitAll()" : preAuthorize.value();
String preAuthorizeExpression = preAuthorize == null ? "permitAll" : preAuthorize.value();
String preFilterExpression = preFilter == null ? null : preFilter.value();
String filterObject = preFilter == null ? null : preFilter.filterTarget();
String postAuthorizeExpression = postAuthorize == null ? null : postAuthorize.value();
@ -106,7 +126,6 @@ public class ExpressionAnnotationMethodDefinitionSource extends AbstractFallback
throw new SecurityConfigurationException("Failed to parse expression '" + e.getExpressionString() + "'", e);
}
List<ConfigAttribute> attrs = new ArrayList<ConfigAttribute>(2);
if (pre != null) {
attrs.add(pre);

View File

@ -1,117 +1,33 @@
package org.springframework.security.intercept.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.security.ConfigAttribute;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Abstract implementation of {@link MethodDefinitionSource} that supports both Spring AOP and AspectJ and
* caches configuration attribute resolution from: 1. specific target method; 2. target class; 3. declaring method;
* 4. declaring class/interface.
*
* performs attribute resolution from: 1. specific target method; 2. target class; 3. declaring method;
* 4. declaring class/interface. Use with {@link DelegatingMethodDefinitionSource} for caching support.
* <p>
* This class mimics the behaviour of Spring's AbstractFallbackTransactionAttributeSource class.
* </p>
*
* <p>
* Note that this class cannot extract security metadata where that metadata is expressed by way of
* a target method/class (ie #1 and #2 above) AND the target method/class is encapsulated in another
* a target method/class (i.e. #1 and #2 above) AND the target method/class is encapsulated in another
* proxy object. Spring Security does not walk a proxy chain to locate the concrete/final target object.
* Consider making Spring Security your final advisor (so it advises the final target, as opposed to
* another proxy), move the metadata to declared methods or interfaces the proxy implements, or provide
* your own replacement <tt>MethodDefinitionSource</tt>.
* </p>
*
* @author Ben Alex
* @author Luke taylor
* @version $Id$
* @since 2.0
*/
public abstract class AbstractFallbackMethodDefinitionSource implements MethodDefinitionSource {
protected final Log logger = LogFactory.getLog(getClass());
private final static List<ConfigAttribute> NULL_CONFIG_ATTRIBUTE = new ArrayList<ConfigAttribute>(0);
private final Map<DefaultCacheKey, List<ConfigAttribute>> attributeCache = new HashMap();
public List<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
Assert.notNull(object, "Object cannot be null");
if (object instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) object;
Object target = mi.getThis();
return getAttributes(mi.getMethod(), target == null ? null : target.getClass());
}
if (object instanceof JoinPoint) {
JoinPoint jp = (JoinPoint) object;
Class targetClass = jp.getTarget().getClass();
String targetMethodName = jp.getStaticPart().getSignature().getName();
Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
Class declaringType = ((CodeSignature) jp.getStaticPart().getSignature()).getDeclaringType();
Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types);
Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'");
return getAttributes(method, targetClass);
}
throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint");
}
public final boolean supports(Class clazz) {
return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz));
}
public abstract class AbstractFallbackMethodDefinitionSource extends AbstractMethodDefinitionSource {
public List<ConfigAttribute> getAttributes(Method method, Class targetClass) {
// First, see if we have a cached value.
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (attributeCache) {
List<ConfigAttribute> cached = attributeCache.get(cacheKey);
if (cached != null) {
// Value will either be canonical value indicating there is no config attribute,
// or an actual config attribute.
if (cached == NULL_CONFIG_ATTRIBUTE) {
return null;
}
else {
return cached;
}
}
else {
// We need to work it out.
List<ConfigAttribute> attributes = computeAttributes(method, targetClass);
// Put it in the cache.
if (attributes == null) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Adding security method [" + cacheKey + "] with attributes " + attributes);
}
this.attributeCache.put(cacheKey, attributes);
}
return attributes;
}
}
}
/**
*
* @param method the method for the current invocation (never <code>null</code>)
* @param targetClass the target class for this invocation (may be <code>null</code>)
* @return
*/
private List<ConfigAttribute> computeAttributes(Method method, Class targetClass) {
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
@ -137,7 +53,6 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe
return findAttributes(method.getDeclaringClass());
}
return null;
}
/**
@ -169,35 +84,5 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe
*/
protected abstract List<ConfigAttribute> findAttributes(Class clazz);
private static class DefaultCacheKey {
private final Method method;
private final Class targetClass;
public DefaultCacheKey(Method method, Class targetClass) {
this.method = method;
this.targetClass = targetClass;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof DefaultCacheKey)) {
return false;
}
DefaultCacheKey otherKey = (DefaultCacheKey) other;
return (this.method.equals(otherKey.method) &&
ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
}
public int hashCode() {
return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
}
public String toString() {
return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]";
}
}
}

View File

@ -26,73 +26,50 @@ import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.List;
/**
* Abstract implementation of <code>MethodDefinitionSource</code>.
* Abstract implementation of <tt>MethodDefinitionSource</tt> which resolves the secured object type to
* either a MethodInvocation or a JoinPoint.
*
* @author Ben Alex
* @deprecated No longer used within the framework and will be removed
* @author Luke Taylor
* @version $Id$
*/
public abstract class AbstractMethodDefinitionSource implements MethodDefinitionSource {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(AbstractMethodDefinitionSource.class);
protected final Log logger = LogFactory.getLog(getClass());
//~ Methods ========================================================================================================
public List<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
Assert.notNull(object, "Object cannot be null");
public final List<ConfigAttribute> getAttributes(Object object) {
if (object instanceof MethodInvocation) {
return this.lookupAttributes(((MethodInvocation) object).getMethod());
MethodInvocation mi = (MethodInvocation) object;
Object target = mi.getThis();
return getAttributes(mi.getMethod(), target == null ? null : target.getClass());
}
if (object instanceof JoinPoint) {
JoinPoint jp = (JoinPoint) object;
Class targetClazz = jp.getTarget().getClass();
Class targetClass = jp.getTarget().getClass();
String targetMethodName = jp.getStaticPart().getSignature().getName();
Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
Class declaringType = ((CodeSignature) jp.getStaticPart().getSignature()).getDeclaringType();
if (logger.isDebugEnabled()) {
logger.debug("Target Class: " + targetClazz);
logger.debug("Target Method Name: " + targetMethodName);
Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types);
Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'");
for (int i = 0; i < types.length; i++) {
if (logger.isDebugEnabled()) {
logger.debug("Target Method Arg #" + i + ": " + types[i]);
}
}
}
try {
return this.lookupAttributes(targetClazz.getMethod(targetMethodName, types));
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException("Could not obtain target method from JoinPoint: " + jp);
}
return getAttributes(method, targetClass);
}
throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint");
throw new IllegalArgumentException("Object must be a non-null MethodInvocation or JoinPoint");
}
/**
* Performs the actual lookup of the relevant <code>ConfigAttributeDefinition</code> for the specified
* <code>Method</code> which is subject of the method invocation.<P>Provided so subclasses need only to
* provide one basic method to properly interface with the <code>MethodDefinitionSource</code>.</p>
* <p>Returns <code>null</code> if there are no matching attributes for the method.</p>
*
* @param method the method being invoked for which configuration attributes should be looked up
*
* @return the <code>ConfigAttributeDefinition</code> that applies to the specified <code>Method</code>
*/
protected abstract List<ConfigAttribute> lookupAttributes(Method method);
public boolean supports(Class clazz) {
public final boolean supports(Class clazz) {
return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz));
}
}

View File

@ -2,52 +2,75 @@ package org.springframework.security.intercept.method;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Automatically tries a series of method definition sources, relying on the first source of metadata
* that provides a non-null response.
* that provides a non-null response. Provides automatic caching of the retrieved metadata.
*
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
*/
public final class DelegatingMethodDefinitionSource implements MethodDefinitionSource, InitializingBean {
private List methodDefinitionSources;
public final class DelegatingMethodDefinitionSource extends AbstractMethodDefinitionSource implements InitializingBean {
private final static List<ConfigAttribute> NULL_CONFIG_ATTRIBUTE = Collections.emptyList();
private List<MethodDefinitionSource> methodDefinitionSources;
private final Map<DefaultCacheKey, List<ConfigAttribute>> attributeCache = new HashMap();
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notEmpty(methodDefinitionSources, "A list of MethodDefinitionSources is required");
}
public List<ConfigAttribute> getAttributes(Method method, Class targetClass) {
Iterator i = methodDefinitionSources.iterator();
while (i.hasNext()) {
MethodDefinitionSource s = (MethodDefinitionSource) i.next();
List<ConfigAttribute> result = s.getAttributes(method, targetClass);
if (result != null) {
return result;
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (attributeCache) {
List<ConfigAttribute> cached = attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached == NULL_CONFIG_ATTRIBUTE) {
return null;
}
}
return null;
}
public List<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
Iterator i = methodDefinitionSources.iterator();
while (i.hasNext()) {
MethodDefinitionSource s = (MethodDefinitionSource) i.next();
List<ConfigAttribute> result = s.getAttributes(object);
if (result != null) {
return result;
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
List<ConfigAttribute> attributes = null;
for (MethodDefinitionSource s : methodDefinitionSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null) {
break;
}
}
// Put it in the cache.
if (attributes == null) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("Adding security method [" + cacheKey + "] with attributes " + attributes);
}
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
return null;
}
public Collection<List<? extends ConfigAttribute>> getConfigAttributeDefinitions() {
@ -63,19 +86,40 @@ public final class DelegatingMethodDefinitionSource implements MethodDefinitionS
return set;
}
public boolean supports(Class clazz) {
Iterator i = methodDefinitionSources.iterator();
while (i.hasNext()) {
MethodDefinitionSource s = (MethodDefinitionSource) i.next();
if (s.supports(clazz)) {
return true;
}
}
return false;
}
public void setMethodDefinitionSources(List methodDefinitionSources) {
Assert.notEmpty(methodDefinitionSources, "A list of MethodDefinitionSources is required");
this.methodDefinitionSources = methodDefinitionSources;
}
//~ Inner Classes ==================================================================================================
private static class DefaultCacheKey {
private final Method method;
private final Class targetClass;
public DefaultCacheKey(Method method, Class targetClass) {
this.method = method;
this.targetClass = targetClass;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof DefaultCacheKey)) {
return false;
}
DefaultCacheKey otherKey = (DefaultCacheKey) other;
return (this.method.equals(otherKey.method) &&
ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
}
public int hashCode() {
return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
}
public String toString() {
return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]";
}
}
}

View File

@ -18,14 +18,11 @@ package org.springframework.security.intercept.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
@ -47,9 +44,6 @@ import org.springframework.util.ClassUtils;
* @since 2.0
*/
public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource implements BeanClassLoaderAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(MapBasedMethodDefinitionSource.class);
//~ Instance fields ================================================================================================
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

View File

@ -0,0 +1,169 @@
package org.springframework.security.expression.support;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.expression.annotation.PostAuthorize;
import org.springframework.security.expression.annotation.PostFilter;
import org.springframework.security.expression.annotation.PreAuthorize;
import org.springframework.security.expression.annotation.PreFilter;
import org.springframework.security.intercept.method.MockMethodInvocation;
public class ExpressionAnnotationMethodDefinitionSourceTests {
private ExpressionAnnotationMethodDefinitionSource mds = new ExpressionAnnotationMethodDefinitionSource();
private MockMethodInvocation voidImpl1;
private MockMethodInvocation voidImpl2;
private MockMethodInvocation voidImpl3;
private MockMethodInvocation listImpl1;
private MockMethodInvocation notherListImpl1;
private MockMethodInvocation notherListImpl2;
@Before
public void setUpData() throws Exception {
voidImpl1 = new MockMethodInvocation(new ReturnVoidImpl1(), ReturnVoid.class, "doSomething", List.class);
voidImpl2 = new MockMethodInvocation(new ReturnVoidImpl2(), ReturnVoid.class, "doSomething", List.class);
voidImpl3 = new MockMethodInvocation(new ReturnVoidImpl3(), ReturnVoid.class, "doSomething", List.class);
listImpl1 = new MockMethodInvocation(new ReturnAListImpl1(), ReturnAList.class, "doSomething", List.class);
notherListImpl1 = new MockMethodInvocation(new ReturnAnotherListImpl1(), ReturnAnotherList.class, "doSomething", List.class);
notherListImpl2 = new MockMethodInvocation(new ReturnAnotherListImpl2(), ReturnAnotherList.class, "doSomething", List.class);
}
@Test
public void classLevelPreAnnotationIsPickedUpWhenNoMethodLevelExists() throws Exception {
List<ConfigAttribute> attrs = mds.getAttributes(voidImpl1);
assertEquals(1, attrs.size());
assertTrue(attrs.get(0) instanceof PreInvocationExpressionConfigAttribute);
PreInvocationExpressionConfigAttribute pre = (PreInvocationExpressionConfigAttribute) attrs.get(0);
assertNotNull(pre.getAuthorizeExpression());
assertEquals("someExpression", pre.getAuthorizeExpression().getExpressionString());
assertNull(pre.getFilterExpression());
}
@Test
public void mixedClassAndMethodPreAnnotationsAreBothIncluded() {
List<ConfigAttribute> attrs = mds.getAttributes(voidImpl2);
assertEquals(1, attrs.size());
assertTrue(attrs.get(0) instanceof PreInvocationExpressionConfigAttribute);
PreInvocationExpressionConfigAttribute pre = (PreInvocationExpressionConfigAttribute)attrs.get(0);
assertEquals("someExpression", pre.getAuthorizeExpression().getExpressionString());
assertNotNull(pre.getFilterExpression());
assertEquals("somePreFilterExpression", pre.getFilterExpression().getExpressionString());
}
@Test
public void methodWithPreFilterOnlyIsAllowed() {
List<ConfigAttribute> attrs = mds.getAttributes(voidImpl3);
assertEquals(1, attrs.size());
assertTrue(attrs.get(0) instanceof PreInvocationExpressionConfigAttribute);
PreInvocationExpressionConfigAttribute pre = (PreInvocationExpressionConfigAttribute)attrs.get(0);
assertEquals("permitAll", pre.getAuthorizeExpression().getExpressionString());
assertNotNull(pre.getFilterExpression());
assertEquals("somePreFilterExpression", pre.getFilterExpression().getExpressionString());
}
@Test
public void methodWithPostFilterOnlyIsAllowed() {
List<ConfigAttribute> attrs = mds.getAttributes(listImpl1);
assertEquals(2, attrs.size());
assertTrue(attrs.get(0) instanceof PreInvocationExpressionConfigAttribute);
assertTrue(attrs.get(1) instanceof PostInvocationExpressionConfigAttribute);
PreInvocationExpressionConfigAttribute pre = (PreInvocationExpressionConfigAttribute)attrs.get(0);
PostInvocationExpressionConfigAttribute post = (PostInvocationExpressionConfigAttribute)attrs.get(1);
assertEquals("permitAll", pre.getAuthorizeExpression().getExpressionString());
assertNotNull(post.getFilterExpression());
assertEquals("somePostFilterExpression", post.getFilterExpression().getExpressionString());
}
@Test
public void interfaceAttributesAreIncluded() {
List<ConfigAttribute> attrs = mds.getAttributes(notherListImpl1);
assertEquals(1, attrs.size());
assertTrue(attrs.get(0) instanceof PreInvocationExpressionConfigAttribute);
PreInvocationExpressionConfigAttribute pre = (PreInvocationExpressionConfigAttribute)attrs.get(0);
assertNotNull(pre.getFilterExpression());
assertNotNull(pre.getAuthorizeExpression());
assertEquals("interfaceMethodAuthzExpression", pre.getAuthorizeExpression().getExpressionString());
assertEquals("interfacePreFilterExpression", pre.getFilterExpression().getExpressionString());
}
@Test
public void classAttributesTakesPrecedeceOverInterfaceAttributes() {
List<ConfigAttribute> attrs = mds.getAttributes(notherListImpl2);
assertEquals(1, attrs.size());
assertTrue(attrs.get(0) instanceof PreInvocationExpressionConfigAttribute);
PreInvocationExpressionConfigAttribute pre = (PreInvocationExpressionConfigAttribute)attrs.get(0);
assertNotNull(pre.getFilterExpression());
assertNotNull(pre.getAuthorizeExpression());
assertEquals("interfaceMethodAuthzExpression", pre.getAuthorizeExpression().getExpressionString());
assertEquals("classMethodPreFilterExpression", pre.getFilterExpression().getExpressionString());
}
//~ Inner Classes ==================================================================================================
public static interface ReturnVoid {
public void doSomething(List param);
}
public static interface ReturnAList {
public List doSomething(List param);
}
@PreAuthorize("interfaceAuthzExpression")
public static interface ReturnAnotherList {
@PreAuthorize("interfaceMethodAuthzExpression")
@PreFilter(filterTarget="param", value="interfacePreFilterExpression")
public List doSomething(List param);
}
@PreAuthorize("someExpression")
public static class ReturnVoidImpl1 implements ReturnVoid {
public void doSomething(List param) {}
}
@PreAuthorize("someExpression")
public static class ReturnVoidImpl2 implements ReturnVoid {
@PreFilter(filterTarget="param", value="somePreFilterExpression")
public void doSomething(List param) {}
}
public static class ReturnVoidImpl3 implements ReturnVoid {
@PreFilter(filterTarget="param", value="somePreFilterExpression")
public void doSomething(List param) {}
}
public static class ReturnAListImpl1 implements ReturnAList {
@PostFilter("somePostFilterExpression")
public List doSomething(List param) {return param;}
}
public static class ReturnAListImpl2 implements ReturnAList {
@PreAuthorize("someExpression")
@PreFilter(filterTarget="param", value="somePreFilterExpression")
@PostFilter("somePostFilterExpression")
@PostAuthorize("somePostAuthorizeExpression")
public List doSomething(List param) {return param;}
}
public static class ReturnAnotherListImpl1 implements ReturnAnotherList {
public List doSomething(List param) {return param;}
}
public static class ReturnAnotherListImpl2 implements ReturnAnotherList {
@PreFilter(filterTarget="param", value="classMethodPreFilterExpression")
public List doSomething(List param) {return param;}
}
}

View File

@ -35,5 +35,3 @@ public class MockMethodInvocation implements MethodInvocation {
return null;
}
}

View File

@ -6,7 +6,7 @@ log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p %c - %m%n
log4j.appender.stdout.layout.ConversionPattern=%d %p %c{1} - %m%n
log4j.category.org.springframework.security=DEBUG
log4j.category.org.springframework.ldap=DEBUG