Skip to content

Commit 281814a

Browse files
committed
Add MethodExpressionAuthorizationManager
Closes gh-11493
1 parent 51475e2 commit 281814a

File tree

3 files changed

+217
-1
lines changed

3 files changed

+217
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-2021 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.authorization;
18+
19+
import org.springframework.expression.Expression;
20+
21+
/**
22+
* Represents an {@link AuthorizationDecision} based on a {@link Expression}
23+
*
24+
* @author Marcus Da Coregio
25+
* @since 5.6
26+
*/
27+
public class ExpressionAuthorizationDecision extends AuthorizationDecision {
28+
29+
private final Expression expression;
30+
31+
public ExpressionAuthorizationDecision(boolean granted, Expression expressionAttribute) {
32+
super(granted);
33+
this.expression = expressionAttribute;
34+
}
35+
36+
public Expression getExpression() {
37+
return this.expression;
38+
}
39+
40+
@Override
41+
public String toString() {
42+
return getClass().getSimpleName() + " [" + "granted=" + isGranted() + ", expressionAttribute=" + this.expression
43+
+ ']';
44+
}
45+
46+
}

core/src/main/java/org/springframework/security/authorization/method/MethodExpressionAuthorizationManager.java

+67-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,72 @@
1616

1717
package org.springframework.security.authorization.method;
1818

19-
public class MethodExpressionAuthorizationManager {
19+
import java.util.function.Supplier;
20+
21+
import org.aopalliance.intercept.MethodInvocation;
22+
23+
import org.springframework.expression.EvaluationContext;
24+
import org.springframework.expression.Expression;
25+
import org.springframework.security.access.expression.ExpressionUtils;
26+
import org.springframework.security.access.expression.SecurityExpressionHandler;
27+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
28+
import org.springframework.security.authorization.AuthorizationDecision;
29+
import org.springframework.security.authorization.AuthorizationManager;
30+
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
31+
import org.springframework.security.core.Authentication;
32+
import org.springframework.util.Assert;
33+
34+
/**
35+
* An expression-based {@link AuthorizationManager} that determines the access by
36+
* evaluating the provided expression against the {@link MethodInvocation}.
37+
*
38+
* @author Josh Cummings
39+
* @since 5.8
40+
*/
41+
public final class MethodExpressionAuthorizationManager implements AuthorizationManager<MethodInvocation> {
42+
43+
private SecurityExpressionHandler<MethodInvocation> expressionHandler = new DefaultMethodSecurityExpressionHandler();
44+
45+
private Expression expression;
46+
47+
/**
48+
* Creates an instance.
49+
* @param expressionString the raw expression string to parse
50+
*/
51+
public MethodExpressionAuthorizationManager(String expressionString) {
52+
Assert.hasText(expressionString, "expressionString cannot be empty");
53+
this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
54+
}
55+
56+
/**
57+
* Sets the {@link SecurityExpressionHandler} to be used. The default is
58+
* {@link DefaultMethodSecurityExpressionHandler}.
59+
* @param expressionHandler the {@link SecurityExpressionHandler} to use
60+
*/
61+
public void setExpressionHandler(SecurityExpressionHandler<MethodInvocation> expressionHandler) {
62+
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
63+
this.expressionHandler = expressionHandler;
64+
this.expression = expressionHandler.getExpressionParser()
65+
.parseExpression(this.expression.getExpressionString());
66+
}
67+
68+
/**
69+
* Determines the access by evaluating the provided expression.
70+
* @param authentication the {@link Supplier} of the {@link Authentication} to check
71+
* @param context the {@link MethodInvocation} to check
72+
* @return an {@link ExpressionAuthorizationDecision} based on the evaluated
73+
* expression
74+
*/
75+
@Override
76+
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation context) {
77+
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context);
78+
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
79+
return new ExpressionAuthorizationDecision(granted, this.expression);
80+
}
81+
82+
@Override
83+
public String toString() {
84+
return "WebExpressionAuthorizationManager[expression='" + this.expression + "']";
85+
}
2086

