6 changed files with 471 additions and 3 deletions
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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 + "']"; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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 + "']"; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,105 @@ |
|||||||
|
/* |
||||||
|
* 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.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.expression.Expression; |
||||||
|
import org.springframework.expression.ExpressionParser; |
||||||
|
import org.springframework.mock.web.MockHttpServletRequest; |
||||||
|
import org.springframework.security.authentication.TestAuthentication; |
||||||
|
import org.springframework.security.authorization.AuthorizationDecision; |
||||||
|
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.BDDMockito.given; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link WebExpressionAuthorizationManager}. |
||||||
|
* |
||||||
|
* @author Evgeniy Cheban |
||||||
|
*/ |
||||||
|
class WebExpressionAuthorizationManagerTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
void instantiateWhenExpressionStringNullThenIllegalArgumentException() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new WebExpressionAuthorizationManager(null)) |
||||||
|
.withMessage("expressionString cannot be empty"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void instantiateWhenExpressionStringEmptyThenIllegalArgumentException() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new WebExpressionAuthorizationManager("")) |
||||||
|
.withMessage("expressionString cannot be empty"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void instantiateWhenExpressionStringBlankThenIllegalArgumentException() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new WebExpressionAuthorizationManager(" ")) |
||||||
|
.withMessage("expressionString cannot be empty"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void instantiateWhenExpressionHandlerNotSetThenDefaultUsed() { |
||||||
|
WebExpressionAuthorizationManager manager = new WebExpressionAuthorizationManager("hasRole('ADMIN')"); |
||||||
|
assertThat(manager).extracting("expressionHandler").isInstanceOf(DefaultHttpSecurityExpressionHandler.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void setExpressionHandlerWhenNullThenIllegalArgumentException() { |
||||||
|
WebExpressionAuthorizationManager manager = new WebExpressionAuthorizationManager("hasRole('ADMIN')"); |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null)) |
||||||
|
.withMessage("expressionHandler cannot be null"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void setExpressionHandlerWhenNotNullThenVerifyExpressionHandler() { |
||||||
|
String expressionString = "hasRole('ADMIN')"; |
||||||
|
WebExpressionAuthorizationManager manager = new WebExpressionAuthorizationManager(expressionString); |
||||||
|
DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler(); |
||||||
|
ExpressionParser mockExpressionParser = mock(ExpressionParser.class); |
||||||
|
Expression mockExpression = mock(Expression.class); |
||||||
|
given(mockExpressionParser.parseExpression(expressionString)).willReturn(mockExpression); |
||||||
|
expressionHandler.setExpressionParser(mockExpressionParser); |
||||||
|
manager.setExpressionHandler(expressionHandler); |
||||||
|
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler); |
||||||
|
assertThat(manager).extracting("expression").isEqualTo(mockExpression); |
||||||
|
verify(mockExpressionParser).parseExpression(expressionString); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void checkWhenExpressionHasRoleAdminConfiguredAndRoleAdminThenGrantedDecision() { |
||||||
|
WebExpressionAuthorizationManager manager = new WebExpressionAuthorizationManager("hasRole('ADMIN')"); |
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, |
||||||
|
new RequestAuthorizationContext(new MockHttpServletRequest())); |
||||||
|
assertThat(decision).isNotNull(); |
||||||
|
assertThat(decision.isGranted()).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void checkWhenExpressionHasRoleAdminConfiguredAndRoleUserThenDeniedDecision() { |
||||||
|
WebExpressionAuthorizationManager manager = new WebExpressionAuthorizationManager("hasRole('ADMIN')"); |
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, |
||||||
|
new RequestAuthorizationContext(new MockHttpServletRequest())); |
||||||
|
assertThat(decision).isNotNull(); |
||||||
|
assertThat(decision.isGranted()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue