diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.groovy deleted file mode 100644 index 8551347506..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.groovy +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * 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.config.annotation.method.configuration - -import static org.assertj.core.api.Assertions.assertThat -import static org.junit.Assert.fail - -import java.io.Serializable; - -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.context.annotation.Configuration -import org.springframework.security.access.AccessDeniedException -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler -import org.springframework.security.authentication.TestingAuthenticationToken -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.method.configuration.NamespaceGlobalMethodSecurityTests.BaseMethodConfig; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder - -/** - * - * @author Rob Winch - */ -public class NamespaceGlobalMethodSecurityExpressionHandlerTests extends BaseSpringSpec { - def setup() { - SecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "password","ROLE_USER")) - } - - def "global-method-security/expression-handler @PreAuthorize"() { - setup: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - when: - service.hasPermission("granted") - then: - noExceptionThrown() - when: - service.hasPermission("denied") - then: - thrown(AccessDeniedException) - } - - def "global-method-security/expression-handler @PostAuthorize"() { - setup: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - when: - service.postHasPermission("granted") - then: - noExceptionThrown() - when: - service.postHasPermission("denied") - then: - thrown(AccessDeniedException) - } - - @EnableGlobalMethodSecurity(prePostEnabled = true) - public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler() - expressionHandler.permissionEvaluator = new PermissionEvaluator() { - boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { - "granted" == targetDomainObject - } - boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { - throw new UnsupportedOperationException() - } - } - return expressionHandler - } - } -} diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.groovy deleted file mode 100644 index 1eec425878..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.groovy +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright 2002-2013 the original author or authors. - * - * 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.config.annotation.method.configuration - -import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource -import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor - -import static org.assertj.core.api.Assertions.assertThat -import static org.junit.Assert.fail - -import java.lang.reflect.Method - -import org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect -import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator -import org.springframework.beans.factory.BeanCreationException -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.AdviceMode -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Import -import org.springframework.core.Ordered -import org.springframework.security.access.AccessDecisionManager -import org.springframework.security.access.AccessDeniedException -import org.springframework.security.access.ConfigAttribute -import org.springframework.security.access.SecurityConfig -import org.springframework.security.access.intercept.AfterInvocationManager -import org.springframework.security.access.intercept.RunAsManager -import org.springframework.security.access.intercept.RunAsManagerImpl -import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor -import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor -import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource -import org.springframework.security.access.method.MethodSecurityMetadataSource -import org.springframework.security.authentication.AuthenticationManager -import org.springframework.security.authentication.TestingAuthenticationToken -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.authentication.BaseAuthenticationConfig; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolder - -/** - * - * @author Rob Winch - */ -public class NamespaceGlobalMethodSecurityTests extends BaseSpringSpec { - def setup() { - SecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "password","ROLE_USER")) - } - - // --- access-decision-manager-ref --- - - def "custom AccessDecisionManager can be used"() { - setup: "Create an instance with an AccessDecisionManager that always denies access" - context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - when: - service.preAuthorize() - then: - thrown(AccessDeniedException) - when: - service.secured() - then: - thrown(AccessDeniedException) - } - - @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) - public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration { - @Override - protected AccessDecisionManager accessDecisionManager() { - return new DenyAllAccessDecisionManager() - } - - public static class DenyAllAccessDecisionManager implements AccessDecisionManager { - public void decide(Authentication authentication, Object object, Collection configAttributes) { - throw new AccessDeniedException("Always Denied") - } - public boolean supports(ConfigAttribute attribute) { - return true - } - public boolean supports(Class clazz) { - return true - } - } - } - - // --- authentication-manager-ref --- - - def "custom AuthenticationManager can be used"() { - when: - context = new AnnotationConfigApplicationContext(CustomAuthenticationConfig) - MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor) - interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication) - then: - thrown(UnsupportedOperationException) - } - - @EnableGlobalMethodSecurity - public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration { - @Override - protected AuthenticationManager authenticationManager() { - return new AuthenticationManager() { - Authentication authenticate(Authentication authentication) { - throw new UnsupportedOperationException() - } - } - } - } - - // --- jsr250-annotations --- - - def "enable jsr250"() { - when: - context = new AnnotationConfigApplicationContext(Jsr250Config) - MethodSecurityService service = context.getBean(MethodSecurityService) - then: "@Secured and @PreAuthorize are ignored" - service.secured() == null - service.preAuthorize() == null - - when: "@DenyAll method invoked" - service.jsr250() - then: "access is denied" - thrown(AccessDeniedException) - when: "@PermitAll method invoked" - String jsr250PermitAll = service.jsr250PermitAll() - then: "access is allowed" - jsr250PermitAll == null - } - - @EnableGlobalMethodSecurity(jsr250Enabled = true) - @Configuration - public static class Jsr250Config extends BaseMethodConfig { - } - - // --- metadata-source-ref --- - - def "custom MethodSecurityMetadataSource can be used with higher priority than other sources"() { - setup: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomMethodSecurityMetadataSourceConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - when: - service.preAuthorize() - then: - thrown(AccessDeniedException) - when: - service.secured() - then: - thrown(AccessDeniedException) - when: - service.jsr250() - then: - thrown(AccessDeniedException) - } - - @EnableGlobalMethodSecurity - public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() { - return new AbstractMethodSecurityMetadataSource() { - public Collection getAttributes(Method method, Class targetClass) { - // require ROLE_NOBODY for any method on MethodSecurityService class - return MethodSecurityService.isAssignableFrom(targetClass) ? [new SecurityConfig("ROLE_NOBODY")] : [] - } - public Collection getAllConfigAttributes() { - return null - } - } - } - } - - // --- mode --- - - def "aspectj mode works"() { - when: - context = new AnnotationConfigApplicationContext(AspectJModeConfig) - then: - context.getBean(AnnotationSecurityAspect) - context.getBean(AspectJMethodSecurityInterceptor) - } - - @EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, proxyTargetClass = true) - public static class AspectJModeConfig extends BaseMethodConfig { - } - - def "aspectj mode works extending GlobalMethodSecurityConfiguration"() { - when: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,AspectJModeExtendsGMSCConfig) - then: - context.getBean(AnnotationSecurityAspect) - context.getBean(AspectJMethodSecurityInterceptor) - } - - @EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ) - public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration { - } - - // --- order --- - - def order() { - when: - context = new AnnotationConfigApplicationContext(CustomOrderConfig) - MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor) - then: - advisor.order == 135 - } - - @EnableGlobalMethodSecurity(order = 135) - public static class CustomOrderConfig extends BaseMethodConfig { - } - - def "order is defaulted to Ordered.LOWEST_PRECEDENCE when using @EnableGlobalMethodSecurity"() { - when: - context = new AnnotationConfigApplicationContext(DefaultOrderConfig) - MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor) - then: - advisor.order == Ordered.LOWEST_PRECEDENCE - } - - @EnableGlobalMethodSecurity - public static class DefaultOrderConfig extends BaseMethodConfig { - } - - def "order is defaulted to Ordered.LOWEST_PRECEDENCE when extending GlobalMethodSecurityConfiguration"() { - when: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,DefaultOrderExtendsMethodSecurityConfig) - MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor) - then: - advisor.order == Ordered.LOWEST_PRECEDENCE - } - - @EnableGlobalMethodSecurity - public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration { - } - - // --- pre-post-annotations --- - - def preAuthorize() { - when: - context = new AnnotationConfigApplicationContext(PreAuthorizeConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - then: - service.secured() == null - service.jsr250() == null - - when: - service.preAuthorize() - then: - thrown(AccessDeniedException) - } - - @EnableGlobalMethodSecurity(prePostEnabled = true) - @Configuration - public static class PreAuthorizeConfig extends BaseMethodConfig { - } - - def "prePostEnabled extends GlobalMethodSecurityConfiguration"() { - when: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,PreAuthorizeExtendsGMSCConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - then: - service.secured() == null - service.jsr250() == null - - when: - service.preAuthorize() - then: - thrown(AccessDeniedException) - } - - @EnableGlobalMethodSecurity(prePostEnabled = true) - @Configuration - public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration { - } - - // --- proxy-target-class --- - - def "proxying classes works"() { - when: - context = new AnnotationConfigApplicationContext(ProxyTargetClass) - MethodSecurityServiceImpl service = context.getBean(MethodSecurityServiceImpl) - then: - noExceptionThrown() - } - - @EnableGlobalMethodSecurity(proxyTargetClass = true) - @Configuration - public static class ProxyTargetClass extends BaseMethodConfig { - } - - def "proxying interfaces works"() { - when: - context = new AnnotationConfigApplicationContext(PreAuthorizeConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - then: "we get an instance of the interface" - noExceptionThrown() - when: "try to cast to the class" - MethodSecurityServiceImpl serviceImpl = service - then: "we get a class cast exception" - thrown(ClassCastException) - } - - // --- run-as-manager-ref --- - - def "custom RunAsManager"() { - when: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomRunAsManagerConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - then: - service.runAs().authorities.find { it.authority == "ROLE_RUN_AS_SUPER"} - } - - @EnableGlobalMethodSecurity(securedEnabled = true) - public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration { - @Override - protected RunAsManager runAsManager() { - RunAsManagerImpl runAsManager = new RunAsManagerImpl() - runAsManager.setKey("some key") - return runAsManager - } - } - - // --- secured-annotation --- - - def "secured enabled"() { - setup: - context = new AnnotationConfigApplicationContext(SecuredConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - when: - service.secured() - then: - thrown(AccessDeniedException) - and: "service with ROLE_USER allowed" - service.securedUser() == null - and: - service.preAuthorize() == null - service.jsr250() == null - } - - @EnableGlobalMethodSecurity(securedEnabled = true) - @Configuration - public static class SecuredConfig extends BaseMethodConfig { - } - - // --- after-invocation-provider - - def "custom AfterInvocationManager"() { - setup: - context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAfterInvocationManagerConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - when: - service.preAuthorizePermitAll() - then: - AccessDeniedException e = thrown() - e.message == "custom AfterInvocationManager" - } - - @EnableGlobalMethodSecurity(prePostEnabled = true) - public static class CustomAfterInvocationManagerConfig extends GlobalMethodSecurityConfiguration { - @Override - protected AfterInvocationManager afterInvocationManager() { - return new AfterInvocationManagerStub() - } - - public static class AfterInvocationManagerStub implements AfterInvocationManager { - Object decide(Authentication authentication, Object object, Collection attributes, - Object returnedObject) throws AccessDeniedException { - throw new AccessDeniedException("custom AfterInvocationManager") - } - - boolean supports(ConfigAttribute attribute) { - return true - } - boolean supports(Class clazz) { - return true - } - } - } - - // --- misc --- - - def "good error message when no Enable annotation"() { - when: - context = new AnnotationConfigApplicationContext(ExtendsNoEnableAnntotationConfig) - MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor) - interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication) - then: - BeanCreationException e = thrown() - e.message.contains(EnableGlobalMethodSecurity.class.getName() + " is required") - } - - @Configuration - public static class ExtendsNoEnableAnntotationConfig extends GlobalMethodSecurityConfiguration { - @Override - protected AuthenticationManager authenticationManager() { - return new AuthenticationManager() { - Authentication authenticate(Authentication authentication) { - throw new UnsupportedOperationException() - } - } - } - } - - def "import subclass of GlobalMethodSecurityConfiguration"() { - when: - context = new AnnotationConfigApplicationContext(ImportSubclassGMSCConfig) - MethodSecurityService service = context.getBean(MethodSecurityService) - then: - service.secured() == null - service.jsr250() == null - - when: - service.preAuthorize() - then: - thrown(AccessDeniedException) - } - - @Configuration - @Import(PreAuthorizeExtendsGMSCConfig) - public static class ImportSubclassGMSCConfig extends BaseMethodConfig { - } - - @Configuration - public static class BaseMethodConfig extends BaseAuthenticationConfig { - @Bean - public MethodSecurityService methodSecurityService() { - return new MethodSecurityServiceImpl() - } - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java new file mode 100644 index 0000000000..d03c7b2b19 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * 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.config.annotation.method.configuration; + +import org.springframework.context.annotation.Bean; + +/** + * @author Josh Cummings + */ +public class MethodSecurityServiceConfig { + @Bean + MethodSecurityService service() { + return new MethodSecurityServiceImpl(); + } +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.java new file mode 100644 index 0000000000..dbced1c34e --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityExpressionHandlerTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * 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.config.annotation.method.configuration; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.Authentication; +import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.Serializable; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * + * @author Rob Winch + * @author Josh Cummings + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SecurityTestExecutionListeners +public class NamespaceGlobalMethodSecurityExpressionHandlerTests { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired(required = false) + private MethodSecurityService service; + + @Test + @WithMockUser + public void methodSecurityWhenUsingCustomPermissionEvaluatorThenPreAuthorizesAccordingly() { + this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatCode(() -> this.service.hasPermission("granted")) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> this.service.hasPermission("denied")) + .isInstanceOf(AccessDeniedException.class); + } + + @Test + @WithMockUser + public void methodSecurityWhenUsingCustomPermissionEvaluatorThenPostAuthorizesAccordingly() { + this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatCode(() -> this.service.postHasPermission("granted")) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> this.service.postHasPermission("denied")) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(prePostEnabled = true) + public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration { + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + + expressionHandler.setPermissionEvaluator(new PermissionEvaluator() { + public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + return "granted".equals(targetDomainObject); + } + + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { + throw new UnsupportedOperationException(); + } + }); + + return expressionHandler; + } + } +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.java new file mode 100644 index 0000000000..83ae8e4295 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/NamespaceGlobalMethodSecurityTests.java @@ -0,0 +1,514 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * 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.config.annotation.method.configuration; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.*; +import org.springframework.core.Ordered; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.security.access.SecurityConfig; +import org.springframework.security.access.intercept.AfterInvocationManager; +import org.springframework.security.access.intercept.RunAsManager; +import org.springframework.security.access.intercept.RunAsManagerImpl; +import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; +import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor; +import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor; +import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource; +import org.springframework.security.access.method.MethodSecurityMetadataSource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.Authentication; +import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.*; + +/** + * + * @author Rob Winch + * @author Josh Cummings + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SecurityTestExecutionListeners +public class NamespaceGlobalMethodSecurityTests { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired(required = false) + private MethodSecurityService service; + + // --- access-decision-manager-ref --- + + @Test + @WithMockUser + public void methodSecurityWhenCustomAccessDecisionManagerThenAuthorizes() { + this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + + assertThatThrownBy(() -> this.service.secured()) + .isInstanceOf(AccessDeniedException.class); + + } + + @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) + public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected AccessDecisionManager accessDecisionManager() { + return new DenyAllAccessDecisionManager(); + } + + public static class DenyAllAccessDecisionManager implements AccessDecisionManager { + public void decide(Authentication authentication, Object object, Collection configAttributes) { + throw new AccessDeniedException("Always Denied"); + } + public boolean supports(ConfigAttribute attribute) { + return true; + } + public boolean supports(Class clazz) { + return true; + } + } + } + + // --- after-invocation-provider + + @Test + @WithMockUser + public void methodSecurityWhenCustomAfterInvocationManagerThenAuthorizes() { + this.spring.register(CustomAfterInvocationManagerConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatThrownBy(() -> this.service.preAuthorizePermitAll()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(prePostEnabled = true) + public static class CustomAfterInvocationManagerConfig + extends GlobalMethodSecurityConfiguration { + + @Override + protected AfterInvocationManager afterInvocationManager() { + return new AfterInvocationManagerStub(); + } + + public static class AfterInvocationManagerStub implements AfterInvocationManager { + public Object decide(Authentication authentication, + Object object, + Collection attributes, + Object returnedObject) throws AccessDeniedException { + + throw new AccessDeniedException("custom AfterInvocationManager"); + } + + public boolean supports(ConfigAttribute attribute) { + return true; + } + public boolean supports(Class clazz) { + return true; + } + } + } + + // --- authentication-manager-ref --- + + @Test + @WithMockUser + public void methodSecurityWhenCustomAuthenticationManagerThenAuthorizes() { + this.spring.register(CustomAuthenticationConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(UnsupportedOperationException.class); + } + + @EnableGlobalMethodSecurity(prePostEnabled = true) + public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration { + + @Override + public MethodInterceptor methodSecurityInterceptor() throws Exception { + MethodInterceptor interceptor = super.methodSecurityInterceptor(); + ((MethodSecurityInterceptor) interceptor).setAlwaysReauthenticate(true); + return interceptor; + } + + @Override + protected AuthenticationManager authenticationManager() { + return (authentication) -> { + throw new UnsupportedOperationException(); + }; + } + } + + // --- jsr250-annotations --- + + @Test + @WithMockUser + public void methodSecurityWhenJsr250EnabledThenAuthorizes() { + this.spring.register(Jsr250Config.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatCode(() -> this.service.preAuthorize()) + .doesNotThrowAnyException(); + + assertThatCode(() -> this.service.secured()) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> this.service.jsr250()) + .isInstanceOf(AccessDeniedException.class); + + assertThatCode(() -> this.service.jsr250PermitAll()) + .doesNotThrowAnyException(); + + } + + @EnableGlobalMethodSecurity(jsr250Enabled = true) + @Configuration + public static class Jsr250Config { + + } + + // --- metadata-source-ref --- + + @Test + @WithMockUser + public void methodSecurityWhenCustomMethodSecurityMetadataSourceThenAuthorizes() { + this.spring.register(CustomMethodSecurityMetadataSourceConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + + assertThatThrownBy(() -> this.service.secured()) + .isInstanceOf(AccessDeniedException.class); + + assertThatThrownBy(() -> this.service.jsr250()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity + public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() { + return new AbstractMethodSecurityMetadataSource() { + public Collection getAttributes(Method method, Class targetClass) { + // require ROLE_NOBODY for any method on MethodSecurityService interface + return MethodSecurityService.class.isAssignableFrom(targetClass) ? + Arrays.asList(new SecurityConfig("ROLE_NOBODY")) : + Collections.emptyList(); + } + public Collection getAllConfigAttributes() { + return null; + } + }; + } + } + + // --- mode --- + + @Test + @WithMockUser + public void contextRefreshWhenUsingAspectJThenAutowire() throws Exception { + this.spring.register(AspectJModeConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThat(this.spring.getContext().getBean(Class.forName("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"))).isNotNull(); + assertThat(this.spring.getContext().getBean(AspectJMethodSecurityInterceptor.class)).isNotNull(); + + //TODO diagnose why aspectj isn't weaving method security advice around MethodSecurityServiceImpl + } + + @EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, securedEnabled = true) + public static class AspectJModeConfig { + + } + + @Test + public void contextRefreshWhenUsingAspectJAndCustomGlobalMethodSecurityConfigurationThenAutowire() + throws Exception { + + this.spring.register(AspectJModeExtendsGMSCConfig.class).autowire(); + + assertThat(this.spring.getContext().getBean(Class.forName("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"))).isNotNull(); + assertThat(this.spring.getContext().getBean(AspectJMethodSecurityInterceptor.class)).isNotNull(); + + } + + @EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ) + public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration { + } + + // --- order --- + + private static class AdvisorOrderConfig + implements ImportBeanDefinitionRegistrar { + + private static class ExceptingInterceptor implements MethodInterceptor { + @Override + public Object invoke(MethodInvocation invocation) { + throw new UnsupportedOperationException("Deny All"); + } + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + BeanDefinitionBuilder advice = BeanDefinitionBuilder + .rootBeanDefinition(ExceptingInterceptor.class); + registry.registerBeanDefinition("exceptingInterceptor", + advice.getBeanDefinition()); + + BeanDefinitionBuilder advisor = BeanDefinitionBuilder + .rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class); + advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + advisor.addConstructorArgValue("exceptingInterceptor"); + advisor.addConstructorArgReference("methodSecurityMetadataSource"); + advisor.addConstructorArgValue("methodSecurityMetadataSource"); + advisor.addPropertyValue("order", 0); + registry.registerBeanDefinition("exceptingAdvisor", + advisor.getBeanDefinition()); + } + } + + @Test + @WithMockUser + public void methodSecurityWhenOrderSpecifiedThenConfigured() { + this.spring.register(CustomOrderConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThat(this.spring.getContext() + .getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class) + .getOrder()) + .isEqualTo(-135); + + assertThatThrownBy(() -> this.service.jsr250()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(order = -135, jsr250Enabled = true) + @Import(AdvisorOrderConfig.class) + public static class CustomOrderConfig { + + } + + @Test + @WithMockUser + public void methodSecurityWhenOrderUnspecifiedThenConfiguredToLowestPrecedence() { + this.spring.register(DefaultOrderConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThat(this.spring.getContext() + .getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class) + .getOrder()) + .isEqualTo(Ordered.LOWEST_PRECEDENCE); + + assertThatThrownBy(() -> this.service.jsr250()) + .isInstanceOf(UnsupportedOperationException.class); + } + + @EnableGlobalMethodSecurity(jsr250Enabled = true) + @Import(AdvisorOrderConfig.class) + public static class DefaultOrderConfig { + } + + @Test + @WithMockUser + public void methodSecurityWhenOrderUnspecifiedAndCustomGlobalMethodSecurityConfigurationThenConfiguredToLowestPrecedence() { + this.spring.register(DefaultOrderExtendsMethodSecurityConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThat(this.spring.getContext() + .getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class) + .getOrder()) + .isEqualTo(Ordered.LOWEST_PRECEDENCE); + + assertThatThrownBy(() -> this.service.jsr250()) + .isInstanceOf(UnsupportedOperationException.class); + } + + @EnableGlobalMethodSecurity(jsr250Enabled = true) + @Import(AdvisorOrderConfig.class) + public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration { + } + + // --- pre-post-annotations --- + + @Test + @WithMockUser + public void methodSecurityWhenPrePostEnabledThenPreAuthorizes() { + this.spring.register(PreAuthorizeConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatCode(() -> this.service.secured()) + .doesNotThrowAnyException(); + + assertThatCode(() -> this.service.jsr250()) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(prePostEnabled = true) + public static class PreAuthorizeConfig { + } + + @Test + @WithMockUser + public void methodSecurityWhenPrePostEnabledAndCustomGlobalMethodSecurityConfigurationThenPreAuthorizes() { + this.spring.register(PreAuthorizeExtendsGMSCConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatCode(() -> this.service.secured()) + .doesNotThrowAnyException(); + + assertThatCode(() -> this.service.jsr250()) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(prePostEnabled = true) + public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration { + } + + // --- proxy-target-class --- + + @Test + @WithMockUser + public void methodSecurityWhenProxyTargetClassThenDoesNotWireToInterface() { + this.spring.register(ProxyTargetClassConfig.class, MethodSecurityServiceConfig.class).autowire(); + + // make sure service was actually proxied + assertThat(this.service.getClass().getInterfaces()) + .doesNotContain(MethodSecurityService.class); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(proxyTargetClass = true, prePostEnabled = true) + public static class ProxyTargetClassConfig { + } + + @Test + @WithMockUser + public void methodSecurityWhenDefaultProxyThenWiresToInterface() { + this.spring.register(DefaultProxyConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThat(this.service.getClass().getInterfaces()) + .contains(MethodSecurityService.class); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + } + + @EnableGlobalMethodSecurity(prePostEnabled = true) + public static class DefaultProxyConfig { + } + + // --- run-as-manager-ref --- + + @Test + @WithMockUser + public void methodSecurityWhenCustomRunAsManagerThenRunAsWrapsAuthentication() { + this.spring.register(CustomRunAsManagerConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThat(service.runAs().getAuthorities()) + .anyMatch(authority -> "ROLE_RUN_AS_SUPER".equals(authority.getAuthority())); + } + + @EnableGlobalMethodSecurity(securedEnabled = true) + public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected RunAsManager runAsManager() { + RunAsManagerImpl runAsManager = new RunAsManagerImpl(); + runAsManager.setKey("some key"); + return runAsManager; + } + } + + // --- secured-annotation --- + + @Test + @WithMockUser + public void methodSecurityWhenSecuredEnabledThenSecures() { + this.spring.register(SecuredConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatThrownBy(() -> this.service.secured()) + .isInstanceOf(AccessDeniedException.class); + + assertThatCode(() -> this.service.securedUser()) + .doesNotThrowAnyException(); + + assertThatCode(() -> this.service.preAuthorize()) + .doesNotThrowAnyException(); + + assertThatCode(() -> this.service.jsr250()) + .doesNotThrowAnyException(); + } + + @EnableGlobalMethodSecurity(securedEnabled = true) + public static class SecuredConfig { + } + + // --- unsorted --- + + @Test + @WithMockUser + public void methodSecurityWhenMissingEnableAnnotationThenShowsHelpfulError() { + assertThatThrownBy(() -> + this.spring.register(ExtendsNoEnableAnntotationConfig.class).autowire()) + .hasStackTraceContaining(EnableGlobalMethodSecurity.class.getName() + " is required"); + } + + @Configuration + public static class ExtendsNoEnableAnntotationConfig + extends GlobalMethodSecurityConfiguration { + } + + @Test + @WithMockUser + public void methodSecurityWhenImportingGlobalMethodSecurityConfigurationSubclassThenAuthorizes() { + this.spring.register(ImportSubclassGMSCConfig.class, MethodSecurityServiceConfig.class).autowire(); + + assertThatCode(() -> this.service.secured()) + .doesNotThrowAnyException(); + + assertThatCode(() -> this.service.jsr250()) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> this.service.preAuthorize()) + .isInstanceOf(AccessDeniedException.class); + } + + @Configuration + @Import(PreAuthorizeExtendsGMSCConfig.class) + public static class ImportSubclassGMSCConfig { + } +}