SEC-1796: Check for annotated annotations at class/interface level. Previously only the specific security annotation was checked for. By delegating to Spring's AnnotationUtils, custom annotations carrying the security annotation are also detected.

This commit is contained in:
Luke Taylor 2011-08-12 14:29:55 +01:00
parent 8ce4d326f5
commit 74daa68691
7 changed files with 178 additions and 148 deletions

View File

@ -55,7 +55,7 @@ public class SecuredAnnotationSecurityMetadataSource extends AbstractFallbackMet
}
protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
return processAnnotation(clazz.getAnnotation(annotationType));
return processAnnotation(AnnotationUtils.findAnnotation(clazz, annotationType));
}
protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass) {

View File

@ -19,6 +19,7 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
@ -113,7 +114,8 @@ public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor
class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
@SuppressWarnings("unchecked")
public boolean matches(Method m, Class targetClass) {
return attributeSource.getAttributes(m, targetClass) != null;
Collection attributes = attributeSource.getAttributes(m, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
}

View File

@ -1,7 +1,7 @@
package org.springframework.security.access.method;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.*;
import org.springframework.aop.support.AopUtils;
import org.springframework.security.access.ConfigAttribute;
@ -51,7 +51,7 @@ public abstract class AbstractFallbackMethodSecurityMetadataSource extends Abstr
// Last fallback is the class of the original method.
return findAttributes(method.getDeclaringClass());
}
return null;
return Collections.emptyList();
}
/**

View File

@ -2,8 +2,7 @@ package org.springframework.security.access.prepost;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.ConfigAttribute;
@ -38,13 +37,12 @@ public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecur
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
return Collections.emptyList();
}
logger.trace("Looking for Pre/Post 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?
@ -53,7 +51,7 @@ public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecur
if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null ) {
// There is no meta-data so return
logger.trace("No expression annotations found");
return null;
return Collections.emptyList();
}
String preFilterAttribute = preFilter == null ? null : preFilter.value();
@ -78,7 +76,7 @@ public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecur
attrs.trimToSize();
return attrs.isEmpty() ? null : attrs;
return attrs;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
@ -112,23 +110,13 @@ public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecur
}
// Check the class-level (note declaringClass, not targetClass, which may not actually implement the method)
annotation = specificMethod.getDeclaringClass().getAnnotation(annotationClass);
annotation = AnnotationUtils.findAnnotation(specificMethod.getDeclaringClass(), 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;
}

View File

@ -19,9 +19,11 @@ import static org.junit.Assert.*;
import org.junit.*;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.core.GrantedAuthority;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@ -37,7 +39,7 @@ import java.util.*;
* @author Ben Alex
* @author Luke Taylor
*/
public class SecuredAnnotationSecurityMetadataDefinitionSourceTests {
public class SecuredAnnotationSecurityMetadataSourceTests {
//~ Instance fields ================================================================================================
private SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource();
@ -137,6 +139,7 @@ public class SecuredAnnotationSecurityMetadataDefinitionSourceTests {
assertTrue(user && admin);
}
// SEC-1491
@Test
public void customAnnotationAttributesAreFound() throws Exception {
SecuredAnnotationSecurityMetadataSource mds =
@ -145,61 +148,117 @@ public class SecuredAnnotationSecurityMetadataDefinitionSourceTests {
assertEquals(1, attrs.size());
assertEquals(SecurityEnum.ADMIN, attrs.toArray()[0]);
}
}
class Department extends Entity {
public Department(String name) {
super(name);
}
}
@Test
public void annotatedAnnotationAtClassLevelIsDetected() throws Exception {
MockMethodInvocation annotatedAtClassLevel = new MockMethodInvocation(new AnnotatedAnnotationAtClassLevel(), ReturnVoid.class, "doSomething", List.class);
interface DepartmentService extends BusinessService {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtClassLevel).toArray(new ConfigAttribute[0]);
@Secured({"ROLE_USER"})
Department someUserMethod3(Department dept);
}
class DepartmentServiceImpl extends BusinessServiceImpl<Department> implements DepartmentService {
@Secured({"ROLE_ADMIN"})
public Department someUserMethod3(final Department dept) {
return super.someUserMethod3(dept);
}
}
// SEC-1491 Related classes. PoC for custom annotation with enum value.
@CustomSecurityAnnotation(SecurityEnum.ADMIN)
interface CustomAnnotatedService {
}
class CustomAnnotatedServiceImpl implements CustomAnnotatedService {
}
enum SecurityEnum implements ConfigAttribute, GrantedAuthority {
ADMIN,
USER;
public String getAttribute() {
return toString();
assertEquals(1, attrs.length);
assertEquals("CUSTOM", attrs[0].getAttribute());
}
public String getAuthority() {
return toString();
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface CustomSecurityAnnotation {
SecurityEnum[] value();
}
class CustomSecurityAnnotationMetadataExtractor implements AnnotationMetadataExtractor<CustomSecurityAnnotation> {
public Collection<? extends ConfigAttribute> extractAttributes(CustomSecurityAnnotation securityAnnotation) {
SecurityEnum[] values = securityAnnotation.value();
return EnumSet.copyOf(Arrays.asList(values));
@Test
public void annotatedAnnotationAtInterfaceLevelIsDetected() throws Exception {
MockMethodInvocation annotatedAtInterfaceLevel = new MockMethodInvocation(new AnnotatedAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething", List.class);
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtInterfaceLevel).toArray(new ConfigAttribute[0]);
assertEquals(1, attrs.length);
assertEquals("CUSTOM", attrs[0].getAttribute());
}
@Test
public void annotatedAnnotationAtMethodLevelIsDetected() throws Exception {
MockMethodInvocation annotatedAtMethodLevel = new MockMethodInvocation(new AnnotatedAnnotationAtMethodLevel(), ReturnVoid.class, "doSomething", List.class);
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtMethodLevel).toArray(new ConfigAttribute[0]);
assertEquals(1, attrs.length);
assertEquals("CUSTOM", attrs[0].getAttribute());
}
// Inner classes
class Department extends Entity {
public Department(String name) {
super(name);
}
}
interface DepartmentService extends BusinessService {
@Secured({"ROLE_USER"})
Department someUserMethod3(Department dept);
}
class DepartmentServiceImpl extends BusinessServiceImpl<Department> implements DepartmentService {
@Secured({"ROLE_ADMIN"})
public Department someUserMethod3(final Department dept) {
return super.someUserMethod3(dept);
}
}
// SEC-1491 Related classes. PoC for custom annotation with enum value.
@CustomSecurityAnnotation(SecurityEnum.ADMIN)
interface CustomAnnotatedService {
}
class CustomAnnotatedServiceImpl implements CustomAnnotatedService {
}
enum SecurityEnum implements ConfigAttribute, GrantedAuthority {
ADMIN,
USER;
public String getAttribute() {
return toString();
}
public String getAuthority() {
return toString();
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface CustomSecurityAnnotation {
SecurityEnum[] value();
}
class CustomSecurityAnnotationMetadataExtractor implements AnnotationMetadataExtractor<CustomSecurityAnnotation> {
public Collection<? extends ConfigAttribute> extractAttributes(CustomSecurityAnnotation securityAnnotation) {
SecurityEnum[] values = securityAnnotation.value();
return EnumSet.copyOf(Arrays.asList(values));
}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Secured("CUSTOM")
public @interface AnnotatedAnnotation {}
public static interface ReturnVoid {
public void doSomething(List<?> param);
}
@AnnotatedAnnotation
public static interface ReturnVoid2 {
public void doSomething(List<?> param);
}
@AnnotatedAnnotation
public static class AnnotatedAnnotationAtClassLevel implements ReturnVoid {
public void doSomething(List<?> param) {}
}
public static class AnnotatedAnnotationAtInterfaceLevel implements ReturnVoid2 {
public void doSomething(List<?> param) {}
}
public static class AnnotatedAnnotationAtMethodLevel implements ReturnVoid {
@AnnotatedAnnotation
public void doSomething(List<?> param) {}
}
}

View File

@ -2,6 +2,11 @@ package org.springframework.security.access.expression.method;
import static org.junit.Assert.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import org.junit.Before;
@ -29,6 +34,9 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
private MockMethodInvocation listImpl1;
private MockMethodInvocation notherListImpl1;
private MockMethodInvocation notherListImpl2;
private MockMethodInvocation annotatedAtClassLevel;
private MockMethodInvocation annotatedAtInterfaceLevel;
private MockMethodInvocation annotatedAtMethodLevel;
@Before
public void setUpData() throws Exception {
@ -38,6 +46,9 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
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);
annotatedAtClassLevel = new MockMethodInvocation(new CustomAnnotationAtClassLevel(), ReturnVoid.class, "doSomething", List.class);
annotatedAtInterfaceLevel = new MockMethodInvocation(new CustomAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething", List.class);
annotatedAtMethodLevel = new MockMethodInvocation(new CustomAnnotationAtMethodLevel(), ReturnVoid.class, "doSomething", List.class);
}
@Test
@ -116,6 +127,27 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
assertEquals("classMethodPreFilterExpression", pre.getFilterExpression().getExpressionString());
}
@Test
public void customAnnotationAtClassLevelIsDetected() throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtClassLevel).toArray(new ConfigAttribute[0]);
assertEquals(1, attrs.length);
}
@Test
public void customAnnotationAtInterfaceLevelIsDetected() throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtInterfaceLevel).toArray(new ConfigAttribute[0]);
assertEquals(1, attrs.length);
}
@Test
public void customAnnotationAtMethodLevelIsDetected() throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtMethodLevel).toArray(new ConfigAttribute[0]);
assertEquals(1, attrs.length);
}
//~ Inner Classes ==================================================================================================
public static interface ReturnVoid {
@ -172,4 +204,28 @@ public class PrePostAnnotationSecurityMetadataSourceTests {
public List<?> doSomething(List<?> param) {return param;}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PreAuthorize("customAnnotationExpression")
public @interface CustomAnnotation {}
@CustomAnnotation
public static interface ReturnVoid2 {
public void doSomething(List<?> param);
}
@CustomAnnotation
public static class CustomAnnotationAtClassLevel implements ReturnVoid {
public void doSomething(List<?> param) {}
}
public static class CustomAnnotationAtInterfaceLevel implements ReturnVoid2 {
public void doSomething(List<?> param) {}
}
public static class CustomAnnotationAtMethodLevel implements ReturnVoid {
@CustomAnnotation
public void doSomething(List<?> param) {}
}
}

View File

@ -1,75 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.taglibs.authz;
import junit.framework.TestCase;
import org.springframework.mock.web.MockPageContext;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.el.VariableResolver;
import javax.servlet.jsp.tagext.Tag;
/**
* Test case to implement commons-el expression language expansion.
*/
public class AuthorizeTagExpressionLanguageTests extends TestCase {
//~ Instance fields ================================================================================================
private final JspAuthorizeTag authorizeTag = new JspAuthorizeTag();
private MockPageContext pageContext;
//~ Methods ========================================================================================================
protected void setUp() throws Exception {
pageContext = new MockPageContext() {
public VariableResolver getVariableResolver() {
return null;
}
};
authorizeTag.setPageContext(pageContext);
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("abc", "123", "ROLE_TELLER"));
}
protected void tearDown() throws Exception {
SecurityContextHolder.clearContext();
}
public void testAllGrantedUsesExpressionLanguageWhenExpressionIsEL() throws JspException {
pageContext.setAttribute("authority", "ROLE_TELLER");
authorizeTag.setIfAllGranted("${authority}");
assertEquals("allows body - authority var contains ROLE_TELLER", Tag.EVAL_BODY_INCLUDE,
authorizeTag.doStartTag());
}
public void testAnyGrantedUsesExpressionLanguageWhenExpressionIsEL() throws JspException {
pageContext.setAttribute("authority", "ROLE_TELLER");
authorizeTag.setIfAnyGranted("${authority}");
assertEquals("allows body - authority var contains ROLE_TELLER", Tag.EVAL_BODY_INCLUDE,
authorizeTag.doStartTag());
}
public void testNotGrantedUsesExpressionLanguageWhenExpressionIsEL() throws JspException {
pageContext.setAttribute("authority", "ROLE_TELLER");
authorizeTag.setIfNotGranted("${authority}");
assertEquals("allows body - authority var contains ROLE_TELLER", Tag.SKIP_BODY, authorizeTag.doStartTag());
}
}