/* * 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.access.annotation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; 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.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; import org.junit.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.annotation.sec2150.MethodInvocationFactory; import org.springframework.security.access.intercept.method.MockMethodInvocation; import org.springframework.security.core.GrantedAuthority; /** * Tests for * {@link org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource} * * @author Mark St.Godard * @author Joe Scalise * @author Ben Alex * @author Luke Taylor */ public class SecuredAnnotationSecurityMetadataSourceTests { // ~ Instance fields // ================================================================================================ private SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource(); // ~ Methods // ======================================================================================================== @Test public void genericsSuperclassDeclarationsAreIncludedWhenSubclassesOverride() { Method method = null; try { method = DepartmentServiceImpl.class.getMethod("someUserMethod3", new Class[] { Department.class }); } catch (NoSuchMethodException unexpected) { fail("Should be a superMethod called 'someUserMethod3' on class!"); } Collection<ConfigAttribute> attrs = mds.findAttributes(method, DepartmentServiceImpl.class); assertThat(attrs).isNotNull(); // expect 1 attribute assertThat(attrs.size() == 1).as("Did not find 1 attribute").isTrue(); // should have 1 SecurityConfig for (ConfigAttribute sc : attrs) { assertThat(sc.getAttribute()).as("Found an incorrect role").isEqualTo( "ROLE_ADMIN"); } Method superMethod = null; try { superMethod = DepartmentServiceImpl.class.getMethod("someUserMethod3", new Class[] { Entity.class }); } catch (NoSuchMethodException unexpected) { fail("Should be a superMethod called 'someUserMethod3' on class!"); } Collection<ConfigAttribute> superAttrs = this.mds.findAttributes(superMethod, DepartmentServiceImpl.class); assertThat(superAttrs).isNotNull(); // This part of the test relates to SEC-274 // expect 1 attribute assertThat(superAttrs).as("Did not find 1 attribute").hasSize(1); // should have 1 SecurityConfig for (ConfigAttribute sc : superAttrs) { assertThat(sc.getAttribute()).as("Found an incorrect role").isEqualTo( "ROLE_ADMIN"); } } @Test public void classLevelAttributesAreFound() { Collection<ConfigAttribute> attrs = this.mds.findAttributes( BusinessService.class); assertThat(attrs).isNotNull(); // expect 1 annotation assertThat(attrs).hasSize(1); // should have 1 SecurityConfig SecurityConfig sc = (SecurityConfig) attrs.toArray()[0]; assertThat(sc.getAttribute()).isEqualTo("ROLE_USER"); } @Test public void methodLevelAttributesAreFound() { Method method = null; try { method = BusinessService.class.getMethod("someUserAndAdminMethod", new Class[] {}); } catch (NoSuchMethodException unexpected) { fail("Should be a method called 'someUserAndAdminMethod' on class!"); } Collection<ConfigAttribute> attrs = this.mds.findAttributes(method, BusinessService.class); assertThat(attrs).isNotNull(); // expect 2 attributes assertThat(attrs).hasSize(2); boolean user = false; boolean admin = false; // should have 2 SecurityConfigs for (ConfigAttribute sc : attrs) { assertThat(sc instanceof SecurityConfig).isTrue(); if (sc.getAttribute().equals("ROLE_USER")) { user = true; } else if (sc.getAttribute().equals("ROLE_ADMIN")) { admin = true; } } // expect to have ROLE_USER and ROLE_ADMIN assertThat(user && admin).isTrue(); } // SEC-1491 @Test public void customAnnotationAttributesAreFound() throws Exception { SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource( new CustomSecurityAnnotationMetadataExtractor()); Collection<ConfigAttribute> attrs = mds.findAttributes( CustomAnnotatedService.class); assertThat(attrs).hasSize(1); assertThat(attrs.toArray()[0]).isEqualTo(SecurityEnum.ADMIN); } @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]); assertThat(attrs.length).isEqualTo(1); assertThat(attrs[0].getAttribute()).isEqualTo("CUSTOM"); } @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]); assertThat(attrs.length).isEqualTo(1); assertThat(attrs[0].getAttribute()).isEqualTo("CUSTOM"); } @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]); assertThat(attrs.length).isEqualTo(1); assertThat(attrs[0].getAttribute()).isEqualTo("CUSTOM"); } @Test public void proxyFactoryInterfaceAttributesFound() throws Exception { MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation(); Collection<ConfigAttribute> attributes = mds.getAttributes(mi); assertThat(attributes.size()).isEqualTo(1); assertThat(attributes).extracting("attribute").containsOnly("ROLE_PERSON"); } // Inner classes class Department extends Entity { public Department(String name) { super(name); } } interface DepartmentService extends BusinessService { @Secured({ "ROLE_USER" }) Department someUserMethod3(Department dept); } @SuppressWarnings("serial") 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) { } } }