2187
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2002-2022 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.authorization.method;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.platform.commons.util.ReflectionUtils;
21+
22+
import org.springframework.expression.Expression;
23+
import org.springframework.expression.ExpressionParser;
24+
import org.springframework.security.access.annotation.BusinessService;
25+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
26+
import org.springframework.security.authentication.TestAuthentication;
27+
import org.springframework.security.authorization.AuthorizationDecision;
28+
import org.springframework.security.util.SimpleMethodInvocation;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
32+
import static org.mockito.BDDMockito.given;
33+
import static org.mockito.Mockito.mock;
34+
import static org.mockito.Mockito.verify;
35+
36+
class MethodExpressionAuthorizationManagerTests {
37+
38+
@Test
39+
void instantiateWhenExpressionStringNullThenIllegalArgumentException() {
40+
assertThatIllegalArgumentException().isThrownBy(() -> new MethodExpressionAuthorizationManager(null))
41+
.withMessage("expressionString cannot be empty");
42+
}
43+
44+
@Test
45+
void instantiateWhenExpressionStringEmptyThenIllegalArgumentException() {
46+
assertThatIllegalArgumentException().isThrownBy(() -> new MethodExpressionAuthorizationManager(""))
47+
.withMessage("expressionString cannot be empty");
48+
}
49+
50+
@Test
51+
void instantiateWhenExpressionStringBlankThenIllegalArgumentException() {
52+
assertThatIllegalArgumentException().isThrownBy(() -> new MethodExpressionAuthorizationManager(" "))
53+
.withMessage("expressionString cannot be empty");
54+
}
55+
56+
@Test
57+
void instantiateWhenExpressionHandlerNotSetThenDefaultUsed() {
58+
MethodExpressionAuthorizationManager manager = new MethodExpressionAuthorizationManager("hasRole('ADMIN')");
59+
assertThat(manager).extracting("expressionHandler").isInstanceOf(DefaultMethodSecurityExpressionHandler.class);
60+
}
61+
62+
@Test
63+
void setExpressionHandlerWhenNullThenIllegalArgumentException() {
64+
MethodExpressionAuthorizationManager manager = new MethodExpressionAuthorizationManager("hasRole('ADMIN')");
65+
assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
66+
.withMessage("expressionHandler cannot be null");
67+
}
68+
69+
@Test
70+
void setExpressionHandlerWhenNotNullThenVerifyExpressionHandler() {
71+
String expressionString = "hasRole('ADMIN')";
72+
MethodExpressionAuthorizationManager manager = new MethodExpressionAuthorizationManager(expressionString);
73+
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
74+
ExpressionParser mockExpressionParser = mock(ExpressionParser.class);
75+
Expression mockExpression = mock(Expression.class);
76+
given(mockExpressionParser.parseExpression(expressionString)).willReturn(mockExpression);
77+
expressionHandler.setExpressionParser(mockExpressionParser);
78+
manager.setExpressionHandler(expressionHandler);
79+
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
80+
assertThat(manager).extracting("expression").isEqualTo(mockExpression);
81+
verify(mockExpressionParser).parseExpression(expressionString);
82+
}
83+
84+
@Test
85+
void checkWhenExpressionHasRoleAdminConfiguredAndRoleAdminThenGrantedDecision() {
86+
MethodExpressionAuthorizationManager manager = new MethodExpressionAuthorizationManager("hasRole('ADMIN')");
87+
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
88+
new SimpleMethodInvocation(new Object(),
89+
ReflectionUtils.getRequiredMethod(BusinessService.class, "someAdminMethod")));
90+
assertThat(decision).isNotNull();
91+
assertThat(decision.isGranted()).isTrue();
92+
}
93+
94+
@Test
95+
void checkWhenExpressionHasRoleAdminConfiguredAndRoleUserThenDeniedDecision() {
96+
MethodExpressionAuthorizationManager manager = new MethodExpressionAuthorizationManager("hasRole('ADMIN')");
97+
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
98+
new SimpleMethodInvocation(new Object(),
99+
ReflectionUtils.getRequiredMethod(BusinessService.class, "someAdminMethod")));
100+
assertThat(decision).isNotNull();
101+
assertThat(decision.isGranted()).isFalse();
102+
}
103+
104+
}

0 commit comments

Comments
 (0)