Skip to content

Commit 0814136

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

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
4646
import org.springframework.test.web.servlet.request.RequestPostProcessor;
4747
import org.springframework.web.bind.annotation.GetMapping;
48+
import org.springframework.web.bind.annotation.PathVariable;
4849
import org.springframework.web.bind.annotation.PostMapping;
50+
import org.springframework.web.bind.annotation.RequestMapping;
4951
import org.springframework.web.bind.annotation.RestController;
5052
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
5153

@@ -474,6 +476,15 @@ public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsOtherThen
474476
this.mvc.perform(requestFromOtherHost).andExpect(status().isForbidden());
475477
}
476478

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

848859
}
849860

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

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

Lines changed: 4 additions & 0 deletions
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)