diff --git a/core/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java b/core/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java new file mode 100644 index 0000000000..ac7bfac65e --- /dev/null +++ b/core/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java @@ -0,0 +1,19 @@ +package org.springframework.security.access.annotation; + +import org.springframework.security.access.ConfigAttribute; + +import java.lang.annotation.Annotation; +import java.util.*; + +/** + * Strategy to process a custom security annotation to extract the relevant {@code ConfigAttribute}s for + * securing a method. + *

+ * Used by {@code SecuredAnnotationSecurityMetadataSource}. + * + * @author Luke Taylor + */ +public interface AnnotationMetadataExtractor { + + Collection extractAttributes(A securityAnnotation); +} diff --git a/core/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java b/core/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java index 162eeb52d3..e86355ed0f 100644 --- a/core/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java +++ b/core/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java @@ -17,41 +17,68 @@ package org.springframework.security.access.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; +import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource; +import org.springframework.util.Assert; /** * Sources method security metadata from Spring Security's {@link Secured} annotation. + *

+ * Can also be used with custom security annotations by injecting an {@link AnnotationMetadataExtractor}. + * The annotation type will then be obtained from the generic parameter type supplied to this interface * * @author Ben Alex + * @author Luke Taylor */ +@SuppressWarnings({"unchecked"}) public class SecuredAnnotationSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { + private AnnotationMetadataExtractor annotationExtractor; + private Class annotationType; + + public SecuredAnnotationSecurityMetadataSource() { + this(new SecuredAnnotationMetadataExtractor()); + } + + public SecuredAnnotationSecurityMetadataSource(AnnotationMetadataExtractor annotationMetadataExtractor) { + Assert.notNull(annotationMetadataExtractor); + annotationExtractor = annotationMetadataExtractor; + annotationType = (Class) GenericTypeResolver.resolveTypeArgument( + annotationExtractor.getClass(), AnnotationMetadataExtractor.class); + Assert.notNull(annotationType, annotationExtractor.getClass().getName() + + " must supply a generic parameter for AnnotationMetadataExtractor"); + } protected Collection findAttributes(Class clazz) { - return processAnnotation(clazz.getAnnotation(Secured.class)); + return processAnnotation(clazz.getAnnotation(annotationType)); } protected Collection findAttributes(Method method, Class targetClass) { - return processAnnotation(AnnotationUtils.findAnnotation(method, Secured.class)); + return processAnnotation(AnnotationUtils.findAnnotation(method, annotationType)); } public Collection getAllConfigAttributes() { return null; } - private List processAnnotation(Annotation a) { - if (a == null || !(a instanceof Secured)) { + private Collection processAnnotation(Annotation a) { + if (a == null) { return null; } - String[] attributeTokens = ((Secured) a).value(); + return annotationExtractor.extractAttributes(a); + } +} + +class SecuredAnnotationMetadataExtractor implements AnnotationMetadataExtractor { + + public Collection extractAttributes(Secured secured) { + String[] attributeTokens = secured.value(); List attributes = new ArrayList(attributeTokens.length); for(String token : attributeTokens) { diff --git a/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java b/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java index 6f988b21fd..721d72b91f 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java +++ b/core/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java @@ -3,8 +3,6 @@ package org.springframework.security.access.annotation; import java.util.ArrayList; import java.util.List; -import org.springframework.security.access.annotation.Secured; - /** * * @author Joe Scalise diff --git a/core/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataDefinitionSourceTests.java b/core/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataDefinitionSourceTests.java index 980dcbff55..9f89d96a9e 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataDefinitionSourceTests.java +++ b/core/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataDefinitionSourceTests.java @@ -16,12 +16,18 @@ package org.springframework.security.access.annotation; 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.lang.reflect.Method; -import java.util.Collection; +import java.util.*; import org.junit.*; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; +import org.springframework.security.core.GrantedAuthority; /** @@ -82,6 +88,7 @@ public class SecuredAnnotationSecurityMetadataDefinitionSourceTests { } } + @Test public void classLevelAttributesAreFound() { Collection attrs = this.mds.findAttributes(BusinessService.class); @@ -96,6 +103,7 @@ public class SecuredAnnotationSecurityMetadataDefinitionSourceTests { assertEquals("ROLE_USER", sc.getAttribute()); } + @Test public void methodLevelAttributesAreFound() { Method method = null; @@ -130,26 +138,20 @@ public class SecuredAnnotationSecurityMetadataDefinitionSourceTests { assertTrue(user && admin); } -} - -class Entity { - public Entity(String someParameter) {} + @Test + public void customAnnotationAttributesAreFound() throws Exception { + SecuredAnnotationSecurityMetadataSource mds = + new SecuredAnnotationSecurityMetadataSource(new CustomSecurityAnnotationMetadataExtractor()); + Collection attrs = mds.findAttributes(CustomAnnotatedService.class); + assertEquals(1, attrs.size()); + assertEquals(SecurityEnum.ADMIN, attrs.toArray()[0]); + } } class Department extends Entity { - private boolean active = true; - public Department(String name) { super(name); } - - public boolean isActive() { - return this.active; - } - - void deactive() { - this.active = true; - } } interface DepartmentService extends BusinessService { @@ -158,10 +160,47 @@ interface DepartmentService extends BusinessService { Department someUserMethod3(Department dept); } -class DepartmentServiceImpl extends BusinessServiceImpl implements DepartmentService { +class DepartmentServiceImpl extends BusinessServiceImpl 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 { + + public Collection extractAttributes(CustomSecurityAnnotation securityAnnotation) { + SecurityEnum[] values = securityAnnotation.value(); + + return EnumSet.copyOf(Arrays.asList(values)); + } +}