Explorar o código

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.

Luke Taylor %!s(int64=14) %!d(string=hai) anos
pai
achega
74daa68691

+ 1 - 1
core/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java

@@ -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) {

+ 3 - 1
core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java

@@ -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();
         }
     }
 }

+ 2 - 2
core/src/main/java/org/springframework/security/access/method/AbstractFallbackMethodSecurityMetadataSource.java

@@ -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();
     }
 
     /**

+ 5 - 17
core/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java

@@ -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;
     }
 

+ 97 - 38
core/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataDefinitionSourceTests.java → core/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSourceTests.java

@@ -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);
+
+        ConfigAttribute[] attrs = mds.getAttributes(annotatedAtClassLevel).toArray(new ConfigAttribute[0]);
+
+        assertEquals(1, attrs.length);
+        assertEquals("CUSTOM", attrs[0].getAttribute());
     }
-}
 
-interface DepartmentService extends BusinessService {
+    @Test
+    public void annotatedAnnotationAtInterfaceLevelIsDetected() throws Exception {
+        MockMethodInvocation annotatedAtInterfaceLevel = new MockMethodInvocation(new AnnotatedAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething", List.class);
 
-    @Secured({"ROLE_USER"})
-    Department someUserMethod3(Department dept);
-}
+        ConfigAttribute[] attrs = mds.getAttributes(annotatedAtInterfaceLevel).toArray(new ConfigAttribute[0]);
 
-class DepartmentServiceImpl extends BusinessServiceImpl<Department> implements DepartmentService {
+        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]);
 
-    @Secured({"ROLE_ADMIN"})
-    public Department someUserMethod3(final Department dept) {
-        return super.someUserMethod3(dept);
+        assertEquals(1, attrs.length);
+        assertEquals("CUSTOM", attrs[0].getAttribute());
     }
-}
 
-// SEC-1491 Related classes. PoC for custom annotation with enum value.
+    // Inner classes
+    class Department extends Entity {
+        public Department(String name) {
+            super(name);
+        }
+    }
 
-@CustomSecurityAnnotation(SecurityEnum.ADMIN)
-interface CustomAnnotatedService {
-}
+    interface DepartmentService extends BusinessService {
+        @Secured({"ROLE_USER"})
+        Department someUserMethod3(Department dept);
+    }
 
-class CustomAnnotatedServiceImpl implements CustomAnnotatedService {
-}
+    class DepartmentServiceImpl extends BusinessServiceImpl<Department> implements DepartmentService {
+        @Secured({"ROLE_ADMIN"})
+        public Department someUserMethod3(final Department dept) {
+            return super.someUserMethod3(dept);
+        }
+    }
 
-enum SecurityEnum implements ConfigAttribute, GrantedAuthority {
-    ADMIN,
-    USER;
+    // SEC-1491 Related classes. PoC for custom annotation with enum value.
 
-    public String getAttribute() {
-        return toString();
+    @CustomSecurityAnnotation(SecurityEnum.ADMIN)
+    interface CustomAnnotatedService {
     }
 
-    public String getAuthority() {
-        return toString();
+    class CustomAnnotatedServiceImpl implements CustomAnnotatedService {
     }
-}
 
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@interface CustomSecurityAnnotation {
-    SecurityEnum[] value();
-}
+    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> {
+    class CustomSecurityAnnotationMetadataExtractor implements AnnotationMetadataExtractor<CustomSecurityAnnotation> {
+        public Collection<? extends ConfigAttribute> extractAttributes(CustomSecurityAnnotation securityAnnotation) {
+            SecurityEnum[] values = securityAnnotation.value();
 
-    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) {}
+    }
 
-        return EnumSet.copyOf(Arrays.asList(values));
+    public static class AnnotatedAnnotationAtMethodLevel implements ReturnVoid {
+        @AnnotatedAnnotation
+        public void doSomething(List<?> param) {}
     }
 }

+ 56 - 0
core/src/test/java/org/springframework/security/access/expression/method/PrePostAnnotationSecurityMetadataSourceTests.java

@@ -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) {}
+    }
 }

+ 0 - 75
taglibs/src/test/java/org/springframework/security/taglibs/authz/AuthorizeTagExpressionLanguageTests.java

@@ -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());
-    }
-}