Skip to content

Add AuthorizationManager that uses ExpressionHandler #11171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -396,6 +398,90 @@ public void getWhenAnyRequestAuthenticatedConfiguredAndUserLoggedInThenRespondsW
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}

@Test
public void getWhenExpressionHasRoleUserConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(ExpressionRoleUserConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.roles("USER"));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}

@Test
public void getWhenExpressionHasRoleUserConfiguredAndRoleIsAdminThenRespondsWithForbidden() throws Exception {
this.spring.register(ExpressionRoleUserConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithAdmin = get("/")
.with(user("user")
.roles("ADMIN"));
// @formatter:on
this.mvc.perform(requestWithAdmin).andExpect(status().isForbidden());
}

@Test
public void getWhenExpressionRoleUserOrAdminConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(ExpressionRoleUserOrAdminConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.roles("USER"));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}

@Test
public void getWhenExpressionRoleUserOrAdminConfiguredAndRoleIsAdminThenRespondsWithOk() throws Exception {
this.spring.register(ExpressionRoleUserOrAdminConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithAdmin = get("/")
.with(user("user")
.roles("ADMIN"));
// @formatter:on
this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
}

@Test
public void getWhenExpressionRoleUserOrAdminConfiguredAndRoleIsOtherThenRespondsWithForbidden() throws Exception {
this.spring.register(ExpressionRoleUserOrAdminConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithRoleOther = get("/")
.with(user("user")
.roles("OTHER"));
// @formatter:on
this.mvc.perform(requestWithRoleOther).andExpect(status().isForbidden());
}

@Test
public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
throws Exception {
this.spring.register(ExpressionIpAddressLocalhostConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestFromLocalhost = get("/")
.with(remoteAddress("127.0.0.1"));
// @formatter:on
this.mvc.perform(requestFromLocalhost).andExpect(status().isOk());
}

@Test
public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsOtherThenRespondsWithForbidden()
throws Exception {
this.spring.register(ExpressionIpAddressLocalhostConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestFromOtherHost = get("/")
.with(remoteAddress("192.168.0.1"));
// @formatter:on
this.mvc.perform(requestFromOtherHost).andExpect(status().isForbidden());
}

private static RequestPostProcessor remoteAddress(String remoteAddress) {
return (request) -> {
request.setRemoteAddr(remoteAddress);
return request;
};
}

@EnableWebSecurity
static class NoRequestsConfig {

Expand Down Expand Up @@ -714,6 +800,54 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

}

@EnableWebSecurity
static class ExpressionRoleUserConfig {

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((requests) -> requests
.anyRequest().access(new WebExpressionAuthorizationManager("hasRole('USER')"))
)
.build();
// @formatter:on
}

}

@EnableWebSecurity
static class ExpressionRoleUserOrAdminConfig {

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((requests) -> requests
.anyRequest().access(new WebExpressionAuthorizationManager("hasRole('USER') or hasRole('ADMIN')"))
)
.build();
// @formatter:on
}

}

@EnableWebSecurity
static class ExpressionIpAddressLocalhostConfig {

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((requests) -> requests
.anyRequest().access(new WebExpressionAuthorizationManager("hasIpAddress('127.0.0.1')"))
)
.build();
// @formatter:on
}

}

@Configuration
static class AuthorizationEventPublisherConfig {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2002-2022 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.springframework.security.access.expression.AbstractSecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.util.Assert;

/**
* A {@link SecurityExpressionHandler} that uses a {@link RequestAuthorizationContext} to
* create a {@link WebSecurityExpressionRoot}.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpressionHandler<RequestAuthorizationContext>
implements SecurityExpressionHandler<RequestAuthorizationContext> {

private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

private String defaultRolePrefix = "ROLE_";

@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
RequestAuthorizationContext context) {
WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, context.getRequest());
root.setRoleHierarchy(getRoleHierarchy());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setDefaultRolePrefix(this.defaultRolePrefix);
return root;
}

/**
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
* {@link AuthenticationTrustResolverImpl}.
* @param trustResolver the {@link AuthenticationTrustResolver} to use
*/
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
}

/**
* Sets the default prefix to be added to
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...)}
* or
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String)}.
* For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN") is passed in, then the
* role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default).
* @param defaultRolePrefix the default prefix to add to roles. The default is
* "ROLE_".
*/
public void setDefaultRolePrefix(String defaultRolePrefix) {
Assert.hasText(defaultRolePrefix, "defaultRolePrefix cannot be empty");
this.defaultRolePrefix = defaultRolePrefix;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2002-2022 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.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationDecision;

/**
* An expression-based {@link AuthorizationDecision}.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class ExpressionAuthorizationDecision extends AuthorizationDecision {

private final Expression expression;

/**
* Creates an instance.
* @param granted the decision to use
* @param expression the {@link Expression} to use
*/
public ExpressionAuthorizationDecision(boolean granted, Expression expression) {
super(granted);
this.expression = expression;
}

/**
* Returns the {@link Expression}.
* @return the {@link Expression} to use
*/
public Expression getExpression() {
return this.expression;
}

@Override
public String toString() {
return "ExpressionAuthorizationDecision[granted=" + isGranted() + ", expression='" + this.expression + "']";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2002-2022 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 java.util.function.Supplier;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.util.Assert;

/**
* An expression-based {@link AuthorizationManager} that determines the access by
* evaluating the provided expression.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class WebExpressionAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

private SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler = new DefaultHttpSecurityExpressionHandler();

private Expression expression;

/**
* Creates an instance.
* @param expressionString the raw expression string to parse
*/
public WebExpressionAuthorizationManager(String expressionString) {
Assert.hasText(expressionString, "expressionString cannot be empty");
this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
}

/**
* Sets the {@link SecurityExpressionHandler} to be used. The default is
* {@link DefaultHttpSecurityExpressionHandler}.
* @param expressionHandler the {@link SecurityExpressionHandler} to use
*/
public void setExpressionHandler(SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
this.expression = expressionHandler.getExpressionParser()
.parseExpression(this.expression.getExpressionString());
}

/**
* Determines the access by evaluating the provided expression.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param context the {@link RequestAuthorizationContext} to check
* @return an {@link ExpressionAuthorizationDecision} based on the evaluated
* expression
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), context);
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
return new ExpressionAuthorizationDecision(granted, this.expression);
}

@Override
public String toString() {
return "WebExpressionAuthorizationManager[expression='" + this.expression + "']";
}

}
Loading