Skip to content

Commit ffaf5b4

Browse files
committed
Polish WebExpressionAuthorizationManager
- Add support for request variables - Added additional tests Issue gh-11105
1 parent 07b0be3 commit ffaf5b4

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

+40
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
4747
import org.springframework.test.web.servlet.request.RequestPostProcessor;
4848
import org.springframework.web.bind.annotation.GetMapping;
49+
import org.springframework.web.bind.annotation.PathVariable;
4950
import org.springframework.web.bind.annotation.PostMapping;
51+
import org.springframework.web.bind.annotation.RequestMapping;
5052
import org.springframework.web.bind.annotation.RestController;
5153
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
5254

@@ -475,6 +477,15 @@ public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsOtherThen
475477
this.mvc.perform(requestFromOtherHost).andExpect(status().isForbidden());
476478
}
477479

480+
@Test
481+
public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throws Exception {
482+
this.spring.register(MvcMatcherPathVariablesInLambdaConfig.class).autowire();
483+
MockHttpServletRequestBuilder request = get("/user/user");
484+
this.mvc.perform(request).andExpect(status().isOk());
485+
request = get("/user/deny");
486+
this.mvc.perform(request).andExpect(status().isUnauthorized());
487+
}
488+
478489
private static RequestPostProcessor remoteAddress(String remoteAddress) {
479490
return (request) -> {
480491
request.setRemoteAddr(remoteAddress);
@@ -848,6 +859,35 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
848859

849860
}
850861

862+
@EnableWebSecurity
863+
@Configuration
864+
@EnableWebMvc
865+
static class MvcMatcherPathVariablesInLambdaConfig {
866+
867+
@Bean
868+
SecurityFilterChain chain(HttpSecurity http) throws Exception {
869+
// @formatter:off
870+
http
871+
.httpBasic(withDefaults())
872+
.authorizeHttpRequests((requests) -> requests
873+
.mvcMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'"))
874+
);
875+
// @formatter:on
876+
return http.build();
877+
}
878+
879+
@RestController
880+
static class PathController {
881+
882+
@RequestMapping("/user/{username}")
883+
String path(@PathVariable("username") String username) {
884+
return username;
885+
}
886+
887+
}
888+
889+
}
890+
851891
@Configuration
852892
static class AuthorizationEventPublisherConfig {
853893

web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.web.access.expression;
1818

19+
import java.util.Map;
1920
import java.util.function.Supplier;
2021

2122
import org.springframework.expression.EvaluationContext;
@@ -72,6 +73,9 @@ public void setExpressionHandler(SecurityExpressionHandler<RequestAuthorizationC
7273
@Override
7374
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
7475
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), context);
76+
for (Map.Entry<String, String> entry : context.getVariables().entrySet()) {
77+
ctx.setVariable(entry.getKey(), entry.getValue());
78+
}
7579
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
7680
return new ExpressionAuthorizationDecision(granted, this.expression);
7781
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.access.expression;
18+
19+
import org.junit.jupiter.api.AfterEach;
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.mockito.Mock;
24+
import org.mockito.junit.jupiter.MockitoExtension;
25+
26+
import org.springframework.beans.factory.support.RootBeanDefinition;
27+
import org.springframework.context.support.StaticApplicationContext;
28+
import org.springframework.expression.EvaluationContext;
29+
import org.springframework.expression.Expression;
30+
import org.springframework.expression.ExpressionParser;
31+
import org.springframework.security.access.SecurityConfig;
32+
import org.springframework.security.authentication.AuthenticationTrustResolver;
33+
import org.springframework.security.core.Authentication;
34+
import org.springframework.security.core.context.SecurityContextHolder;
35+
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
39+
import static org.mockito.Mockito.mock;
40+
import static org.mockito.Mockito.verify;
41+
42+
@ExtendWith(MockitoExtension.class)
43+
public class DefaultHttpSecurityExpressionHandlerTests {
44+
45+
@Mock
46+
private AuthenticationTrustResolver trustResolver;
47+
48+
@Mock
49+
private Authentication authentication;
50+
51+
@Mock
52+
private RequestAuthorizationContext context;
53+
54+
private DefaultHttpSecurityExpressionHandler handler;
55+
56+
@BeforeEach
57+
public void setup() {
58+
this.handler = new DefaultHttpSecurityExpressionHandler();
59+
}
60+
61+
@AfterEach
62+
public void cleanup() {
63+
SecurityContextHolder.clearContext();
64+
}
65+
66+
@Test
67+
public void expressionPropertiesAreResolvedAgainstAppContextBeans() {
68+
StaticApplicationContext appContext = new StaticApplicationContext();
69+
RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
70+
bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A");
71+
appContext.registerBeanDefinition("role", bean);
72+
this.handler.setApplicationContext(appContext);
73+
EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class),
74+
mock(RequestAuthorizationContext.class));
75+
ExpressionParser parser = this.handler.getExpressionParser();
76+
assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
77+
assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
78+
}
79+
80+
@Test
81+
public void setTrustResolverNull() {
82+
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null));
83+
}
84+
85+
@Test
86+
public void createEvaluationContextCustomTrustResolver() {
87+
this.handler.setTrustResolver(this.trustResolver);
88+
Expression expression = this.handler.getExpressionParser().parseExpression("anonymous");
89+
EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.context);
90+
assertThat(expression.getValue(context, Boolean.class)).isFalse();
91+
verify(this.trustResolver).isAnonymous(this.authentication);
92+
}
93+
94+
}

0 commit comments

Comments
 (0)