Browse Source

SEC-936: NPE in AbstractFallbackMethodDefinitionSource

http://jira.springframework.org/browse/SEC-936. Changed to check if the value of MethodInvocation.getThis() is null to prevent NPE. MapBasedMethodDefinitionSource now ignores calls to findAttributes() with a null target class (all its entries require a class) and the fallback option in AbstractFallbackMethodDefinitionSource is used if the targetClass is null (i.e. Method.getDeclaringClass() will be used as the Class)
2.0.x
Luke Taylor 18 years ago
parent
commit
3bf5e406b7
  1. 215
      core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java
  2. 10
      core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java

215
core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java

@ -18,11 +18,11 @@ import org.springframework.util.ObjectUtils;
* Abstract implementation of {@link MethodDefinitionSource} that supports both Spring AOP and AspectJ and * 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; * caches configuration attribute resolution from: 1. specific target method; 2. target class; 3. declaring method;
* 4. declaring class/interface. * 4. declaring class/interface.
* *
* <p> * <p>
* This class mimics the behaviour of Spring's AbstractFallbackTransactionAttributeSource class. * This class mimics the behaviour of Spring's AbstractFallbackTransactionAttributeSource class.
* </p> * </p>
* *
* <p> * <p>
* Note that this class cannot extract security metadata where that metadata is expressed by way of * 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 (ie #1 and #2 above) AND the target method/class is encapsulated in another
@ -31,7 +31,7 @@ import org.springframework.util.ObjectUtils;
* another proxy), move the metadata to declared methods or interfaces the proxy implements, or provide * another proxy), move the metadata to declared methods or interfaces the proxy implements, or provide
* your own replacement <tt>MethodDefinitionSource</tt>. * your own replacement <tt>MethodDefinitionSource</tt>.
* </p> * </p>
* *
* @author Ben Alex * @author Ben Alex
* @version $Id$ * @version $Id$
* @since 2.0 * @since 2.0
@ -39,15 +39,16 @@ import org.springframework.util.ObjectUtils;
public abstract class AbstractFallbackMethodDefinitionSource implements MethodDefinitionSource { public abstract class AbstractFallbackMethodDefinitionSource implements MethodDefinitionSource {
private static final Log logger = LogFactory.getLog(AbstractFallbackMethodDefinitionSource.class); private static final Log logger = LogFactory.getLog(AbstractFallbackMethodDefinitionSource.class);
private final static Object NULL_CONFIG_ATTRIBUTE = new Object(); private final static Object NULL_CONFIG_ATTRIBUTE = new Object();
private final Map attributeCache = new HashMap(); private final Map attributeCache = new HashMap();
public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException { public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
Assert.notNull(object, "Object cannot be null"); Assert.notNull(object, "Object cannot be null");
if (object instanceof MethodInvocation) { if (object instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) object; MethodInvocation mi = (MethodInvocation) object;
return getAttributes(mi.getMethod(), mi.getThis().getClass()); Object target = mi.getThis();
return getAttributes(mi.getMethod(), target == null ? null : target.getClass());
} }
if (object instanceof JoinPoint) { if (object instanceof JoinPoint) {
@ -59,143 +60,143 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe
Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types); Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types);
Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'"); Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'");
return getAttributes(method, targetClass); return getAttributes(method, targetClass);
} }
throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint"); throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint");
} }
public final boolean supports(Class clazz) { public final boolean supports(Class clazz) {
return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz)); return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz));
} }
public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) {
// First, see if we have a cached value. // First, see if we have a cached value.
Object cacheKey = new DefaultCacheKey(method, targetClass); Object cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) { synchronized (this.attributeCache) {
Object cached = this.attributeCache.get(cacheKey); Object cached = this.attributeCache.get(cacheKey);
if (cached != null) { if (cached != null) {
// Value will either be canonical value indicating there is no config attribute, // Value will either be canonical value indicating there is no config attribute,
// or an actual config attribute. // or an actual config attribute.
if (cached == NULL_CONFIG_ATTRIBUTE) { if (cached == NULL_CONFIG_ATTRIBUTE) {
return null; return null;
} }
else { else {
return (ConfigAttributeDefinition) cached; return (ConfigAttributeDefinition) cached;
} }
} }
else { else {
// We need to work it out. // We need to work it out.
ConfigAttributeDefinition cfgAtt = computeAttributes(method, targetClass); ConfigAttributeDefinition cfgAtt = computeAttributes(method, targetClass);
// Put it in the cache. // Put it in the cache.
if (cfgAtt == null) { if (cfgAtt == null) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
} }
else { else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Adding security method [" + cacheKey + "] with attribute [" + cfgAtt + "]"); logger.debug("Adding security method [" + cacheKey + "] with attribute [" + cfgAtt + "]");
} }
this.attributeCache.put(cacheKey, cfgAtt); this.attributeCache.put(cacheKey, cfgAtt);
} }
return cfgAtt; return cfgAtt;
} }
} }
} }
/** /**
* *
* @param method the method for the current invocation (never <code>null</code>) * @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>) * @param targetClass the target class for this invocation (may be <code>null</code>)
* @return * @return
*/ */
private ConfigAttributeDefinition computeAttributes(Method method, Class targetClass) { private ConfigAttributeDefinition computeAttributes(Method method, Class targetClass) {
// The method may be on an interface, but we need attributes from the target class. // 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. // If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class. // First try is the method in the target class.
ConfigAttributeDefinition attr = findAttributes(specificMethod, targetClass); ConfigAttributeDefinition attr = findAttributes(specificMethod, targetClass);
if (attr != null) { if (attr != null) {
return attr; return attr;
} }
// Second try is the config attribute on the target class. // Second try is the config attribute on the target class.
attr = findAttributes(specificMethod.getDeclaringClass()); attr = findAttributes(specificMethod.getDeclaringClass());
if (attr != null) { if (attr != null) {
return attr; return attr;
} }
if (specificMethod != method) { if (specificMethod != method || targetClass == null) {
// Fallback is to look at the original method. // Fallback is to look at the original method.
attr = findAttributes(method, method.getDeclaringClass()); attr = findAttributes(method, method.getDeclaringClass());
if (attr != null) { if (attr != null) {
return attr; return attr;
} }
// Last fallback is the class of the original method. // Last fallback is the class of the original method.
return findAttributes(method.getDeclaringClass()); return findAttributes(method.getDeclaringClass());
} }
return null; return null;
} }
/** /**
* Obtains the security metadata applicable to the specified method invocation. * Obtains the security metadata applicable to the specified method invocation.
* *
* <p> * <p>
* Note that the {@link Method#getDeclaringClass()} may not equal the <code>targetClass</code>. * Note that the {@link Method#getDeclaringClass()} may not equal the <code>targetClass</code>.
* Both parameters are provided to assist subclasses which may wish to provide advanced * Both parameters are provided to assist subclasses which may wish to provide advanced
* capabilities related to method metadata being "registered" against a method even if the * capabilities related to method metadata being "registered" against a method even if the
* target class does not declare the method (i.e. the subclass may only inherit the method). * target class does not declare the method (i.e. the subclass may only inherit the method).
* *
* @param method the method for the current invocation (never <code>null</code>) * @param method the method for the current invocation (never <code>null</code>)
* @param targetClass the target class for the invocation (may be <code>null</code>) * @param targetClass the target class for the invocation (may be <code>null</code>)
* @return the security metadata (or null if no metadata applies) * @return the security metadata (or null if no metadata applies)
*/ */
protected abstract ConfigAttributeDefinition findAttributes(Method method, Class targetClass); protected abstract ConfigAttributeDefinition findAttributes(Method method, Class targetClass);
/** /**
* Obtains the security metadata registered against the specified class. * Obtains the security metadata registered against the specified class.
* *
* <p> * <p>
* Subclasses should only return metadata expressed at a class level. Subclasses should NOT * Subclasses should only return metadata expressed at a class level. Subclasses should NOT
* aggregate metadata for each method registered against a class, as the abstract superclass * aggregate metadata for each method registered against a class, as the abstract superclass
* will separate invoke {@link #findAttributes(Method, Class)} for individual methods as * will separate invoke {@link #findAttributes(Method, Class)} for individual methods as
* appropriate. * appropriate.
* *
* @param clazz the target class for the invocation (never <code>null</code>) * @param clazz the target class for the invocation (never <code>null</code>)
* @return the security metadata (or null if no metadata applies) * @return the security metadata (or null if no metadata applies)
*/ */
protected abstract ConfigAttributeDefinition findAttributes(Class clazz); protected abstract ConfigAttributeDefinition findAttributes(Class clazz);
private static class DefaultCacheKey { private static class DefaultCacheKey {
private final Method method; private final Method method;
private final Class targetClass; private final Class targetClass;
public DefaultCacheKey(Method method, Class targetClass) { public DefaultCacheKey(Method method, Class targetClass) {
this.method = method; this.method = method;
this.targetClass = targetClass; this.targetClass = targetClass;
} }
public boolean equals(Object other) { public boolean equals(Object other) {
if (this == other) { if (this == other) {
return true; return true;
} }
if (!(other instanceof DefaultCacheKey)) { if (!(other instanceof DefaultCacheKey)) {
return false; return false;
} }
DefaultCacheKey otherKey = (DefaultCacheKey) other; DefaultCacheKey otherKey = (DefaultCacheKey) other;
return (this.method.equals(otherKey.method) && return (this.method.equals(otherKey.method) &&
ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
} }
public int hashCode() { public int hashCode() {
return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0); return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
} }
public String toString() { public String toString() {
return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]"; return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]";
} }
} }
} }

10
core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java

@ -88,6 +88,10 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
* Will walk the method inheritance tree to find the most specific declaration applicable. * Will walk the method inheritance tree to find the most specific declaration applicable.
*/ */
protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) {
if (targetClass == null) {
return null;
}
return findAttributesSpecifiedAgainst(method, targetClass); return findAttributesSpecifiedAgainst(method, targetClass);
} }
@ -179,9 +183,9 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
/** /**
* Adds configuration attributes for a specific method, for example where the method has been * Adds configuration attributes for a specific method, for example where the method has been
* matched using a pointcut expression. If a match already exists in the map for the method, then * matched using a pointcut expression. If a match already exists in the map for the method, then
* the existing match will be retained, so that if this method is called for a more general pointcut * the existing match will be retained, so that if this method is called for a more general pointcut
* it will not override a more specific one which has already been added. This * it will not override a more specific one which has already been added. This
*/ */
public void addSecureMethod(Class javaType, Method method, ConfigAttributeDefinition attr) { public void addSecureMethod(Class javaType, Method method, ConfigAttributeDefinition attr) {
RegisteredMethod key = new RegisteredMethod(method, javaType); RegisteredMethod key = new RegisteredMethod(method, javaType);

Loading…
Cancel
Save