SEC-2151: Support binding method arguments with Annotations
This allow utilizing method arguments for method access control on interfaces prior to JDK 8.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.security.access.method;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.security.core.parameters.AnnotationParameterNameDiscoverer;
|
||||
|
||||
/**
|
||||
* An annotation that can be used along with
|
||||
* {@link AnnotationParameterNameDiscoverer} to specify parameter names. This is
|
||||
* useful for interfaces prior to JDK 8 which cannot contain the parameter
|
||||
* names.
|
||||
*
|
||||
* @see AnnotationParameterNameDiscoverer
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface P {
|
||||
|
||||
/**
|
||||
* The parameter name
|
||||
* @return
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.security.core.parameters;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.PrioritizedParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Allows finding parameter names using the value attribute of any number of
|
||||
* {@link Annotation} instances. This is useful when needing to discover the
|
||||
* parameter names of interfaces with Spring Security's method level security.
|
||||
* For example, consider the following:
|
||||
*
|
||||
* <pre>
|
||||
* import org.springframework.security.access.method.P;
|
||||
*
|
||||
* @PostAuthorize("#to == returnObject.to")
|
||||
* public Message findMessageByTo(@P("to") String to);
|
||||
* </pre>
|
||||
*
|
||||
* We can make this possible using the following
|
||||
* {@link AnnotationParameterNameDiscoverer}:
|
||||
*
|
||||
* <pre>
|
||||
* ParameterAnnotationsNameDiscoverer discoverer = new ParameterAnnotationsNameDiscoverer(
|
||||
* "org.springframework.security.access.method.P");
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* It is common for users to use {@link AnnotationParameterNameDiscoverer} in
|
||||
* conjuction with {@link PrioritizedParameterNameDiscoverer}. In fact, Spring
|
||||
* Security's {@link DefaultSecurityParameterNameDiscoverer} (which is used by
|
||||
* default with method level security) extends
|
||||
* {@link PrioritizedParameterNameDiscoverer} and will automatically support
|
||||
* both {@link P} and Spring Data's Param annotation if it is found on the
|
||||
* classpath.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It is important that all the parameter names have a supported annotation on
|
||||
* them. Otherwise, the result will be null. For example, consider the
|
||||
* following:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* import org.springframework.security.access.method.P;
|
||||
*
|
||||
* @PostAuthorize("#to == returnObject.to")
|
||||
* public Message findMessageByToAndFrom(@P("to") User to, User from);
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The result of finding parameters on the previous sample will be a null
|
||||
* String[] since only a single parameter contains an annotation. This is mostly
|
||||
* due to the fact that the fallbacks for
|
||||
* {@link PrioritizedParameterNameDiscoverer} are an all or nothing operation.
|
||||
* </p>
|
||||
*
|
||||
* @see DefaultSecurityParameterNameDiscoverer
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class AnnotationParameterNameDiscoverer implements
|
||||
ParameterNameDiscoverer {
|
||||
|
||||
private final Set<String> annotationClassesToUse;
|
||||
|
||||
public AnnotationParameterNameDiscoverer(String... annotationClassToUse) {
|
||||
this(new HashSet<String>(Arrays.asList(annotationClassToUse)));
|
||||
}
|
||||
|
||||
public AnnotationParameterNameDiscoverer(Set<String> annotationClassesToUse) {
|
||||
Assert.notEmpty(annotationClassesToUse,
|
||||
"annotationClassesToUse cannot be null or empty");
|
||||
this.annotationClassesToUse = annotationClassesToUse;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.core.ParameterNameDiscoverer#getParameterNames(java
|
||||
* .lang.reflect.Method)
|
||||
*/
|
||||
public String[] getParameterNames(Method method) {
|
||||
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
String[] paramNames = lookupParameterNames(METHOD_METHODPARAM_FACTORY, originalMethod);
|
||||
if(paramNames != null) {
|
||||
return paramNames;
|
||||
}
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
Class<?>[] interfaces = declaringClass.getInterfaces();
|
||||
for(Class<?> intrfc : interfaces) {
|
||||
Method intrfcMethod = ReflectionUtils.findMethod(intrfc, method.getName(), method.getParameterTypes());
|
||||
if(intrfcMethod != null) {
|
||||
return lookupParameterNames(METHOD_METHODPARAM_FACTORY, intrfcMethod);
|
||||
}
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.core.ParameterNameDiscoverer#getParameterNames(java
|
||||
* .lang.reflect.Constructor)
|
||||
*/
|
||||
public String[] getParameterNames(Constructor<?> constructor) {
|
||||
return lookupParameterNames(CONSTRUCTOR_METHODPARAM_FACTORY,
|
||||
constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter names or null if not found.
|
||||
*
|
||||
* @param parameterNameFactory the {@link ParameterNameFactory} to use
|
||||
* @param t the {@link AccessibleObject} to find the parameter names on (i.e. Method or Constructor)
|
||||
* @return the parameter names or null
|
||||
*/
|
||||
private <T extends AccessibleObject> String[] lookupParameterNames(
|
||||
ParameterNameFactory<T> parameterNameFactory, T t) {
|
||||
int parameterCount = parameterNameFactory.getParamCount(t);
|
||||
String[] paramNames = new String[parameterCount];
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
Annotation[] annotations = parameterNameFactory.findAnnotationsAt(t, i);
|
||||
String parameterName = findParameterName(annotations);
|
||||
if (parameterName == null) {
|
||||
return null;
|
||||
} else {
|
||||
paramNames[i] = parameterName;
|
||||
}
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the parameter name from the provided {@link Annotation}s or null if
|
||||
* it could not find it. The search is done by looking at the value property
|
||||
* of the {@link #annotationClassesToUse}.
|
||||
*
|
||||
* @param parameterAnnotations
|
||||
* the {@link Annotation}'s to search.
|
||||
* @return
|
||||
*/
|
||||
private String findParameterName(Annotation[] parameterAnnotations) {
|
||||
for (Annotation paramAnnotation : parameterAnnotations) {
|
||||
if (annotationClassesToUse.contains(paramAnnotation
|
||||
.annotationType().getName())) {
|
||||
return (String) AnnotationUtils.getValue(paramAnnotation,
|
||||
"value");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final ParameterNameFactory<Constructor<?>> CONSTRUCTOR_METHODPARAM_FACTORY = new ParameterNameFactory<Constructor<?>>() {
|
||||
public int getParamCount(Constructor<?> constructor) {
|
||||
return constructor.getParameterTypes().length;
|
||||
}
|
||||
|
||||
public Annotation[] findAnnotationsAt(Constructor<?> constructor, int index) {
|
||||
return constructor.getParameterAnnotations()[index];
|
||||
}
|
||||
};
|
||||
|
||||
private static final ParameterNameFactory<Method> METHOD_METHODPARAM_FACTORY = new ParameterNameFactory<Method>() {
|
||||
public int getParamCount(Method method) {
|
||||
return method.getParameterTypes().length;
|
||||
}
|
||||
|
||||
public Annotation[] findAnnotationsAt(Method method, int index) {
|
||||
return method.getParameterAnnotations()[index];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Strategy interface for looking up the parameter names.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*
|
||||
* @param <T> the type to inspect (i.e. {@link Method} or {@link Constructor})
|
||||
*/
|
||||
private interface ParameterNameFactory<T extends AccessibleObject> {
|
||||
/**
|
||||
* Gets the parameter count
|
||||
* @param t
|
||||
* @return
|
||||
*/
|
||||
int getParamCount(T t);
|
||||
|
||||
/**
|
||||
* Gets the {@link Annotation}s at a specified index
|
||||
* @param t
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
Annotation[] findAnnotationsAt(T t, int index);
|
||||
}
|
||||
}
|
||||
+21
@@ -16,13 +16,16 @@
|
||||
package org.springframework.security.core.parameters;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.PrioritizedParameterNameDiscoverer;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -33,6 +36,9 @@ import org.springframework.util.ClassUtils;
|
||||
* classpath.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Will use an instance of {@link AnnotationParameterNameDiscoverer} with
|
||||
* {@link P} as a valid annotation. If, Spring Data is on the classpath will
|
||||
* also add Param annotation.</li>
|
||||
* <li>If Spring 4 is on the classpath, then DefaultParameterNameDiscoverer is
|
||||
* added. This attempts to use JDK 8 information first and falls back to
|
||||
* {@link LocalVariableTableParameterNameDiscoverer}.</li>
|
||||
@@ -40,6 +46,8 @@ import org.springframework.util.ClassUtils;
|
||||
* {@link LocalVariableTableParameterNameDiscoverer} is added directly.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see AnnotationParameterNameDiscoverer
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
@@ -53,6 +61,10 @@ public class DefaultSecurityParameterNameDiscoverer extends
|
||||
private static final boolean DEFAULT_PARAM_DISCOVERER_PRESENT =
|
||||
ClassUtils.isPresent(DEFAULT_PARAMETER_NAME_DISCOVERER_CLASSNAME, DefaultSecurityParameterNameDiscoverer.class.getClassLoader());
|
||||
|
||||
private static final String DATA_PARAM_CLASSNAME = "org.springframework.data.repository.query.Param";
|
||||
private static final boolean DATA_PARAM_PRESENT =
|
||||
ClassUtils.isPresent(DATA_PARAM_CLASSNAME, DefaultSecurityParameterNameDiscoverer.class.getClassLoader());
|
||||
|
||||
/**
|
||||
* Creates a new instance with only the default
|
||||
* {@link ParameterNameDiscoverer} instances.
|
||||
@@ -71,6 +83,15 @@ public class DefaultSecurityParameterNameDiscoverer extends
|
||||
for(ParameterNameDiscoverer discover : parameterNameDiscovers) {
|
||||
addDiscoverer(discover);
|
||||
}
|
||||
|
||||
Set<String> annotationClassesToUse = new HashSet<String>(2);
|
||||
annotationClassesToUse.add(P.class.getName());
|
||||
if(DATA_PARAM_PRESENT) {
|
||||
annotationClassesToUse.add(DATA_PARAM_CLASSNAME);
|
||||
}
|
||||
|
||||
addDiscoverer(new AnnotationParameterNameDiscoverer(annotationClassesToUse));
|
||||
|
||||
if (DEFAULT_PARAM_DISCOVERER_PRESENT) {
|
||||
try {
|
||||
Class<? extends ParameterNameDiscoverer> paramNameDiscoverClass = (Class<? extends ParameterNameDiscoverer>) ClassUtils
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.springframework.security.core.parameters;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class AnnotationParameterNameDiscovererTests {
|
||||
private AnnotationParameterNameDiscoverer discoverer;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
discoverer = new AnnotationParameterNameDiscoverer(P.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesInterfaceSingleParam() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByTo", String.class))).isEqualTo(new String [] { "to"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesInterfaceSingleParamAnnotatedWithMultiParams() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByToAndFrom", String.class, String.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesInterfaceNoAnnotation() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByIdNoAnnotation", String.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassSingleParam() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByTo", String.class))).isEqualTo(new String [] { "to"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassSingleParamAnnotatedWithMultiParams() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByToAndFrom", String.class, String.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassNoAnnotation() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByIdNoAnnotation", String.class))).isNull();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getParameterNamesConstructor() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(Impl.class.getConstructor(String.class))).isEqualTo(new String[] { "id"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesConstructorNoAnnotation() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(Impl.class.getConstructor(Long.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassAnnotationOnInterface() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(DaoImpl.class, "findMessageByTo", String.class))).isEqualTo(new String[] {"to"});
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByTo", String.class))).isEqualTo(new String[] {"to"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassAnnotationOnImpl() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByToAndFrom", String.class, String.class))).isNull();
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(DaoImpl.class, "findMessageByToAndFrom", String.class, String.class))).isEqualTo(new String[] {"to", "from"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassAnnotationOnBaseClass() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByIdNoAnnotation", String.class))).isNull();
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(DaoImpl.class, "findMessageByIdNoAnnotation", String.class))).isEqualTo(new String[] {"id"});
|
||||
}
|
||||
|
||||
interface Dao {
|
||||
String findMessageByTo(@P("to") String to);
|
||||
|
||||
String findMessageByToAndFrom(@P("to") String to, String from);
|
||||
|
||||
String findMessageByIdNoAnnotation(String id);
|
||||
}
|
||||
|
||||
static class BaseDaoImpl {
|
||||
public String findMessageByIdNoAnnotation(@P("id") String id) { return null; }
|
||||
}
|
||||
|
||||
static class DaoImpl extends BaseDaoImpl implements Dao {
|
||||
public String findMessageByTo(String to) { return null; }
|
||||
|
||||
public String findMessageByToAndFrom(@P("to") String to, @P("from") String from) { return null; }
|
||||
}
|
||||
|
||||
static class Impl {
|
||||
public Impl(Long dataSourceId) {}
|
||||
|
||||
public Impl(@P("id") String dataSourceId) {}
|
||||
|
||||
String findMessageByTo(@P("to") String to) { return null; }
|
||||
|
||||
String findMessageByToAndFrom(@P("to") String to, String from) { return null; }
|
||||
|
||||
String findMessageByIdNoAnnotation(String id) { return null; }
|
||||
}
|
||||
}
|
||||
+21
-4
@@ -19,12 +19,14 @@ import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
@@ -45,8 +47,16 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
||||
public void constructorDefault() {
|
||||
List<ParameterNameDiscoverer> discoverers = (List<ParameterNameDiscoverer>) ReflectionTestUtils
|
||||
.getField(discoverer, "parameterNameDiscoverers");
|
||||
assertThat(discoverers.size()).isEqualTo(1);
|
||||
assertThat(discoverers.get(0)).isInstanceOf(
|
||||
|
||||
assertThat(discoverers.size()).isEqualTo(2);
|
||||
|
||||
ParameterNameDiscoverer annotationDisc = discoverers.get(0);
|
||||
assertThat(annotationDisc).isInstanceOf(
|
||||
AnnotationParameterNameDiscoverer.class);
|
||||
Set<String> annotationsToUse = (Set<String>)ReflectionTestUtils.getField(annotationDisc, "annotationClassesToUse");
|
||||
assertThat(annotationsToUse).containsOnly(P.class.getName());
|
||||
|
||||
assertThat(discoverers.get(1)).isInstanceOf(
|
||||
DefaultParameterNameDiscoverer.class);
|
||||
}
|
||||
|
||||
@@ -57,10 +67,17 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
||||
List<ParameterNameDiscoverer> discoverers = (List<ParameterNameDiscoverer>) ReflectionTestUtils
|
||||
.getField(discoverer, "parameterNameDiscoverers");
|
||||
|
||||
assertThat(discoverers.size()).isEqualTo(2);
|
||||
assertThat(discoverers.size()).isEqualTo(3);
|
||||
assertThat(discoverers.get(0)).isInstanceOf(
|
||||
LocalVariableTableParameterNameDiscoverer.class);
|
||||
assertThat(discoverers.get(1)).isInstanceOf(
|
||||
|
||||
ParameterNameDiscoverer annotationDisc = discoverers.get(1);
|
||||
assertThat(annotationDisc).isInstanceOf(
|
||||
AnnotationParameterNameDiscoverer.class);
|
||||
Set<String> annotationsToUse = (Set<String>)ReflectionTestUtils.getField(annotationDisc, "annotationClassesToUse");
|
||||
assertThat(annotationsToUse).containsOnly(P.class.getName());
|
||||
|
||||
assertThat(discoverers.get(2)).isInstanceOf(
|
||||
DefaultParameterNameDiscoverer.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user