package org.org.springframework.security; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import junit.framework.TestCase; import org.org.springframework.security.addons.CustomSecurityAnnotationAttributes; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.security.AccessDeniedException; import org.springframework.security.Authentication; import org.springframework.security.SecurityConfig; import org.springframework.security.context.SecurityContext; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.intercept.method.MethodDefinitionAttributes; import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor; import org.springframework.security.providers.AuthenticationProvider; import org.springframework.security.providers.ProviderManager; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.dao.DaoAuthenticationProvider; import org.springframework.security.userdetails.memory.InMemoryDaoImpl; import org.springframework.security.vote.AccessDecisionVoter; import org.springframework.security.vote.RoleVoter; import org.springframework.security.vote.UnanimousBased; public class CustomSecurityAnnotationAttributesTest extends TestCase { @SuppressWarnings("unchecked") public void testAttributesRetrievalOnClass() throws Exception { Map<Class, String[]> mapping = new HashMap<Class, String[]>(); String[] adminRoleNeededCorrespondingRoles = new String[] { "ROLE_SUPERVISOR", "ROLE_USER" }; mapping.put(AdminRoleNeeded.class, adminRoleNeededCorrespondingRoles); // litterally this means that when the domain model specified through the AdminRoleNeeded annotation (meta data) // that an admin role is needed, "ROLE_SUPERVISOR" AND "ROLE_USER" roles are needed on the deployment system. // Of Course , the Set of Strings can contain any Security attribute relevant to Spring security's work, like RUNAS for example. // TEST WITH ANNOTATIONS ON THE INTERFACE MyNotAnnotatedServiceImpl myService = new MyNotAnnotatedServiceImpl(); CustomSecurityAnnotationAttributes annotationAttributes = new CustomSecurityAnnotationAttributes(mapping); try { Collection attributes = annotationAttributes.getAttributes(myService.getClass()); for (Class anInterface : myService.getClass().getInterfaces()) { attributes.addAll(annotationAttributes.getAttributes(anInterface)); } assertNotNull("result should not be null", attributes); for (Object attribute : attributes) { assertTrue("attribute should be a " + SecurityConfig.class.getName(), attribute instanceof SecurityConfig); } assertEquals("All the mapped roles should be returned, no more, no less.", adminRoleNeededCorrespondingRoles.length, attributes.size()); } catch (Exception e) { fail("An unexpected exception occured:" + e.getMessage()); } // TEST WITH ANNOTATIONS ON THE CLASS MyAnnotatedServiceImpl myService2 = new MyAnnotatedServiceImpl(); CustomSecurityAnnotationAttributes annotationAttributes2 = new CustomSecurityAnnotationAttributes(mapping); try { Collection attributes = annotationAttributes2.getAttributes(myService2.getClass()); for (Class anInterface : myService.getClass().getInterfaces()) { attributes.addAll(annotationAttributes.getAttributes(anInterface)); } assertNotNull("result should not be null", attributes); for (Object attribute : attributes) { assertTrue("attribute should be a " + SecurityConfig.class.getName(), attribute instanceof SecurityConfig); } assertEquals("All the mapped roles should be returned, no more, no less.", adminRoleNeededCorrespondingRoles.length, attributes.size()); } catch (Exception e) { fail("An unexpected exception occured:" + e.getMessage()); } } @SuppressWarnings("unchecked") public void testAttributesRetrievalOnMethodFromClass() throws Exception { Map<Class, String[]> mapping = new HashMap<Class, String[]>(); String[] adminRoleNeededCorrespondingRoles = new String[] { "ROLE_SUPERVISOR", "ROLE_USER" }; mapping.put(AdminRoleNeeded.class, adminRoleNeededCorrespondingRoles); // litterally this means that when the domain model specified through the AdminRoleNeeded annotation (meta data) // that an admin role is needed, "ROLE_SUPERVISOR" AND "ROLE_USER" roles are needed on the deployment system. // Of Course , the Set of Strings can contain any Security attribute relevant to Spring security's work, like RUNAS for example. // TEST WITH ANNOTATIONS ON THE INTERFACE MyNotAnnotatedServiceImpl myService = new MyNotAnnotatedServiceImpl(); CustomSecurityAnnotationAttributes annotationAttributes = new CustomSecurityAnnotationAttributes(mapping); try { Method method = myService.getClass().getMethod("getResult", new Class[0]); Collection attributes = annotationAttributes.getAttributes(method); for (Method interfaceMethod : getInterfaceMethods(method)) { attributes.addAll(annotationAttributes.getAttributes(interfaceMethod)); } assertNotNull("result should not be null", attributes); for (Object attribute : attributes) { assertTrue("attribute should be a " + SecurityConfig.class.getName(), attribute instanceof SecurityConfig); } assertEquals("All the mapped roles should be returned, no more, no less.", adminRoleNeededCorrespondingRoles.length, attributes.size()); } catch (Exception e) { fail("An unexpected exception occured:" + e.getMessage()); } // TEST WITH ANNOTATIONS ON THE CLASS MyAnnotatedServiceImpl myService2 = new MyAnnotatedServiceImpl(); CustomSecurityAnnotationAttributes annotationAttributes2 = new CustomSecurityAnnotationAttributes(mapping); try { Method method = myService2.getClass().getMethod("getResult", new Class[0]); Collection attributes = annotationAttributes2.getAttributes(method); for (Method interfaceMethod : getInterfaceMethods(method)) { attributes.addAll(annotationAttributes.getAttributes(interfaceMethod)); } assertNotNull("result should not be null", attributes); for (Object attribute : attributes) { assertTrue("attribute should be a " + SecurityConfig.class.getName(), attribute instanceof SecurityConfig); } assertEquals("All the mapped roles should be returned, no more, no less.", adminRoleNeededCorrespondingRoles.length, attributes.size()); } catch (Exception e) { fail("An unexpected exception occured:" + e.getMessage()); } } private List<Method> getInterfaceMethods(Method method) { List<Method> methods = new LinkedList<Method>(); Class<?>[] interfaces = method.getDeclaringClass().getInterfaces(); for (int i = 0; i < interfaces.length; i++) { Class<?> clazz = interfaces[i]; try { methods.add(clazz.getDeclaredMethod(method.getName(), (Class[]) method.getParameterTypes())); } catch (Exception e) { // this won't happen since we are getting a method from an interface that // the declaring class implements } } return methods; } @SuppressWarnings("unchecked") public void testUnsupportedOperations() throws Exception { Map<Class, String[]> mapping = new HashMap<Class, String[]>(); String[] adminRoleNeededCorrespondingRoles = new String[] { "ROLE_SUPERVISOR", "ROLE_USER" }; mapping.put(AdminRoleNeeded.class, adminRoleNeededCorrespondingRoles); // litterally this means that when the domain model specified through the AdminRoleNeeded annotation (meta data) // that an admin role is needed, "ROLE_SUPERVISOR" AND "ROLE_USER" roles are needed on the deployment system. // Of Course , the Set of Strings can contain any Security attribute relevant to Spring security's work, like RUNAS for example. IMyAnnotatedServiceInterface myService = new MyNotAnnotatedServiceImpl(); CustomSecurityAnnotationAttributes annotationAttributes = new CustomSecurityAnnotationAttributes(mapping); try { annotationAttributes.getAttributes(myService.getClass().getDeclaredFields()[0]); fail("This attribute does not support this method"); } catch (Exception e) { assertTrue(e instanceof UnsupportedOperationException); } try { annotationAttributes.getAttributes(myService.getClass(), AdminRoleNeeded.class); fail("This attribute does not support this method"); } catch (Exception e) { assertTrue(e instanceof UnsupportedOperationException); } try { annotationAttributes.getAttributes(myService.getClass().getDeclaredFields()[0], AdminRoleNeeded.class); fail("This attribute does not support this method"); } catch (Exception e) { assertTrue(e instanceof UnsupportedOperationException); } try { annotationAttributes.getAttributes(myService.getClass().getMethods()[0], AdminRoleNeeded.class); fail("This attribute does not support this method"); } catch (Exception e) { assertTrue(e instanceof UnsupportedOperationException); } } public void testComplianceWithSpringWithXMLConfiguration() throws Exception { SecurityContextHolder.setContext(new SecurityContext() { private static final long serialVersionUID = 9182609898080024870L; public void setAuthentication(Authentication arg0) { } public Authentication getAuthentication() { return new UsernamePasswordAuthenticationToken("marissa", "koala"); } }); ApplicationContext applicationContext = new ClassPathXmlApplicationContext("test-config1.xml"); IMyAnnotatedServiceInterface myService = (IMyAnnotatedServiceInterface) applicationContext.getBean("myServiceProxy"); IMyNotAnnotatedServiceInterface myService2 = (IMyNotAnnotatedServiceInterface) applicationContext.getBean("myServiceProxy2"); doTestServices(myService, myService2); } @SuppressWarnings("unchecked") public void testComplianceWithSpring() throws Exception { // This test is exactly the same test PropertiesFactoryBean userProperties = new PropertiesFactoryBean(); userProperties.setLocation(new ClassPathResource("users.properties")); userProperties.afterPropertiesSet(); InMemoryDaoImpl inMemoryDaoImpl = new InMemoryDaoImpl(); inMemoryDaoImpl.setUserProperties((Properties) userProperties.getObject()); inMemoryDaoImpl.afterPropertiesSet(); DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(inMemoryDaoImpl); daoAuthenticationProvider.afterPropertiesSet(); List<AuthenticationProvider> providers = new LinkedList<AuthenticationProvider>(); providers.add(daoAuthenticationProvider); ProviderManager authenticationManager = new ProviderManager(); authenticationManager.setProviders(providers); authenticationManager.afterPropertiesSet(); Map<Class, String[]> mapping = new HashMap<Class, String[]>(); String[] adminRoleNeededCorrespondingRoles = new String[] { "ROLE_SUPERVISOR", "ROLE_USER" }; mapping.put(AdminRoleNeeded.class, adminRoleNeededCorrespondingRoles); CustomSecurityAnnotationAttributes annotationAttributes = new CustomSecurityAnnotationAttributes(mapping); MethodDefinitionAttributes methodDefinitionAttributes = new MethodDefinitionAttributes(); methodDefinitionAttributes.setAttributes(annotationAttributes); RoleVoter roleVoter = new RoleVoter(); List<AccessDecisionVoter> decisionVoters = new LinkedList<AccessDecisionVoter>(); decisionVoters.add(roleVoter); UnanimousBased accessDecisionManager = new UnanimousBased(); accessDecisionManager.setAllowIfAllAbstainDecisions(false); accessDecisionManager.setDecisionVoters(decisionVoters); MethodSecurityInterceptor methodSecurityInterceptor = new MethodSecurityInterceptor(); methodSecurityInterceptor.setValidateConfigAttributes(false); methodSecurityInterceptor.setAuthenticationManager(authenticationManager); methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager); methodSecurityInterceptor.setObjectDefinitionSource(methodDefinitionAttributes); MyNotAnnotatedServiceImpl myService = new MyNotAnnotatedServiceImpl(); MyAnnotatedServiceImpl myService2 = new MyAnnotatedServiceImpl(); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setInterfaces(new Class[] { IMyAnnotatedServiceInterface.class }); proxyFactory.setTarget(myService); proxyFactory.addAdvice(methodSecurityInterceptor); ProxyFactory proxyFactory2 = new ProxyFactory(); proxyFactory2.setInterfaces(new Class[] { IMyNotAnnotatedServiceInterface.class }); proxyFactory2.setTarget(myService2); proxyFactory2.addAdvice(methodSecurityInterceptor); IMyAnnotatedServiceInterface myServiceProxy = (IMyAnnotatedServiceInterface) proxyFactory.getProxy(); IMyNotAnnotatedServiceInterface myService2Proxy = (IMyNotAnnotatedServiceInterface) proxyFactory2.getProxy(); doTestServices(myServiceProxy, myService2Proxy); } /** * @param myServiceWithAnnotatedInterface * @param myServiceWithAnnotatedImpl */ private void doTestServices(IMyAnnotatedServiceInterface myServiceWithAnnotatedInterface, IMyNotAnnotatedServiceInterface myServiceWithAnnotatedImpl) { // Setting a security context with an authorized user // (i.e. its granted authorities are matching the ones mapped to the custom annotation) SecurityContextHolder.setContext(new SecurityContext() { private static final long serialVersionUID = 9182609898080024870L; public void setAuthentication(Authentication arg0) { } public Authentication getAuthentication() { return new UsernamePasswordAuthenticationToken("marissa", "koala"); } }); try { myServiceWithAnnotatedInterface.getResult(); myServiceWithAnnotatedInterface.getResult2(); // myServiceWithAnnotatedImpl.getResult(); // myServiceWithAnnotatedImpl.getResult2(); } catch (Exception e) { fail("An unexpected error occured"); } SecurityContextHolder.clearContext(); // Setting a security context with an unauthorized user // (i.e. its granted authorities are NOT matching the ones mapped to the custom annotation) SecurityContextHolder.setContext(new SecurityContext() { private static final long serialVersionUID = 9182609898080024870L; public void setAuthentication(Authentication arg0) { } public Authentication getAuthentication() { return new UsernamePasswordAuthenticationToken("dianne", "emu"); } }); try { myServiceWithAnnotatedInterface.getResult(); fail("should fail with access denied as method is annotated on the Interface"); } catch (Exception e) { assertTrue(e instanceof AccessDeniedException); } try { myServiceWithAnnotatedInterface.getResult2(); fail("should fail with access denied as interface is annotated"); } catch (Exception e) { assertTrue(e instanceof AccessDeniedException); } // try { // myServiceWithAnnotatedImpl.getResult(); // fail("should fail with access denied as method is annotated on the Implementation"); // } catch (Exception e) { // assertTrue(e instanceof AccessDeniedException); // } // try { // myServiceWithAnnotatedImpl.getResult2(); // fail("should fail with access denied as implementation class is annotated"); // } catch (Exception e) { // assertTrue(e instanceof AccessDeniedException); // } SecurityContextHolder.clearContext(); } }