diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java index 7005f32a85..81fcccbb12 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java @@ -46,7 +46,9 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -475,6 +477,15 @@ public class AuthorizeHttpRequestsConfigurerTests { this.mvc.perform(requestFromOtherHost).andExpect(status().isForbidden()); } + @Test + public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throws Exception { + this.spring.register(MvcMatcherPathVariablesInLambdaConfig.class).autowire(); + MockHttpServletRequestBuilder request = get("/user/user"); + this.mvc.perform(request).andExpect(status().isOk()); + request = get("/user/deny"); + this.mvc.perform(request).andExpect(status().isUnauthorized()); + } + private static RequestPostProcessor remoteAddress(String remoteAddress) { return (request) -> { request.setRemoteAddr(remoteAddress); @@ -848,6 +859,35 @@ public class AuthorizeHttpRequestsConfigurerTests { } + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class MvcMatcherPathVariablesInLambdaConfig { + + @Bean + SecurityFilterChain chain(HttpSecurity http) throws Exception { + // @formatter:off + http + .httpBasic(withDefaults()) + .authorizeHttpRequests((requests) -> requests + .mvcMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'")) + ); + // @formatter:on + return http.build(); + } + + @RestController + static class PathController { + + @RequestMapping("/user/{username}") + String path(@PathVariable("username") String username) { + return username; + } + + } + + } + @Configuration static class AuthorizationEventPublisherConfig { diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java index d5d943f5f9..f910c4bf55 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java @@ -16,6 +16,7 @@ package org.springframework.security.web.access.expression; +import java.util.Map; import java.util.function.Supplier; import org.springframework.expression.EvaluationContext; @@ -72,6 +73,9 @@ public final class WebExpressionAuthorizationManager implements AuthorizationMan @Override public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), context); + for (Map.Entry entry : context.getVariables().entrySet()) { + ctx.setVariable(entry.getKey(), entry.getValue()); + } boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx); return new ExpressionAuthorizationDecision(granted, this.expression); } diff --git a/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java b/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java new file mode 100644 index 0000000000..c784765bb0 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2016 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 + * + * https://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.web.access.expression; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.security.access.SecurityConfig; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +public class DefaultHttpSecurityExpressionHandlerTests { + + @Mock + private AuthenticationTrustResolver trustResolver; + + @Mock + private Authentication authentication; + + @Mock + private RequestAuthorizationContext context; + + private DefaultHttpSecurityExpressionHandler handler; + + @BeforeEach + public void setup() { + this.handler = new DefaultHttpSecurityExpressionHandler(); + } + + @AfterEach + public void cleanup() { + SecurityContextHolder.clearContext(); + } + + @Test + public void expressionPropertiesAreResolvedAgainstAppContextBeans() { + StaticApplicationContext appContext = new StaticApplicationContext(); + RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class); + bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A"); + appContext.registerBeanDefinition("role", bean); + this.handler.setApplicationContext(appContext); + EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class), + mock(RequestAuthorizationContext.class)); + ExpressionParser parser = this.handler.getExpressionParser(); + assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue(); + assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue(); + } + + @Test + public void setTrustResolverNull() { + assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null)); + } + + @Test + public void createEvaluationContextCustomTrustResolver() { + this.handler.setTrustResolver(this.trustResolver); + Expression expression = this.handler.getExpressionParser().parseExpression("anonymous"); + EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.context); + assertThat(expression.getValue(context, Boolean.class)).isFalse(); + verify(this.trustResolver).isAnonymous(this.authentication); + } + +}