diff --git a/core-tiger/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java b/core-tiger/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java new file mode 100644 index 0000000000..745632998c --- /dev/null +++ b/core-tiger/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java @@ -0,0 +1,75 @@ +/* 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.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource; + + +/** + * Sources method security metadata from major JSR 250 security annotations. + * + * @author Ben Alex + * @version $Id$ + */ +public class Jsr250MethodDefinitionSource extends AbstractFallbackMethodDefinitionSource { + + protected ConfigAttributeDefinition findAttributes(Class clazz) { + return processAnnotations(clazz.getAnnotations()); + } + + protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { + return processAnnotations(AnnotationUtils.getAnnotations(method)); + } + + public Collection getConfigAttributeDefinitions() { + return null; + } + + private ConfigAttributeDefinition processAnnotations(Annotation[] annotations) { + if (annotations == null || annotations.length == 0) { + return null; + } + for (Annotation a: annotations) { + if (a instanceof DenyAll) { + return new ConfigAttributeDefinition(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); + } + if (a instanceof PermitAll) { + return new ConfigAttributeDefinition(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); + } + if (a instanceof RolesAllowed) { + RolesAllowed ra = (RolesAllowed) a; + List attributes = new ArrayList(); + for (String allowed : ra.value()) { + attributes.add(new Jsr250SecurityConfig(allowed)); + } + return new ConfigAttributeDefinition(attributes); + } + } + return null; + } +} diff --git a/core-tiger/src/main/java/org/springframework/security/annotation/Jsr250SecurityAnnotationAttributes.java b/core-tiger/src/main/java/org/springframework/security/annotation/Jsr250SecurityAnnotationAttributes.java deleted file mode 100644 index 96f843846e..0000000000 --- a/core-tiger/src/main/java/org/springframework/security/annotation/Jsr250SecurityAnnotationAttributes.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.springframework.security.annotation; - -import org.springframework.security.SecurityConfig; -import org.springframework.metadata.Attributes; -import org.springframework.core.annotation.AnnotationUtils; - -import javax.annotation.security.PermitAll; -import javax.annotation.security.RolesAllowed; -import javax.annotation.security.DenyAll; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.ArrayList; -import java.lang.reflect.Method; -import java.lang.reflect.Field; -import java.lang.annotation.Annotation; - -/** - * Java 5 Annotation {@link Attributes} metadata implementation used for secure method interception using - * the security anotations defined in JSR-250. - *

- * This Attributes implementation will return security configuration for classes described using the - * Java JEE 5 annotations (DenyAll, PermitAll and RolesAllowed). - *

- * - * @author Mark St.Godard - * @author Usama Rashwan - * @author Luke Taylor - * @since 2.0 - * - * @see javax.annotation.security.RolesAllowed - */ - -public class Jsr250SecurityAnnotationAttributes implements Attributes { - - //~ Methods ======================================================================================================== - - /** - * Get the RolesAllowed attributes for a given target class. - * This method will return an empty Collection because the call to getAttributes(method) will override the class - * annotation. - * - * @param target The target Object - * @return Empty Collection of SecurityConfig - * - * @see Attributes#getAttributes - */ - public Collection getAttributes(Class target) { - return new HashSet(); - } - - /** - * Get the attributes for a given target method, acording to JSR-250 precedence rules. - * - * @param method The target method - * @return Collection of SecurityConfig - * @see Attributes#getAttributes - */ - public Collection getAttributes(Method method) { - ArrayList attributes = new ArrayList(); - - if (AnnotationUtils.getAnnotation(method, DenyAll.class) != null) { - attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); - - return attributes; - } - - if (AnnotationUtils.getAnnotation(method, PermitAll.class) != null) { - attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); - - return attributes; - } - - RolesAllowed rolesAllowed = AnnotationUtils.getAnnotation(method, RolesAllowed.class); - - if (rolesAllowed != null) { - for (String role : rolesAllowed.value()) { - attributes.add(new Jsr250SecurityConfig(role)); - } - - return attributes; - } - - // Now check the class-level attributes: - if (method.getDeclaringClass().getAnnotation(DenyAll.class) != null) { - attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); - - return attributes; - } - - if (method.getDeclaringClass().getAnnotation(PermitAll.class) != null) { - attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); - - return attributes; - } - - rolesAllowed = method.getDeclaringClass().getAnnotation(RolesAllowed.class); - - if (rolesAllowed != null) { - for (String role : rolesAllowed.value()) { - attributes.add(new Jsr250SecurityConfig(role)); - } - } - - return attributes; - } - - protected Collection populateSecurityConfigWithRolesAllowed (Annotation[] annotations) { - Set attributes = new HashSet(); - for (Annotation annotation : annotations) { - // check for RolesAllowed annotations - if (annotation instanceof RolesAllowed) { - RolesAllowed attr = (RolesAllowed) annotation; - - for (String auth : attr.value()) { - attributes.add(new SecurityConfig(auth)); - } - - break; - } - } - return attributes; - } - - public Collection getAttributes(Class clazz, Class filter) { - throw new UnsupportedOperationException(); - } - - public Collection getAttributes(Method method, Class clazz) { - throw new UnsupportedOperationException(); - } - - public Collection getAttributes(Field field) { - throw new UnsupportedOperationException(); - } - - public Collection getAttributes(Field field, Class clazz) { - throw new UnsupportedOperationException(); - } -} diff --git a/core-tiger/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java b/core-tiger/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java new file mode 100644 index 0000000000..f3fa4915d1 --- /dev/null +++ b/core-tiger/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java @@ -0,0 +1,54 @@ +/* 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.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource; + + +/** + * Sources method security metadata from Spring Security's {@link Secured} annotation. + * + * @author Ben Alex + * @version $Id$ + */ +public class SecuredMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource { + + protected ConfigAttributeDefinition findAttributes(Class clazz) { + return processAnnotation(clazz.getAnnotation(Secured.class)); + } + + protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { + return processAnnotation(AnnotationUtils.findAnnotation(method, Secured.class)); + } + + public Collection getConfigAttributeDefinitions() { + return null; + } + + private ConfigAttributeDefinition processAnnotation(Annotation a) { + if (a == null || !(a instanceof Secured)) { + return null; + } + Secured secured = (Secured) a; + return new ConfigAttributeDefinition(secured.value()); + } +} diff --git a/core-tiger/src/main/java/org/springframework/security/annotation/SecurityAnnotationAttributes.java b/core-tiger/src/main/java/org/springframework/security/annotation/SecurityAnnotationAttributes.java deleted file mode 100644 index 0d3ac04c07..0000000000 --- a/core-tiger/src/main/java/org/springframework/security/annotation/SecurityAnnotationAttributes.java +++ /dev/null @@ -1,137 +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.annotation; - -import org.springframework.security.SecurityConfig; - -import org.springframework.metadata.Attributes; - -import org.springframework.core.annotation.AnnotationUtils; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - - -/** - * Java 5 Annotation Attributes metadata implementation used for secure method interception.

This - * Attributes implementation will return security configuration for classes described using the - * Secured Java 5 annotation. - *

- * The SecurityAnnotationAttributes implementation can be used to configure a - * MethodDefinitionAttributes and MethodSecurityInterceptor bean definition (see below). - *

- * For example: - *

- * <bean id="attributes" class="org.springframework.security.annotation.SecurityAnnotationAttributes"/>
- * <bean id="objectDefinitionSource"
- *     class="org.springframework.security.intercept.method.MethodDefinitionAttributes">
- *         <property name="attributes"><ref local="attributes"/></property>
- * </bean>
- * <bean id="securityInterceptor"
- *     class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor">
- *      . . .
- *      <property name="objectDefinitionSource"><ref local="objectDefinitionSource"/></property>
- * </bean>
- * 
- *

- * These security annotations are similiar to the Commons Attributes approach, however they are using Java 5 - * language-level metadata support. - * - * @author Mark St.Godard - * @version $Id$ - * - * @see org.springframework.security.annotation.Secured - */ -public class SecurityAnnotationAttributes implements Attributes { - //~ Methods ======================================================================================================== - - /** - * Get the Secured attributes for a given target class. - * - * @param target The target method - * - * @return Collection of SecurityConfig - * - * @see Attributes#getAttributes - */ - public Collection getAttributes(Class target) { - Set attributes = new HashSet(); - - for (Annotation annotation : target.getAnnotations()) { - // check for Secured annotations - if (annotation instanceof Secured) { - Secured attr = (Secured) annotation; - - for (String auth : attr.value()) { - attributes.add(new SecurityConfig(auth)); - } - - break; - } - } - - return attributes; - } - - /** - * Get the Secured attributes for a given target method. - * - * @param method The target method - * - * @return Collection of SecurityConfig - * - * @see Attributes#getAttributes - */ - public Collection getAttributes(Method method) { - Set attributes = new HashSet(); - Annotation[] annotations = AnnotationUtils.getAnnotations(method); - - for (Annotation annotation : annotations) { - // check for Secured annotations - if (annotation instanceof Secured) { - Secured attr = (Secured) annotation; - - for (String auth : attr.value()) { - attributes.add(new SecurityConfig(auth)); - } - - break; - } - } - - return attributes; - } - - public Collection getAttributes(Class clazz, Class filter) { - throw new UnsupportedOperationException(); - } - - public Collection getAttributes(Method method, Class clazz) { - throw new UnsupportedOperationException(); - } - - public Collection getAttributes(Field field) { - throw new UnsupportedOperationException(); - } - - public Collection getAttributes(Field field, Class clazz) { - throw new UnsupportedOperationException(); - } -} diff --git a/core-tiger/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java b/core-tiger/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java new file mode 100644 index 0000000000..56762dd1fc --- /dev/null +++ b/core-tiger/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java @@ -0,0 +1,104 @@ +package org.springframework.security.annotation; + +import static org.junit.Assert.assertEquals; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springframework.security.ConfigAttributeDefinition; + +/** + * @author Luke Taylor + * @author Ben Alex + * @version $Id$ + */ +public class Jsr250MethodDefinitionSourceTests { + Jsr250MethodDefinitionSource mds = new Jsr250MethodDefinitionSource(); + A a = new A(); + UserAllowedClass userAllowed = new UserAllowedClass(); + DenyAllClass denyAll = new DenyAllClass(); + + @Test + public void methodWithRolesAllowedHasCorrectAttribute() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(a.getClass().getMethod("adminMethod"), null); + assertEquals(1, accessAttributes.getConfigAttributes().size()); + assertEquals("ADMIN", accessAttributes.getConfigAttributes().iterator().next().toString()); + } + + @Test + public void permitAllMethodHasPermitAllAttribute() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(a.getClass().getMethod("permitAllMethod"), null); + assertEquals(1, accessAttributes.getConfigAttributes().size()); + assertEquals("javax.annotation.security.PermitAll", accessAttributes.getConfigAttributes().iterator().next().toString()); + } + + @Test + public void noRoleMethodHasDenyAllAttributeWithDenyAllClass() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(denyAll.getClass()); + assertEquals(1, accessAttributes.getConfigAttributes().size()); + assertEquals("javax.annotation.security.DenyAll", accessAttributes.getConfigAttributes().iterator().next().toString()); + } + + @Test + public void adminMethodHasAdminAttributeWithDenyAllClass() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(denyAll.getClass().getMethod("adminMethod"), null); + assertEquals(1, accessAttributes.getConfigAttributes().size()); + assertEquals("ADMIN", accessAttributes.getConfigAttributes().iterator().next().toString()); + } + + @Test + public void noRoleMethodHasNoAttributes() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(a.getClass().getMethod("noRoleMethod"), null); + Assert.assertNull(accessAttributes); + } + + @Test + public void classRoleIsAppliedToNoRoleMethod() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(userAllowed.getClass().getMethod("noRoleMethod"), null); + Assert.assertNull(accessAttributes); + } + + @Test + public void methodRoleOverridesClassRole() throws Exception { + ConfigAttributeDefinition accessAttributes = mds.findAttributes(userAllowed.getClass().getMethod("adminMethod"), null); + assertEquals(1, accessAttributes.getConfigAttributes().size()); + assertEquals("ADMIN", accessAttributes.getConfigAttributes().iterator().next().toString()); + } + +//~ Inner Classes ====================================================================================================== + + public static class A { + + public void noRoleMethod() {} + + @RolesAllowed("ADMIN") + public void adminMethod() {} + + @PermitAll + public void permitAllMethod() {} + } + + @RolesAllowed("USER") + public static class UserAllowedClass { + public void noRoleMethod() {} + + @RolesAllowed("ADMIN") + public void adminMethod() {} + } + + @DenyAll + public static class DenyAllClass { + + public void noRoleMethod() {} + + @RolesAllowed("ADMIN") + public void adminMethod() {} + } + + + +} diff --git a/core-tiger/src/test/java/org/springframework/security/annotation/Jsr250SecurityAnnotationAttributesTests.java b/core-tiger/src/test/java/org/springframework/security/annotation/Jsr250SecurityAnnotationAttributesTests.java deleted file mode 100644 index 5db75e3d58..0000000000 --- a/core-tiger/src/test/java/org/springframework/security/annotation/Jsr250SecurityAnnotationAttributesTests.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.springframework.security.annotation; - -import org.springframework.security.SecurityConfig; - -import org.junit.Test; -import static org.junit.Assert.*; - -import java.util.List; -import java.util.ArrayList; - -import javax.annotation.security.RolesAllowed; -import javax.annotation.security.PermitAll; -import javax.annotation.security.DenyAll; - -/** - * @author Luke Taylor - * @version $Id$ - */ -public class Jsr250SecurityAnnotationAttributesTests { - Jsr250SecurityAnnotationAttributes attributes = new Jsr250SecurityAnnotationAttributes(); - A a = new A(); - UserAllowedClass userAllowed = new UserAllowedClass(); - DenyAllClass denyAll = new DenyAllClass(); - - @Test - public void methodWithRolesAllowedHasCorrectAttribute() throws Exception { -// Method[] methods = a.getClass().getMethods(); - - List accessAttributes = - new ArrayList(attributes.getAttributes(a.getClass().getMethod("adminMethod"))); - assertEquals(1, accessAttributes.size()); - assertEquals("ADMIN", accessAttributes.get(0).getAttribute()); - } - - @Test - public void permitAllMethodHasPermitAllAttribute() throws Exception { - List accessAttributes = - new ArrayList(attributes.getAttributes(a.getClass().getMethod("permitAllMethod"))); - assertEquals(1, accessAttributes.size()); - assertEquals("javax.annotation.security.PermitAll", accessAttributes.get(0).getAttribute()); - } - - @Test - public void noRoleMethodHasDenyAllAttributeWithDenyAllClass() throws Exception { - List accessAttributes = - new ArrayList(attributes.getAttributes(denyAll.getClass().getMethod("noRoleMethod"))); - assertEquals(1, accessAttributes.size()); - assertEquals("javax.annotation.security.DenyAll", accessAttributes.get(0).getAttribute()); - } - - @Test - public void adminMethodHasAdminAttributeWithDenyAllClass() throws Exception { - List accessAttributes = - new ArrayList(attributes.getAttributes(denyAll.getClass().getMethod("adminMethod"))); - assertEquals(1, accessAttributes.size()); - assertEquals("ADMIN", accessAttributes.get(0).getAttribute()); - } - - @Test - public void noRoleMethodHasNoAttributes() throws Exception { - List accessAttributes = - new ArrayList(attributes.getAttributes(a.getClass().getMethod("noRoleMethod"))); - assertEquals(0, accessAttributes.size()); - } - - @Test - public void classRoleIsAppliedToNoRoleMethod() throws Exception { - List accessAttributes = - new ArrayList(attributes.getAttributes(userAllowed.getClass().getMethod("noRoleMethod"))); - assertEquals(1, accessAttributes.size()); - assertEquals("USER", accessAttributes.get(0).getAttribute()); - } - - @Test - public void methodRoleOverridesClassRole() throws Exception { - List accessAttributes = - new ArrayList(attributes.getAttributes(userAllowed.getClass().getMethod("adminMethod"))); - assertEquals(1, accessAttributes.size()); - assertEquals("ADMIN", accessAttributes.get(0).getAttribute()); - } - -//~ Inner Classes ====================================================================================================== - - public static class A { - - public void noRoleMethod() {} - - @RolesAllowed("ADMIN") - public void adminMethod() {} - - @PermitAll - public void permitAllMethod() {} - } - - @RolesAllowed("USER") - public static class UserAllowedClass { - public void noRoleMethod() {} - - @RolesAllowed("ADMIN") - public void adminMethod() {} - } - - @DenyAll - public static class DenyAllClass { - - public void noRoleMethod() {} - - @RolesAllowed("ADMIN") - public void adminMethod() {} - } - - - -} diff --git a/core-tiger/src/test/java/org/springframework/security/annotation/MethodDefinitionSourceEditorTigerTests.java b/core-tiger/src/test/java/org/springframework/security/annotation/MethodDefinitionSourceEditorTigerTests.java index 21670f06ca..55d1ffe47e 100644 --- a/core-tiger/src/test/java/org/springframework/security/annotation/MethodDefinitionSourceEditorTigerTests.java +++ b/core-tiger/src/test/java/org/springframework/security/annotation/MethodDefinitionSourceEditorTigerTests.java @@ -20,20 +20,17 @@ import java.lang.reflect.Method; import junit.framework.TestCase; +import org.aopalliance.intercept.MethodInvocation; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.annotation.test.Entity; -import org.springframework.security.annotation.test.OrganisationService; -import org.springframework.security.annotation.test.PersonService; import org.springframework.security.annotation.test.PersonServiceImpl; import org.springframework.security.annotation.test.Service; -import org.springframework.security.annotation.test.ServiceImpl; -import org.springframework.security.intercept.method.MethodDefinitionMap; +import org.springframework.security.intercept.method.MapBasedMethodDefinitionSource; import org.springframework.security.intercept.method.MethodDefinitionSourceEditor; -import org.aopalliance.intercept.MethodInvocation; /** - * Extra tests to demonstrate generics behaviour with MethodDefinitionMap. + * Extra tests to demonstrate generics behaviour with MapBasedMethodDefinitionSource. * * @author Ben Alex * @version $Id$ @@ -41,58 +38,53 @@ import org.aopalliance.intercept.MethodInvocation; public class MethodDefinitionSourceEditorTigerTests extends TestCase { //~ Methods ======================================================================================================== - public void testConcreteClassInvocationsAlsoReturnDefinitionsAgainstInterface() throws Exception { + public void testConcreteClassInvocations() throws Exception { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText( - "org.springframework.security.annotation.test.Service.makeLower*=ROLE_FROM_INTERFACE\r\norg.springframework.security.annotation.test.Service.makeUpper*=ROLE_FROM_INTERFACE\r\norg.springframework.security.annotation.test.ServiceImpl.makeUpper*=ROLE_FROM_IMPLEMENTATION"); + "org.springframework.security.annotation.test.Service.makeLower*=ROLE_FROM_INTERFACE\r\n" + + "org.springframework.security.annotation.test.Service.makeUpper*=ROLE_FROM_INTERFACE\r\n" + + "org.springframework.security.annotation.test.ServiceImpl.makeUpper*=ROLE_FROM_IMPLEMENTATION"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); assertEquals(3, map.getMethodMapSize()); ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(Service.class, - "makeLowerCase", new Class[]{Entity.class})); + "makeLowerCase", new Class[]{Entity.class}, new PersonServiceImpl())); ConfigAttributeDefinition expectedMakeLower = new ConfigAttributeDefinition("ROLE_FROM_INTERFACE"); assertEquals(expectedMakeLower, returnedMakeLower); - ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(ServiceImpl.class, - "makeUpperCase", new Class[]{Entity.class})); - ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_IMPLEMENTATION", "ROLE_FROM_INTERFACE"}); + ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(Service.class, + "makeUpperCase", new Class[]{Entity.class}, new PersonServiceImpl())); + ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_IMPLEMENTATION"}); assertEquals(expectedMakeUpper, returnedMakeUpper); } - public void testGenericsSuperclassDeclarationsAreIncludedWhenSubclassesOverride() throws Exception { + public void testBridgeMethodResolution() throws Exception { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText( - "org.springframework.security.annotation.test.Service.makeLower*=ROLE_FROM_INTERFACE\r\norg.springframework.security.annotation.test.Service.makeUpper*=ROLE_FROM_INTERFACE\r\norg.springframework.security.annotation.test.ServiceImpl.makeUpper*=ROLE_FROM_IMPLEMENTATION"); + "org.springframework.security.annotation.test.PersonService.makeUpper*=ROLE_FROM_INTERFACE\r\n" + + "org.springframework.security.annotation.test.ServiceImpl.makeUpper*=ROLE_FROM_ABSTRACT\r\n" + + "org.springframework.security.annotation.test.PersonServiceImpl.makeUpper*=ROLE_FROM_PSI"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); assertEquals(3, map.getMethodMapSize()); - ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(PersonService.class, - "makeLowerCase", new Class[]{Entity.class})); - ConfigAttributeDefinition expectedMakeLower = new ConfigAttributeDefinition("ROLE_FROM_INTERFACE"); - assertEquals(expectedMakeLower, returnedMakeLower); - - ConfigAttributeDefinition returnedMakeLower2 = map.getAttributes(new MockMethodInvocation( - OrganisationService.class, "makeLowerCase", new Class[]{Entity.class})); - ConfigAttributeDefinition expectedMakeLower2 = new ConfigAttributeDefinition("ROLE_FROM_INTERFACE"); - assertEquals(expectedMakeLower2, returnedMakeLower2); - - ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation( - PersonServiceImpl.class, "makeUpperCase", new Class[]{Entity.class})); - ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_IMPLEMENTATION", "ROLE_FROM_INTERFACE"}); + ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(Service.class, + "makeUpperCase", new Class[]{Entity.class}, new PersonServiceImpl())); + ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_PSI"}); assertEquals(expectedMakeUpper, returnedMakeUpper); } //~ Inner Classes ================================================================================================== private class MockMethodInvocation implements MethodInvocation { - Method method; + private Method method; + private Object targetObject; - public MockMethodInvocation(Class clazz, String methodName, Class[] parameterTypes) + public MockMethodInvocation(Class clazz, String methodName, Class[] parameterTypes, Object targetObject) throws NoSuchMethodException { - System.out.println(clazz + " " + methodName + " " + parameterTypes[0]); - method = clazz.getMethod(methodName, parameterTypes); + this.method = clazz.getMethod(methodName, parameterTypes); + this.targetObject = targetObject; } public Object[] getArguments() { @@ -108,11 +100,12 @@ public class MethodDefinitionSourceEditorTigerTests extends TestCase { } public Object getThis() { - return null; + return targetObject; } public Object proceed() throws Throwable { return null; } } + } diff --git a/core-tiger/src/test/java/org/springframework/security/annotation/SecurityAnnotationAttributesTests.java b/core-tiger/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java similarity index 52% rename from core-tiger/src/test/java/org/springframework/security/annotation/SecurityAnnotationAttributesTests.java rename to core-tiger/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java index e29ac34301..1bb761ea6f 100644 --- a/core-tiger/src/test/java/org/springframework/security/annotation/SecurityAnnotationAttributesTests.java +++ b/core-tiger/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java @@ -14,41 +14,33 @@ */ package org.springframework.security.annotation; -import junit.framework.TestCase; +import java.lang.reflect.Method; -import org.springframework.security.SecurityConfig; +import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - -import org.springframework.metadata.Attributes; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import java.util.Collection; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.SecurityConfig; +import org.springframework.util.StringUtils; /** - * Tests for {@link org.springframework.security.annotation.SecurityAnnotationAttributes} + * Tests for {@link org.springframework.security.annotation.SecuredMethodDefinitionSource} * * @author Mark St.Godard * @author Joe Scalise + * @author Ben Alex * @version $Id$ */ -public class SecurityAnnotationAttributesTests extends TestCase { +public class SecuredMethodDefinitionSourceTests extends TestCase { //~ Instance fields ================================================================================================ - private Attributes attributes; - private Log logger = LogFactory.getLog(SecurityAnnotationAttributesTests.class); + private SecuredMethodDefinitionSource mds = new SecuredMethodDefinitionSource();; + private Log logger = LogFactory.getLog(SecuredMethodDefinitionSourceTests.class); //~ Methods ======================================================================================================== - protected void setUp() throws Exception { - // create the Annotations impl - this.attributes = new SecurityAnnotationAttributes(); - } - public void testGenericsSuperclassDeclarationsAreIncludedWhenSubclassesOverride() { Method method = null; @@ -58,20 +50,19 @@ public class SecurityAnnotationAttributesTests extends TestCase { fail("Should be a superMethod called 'someUserMethod3' on class!"); } - Collection attrs = this.attributes.getAttributes(method); - - if (logger.isDebugEnabled()) { - logger.debug("attrs: "); - logger.debug(attrs); - } + ConfigAttributeDefinition attrs = this.mds.findAttributes(method, DepartmentServiceImpl.class); assertNotNull(attrs); + if (logger.isDebugEnabled()) { + logger.debug("attrs: " + StringUtils.collectionToCommaDelimitedString(attrs.getConfigAttributes())); + } + // expect 1 attribute - assertTrue("Did not find 1 attribute", attrs.size() == 1); + assertTrue("Did not find 1 attribute", attrs.getConfigAttributes().size() == 1); // should have 1 SecurityConfig - for (Object obj : attrs) { + for (Object obj : attrs.getConfigAttributes()) { assertTrue(obj instanceof SecurityConfig); SecurityConfig sc = (SecurityConfig) obj; @@ -86,67 +77,39 @@ public class SecurityAnnotationAttributesTests extends TestCase { fail("Should be a superMethod called 'someUserMethod3' on class!"); } - System.out.println(superMethod); - - Collection superAttrs = this.attributes.getAttributes(superMethod); - - if (logger.isDebugEnabled()) { - logger.debug("superAttrs: "); - logger.debug(superAttrs); - } + ConfigAttributeDefinition superAttrs = this.mds.findAttributes(superMethod, DepartmentServiceImpl.class); assertNotNull(superAttrs); - // TODO: Enable this part of the test once we can build against Spring 2.0+ and above only (SEC-274) - /* - // expect 1 attribute - assertTrue("Did not find 1 attribute", superAttrs.size() == 1); - // should have 1 SecurityConfig - for (Object obj : superAttrs) { - assertTrue(obj instanceof SecurityConfig); - SecurityConfig sc = (SecurityConfig) obj; - assertEquals("Found an incorrect role", "ROLE_ADMIN", sc.getAttribute()); - } - */ + if (logger.isDebugEnabled()) { + logger.debug("superAttrs: " + StringUtils.collectionToCommaDelimitedString(superAttrs.getConfigAttributes())); + } + + // This part of the test relates to SEC-274 + // expect 1 attribute + assertTrue("Did not find 1 attribute", superAttrs.getConfigAttributes().size() == 1); + // should have 1 SecurityConfig + for (Object obj : superAttrs.getConfigAttributes()) { + assertTrue(obj instanceof SecurityConfig); + SecurityConfig sc = (SecurityConfig) obj; + assertEquals("Found an incorrect role", "ROLE_ADMIN", sc.getAttribute()); + } } public void testGetAttributesClass() { - Collection attrs = this.attributes.getAttributes(BusinessService.class); + ConfigAttributeDefinition attrs = this.mds.findAttributes(BusinessService.class); assertNotNull(attrs); // expect 1 annotation - assertTrue(attrs.size() == 1); + assertTrue(attrs.getConfigAttributes().size() == 1); // should have 1 SecurityConfig - SecurityConfig sc = (SecurityConfig) attrs.iterator().next(); + SecurityConfig sc = (SecurityConfig) attrs.getConfigAttributes().iterator().next(); assertTrue(sc.getAttribute().equals("ROLE_USER")); } - public void testGetAttributesClassClass() { - try { - this.attributes.getAttributes(BusinessService.class, null); - fail("Unsupported method should have thrown an exception!"); - } catch (UnsupportedOperationException expected) {} - } - - public void testGetAttributesField() { - try { - Field field = null; - this.attributes.getAttributes(field); - fail("Unsupported method should have thrown an exception!"); - } catch (UnsupportedOperationException expected) {} - } - - public void testGetAttributesFieldClass() { - try { - Field field = null; - this.attributes.getAttributes(field, null); - fail("Unsupported method should have thrown an exception!"); - } catch (UnsupportedOperationException expected) {} - } - public void testGetAttributesMethod() { Method method = null; @@ -156,18 +119,18 @@ public class SecurityAnnotationAttributesTests extends TestCase { fail("Should be a method called 'someUserAndAdminMethod' on class!"); } - Collection attrs = this.attributes.getAttributes(method); + ConfigAttributeDefinition attrs = this.mds.findAttributes(method, BusinessService.class); assertNotNull(attrs); // expect 2 attributes - assertTrue(attrs.size() == 2); + assertTrue(attrs.getConfigAttributes().size() == 2); boolean user = false; boolean admin = false; // should have 2 SecurityConfigs - for (Object obj : attrs) { + for (Object obj : attrs.getConfigAttributes()) { assertTrue(obj instanceof SecurityConfig); SecurityConfig sc = (SecurityConfig) obj; @@ -182,19 +145,5 @@ public class SecurityAnnotationAttributesTests extends TestCase { // expect to have ROLE_USER and ROLE_ADMIN assertTrue(user && admin); } - - public void testGetAttributesMethodClass() { - Method method = null; - - try { - method = BusinessService.class.getMethod("someUserAndAdminMethod", new Class[] {}); - } catch (NoSuchMethodException unexpected) { - fail("Should be a method called 'someUserAndAdminMethod' on class!"); - } - - try { - this.attributes.getAttributes(method, null); - fail("Unsupported method should have thrown an exception!"); - } catch (UnsupportedOperationException expected) {} - } + } diff --git a/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java b/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java index e3b4673301..087b6ebbf3 100644 --- a/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java +++ b/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java @@ -15,15 +15,14 @@ package org.springframework.security; -import org.springframework.util.Assert; - import java.io.Serializable; - -import java.util.Iterator; -import java.util.List; -import java.util.Collections; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.springframework.util.Assert; /** @@ -99,6 +98,33 @@ public class ConfigAttributeDefinition implements Serializable { this.configAttributes = Collections.unmodifiableList(new ArrayList(configAttributes)); } + + /** + * Creates a ConfigAttributeDefinition by including only those attributes which implement ConfigAttribute. + * + * @param unfilteredInput a collection of various elements, zero or more which implement ConfigAttribute (can also be null) + * @return a ConfigAttributeDefinition if at least one ConfigAttribute was present, or null if none implemented it + */ + public static ConfigAttributeDefinition createFiltered(Collection unfilteredInput) { + if (unfilteredInput == null) { + return null; + } + + List configAttributes = new ArrayList(); + Iterator i = unfilteredInput.iterator(); + while (i.hasNext()) { + Object element = i.next(); + if (element instanceof ConfigAttribute) { + configAttributes.add(element); + } + } + + if (configAttributes.size() == 0) { + return null; + } + + return new ConfigAttributeDefinition(configAttributes); + } //~ Methods ======================================================================================================== diff --git a/core/src/main/java/org/springframework/security/config/AnnotationDrivenBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/AnnotationDrivenBeanDefinitionParser.java index d3bfa66d47..43631764eb 100644 --- a/core/src/main/java/org/springframework/security/config/AnnotationDrivenBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/AnnotationDrivenBeanDefinitionParser.java @@ -23,8 +23,8 @@ import org.w3c.dom.Element; * @version $Id$ */ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { - public static final String SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.SecurityAnnotationAttributes"; - public static final String JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.Jsr250SecurityAnnotationAttributes"; + public static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource"; + public static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource"; public static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter"; private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; private static final String ATT_USE_JSR250 = "jsr250"; @@ -32,10 +32,12 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element element, ParserContext parserContext) { boolean useJsr250 = "true".equals(element.getAttribute(ATT_USE_JSR250)); String className = useJsr250 ? - JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS : SECURITY_ANNOTATION_ATTRIBUTES_CLASS; + JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS : SECURED_METHOD_DEFINITION_SOURCE_CLASS; + String beanId = useJsr250 ? BeanIds.JSR_250_METHOD_DEFINITION_SOURCE : BeanIds.SECURED_METHOD_DEFINITION_SOURCE; + // Reflectively obtain the Annotation-based ObjectDefinitionSource. - // Reflection is used to avoid a compile-time dependency on SECURITY_ANNOTATION_ATTRIBUTES_CLASS, as this parser is in the Java 4 project whereas the dependency is in the Tiger project. + // Reflection is used to avoid a compile-time dependency on SECURED_METHOD_DEFINITION_SOURCE_CLASS, as this parser is in the Java 4 project whereas the dependency is in the Tiger project. Assert.isTrue(ClassUtils.isPresent(className), "Could not locate class '" + className + "' - please ensure the spring-security-tiger-xxx.jar is in your classpath and you are running Java 5 or above."); Class clazz = null; @@ -47,15 +49,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { RootBeanDefinition securityAnnotations = new RootBeanDefinition(clazz); securityAnnotations.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(BeanIds.SECURITY_ANNOTATION_ATTRIBUTES, securityAnnotations); - - RootBeanDefinition methodDefinitionAttributes = new RootBeanDefinition(MethodDefinitionAttributes.class); - methodDefinitionAttributes.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - methodDefinitionAttributes.getPropertyValues().addPropertyValue("attributes", new RuntimeBeanReference(BeanIds.SECURITY_ANNOTATION_ATTRIBUTES)); - parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_DEFINITION_ATTRIBUTES, methodDefinitionAttributes); - - RootBeanDefinition interceptor = new RootBeanDefinition(MethodSecurityInterceptor.class); - interceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + parserContext.getRegistry().registerBeanDefinition(beanId, securityAnnotations); String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); @@ -69,15 +63,22 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { accessManagerId = BeanIds.ACCESS_MANAGER; } + // MethodSecurityInterceptor + + RootBeanDefinition interceptor = new RootBeanDefinition(MethodSecurityInterceptor.class); + interceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + interceptor.getPropertyValues().addPropertyValue("accessDecisionManager", new RuntimeBeanReference(accessManagerId)); interceptor.getPropertyValues().addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER)); - interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(BeanIds.METHOD_DEFINITION_ATTRIBUTES)); + interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(beanId)); parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR, interceptor); + // MethodDefinitionSourceAdvisor + RootBeanDefinition advisor = new RootBeanDefinition(MethodDefinitionSourceAdvisor.class); advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor); diff --git a/core/src/main/java/org/springframework/security/config/BeanIds.java b/core/src/main/java/org/springframework/security/config/BeanIds.java index e6613d5f6f..a4c45860e4 100644 --- a/core/src/main/java/org/springframework/security/config/BeanIds.java +++ b/core/src/main/java/org/springframework/security/config/BeanIds.java @@ -48,8 +48,8 @@ public abstract class BeanIds { public static final String SECURITY_CONTEXT_HOLDER_AWARE_REQUEST_FILTER = "_securityContextHolderAwareRequestFilter"; public static final String METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor"; public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor"; - public static final String SECURITY_ANNOTATION_ATTRIBUTES = "_securityAnnotationAttributes"; - public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes"; + public static final String SECURED_METHOD_DEFINITION_SOURCE = "_securedMethodDefinitionSource"; + public static final String JSR_250_METHOD_DEFINITION_SOURCE = "_jsr250MethodDefinitionSource"; public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer"; public static final String CONTEXT_SOURCE = "_securityContextSource"; public static final String PORT_MAPPER = "_portMapper"; diff --git a/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java b/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java index 9d23230358..97e5907cfe 100644 --- a/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java +++ b/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java @@ -1,28 +1,24 @@ package org.springframework.security.config; +import java.util.Iterator; +import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.config.AbstractInterceptorDrivenBeanDefinitionDecorator; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionDecorator; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.security.ConfigAttributeEditor; -import org.springframework.security.intercept.method.MethodDefinitionMap; import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor; -import org.springframework.util.xml.DomUtils; import org.springframework.util.StringUtils; - +import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; import org.w3c.dom.Node; -import java.util.Iterator; -import java.util.List; - /** * @author Luke Taylor * @author Ben Alex @@ -35,17 +31,16 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { ConfigUtils.registerProviderManagerIfNecessary(parserContext); ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext); - + return delegate.decorate(node, definition, parserContext); } } /** - * This is the real class which does the work. We need acccess to the ParserContext in order to do bean + * This is the real class which does the work. We need access to the ParserContext in order to do bean * registration. */ class InternalInterceptMethodsBeanDefinitionDecorator extends AbstractInterceptorDrivenBeanDefinitionDecorator { - static final String ATT_CLASS = "class"; static final String ATT_METHOD = "method"; static final String ATT_ACCESS = "access"; private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; @@ -68,24 +63,35 @@ class InternalInterceptMethodsBeanDefinitionDecorator extends AbstractIntercepto interceptor.addPropertyValue("accessDecisionManager", new RuntimeBeanReference(accessManagerId)); interceptor.addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER)); + // Lookup parent bean information + Element parent = (Element) node.getParentNode(); + String parentBeanClass = parent.getAttribute("class"); + String parentBeanId = parent.getAttribute("id"); + parent = null; + // Parse the included methods List methods = DomUtils.getChildElementsByTagName(interceptMethodsElt, Elements.PROTECT); - MethodDefinitionMap methodMap = new MethodDefinitionMap(); - ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor(); + StringBuffer sb = new StringBuffer(); + for (Iterator i = methods.iterator(); i.hasNext();) { Element protectmethodElt = (Element) i.next(); String accessConfig = protectmethodElt.getAttribute(ATT_ACCESS); - attributeEditor.setAsText(accessConfig); -// TODO: We want to use just the method names, but MethodDefinitionMap won't work that way. -// methodMap.addSecureMethod(targetClass, protectmethodElt.getAttribute("method"), -// (ConfigAttributeDefinition) attributeEditor.getValue()); - methodMap.addSecureMethod(protectmethodElt.getAttribute(ATT_METHOD), - (ConfigAttributeDefinition) attributeEditor.getValue()); + // Support inference of class names + String methodName = protectmethodElt.getAttribute(ATT_METHOD); + + if (methodName.lastIndexOf(".") == -1) { + if (parentBeanClass != null && !"".equals(parentBeanClass)) { + methodName = parentBeanClass + "." + methodName; + } + } + + // Rely on the default property editor for MethodSecurityInterceptor.setObjectDefinitionSource to setup the MethodDefinitionSource + sb.append(methodName + "=" + accessConfig).append("\r\n"); } - - interceptor.addPropertyValue("objectDefinitionSource", methodMap); + + interceptor.addPropertyValue("objectDefinitionSource", sb.toString()); return interceptor.getBeanDefinition(); } diff --git a/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java b/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java new file mode 100644 index 0000000000..4997c2c451 --- /dev/null +++ b/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java @@ -0,0 +1,200 @@ +package org.springframework.security.intercept.method; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.CodeSignature; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Abstract implementation of {@link MethodDefinitionSource} that supports both Spring AOP and AspectJ and + * caches configuration attribute resolution from: 1. specific target method; 2. target class; 3. declaring method; + * 4. declaring class/interface. + * + *

+ * This class mimics the behaviour of Spring's AbstractFallbackTransactionAttributeSource class. + *

+ * + *

+ * Note that this class cannot extract security metadata where that metadata is expressed by way of + * a target method/class (ie #1 and #2 above) AND the target method/class is encapsulated in another + * proxy object. Spring Security does not walk a proxy chain to locate the concrete/final target object. + * Consider making Spring Security your final advisor (so it advises the final target, as opposed to + * another proxy), move the metadata to declared methods or interfaces the proxy implements, or provide + * your own replacement MethodDefinitionSource. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public abstract class AbstractFallbackMethodDefinitionSource implements MethodDefinitionSource { + + private static final Log logger = LogFactory.getLog(AbstractFallbackMethodDefinitionSource.class); + private final static Object NULL_CONFIG_ATTRIBUTE = new Object(); + private final Map attributeCache = new HashMap(); + + public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException { + Assert.notNull(object, "Object cannot be null"); + + if (object instanceof MethodInvocation) { + MethodInvocation mi = (MethodInvocation) object; + return getAttributes(mi.getMethod(), mi.getThis().getClass()); + } + + if (object instanceof JoinPoint) { + JoinPoint jp = (JoinPoint) object; + Class targetClass = jp.getTarget().getClass(); + String targetMethodName = jp.getStaticPart().getSignature().getName(); + Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes(); + Class declaringType = ((CodeSignature) jp.getStaticPart().getSignature()).getDeclaringType(); + + Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types); + Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'"); + + return getAttributes(method, targetClass); + } + + throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint"); + } + + public final boolean supports(Class clazz) { + return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz)); + } + + public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { + // First, see if we have a cached value. + Object cacheKey = new DefaultCacheKey(method, targetClass); + synchronized (this.attributeCache) { + Object cached = this.attributeCache.get(cacheKey); + if (cached != null) { + // Value will either be canonical value indicating there is no config attribute, + // or an actual config attribute. + if (cached == NULL_CONFIG_ATTRIBUTE) { + return null; + } + else { + return (ConfigAttributeDefinition) cached; + } + } + else { + // We need to work it out. + ConfigAttributeDefinition cfgAtt = computeAttributes(method, targetClass); + // Put it in the cache. + if (cfgAtt == null) { + this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Adding security method [" + cacheKey + "] with attribute [" + cfgAtt + "]"); + } + this.attributeCache.put(cacheKey, cfgAtt); + } + return cfgAtt; + } + } + } + + /** + * + * @param method the method for the current invocation (never null) + * @param targetClass the target class for this invocation (may be null) + * @return + */ + private ConfigAttributeDefinition computeAttributes(Method method, Class targetClass) { + // The method may be on an interface, but we need attributes from the target class. + // If the target class is null, the method will be unchanged. + Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); + // First try is the method in the target class. + ConfigAttributeDefinition attr = findAttributes(specificMethod, targetClass); + if (attr != null) { + return attr; + } + + // Second try is the config attribute on the target class. + attr = findAttributes(specificMethod.getDeclaringClass()); + if (attr != null) { + return attr; + } + + if (specificMethod != method) { + // Fallback is to look at the original method. + attr = findAttributes(method, method.getDeclaringClass()); + if (attr != null) { + return attr; + } + // Last fallback is the class of the original method. + return findAttributes(method.getDeclaringClass()); + } + return null; + + } + + /** + * Obtains the security metadata applicable to the specified method invocation. + * + *

+ * Note that the {@link Method#getDeclaringClass()} may not equal the targetClass. + * Both parameters are provided to assist subclasses which may wish to provide advanced + * capabilities related to method metadata being "registered" against a method even if the + * target class does not declare the method (ie the subclass may only inherit the method). + * + * @param method the method for the current invocation (never null) + * @param targetClass the target class for the invocation (may be null) + * @return the security metadata (or null if no metadata applies) + */ + protected abstract ConfigAttributeDefinition findAttributes(Method method, Class targetClass); + + /** + * Obtains the security metadata registered against the specified class. + * + *

+ * Subclasses should only return metadata expressed at a class level. Subclasses should NOT + * aggregate metadata for each method registered against a class, as the abstract superclass + * will separate invoke {@link #findAttributes(Method, Class)} for individual methods as + * appropriate. + * + * @param clazz the target class for the invocation (never null) + * @return the security metadata (or null if no metadata applies) + */ + protected abstract ConfigAttributeDefinition findAttributes(Class clazz); + + private static class DefaultCacheKey { + + private final Method method; + private final Class targetClass; + + public DefaultCacheKey(Method method, Class targetClass) { + this.method = method; + this.targetClass = targetClass; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof DefaultCacheKey)) { + return false; + } + DefaultCacheKey otherKey = (DefaultCacheKey) other; + return (this.method.equals(otherKey.method) && + ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); + } + + public int hashCode() { + return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0); + } + + public String toString() { + return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]"; + } +} + +} diff --git a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionMap.java b/core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java similarity index 54% rename from core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionMap.java rename to core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java index 2ae835843d..d9bda9ff7f 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionMap.java +++ b/core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java @@ -15,63 +15,59 @@ package org.springframework.security.intercept.method; -import org.springframework.security.ConfigAttributeDefinition; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import java.lang.reflect.Method; - import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Collection; -import java.util.Collections; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** - * Stores a {@link ConfigAttributeDefinition} for each method signature defined in a bean context.

For - * consistency with {@link MethodDefinitionAttributes} as well as support for - * MethodDefinitionSourceAdvisor, this implementation will return a - * ConfigAttributeDefinition containing all configuration attributes defined against: - *

- *

- *

In general you should therefore define the interface methods of your secure objects, not the - * implementations. For example, define com.company.Foo.findAll=ROLE_TEST but not - * com.company.FooImpl.findAll=ROLE_TEST.

- * + * Stores a {@link ConfigAttributeDefinition} for a method or class signature. + * + *

+ * This class is the preferred implementation of {@link MethodDefinitionSource} for XML-based + * definition of method security metadata. To assist in XML-based definition, wildcard support + * is provided. + *

+ * * @author Ben Alex - * @version $Id$ + * @version $Id: MethodDefinitionMap.java 2558 2008-01-30 15:43:40Z luke_t $ */ -public class MethodDefinitionMap extends AbstractMethodDefinitionSource { +public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource implements BeanClassLoaderAware { //~ Static fields/initializers ===================================================================================== - private static final Log logger = LogFactory.getLog(MethodDefinitionMap.class); + private static final Log logger = LogFactory.getLog(MapBasedMethodDefinitionSource.class); //~ Instance fields ================================================================================================ + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); - /** Map from Method to ApplicationDefinition */ + /** Map from RegisteredMethod to ConfigAttributeDefinition */ protected Map methodMap = new HashMap(); - /** Map from Method to name pattern used for registration */ + /** Map from RegisteredMethod to name pattern used for registration */ private Map nameMap = new HashMap(); //~ Methods ======================================================================================================== - public MethodDefinitionMap() { + public MapBasedMethodDefinitionSource() { } /** - * Creates the MethodDefinitionMap from a + * Creates the MapBasedMethodDefinitionSource from a * @param methodMap map of method names to ConfigAttributeDefinitions. */ - public MethodDefinitionMap(Map methodMap) { + public MapBasedMethodDefinitionSource(Map methodMap) { Iterator iterator = methodMap.entrySet().iterator(); while (iterator.hasNext()) { @@ -80,15 +76,44 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { } } + /** + * Implementation does not support class-level attributes. + */ + protected ConfigAttributeDefinition findAttributes(Class clazz) { + return null; + } + + /** + * Will walk the method inheritance tree to find the most specific declaration applicable. + */ + protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { + return findAttributesSpecifiedAgainst(method, targetClass); + } + + private ConfigAttributeDefinition findAttributesSpecifiedAgainst(Method method, Class clazz) { + RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz); + if (methodMap.containsKey(registeredMethod)) { + return (ConfigAttributeDefinition) methodMap.get(registeredMethod); + } + // Search superclass + if (clazz.getSuperclass() != null) { + return findAttributesSpecifiedAgainst(method, clazz.getSuperclass()); + } + return null; + } + /** - * Add configuration attributes for a secure method. Method names can end or start with * - * for matching multiple methods. + * Add configuration attributes for a secure method. * * @param method the method to be secured * @param attr required authorities associated with the method */ - public void addSecureMethod(Method method, ConfigAttributeDefinition attr) { - logger.info("Adding secure method [" + method + "] with attributes [" + attr + "]"); + private void addSecureMethod(RegisteredMethod method, ConfigAttributeDefinition attr) { + Assert.notNull(method, "RegisteredMethod required"); + Assert.notNull(attr, "Configuration attribute required"); + if (logger.isInfoEnabled()) { + logger.info("Adding secure method [" + method + "] with attributes [" + attr + "]"); + } this.methodMap.put(method, attr); } @@ -96,47 +121,41 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { * Add configuration attributes for a secure method. Method names can end or start with * * for matching multiple methods. * - * @param name class and method name, separated by a dot + * @param name type and method name, separated by a dot * @param attr required authorities associated with the method - * - * @throws IllegalArgumentException DOCUMENT ME! */ public void addSecureMethod(String name, ConfigAttributeDefinition attr) { - int lastDotIndex = name.lastIndexOf("."); + int lastDotIndex = name.lastIndexOf("."); if (lastDotIndex == -1) { throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName"); } - String className = name.substring(0, lastDotIndex); String methodName = name.substring(lastDotIndex + 1); - - try { - Class clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); - addSecureMethod(clazz, methodName, attr); - } catch (ClassNotFoundException ex) { - throw new IllegalArgumentException("Class '" + className + "' not found"); - } + Assert.hasText(methodName, "Method not found for '" + name + "'"); + + String typeName = name.substring(0, lastDotIndex); + Class type = ClassUtils.resolveClassName(typeName, this.beanClassLoader); + + addSecureMethod(type, methodName, attr); } /** - * Add configuration attributes for a secure method. Method names can end or start with * + * Add configuration attributes for a secure method. Mapped method names can end or start with * * for matching multiple methods. - * - * @param clazz target interface or class - * @param mappedName mapped method name + * + * @param javaType target interface or class the security configuration attribute applies to + * @param mappedName mapped method name, which the javaType has declared or inherited * @param attr required authorities associated with the method - * - * @throws IllegalArgumentException DOCUMENT ME! */ - public void addSecureMethod(Class clazz, String mappedName, ConfigAttributeDefinition attr) { - String name = clazz.getName() + '.' + mappedName; + public void addSecureMethod(Class javaType, String mappedName, ConfigAttributeDefinition attr) { + String name = javaType.getName() + '.' + mappedName; if (logger.isDebugEnabled()) { - logger.debug("Adding secure method [" + name + "] with attributes [" + attr + "]"); + logger.debug("Request to add secure method [" + name + "] with attributes [" + attr + "]"); } - Method[] methods = clazz.getDeclaredMethods(); + Method[] methods = javaType.getMethods(); List matchingMethods = new ArrayList(); for (int i = 0; i < methods.length; i++) { @@ -146,13 +165,14 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { } if (matchingMethods.isEmpty()) { - throw new IllegalArgumentException("Couldn't find method '" + mappedName + "' on " + clazz); + throw new IllegalArgumentException("Couldn't find method '" + mappedName + "' on '" + javaType + "'"); } // register all matching methods for (Iterator it = matchingMethods.iterator(); it.hasNext();) { Method method = (Method) it.next(); - String regMethodName = (String) this.nameMap.get(method); + RegisteredMethod registeredMethod = new RegisteredMethod(method, javaType); + String regMethodName = (String) this.nameMap.get(registeredMethod); if ((regMethodName == null) || (!regMethodName.equals(name) && (regMethodName.length() <= name.length()))) { // no already registered method name, or more specific @@ -162,8 +182,8 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { + "] is more specific than [" + regMethodName + "]"); } - this.nameMap.put(method, name); - addSecureMethod(method, attr); + this.nameMap.put(registeredMethod, name); + addSecureMethod(registeredMethod, attr); } else { logger.debug("Keeping attributes for secure method [" + method + "]: current name [" + name + "] is not more specific than [" + regMethodName + "]"); @@ -172,9 +192,7 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { } /** - * Obtains the configuration attributes explicitly defined against this bean. This method will not return - * implicit configuration attributes that may be returned by {@link #lookupAttributes(Method)} as it does not have - * access to a method invocation at this time. + * Obtains the configuration attributes explicitly defined against this bean. * * @return the attributes explicitly defined against this bean */ @@ -182,17 +200,6 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { return Collections.unmodifiableCollection(methodMap.values()); } - /** - * Obtains the number of configuration attributes explicitly defined against this bean. This method will - * not return implicit configuration attributes that may be returned by {@link #lookupAttributes(Method)} as it - * does not have access to a method invocation at this time. - * - * @return the number of configuration attributes explicitly defined against this bean - */ - public int getMethodMapSize() { - return this.methodMap.size(); - } - /** * Return if the given method name matches the mapped name. The default implementation checks for "xxx" and * "xxx" matches. @@ -245,4 +252,55 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource { attributes.addAll(toMerge.getConfigAttributes()); } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + Assert.notNull(beanClassLoader, "Bean class loader required"); + this.beanClassLoader = beanClassLoader; + } + + /** + * @return map size (for unit tests and diagnostics) + */ + public int getMethodMapSize() { + return methodMap.size(); + } + + /** + * Stores both the Java Method as well as the Class we obtained the Method from. This is necessary because Method only + * provides us access to the declaring class. It doesn't provide a way for us to introspect which Class the Method + * was registered against. If a given Class inherits and redeclares a method (ie calls super();) the registered Class + * and delcaring Class are the same. If a given class merely inherits but does not redeclare a method, the registered + * Class will be the Class we're invoking against and the Method will provide details of the declared class. + */ + private class RegisteredMethod { + private Method method; + private Class registeredJavaType; + + public RegisteredMethod(Method method, Class registeredJavaType) { + Assert.notNull(method, "Method required"); + Assert.notNull(registeredJavaType, "Registered Java Type required"); + this.method = method; + this.registeredJavaType = registeredJavaType; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && obj instanceof RegisteredMethod) { + RegisteredMethod rhs = (RegisteredMethod) obj; + return method.equals(rhs.method) && registeredJavaType.equals(rhs.registeredJavaType); + } + return false; + } + + public int hashCode() { + return method.hashCode() * registeredJavaType.hashCode(); + } + + public String toString() { + return "RegisteredMethod[" + registeredJavaType.getName() + "; " + method + "]"; + } + } + } diff --git a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java index a01dcc6926..cddfae5298 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java +++ b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java @@ -15,122 +15,57 @@ package org.springframework.security.intercept.method; +import java.lang.reflect.Method; +import java.util.Collection; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.metadata.Attributes; import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; - -import org.springframework.metadata.Attributes; - -import java.lang.reflect.Method; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ArrayList; +import org.springframework.util.Assert; /** * Provides {@link ConfigAttributeDefinition}s for a method signature (via the lookupAttributes method) - * by delegating to a configured {@link Attributes} object. The latter may use Java 5 annotations, Commons attributes + * by delegating to a configured {@link Attributes} object. The latter may use Commons attributes * or some other approach to determine the ConfigAttributes which apply. - *

- * This class will only detect those attributes which are defined for: - *

+ * *

* Note that attributes defined against parent classes (either for their methods or interfaces) are not * detected. The attributes must be defined against an explicit method or interface on the intercepted class. *

+ * * Attributes detected that do not implement {@link ConfigAttribute} will be ignored. * * @author Cameron Braid * @author Ben Alex * @version $Id$ */ -public class MethodDefinitionAttributes extends AbstractMethodDefinitionSource { +public class MethodDefinitionAttributes extends AbstractFallbackMethodDefinitionSource implements InitializingBean { //~ Instance fields ================================================================================================ private Attributes attributes; //~ Methods ======================================================================================================== - private void add(List definition, Collection attribs) { - for (Iterator iter = attribs.iterator(); iter.hasNext();) { - Object o = iter.next(); - - if (o instanceof ConfigAttribute) { - definition.add(o); - } - } - } - - private void addClassAttributes(List definition, Class[] clazz) { - for (int i = 0; i < clazz.length; i++) { - Collection classAttributes = attributes.getAttributes(clazz[i]); - - if (classAttributes != null) { - add(definition, classAttributes); - } - } - } - - private void addInterfaceMethodAttributes(List definition, Method method) { - Class[] interfaces = method.getDeclaringClass().getInterfaces(); - - for (int i = 0; i < interfaces.length; i++) { - Class clazz = interfaces[i]; - - try { - Method m = clazz.getDeclaredMethod(method.getName(), (Class[]) method.getParameterTypes()); - addMethodAttributes(definition, m); - } catch (Exception e) { - // this won't happen since we are getting a method from an interface that - // the declaring class implements - } - } - } - - private void addMethodAttributes(List definition, Method method) { - // add the method level attributes - Collection methodAttributes = attributes.getAttributes(method); - - if (methodAttributes != null) { - add(definition, methodAttributes); - } - } + public void afterPropertiesSet() throws Exception { + Assert.notNull(attributes, "attributes required"); + } public Collection getConfigAttributeDefinitions() { return null; } + + protected ConfigAttributeDefinition findAttributes(Class clazz) { + return ConfigAttributeDefinition.createFiltered(attributes.getAttributes(clazz)); + } - protected ConfigAttributeDefinition lookupAttributes(Method method) { - Class interceptedClass = method.getDeclaringClass(); - List attributes = new ArrayList(); - - // add the class level attributes for the implementing class - addClassAttributes(attributes, new Class[] {interceptedClass}); - - // add the class level attributes for the implemented interfaces - addClassAttributes(attributes, interceptedClass.getInterfaces()); - - // add the method level attributes for the implemented method - addMethodAttributes(attributes, method); - - // add the method level attributes for the implemented intreface methods - addInterfaceMethodAttributes(attributes, method); - - if (attributes.size() == 0) { - return null; - } - - return new ConfigAttributeDefinition(attributes); - } + protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { + return ConfigAttributeDefinition.createFiltered(attributes.getAttributes(method)); + } public void setAttributes(Attributes attributes) { + Assert.notNull(attributes, "Attributes required"); this.attributes = attributes; } } diff --git a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSource.java b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSource.java index ff3e9f8289..fd97377067 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSource.java +++ b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSource.java @@ -15,14 +15,19 @@ package org.springframework.security.intercept.method; +import java.lang.reflect.Method; + +import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.intercept.ObjectDefinitionSource; /** - * Marker interface for ObjectDefinitionSource implementations + * Interface for ObjectDefinitionSource implementations * that are designed to perform lookups keyed on Methods. * * @author Ben Alex * @version $Id$ */ -public interface MethodDefinitionSource extends ObjectDefinitionSource {} +public interface MethodDefinitionSource extends ObjectDefinitionSource { + public ConfigAttributeDefinition getAttributes(Method method, Class targetClass); +} diff --git a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java index 2c52a357bd..296d99b97c 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java +++ b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java @@ -33,7 +33,7 @@ import java.util.LinkedHashMap; /** * Property editor to assist with the setup of a {@link MethodDefinitionSource}. - *

The class creates and populates a {@link MethodDefinitionMap}.

+ *

The class creates and populates a {@link MapBasedMethodDefinitionSource}.

* * @author Ben Alex * @version $Id$ @@ -47,7 +47,7 @@ public class MethodDefinitionSourceEditor extends PropertyEditorSupport { public void setAsText(String s) throws IllegalArgumentException { if ((s == null) || "".equals(s)) { - setValue(new MethodDefinitionMap()); + setValue(new MapBasedMethodDefinitionSource()); return; } @@ -69,6 +69,6 @@ public class MethodDefinitionSourceEditor extends PropertyEditorSupport { mappings.put(name, new ConfigAttributeDefinition(tokens)); } - setValue(new MethodDefinitionMap(mappings)); + setValue(new MapBasedMethodDefinitionSource(mappings)); } } diff --git a/core/src/main/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisor.java b/core/src/main/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisor.java index 6cadb60449..b99b992c7a 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisor.java +++ b/core/src/main/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisor.java @@ -21,11 +21,10 @@ import java.lang.reflect.Method; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.Pointcut; -import org.springframework.aop.framework.AopConfigException; import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.aop.support.StaticMethodMatcherPointcut; import org.springframework.security.intercept.method.MethodDefinitionSource; - +import org.springframework.util.Assert; /** * Advisor driven by a {@link MethodDefinitionSource}, used to exclude a {@link MethodSecurityInterceptor} from @@ -55,10 +54,7 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor { public MethodDefinitionSourceAdvisor(MethodSecurityInterceptor advice) { this.interceptor = advice; - if (advice.getObjectDefinitionSource() == null) { - throw new AopConfigException("Cannot construct a MethodDefinitionSourceAdvisor using a " - + "MethodSecurityInterceptor that has no ObjectDefinitionSource configured"); - } + Assert.notNull(advice.getObjectDefinitionSource(), "Cannot construct a MethodDefinitionSourceAdvisor using a MethodSecurityInterceptor that has no ObjectDefinitionSource configured"); this.attributeSource = advice.getObjectDefinitionSource(); this.pointcut = new MethodDefinitionSourcePointcut(); @@ -78,9 +74,7 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor { class MethodDefinitionSourcePointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { - MethodInvocation methodInvocation = new InternalMethodInvocation(m); - - return attributeSource.getAttributes(methodInvocation) != null; + return attributeSource.getAttributes(m, targetClass) != null; } } @@ -92,9 +86,11 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor { */ class InternalMethodInvocation implements MethodInvocation { private Method method; + private Class targetClass; - public InternalMethodInvocation(Method method) { + public InternalMethodInvocation(Method method, Class targetClass) { this.method = method; + this.targetClass = targetClass; } protected InternalMethodInvocation() { @@ -114,7 +110,7 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor { } public Object getThis() { - throw new UnsupportedOperationException(); + return this.targetClass; } public Object proceed() throws Throwable { diff --git a/core/src/main/java/org/springframework/security/util/MethodInvocationUtils.java b/core/src/main/java/org/springframework/security/util/MethodInvocationUtils.java index 77a7a24b0d..6b3165d1e9 100644 --- a/core/src/main/java/org/springframework/security/util/MethodInvocationUtils.java +++ b/core/src/main/java/org/springframework/security/util/MethodInvocationUtils.java @@ -15,15 +15,15 @@ package org.springframework.security.util; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.util.Assert; - import java.lang.reflect.Method; - import java.util.ArrayList; import java.util.List; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.util.Assert; + /** * Static utility methods for creating MethodInvocations usable within Spring Security. @@ -76,8 +76,25 @@ public final class MethodInvocationUtils { classArgs = (Class[]) list.toArray(new Class[] {}); } + + // Determine the type that declares the requested method, taking into account proxies + Class target = AopUtils.getTargetClass(object); + if (object instanceof Advised) { + Advised a = (Advised) object; + if (!a.isProxyTargetClass()) { + Class[] possibleInterfaces = a.getProxiedInterfaces(); + for (int i = 0; i < possibleInterfaces.length; i++) { + try { + possibleInterfaces[i].getMethod(methodName, classArgs); + // to get here means no exception happened + target = possibleInterfaces[i]; + break; + } catch (Exception tryTheNextOne) {} + } + } + } - return createFromClass(object.getClass(), methodName, classArgs, args); + return createFromClass(object, target, methodName, classArgs, args); } /** @@ -89,21 +106,22 @@ public final class MethodInvocationUtils { * @return a MethodInvocation, or null if there was a problem */ public static MethodInvocation createFromClass(Class clazz, String methodName) { - return createFromClass(clazz, methodName, null, null); + return createFromClass(null, clazz, methodName, null, null); } /** * Generates a MethodInvocation for specified methodName on the passed class, * using the args to locate the method. * + * @param targetObject the object being invoked * @param clazz the class of object that will be used to find the relevant Method * @param methodName the name of the method to find * @param classArgs arguments that are required to locate the relevant method signature * @param args the actual arguments that should be passed to SimpleMethodInvocation * @return a MethodInvocation, or null if there was a problem */ - public static MethodInvocation createFromClass(Class clazz, String methodName, Class[] classArgs, Object[] args) { - Assert.notNull(clazz, "Class required"); + public static MethodInvocation createFromClass(Object targetObject, Class clazz, String methodName, Class[] classArgs, Object[] args) { + Assert.notNull(clazz, "Class required"); Assert.hasText(methodName, "MethodName required"); Method method; @@ -114,6 +132,6 @@ public final class MethodInvocationUtils { return null; } - return new SimpleMethodInvocation(method, args); + return new SimpleMethodInvocation(targetObject, method, args); } } diff --git a/core/src/main/java/org/springframework/security/util/SimpleMethodInvocation.java b/core/src/main/java/org/springframework/security/util/SimpleMethodInvocation.java index 9e31767fc9..234e270317 100644 --- a/core/src/main/java/org/springframework/security/util/SimpleMethodInvocation.java +++ b/core/src/main/java/org/springframework/security/util/SimpleMethodInvocation.java @@ -32,11 +32,13 @@ public class SimpleMethodInvocation implements MethodInvocation { private Method method; private Object[] arguments; + private Object targetObject; //~ Constructors =================================================================================================== - public SimpleMethodInvocation(Method method, Object[] arguments) { - this.method = method; + public SimpleMethodInvocation(Object targetObject, Method method, Object[] arguments) { + this.targetObject = targetObject; + this.method = method; this.arguments = arguments; } @@ -57,7 +59,7 @@ public class SimpleMethodInvocation implements MethodInvocation { } public Object getThis() { - throw new UnsupportedOperationException("mock method not implemented"); + return targetObject; } public Object proceed() throws Throwable { diff --git a/core/src/test/java/org/springframework/security/MockJoinPoint.java b/core/src/test/java/org/springframework/security/MockJoinPoint.java index e21cad6263..2b587ee171 100644 --- a/core/src/test/java/org/springframework/security/MockJoinPoint.java +++ b/core/src/test/java/org/springframework/security/MockJoinPoint.java @@ -34,12 +34,14 @@ public class MockJoinPoint implements JoinPoint { private Method beingInvoked; private Object object; + private Class declaringType; //~ Constructors =================================================================================================== public MockJoinPoint(Object object, Method beingInvoked) { this.object = object; this.beingInvoked = beingInvoked; + this.declaringType = object.getClass(); } //~ Methods ======================================================================================================== @@ -61,7 +63,7 @@ public class MockJoinPoint implements JoinPoint { } public StaticPart getStaticPart() { - return new MockStaticPart(beingInvoked); + return new MockStaticPart(beingInvoked, declaringType); } public Object getTarget() { @@ -84,13 +86,15 @@ public class MockJoinPoint implements JoinPoint { private class MockCodeSignature implements CodeSignature { private Method beingInvoked; + private Class declaringType; - public MockCodeSignature(Method beingInvoked) { + public MockCodeSignature(Method beingInvoked, Class declaringType) { this.beingInvoked = beingInvoked; + this.declaringType = declaringType; } public Class getDeclaringType() { - throw new UnsupportedOperationException("mock not implemented"); + return this.declaringType; } public String getDeclaringTypeName() { @@ -128,9 +132,11 @@ public class MockJoinPoint implements JoinPoint { private class MockStaticPart implements StaticPart { private Method beingInvoked; - - public MockStaticPart(Method beingInvoked) { + private Class declaringType; + + public MockStaticPart(Method beingInvoked, Class declaringType) { this.beingInvoked = beingInvoked; + this.declaringType = declaringType; } public String getKind() { @@ -138,7 +144,7 @@ public class MockJoinPoint implements JoinPoint { } public Signature getSignature() { - return new MockCodeSignature(beingInvoked); + return new MockCodeSignature(beingInvoked, declaringType); } public SourceLocation getSourceLocation() { diff --git a/core/src/test/java/org/springframework/security/OtherTargetObject.java b/core/src/test/java/org/springframework/security/OtherTargetObject.java index c003698fe4..5d57ca56f7 100644 --- a/core/src/test/java/org/springframework/security/OtherTargetObject.java +++ b/core/src/test/java/org/springframework/security/OtherTargetObject.java @@ -29,10 +29,6 @@ package org.springframework.security; public class OtherTargetObject extends TargetObject implements ITargetObject { //~ Methods ======================================================================================================== - public int countLength(String input) { - return super.countLength(input); - } - public String makeLowerCase(String input) { return super.makeLowerCase(input); } diff --git a/core/src/test/java/org/springframework/security/context/rmi/ContextPropagatingRemoteInvocationTests.java b/core/src/test/java/org/springframework/security/context/rmi/ContextPropagatingRemoteInvocationTests.java index 4cf8f4e2a8..47c9821575 100644 --- a/core/src/test/java/org/springframework/security/context/rmi/ContextPropagatingRemoteInvocationTests.java +++ b/core/src/test/java/org/springframework/security/context/rmi/ContextPropagatingRemoteInvocationTests.java @@ -59,7 +59,7 @@ public class ContextPropagatingRemoteInvocationTests extends TestCase { throws Exception { Class clazz = TargetObject.class; Method method = clazz.getMethod("makeLowerCase", new Class[] {String.class}); - MethodInvocation mi = new SimpleMethodInvocation(method, new Object[] {"SOME_STRING"}); + MethodInvocation mi = new SimpleMethodInvocation(new TargetObject(), method, new Object[] {"SOME_STRING"}); ContextPropagatingRemoteInvocationFactory factory = new ContextPropagatingRemoteInvocationFactory(); diff --git a/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java b/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java index fabc913315..e693c20ebd 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java @@ -15,33 +15,13 @@ package org.springframework.security.intercept.method; -import junit.framework.TestCase; - -import org.springframework.security.ConfigAttribute; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.ITargetObject; -import org.springframework.security.OtherTargetObject; -import org.springframework.security.SecurityConfig; -import org.springframework.security.TargetObject; - -import org.springframework.security.acl.basic.SomeDomain; - -import org.springframework.security.context.SecurityContextHolder; - -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; - -import org.springframework.security.util.SimpleMethodInvocation; - -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; +import junit.framework.Assert; + +import org.junit.Test; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.ITargetObject; /** @@ -51,180 +31,27 @@ import java.util.Set; * @author Ben Alex * @version $Id$ */ -public class MethodDefinitionAttributesTests extends TestCase { - //~ Instance fields ================================================================================================ +public class MethodDefinitionAttributesTests { - ClassPathXmlApplicationContext applicationContext; - - //~ Constructors =================================================================================================== - - public MethodDefinitionAttributesTests(String a) { - super(a); + private MethodDefinitionAttributes build() { + MethodDefinitionAttributes mda = new MethodDefinitionAttributes(); + mda.setAttributes(new MockAttributes()); + return mda; + } + + @Test + public void testMethodsReturned() throws Exception { + Class clazz = ITargetObject.class; + Method method = clazz.getMethod("countLength", new Class[] {String.class}); + ConfigAttributeDefinition result = build().findAttributes(method, ITargetObject.class); + Assert.assertEquals(1, result.getConfigAttributes().size()); } - //~ Methods ======================================================================================================== - - - protected void tearDown() throws Exception { - super.tearDown(); - SecurityContextHolder.clearContext(); + @Test + public void testClassesReturned() throws Exception { + Class clazz = ITargetObject.class; + ConfigAttributeDefinition result = build().findAttributes(ITargetObject.class); + Assert.assertEquals(1, result.getConfigAttributes().size()); } - private ConfigAttributeDefinition getConfigAttributeDefinition(Class clazz, String methodName, Class[] args) - throws Exception { - - final Method method = clazz.getMethod(methodName, args); - MethodDefinitionAttributes source = new MethodDefinitionAttributes(); - source.setAttributes(new MockAttributes()); - - ConfigAttributeDefinition config = source.getAttributes(new SimpleMethodInvocation() { - public Method getMethod() { - return method; - } - }); - - return config; - } - - private ITargetObject makeInterceptedTarget() { - ApplicationContext context = new ClassPathXmlApplicationContext( - "org/springframework/security/intercept/method/applicationContext.xml"); - - return (ITargetObject) context.getBean("target"); - } - - public final void setUp() throws Exception { - super.setUp(); - } - - public void testAttributesForInterfaceTargetObject() throws Exception { - ConfigAttributeDefinition def1 = getConfigAttributeDefinition(ITargetObject.class, "countLength", - new Class[] {String.class}); - Set set1 = toSet(def1); - assertTrue(set1.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set1.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_COUNT_LENGTH"))); - - ConfigAttributeDefinition def2 = getConfigAttributeDefinition(ITargetObject.class, "makeLowerCase", - new Class[] {String.class}); - Set set2 = toSet(def2); - assertTrue(set2.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set2.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_MAKE_LOWER_CASE"))); - - ConfigAttributeDefinition def3 = getConfigAttributeDefinition(ITargetObject.class, "makeUpperCase", - new Class[] {String.class}); - Set set3 = toSet(def3); - assertTrue(set3.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set3.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_MAKE_UPPER_CASE"))); - } - - public void testAttributesForOtherTargetObject() throws Exception { - ConfigAttributeDefinition def1 = getConfigAttributeDefinition(OtherTargetObject.class, "countLength", - new Class[] {String.class}); - Set set1 = toSet(def1); - assertTrue(set1.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set1.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_COUNT_LENGTH"))); - - // Confirm MOCK_CLASS_METHOD_COUNT_LENGTH not added, as it's a String (not a ConfigAttribute) - // Confirm also MOCK_CLASS not added, as we return null for class - assertEquals(2, set1.size()); - - ConfigAttributeDefinition def2 = getConfigAttributeDefinition(OtherTargetObject.class, "makeLowerCase", - new Class[] {String.class}); - Set set2 = toSet(def2); - assertTrue(set2.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set2.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_MAKE_LOWER_CASE"))); - assertTrue(set2.contains(new SecurityConfig("MOCK_CLASS_METHOD_MAKE_LOWER_CASE"))); - - // Confirm MOCK_CLASS not added, as we return null for class - assertEquals(3, set2.size()); - - ConfigAttributeDefinition def3 = getConfigAttributeDefinition(OtherTargetObject.class, "makeUpperCase", - new Class[] {String.class}); - Set set3 = toSet(def3); - assertTrue(set3.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set3.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_MAKE_UPPER_CASE"))); - assertTrue(set3.contains(new SecurityConfig("RUN_AS"))); // defined against interface - - assertEquals(3, set3.size()); - } - - public void testAttributesForTargetObject() throws Exception { - ConfigAttributeDefinition def1 = getConfigAttributeDefinition(TargetObject.class, "countLength", - new Class[] {String.class}); - Set set1 = toSet(def1); - assertTrue(set1.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set1.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_COUNT_LENGTH"))); - - assertTrue(set1.contains(new SecurityConfig("MOCK_CLASS"))); - - // Confirm the MOCK_CLASS_METHOD_COUNT_LENGTH was not added, as it's not a ConfigAttribute - assertEquals(3, set1.size()); - - ConfigAttributeDefinition def2 = getConfigAttributeDefinition(TargetObject.class, "makeLowerCase", - new Class[] {String.class}); - Set set2 = toSet(def2); - assertTrue(set2.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set2.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_MAKE_LOWER_CASE"))); - assertTrue(set2.contains(new SecurityConfig("MOCK_CLASS"))); - assertTrue(set2.contains(new SecurityConfig("MOCK_CLASS_METHOD_MAKE_LOWER_CASE"))); - assertEquals(4, set2.size()); - - ConfigAttributeDefinition def3 = getConfigAttributeDefinition(TargetObject.class, "makeUpperCase", - new Class[] {String.class}); - Set set3 = toSet(def3); - assertTrue(set3.contains(new SecurityConfig("MOCK_INTERFACE"))); - assertTrue(set3.contains(new SecurityConfig("MOCK_INTERFACE_METHOD_MAKE_UPPER_CASE"))); - assertTrue(set3.contains(new SecurityConfig("MOCK_CLASS"))); - assertTrue(set3.contains(new SecurityConfig("MOCK_CLASS_METHOD_MAKE_UPPER_CASE"))); - assertTrue(set3.contains(new SecurityConfig("RUN_AS"))); - assertEquals(5, set3.size()); - } - - public void testMethodCallWithRunAsReplacement() throws Exception { - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", - new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_INTERFACE_METHOD_MAKE_UPPER_CASE")}); - SecurityContextHolder.getContext().setAuthentication(token); - - ITargetObject target = makeInterceptedTarget(); - String result = target.makeUpperCase("hello"); - assertEquals("HELLO org.springframework.security.MockRunAsAuthenticationToken true", result); - } - - public void testMethodCallWithoutRunAsReplacement() throws Exception { - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", - new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_INTERFACE_METHOD_MAKE_LOWER_CASE")}); - SecurityContextHolder.getContext().setAuthentication(token); - - ITargetObject target = makeInterceptedTarget(); - String result = target.makeLowerCase("HELLO"); - - assertEquals("hello org.springframework.security.providers.UsernamePasswordAuthenticationToken true", result); - } - - public void testNullReturnedIfZeroAttributesDefinedForMethodInvocation() - throws Exception { - // SomeDomain is not defined in the MockAttributes() - // (which getConfigAttributeDefinition refers to) - ConfigAttributeDefinition def = getConfigAttributeDefinition(SomeDomain.class, "getId", null); - assertNull(def); - } - - /** - * convert a ConfigAttributeDefinition into a set of ConfigAttribute(s) - * - * @param def the ConfigAttributeDefinition to cover - * - * @return a Set of ConfigAttributes - */ - private Set toSet(ConfigAttributeDefinition def) { - Set set = new HashSet(); - Iterator i = def.getConfigAttributes().iterator(); - - while (i.hasNext()) { - ConfigAttribute a = (ConfigAttribute) i.next(); - set.add(a); - } - - return set; - } } diff --git a/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditorTests.java b/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditorTests.java index ca81a788ec..c29e125d9e 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditorTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditorTests.java @@ -18,7 +18,9 @@ package org.springframework.security.intercept.method; import junit.framework.TestCase; import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.ITargetObject; import org.springframework.security.MockJoinPoint; +import org.springframework.security.OtherTargetObject; import org.springframework.security.TargetObject; import org.aopalliance.intercept.MethodInvocation; @@ -30,7 +32,7 @@ import java.util.Iterator; /** - * Tests {@link MethodDefinitionSourceEditor} and its associated {@link MethodDefinitionMap}. + * Tests {@link MethodDefinitionSourceEditor} and its associated {@link MapBasedMethodDefinitionSource}. * * @author Ben Alex * @version $Id$ @@ -55,7 +57,7 @@ public class MethodDefinitionSourceEditorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText("org.springframework.security.TargetObject.countLength=ROLE_ONE,ROLE_TWO,RUN_AS_ENTRY"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); Class clazz = TargetObject.class; Method method = clazz.getMethod("countLength", new Class[] {String.class}); @@ -101,32 +103,41 @@ public class MethodDefinitionSourceEditorTests extends TestCase { } } - public void testConcreteClassInvocationsAlsoReturnDefinitionsAgainstInterface() - throws Exception { + public void testConcreteClassInvocationsAlsoReturnDefinitionsAgainstInterface() throws Exception { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText( - "org.springframework.security.ITargetObject.makeLower*=ROLE_FROM_INTERFACE\r\norg.springframework.security.ITargetObject.makeUpper*=ROLE_FROM_INTERFACE\r\norg.springframework.security.TargetObject.makeUpper*=ROLE_FROM_IMPLEMENTATION"); + "org.springframework.security.ITargetObject.computeHashCode*=ROLE_FROM_INTERFACE\r\n" + + "org.springframework.security.ITargetObject.makeLower*=ROLE_FROM_INTERFACE\r\n" + + "org.springframework.security.ITargetObject.makeUpper*=ROLE_FROM_INTERFACE\r\n" + + "org.springframework.security.TargetObject.computeHashCode*=ROLE_FROM_TO\r\n" + + "org.springframework.security.OtherTargetObject.computeHashCode*=ROLE_FROM_OTO\r\n" + + "org.springframework.security.OtherTargetObject.makeUpper*=ROLE_FROM_IMPLEMENTATION"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); - assertEquals(3, map.getMethodMapSize()); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); + assertEquals(6, map.getMethodMapSize()); - ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(TargetObject.class, - "makeLowerCase", new Class[] {String.class})); + ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(ITargetObject.class, "makeLowerCase", new Class[] {String.class}, new OtherTargetObject())); ConfigAttributeDefinition expectedMakeLower = new ConfigAttributeDefinition("ROLE_FROM_INTERFACE"); assertEquals(expectedMakeLower, returnedMakeLower); - ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(TargetObject.class, - "makeUpperCase", new Class[] {String.class})); - ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition( - new String[]{"ROLE_FROM_IMPLEMENTATION","ROLE_FROM_INTERFACE"}); + ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(ITargetObject.class, "makeUpperCase", new Class[] {String.class}, new OtherTargetObject())); + ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_IMPLEMENTATION"}); assertEquals(expectedMakeUpper, returnedMakeUpper); + + ConfigAttributeDefinition returnedComputeHashCode = map.getAttributes(new MockMethodInvocation(ITargetObject.class, "computeHashCode", new Class[] {String.class}, new OtherTargetObject())); + ConfigAttributeDefinition expectedComputeHashCode = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_OTO"}); + assertEquals(expectedComputeHashCode, returnedComputeHashCode); + + returnedComputeHashCode = map.getAttributes(new MockMethodInvocation(ITargetObject.class, "computeHashCode", new Class[] {String.class}, new TargetObject())); + expectedComputeHashCode = new ConfigAttributeDefinition(new String[]{"ROLE_FROM_TO"}); + assertEquals(expectedComputeHashCode, returnedComputeHashCode); } public void testEmptyStringReturnsEmptyMap() { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText(""); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); assertEquals(0, map.getMethodMapSize()); } @@ -135,7 +146,7 @@ public class MethodDefinitionSourceEditorTests extends TestCase { editor.setAsText( "org.springframework.security.TargetObject.countLength=ROLE_ONE,ROLE_TWO,RUN_AS_ENTRY\r\norg.springframework.security.TargetObject.make*=ROLE_NINE,ROLE_SUPERVISOR"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); Iterator iter = map.getConfigAttributeDefinitions().iterator(); int counter = 0; @@ -152,7 +163,7 @@ public class MethodDefinitionSourceEditorTests extends TestCase { editor.setAsText( "org.springframework.security.TargetObject.countLength=ROLE_ONE,ROLE_TWO,RUN_AS_ENTRY\r\norg.springframework.security.TargetObject.make*=ROLE_NINE,ROLE_SUPERVISOR"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); assertEquals(3, map.getMethodMapSize()); } @@ -161,21 +172,21 @@ public class MethodDefinitionSourceEditorTests extends TestCase { editor.setAsText( "org.springframework.security.TargetObject.*=ROLE_GENERAL\r\norg.springframework.security.TargetObject.makeLower*=ROLE_LOWER\r\norg.springframework.security.TargetObject.make*=ROLE_MAKE\r\norg.springframework.security.TargetObject.makeUpper*=ROLE_UPPER"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); - assertEquals(5, map.getMethodMapSize()); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); + assertEquals(14, map.getMethodMapSize()); - ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(TargetObject.class, - "makeLowerCase", new Class[] {String.class})); + ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(ITargetObject.class, + "makeLowerCase", new Class[] {String.class}, new TargetObject())); ConfigAttributeDefinition expectedMakeLower = new ConfigAttributeDefinition("ROLE_LOWER"); assertEquals(expectedMakeLower, returnedMakeLower); - ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(TargetObject.class, - "makeUpperCase", new Class[] {String.class})); + ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(ITargetObject.class, + "makeUpperCase", new Class[] {String.class}, new TargetObject())); ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition("ROLE_UPPER"); assertEquals(expectedMakeUpper, returnedMakeUpper); - ConfigAttributeDefinition returnedCountLength = map.getAttributes(new MockMethodInvocation(TargetObject.class, - "countLength", new Class[] {String.class})); + ConfigAttributeDefinition returnedCountLength = map.getAttributes(new MockMethodInvocation(ITargetObject.class, + "countLength", new Class[] {String.class}, new TargetObject())); ConfigAttributeDefinition expectedCountLength = new ConfigAttributeDefinition("ROLE_GENERAL"); assertEquals(expectedCountLength, returnedCountLength); } @@ -184,10 +195,10 @@ public class MethodDefinitionSourceEditorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText("org.springframework.security.TargetObject.countLength=ROLE_ONE,ROLE_TWO,RUN_AS_ENTRY"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); ConfigAttributeDefinition configAttributeDefinition = map.getAttributes(new MockMethodInvocation( - TargetObject.class, "makeLowerCase", new Class[] {String.class})); + ITargetObject.class, "makeLowerCase", new Class[] {String.class}, new TargetObject())); assertNull(configAttributeDefinition); } @@ -195,7 +206,7 @@ public class MethodDefinitionSourceEditorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText(null); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); assertEquals(0, map.getMethodMapSize()); } @@ -203,10 +214,10 @@ public class MethodDefinitionSourceEditorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText("org.springframework.security.TargetObject.countLength=ROLE_ONE,ROLE_TWO,RUN_AS_ENTRY"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); - ConfigAttributeDefinition returnedCountLength = map.getAttributes(new MockMethodInvocation(TargetObject.class, - "countLength", new Class[] {String.class})); + ConfigAttributeDefinition returnedCountLength = map.getAttributes(new MockMethodInvocation(ITargetObject.class, + "countLength", new Class[] {String.class}, new TargetObject())); ConfigAttributeDefinition expectedCountLength = new ConfigAttributeDefinition( new String[] {"ROLE_ONE", "ROLE_TWO", "RUN_AS_ENTRY"}); assertEquals(expectedCountLength, returnedCountLength); @@ -215,11 +226,13 @@ public class MethodDefinitionSourceEditorTests extends TestCase { //~ Inner Classes ================================================================================================== private class MockMethodInvocation implements MethodInvocation { - Method method; + private Method method; + private Object targetObject; - public MockMethodInvocation(Class clazz, String methodName, Class[] parameterTypes) + public MockMethodInvocation(Class clazz, String methodName, Class[] parameterTypes, Object targetObject) throws NoSuchMethodException { - method = clazz.getMethod(methodName, parameterTypes); + this.method = clazz.getMethod(methodName, parameterTypes); + this.targetObject = targetObject; } public Object[] getArguments() { @@ -235,7 +248,7 @@ public class MethodDefinitionSourceEditorTests extends TestCase { } public Object getThis() { - return null; + return targetObject; } public Object proceed() throws Throwable { diff --git a/core/src/test/java/org/springframework/security/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java b/core/src/test/java/org/springframework/security/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java index a9ee9fdd12..d7a3fafb75 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java @@ -20,6 +20,7 @@ import junit.framework.TestCase; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.ITargetObject; +import org.springframework.security.OtherTargetObject; import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor; @@ -88,7 +89,7 @@ public class MethodInvocationPrivilegeEvaluatorTests extends TestCase { throws Exception { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_LOWER")}); - MethodInvocation mi = MethodInvocationUtils.createFromClass(ITargetObject.class, "makeLowerCase", + MethodInvocation mi = MethodInvocationUtils.createFromClass(new OtherTargetObject(), ITargetObject.class, "makeLowerCase", new Class[] {String.class}, new Object[] {"Hello world"}); MethodSecurityInterceptor interceptor = makeSecurityInterceptor(); @@ -117,7 +118,7 @@ public class MethodInvocationPrivilegeEvaluatorTests extends TestCase { throws Exception { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_NOT_HELD")}); - MethodInvocation mi = MethodInvocationUtils.createFromClass(ITargetObject.class, "makeLowerCase", + MethodInvocation mi = MethodInvocationUtils.createFromClass(new OtherTargetObject(), ITargetObject.class, "makeLowerCase", new Class[] {String.class}, new Object[] {"helloWorld"}); MethodSecurityInterceptor interceptor = makeSecurityInterceptor(); diff --git a/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java b/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java index 21491b8e0f..232dabc42a 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java +++ b/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java @@ -71,4 +71,8 @@ public class MockMethodDefinitionSource extends AbstractMethodDefinitionSource { protected ConfigAttributeDefinition lookupAttributes(Method method) { throw new UnsupportedOperationException("mock method not implemented"); } + + public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { + throw new UnsupportedOperationException("mock method not implemented"); + } } diff --git a/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisorTests.java b/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisorTests.java index 3170d79e84..335bc055d8 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisorTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodDefinitionSourceAdvisorTests.java @@ -15,17 +15,14 @@ package org.springframework.security.intercept.method.aopalliance; +import java.lang.reflect.Method; + import junit.framework.TestCase; import org.springframework.security.TargetObject; - -import org.springframework.security.intercept.method.MethodDefinitionMap; +import org.springframework.security.intercept.method.MapBasedMethodDefinitionSource; import org.springframework.security.intercept.method.MethodDefinitionSourceEditor; -import org.springframework.aop.framework.AopConfigException; - -import java.lang.reflect.Method; - /** * Tests {@link MethodDefinitionSourceAdvisor}. @@ -50,7 +47,7 @@ public class MethodDefinitionSourceAdvisorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText("org.springframework.security.TargetObject.countLength=ROLE_NOT_USED"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); MethodSecurityInterceptor msi = new MethodSecurityInterceptor(); msi.setObjectDefinitionSource(map); @@ -90,7 +87,7 @@ public class MethodDefinitionSourceAdvisorTests extends TestCase { try { new MethodDefinitionSourceAdvisor(msi); fail("Should have detected null ObjectDefinitionSource and thrown AopConfigException"); - } catch (AopConfigException expected) { + } catch (IllegalArgumentException expected) { assertTrue(true); } } @@ -99,7 +96,7 @@ public class MethodDefinitionSourceAdvisorTests extends TestCase { Class clazz = TargetObject.class; Method method = clazz.getMethod("countLength", new Class[] {String.class}); - MethodDefinitionSourceAdvisor.InternalMethodInvocation imi = new MethodDefinitionSourceAdvisor(getInterceptor()).new InternalMethodInvocation(method); + MethodDefinitionSourceAdvisor.InternalMethodInvocation imi = new MethodDefinitionSourceAdvisor(getInterceptor()).new InternalMethodInvocation(method, clazz); try { imi.getArguments(); @@ -115,13 +112,6 @@ public class MethodDefinitionSourceAdvisorTests extends TestCase { assertTrue(true); } - try { - imi.getThis(); - fail("Should have thrown UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - assertTrue(true); - } - try { imi.proceed(); fail("Should have thrown UnsupportedOperationException"); diff --git a/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodSecurityInterceptorTests.java b/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodSecurityInterceptorTests.java index 287596ee3a..638fca73a2 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodSecurityInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/aopalliance/MethodSecurityInterceptorTests.java @@ -455,7 +455,11 @@ public class MethodSecurityInterceptorTests extends TestCase { throw new UnsupportedOperationException("mock method not implemented"); } - public boolean supports(Class clazz) { + public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { + throw new UnsupportedOperationException("mock method not implemented"); + } + + public boolean supports(Class clazz) { if (String.class.isAssignableFrom(clazz)) { return true; } else { diff --git a/core/src/test/java/org/springframework/security/intercept/method/aspectj/AspectJSecurityInterceptorTests.java b/core/src/test/java/org/springframework/security/intercept/method/aspectj/AspectJSecurityInterceptorTests.java index ade24bed97..41c9318a26 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/aspectj/AspectJSecurityInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/aspectj/AspectJSecurityInterceptorTests.java @@ -15,27 +15,24 @@ package org.springframework.security.intercept.method.aspectj; +import java.lang.reflect.Method; + import junit.framework.TestCase; import org.springframework.security.AccessDeniedException; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.MockAccessDecisionManager; +import org.springframework.security.MockApplicationEventPublisher; import org.springframework.security.MockAuthenticationManager; import org.springframework.security.MockJoinPoint; import org.springframework.security.MockRunAsManager; import org.springframework.security.TargetObject; -import org.springframework.security.MockApplicationEventPublisher; - import org.springframework.security.context.SecurityContextHolder; - -import org.springframework.security.intercept.method.MethodDefinitionMap; +import org.springframework.security.intercept.method.MapBasedMethodDefinitionSource; import org.springframework.security.intercept.method.MethodDefinitionSourceEditor; - import org.springframework.security.providers.TestingAuthenticationToken; -import java.lang.reflect.Method; - /** * Tests {@link AspectJSecurityInterceptor}. @@ -74,7 +71,7 @@ public class AspectJSecurityInterceptorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText("org.springframework.security.TargetObject.countLength=MOCK_ONE,MOCK_TWO"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); si.setObjectDefinitionSource(map); assertEquals(map, si.getObjectDefinitionSource()); @@ -105,7 +102,7 @@ public class AspectJSecurityInterceptorTests extends TestCase { MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor(); editor.setAsText("org.springframework.security.TargetObject.countLength=MOCK_ONE,MOCK_TWO"); - MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue(); + MapBasedMethodDefinitionSource map = (MapBasedMethodDefinitionSource) editor.getValue(); si.setObjectDefinitionSource(map); si.afterPropertiesSet(); diff --git a/core/src/test/java/org/springframework/security/vote/BasicAclEntryVoterTests.java b/core/src/test/java/org/springframework/security/vote/BasicAclEntryVoterTests.java index f726025de6..581234f63e 100644 --- a/core/src/test/java/org/springframework/security/vote/BasicAclEntryVoterTests.java +++ b/core/src/test/java/org/springframework/security/vote/BasicAclEntryVoterTests.java @@ -56,7 +56,7 @@ public class BasicAclEntryVoterTests extends TestCase { Class clazz = SomeDomainObjectManager.class; Method method = clazz.getMethod("someServiceMethod", new Class[] {SomeDomainObject.class}); - return new SimpleMethodInvocation(method, new Object[] {domainObject}); + return new SimpleMethodInvocation(new SomeDomainObjectManager(), method, new Object[] {domainObject}); } public static void main(String[] args) { @@ -419,7 +419,7 @@ public class BasicAclEntryVoterTests extends TestCase { Class clazz = String.class; Method method = clazz.getMethod("toString", new Class[]{}); - MethodInvocation mi = new SimpleMethodInvocation(method, new Object[]{domainObject}); + MethodInvocation mi = new SimpleMethodInvocation(new String(), method, new Object[]{domainObject}); try { voter.vote(new UsernamePasswordAuthenticationToken("rod", null), mi, attr); diff --git a/core/src/test/resources/org/springframework/security/config/method-security.xml b/core/src/test/resources/org/springframework/security/config/method-security.xml index d34240fce4..5dfa743af2 100644 --- a/core/src/test/resources/org/springframework/security/config/method-security.xml +++ b/core/src/test/resources/org/springframework/security/config/method-security.xml @@ -11,8 +11,8 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc - - + + diff --git a/sandbox/other/src/main/java/org/springframework/security/AspectJParsingTests.java b/sandbox/other/src/main/java/org/springframework/security/AspectJParsingTests.java new file mode 100644 index 0000000000..d487c6bf65 --- /dev/null +++ b/sandbox/other/src/main/java/org/springframework/security/AspectJParsingTests.java @@ -0,0 +1,51 @@ +package org.springframework.security; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Assert; + +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.weaver.tools.PointcutExpression; +import org.aspectj.weaver.tools.PointcutParser; +import org.aspectj.weaver.tools.PointcutPrimitive; +import org.junit.Test; + +/** + * A quick play with AspectJ pointcut parsing. Was contemplating using this for MapBasedMethodDefinitionSource refactoring, + * but decided to revisit at a future point. Requires aspectjweaver-1.5.3.jar in classpath. + * + * @author Ben Alex + */ + +public class AspectJParsingTests { + private static final Set DEFAULT_SUPPORTED_PRIMITIVES = new HashSet(); + + @Pointcut("execution(int TargetObject.countLength(String))") + public void goodPointcut() {} + + static { + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.CALL); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET); + } + + @Test + public void testMatches() throws Exception { + PointcutParser parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(DEFAULT_SUPPORTED_PRIMITIVES); + PointcutExpression expression = parser.parsePointcutExpression("org.springframework.security.AspectJParsingTests.goodPointcut()"); + + Method exec = OtherTargetObject.class.getMethod("countLength", new Class[] {String.class}); + Assert.assertTrue(expression.matchesMethodExecution(exec).alwaysMatches()); + } + +}