Revert "Support SpEL Returning AuthorizationDecision"

This reverts commit 77f2977c55.
This commit is contained in:
Josh Cummings
2024-04-04 11:31:45 -06:00
parent 77f2977c55
commit 0a9c482f62
28 changed files with 199 additions and 520 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2016 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.
@@ -19,43 +19,12 @@ package org.springframework.security.access.expression;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
public final class ExpressionUtils {
private ExpressionUtils() {
}
/**
* Evaluate a SpEL expression and coerce into an {@link AuthorizationDecision}
* @param expr a SpEL expression
* @param ctx an {@link EvaluationContext}
* @return the resulting {@link AuthorizationDecision}
* @since 6.3
*/
public static AuthorizationResult evaluate(Expression expr, EvaluationContext ctx) {
try {
Object result = expr.getValue(ctx);
if (result instanceof AuthorizationResult decision) {
return decision;
}
if (result instanceof Boolean granted) {
return new ExpressionAuthorizationDecision(granted, expr);
}
if (result == null) {
return null;
}
throw new IllegalArgumentException(
"SpEL expression must return either a Boolean or an AuthorizationDecision");
}
catch (EvaluationException ex) {
throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
ex);
}
}
public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
try {
return expr.getValue(ctx, Boolean.class);

View File

@@ -21,17 +21,11 @@ import java.util.function.Supplier;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
@@ -42,8 +36,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware,
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware {
private final ObservationRegistry registry;
@@ -53,19 +46,9 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
}
@Override
@@ -115,15 +98,4 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
this.messages = new MessageSourceAccessor(messageSource);
}
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult);
}
@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
}
}

View File

@@ -20,15 +20,9 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -38,8 +32,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>,
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
private final ObservationRegistry registry;
@@ -47,20 +40,10 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
ReactiveAuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
}
@Override
@@ -98,15 +81,4 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
this.convention = convention;
}
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult);
}
@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
}
}

View File

@@ -184,7 +184,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
}
private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(mi, decision);
}
return this.defaultPostProcessor.postProcessResult(mi, decision);

View File

@@ -159,7 +159,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
return Mono.just(methodInvocationResult.getResult());
}
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
}
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);

View File

@@ -257,7 +257,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
}
private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, decision);
}
return this.defaultHandler.handle(mi, decision);

View File

@@ -162,7 +162,7 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocation mi) {
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, decision);
}
return this.defaultHandler.handle(mi, decision);

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2024 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.authorization.method;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import org.springframework.util.Assert;
class PostAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision
implements MethodAuthorizationDeniedPostProcessor {
private final MethodAuthorizationDeniedPostProcessor postProcessor;
PostAuthorizeAuthorizationDecision(boolean granted, Expression expression,
MethodAuthorizationDeniedPostProcessor postProcessor) {
super(granted, expression);
Assert.notNull(postProcessor, "postProcessor cannot be null");
this.postProcessor = postProcessor;
}
@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) {
return this.postProcessor.postProcessResult(methodInvocationResult, result);
}
}

View File

@@ -27,7 +27,6 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
/**
@@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication;
* @author Evgeniy Cheban
* @since 5.6
*/
public final class PostAuthorizeAuthorizationManager
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
@@ -90,18 +88,13 @@ public final class PostAuthorizeAuthorizationManager
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
expressionHandler.setReturnObject(mi.getResult(), ctx);
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
}
@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.getExpression(), ctx);
return new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(),
postAuthorizeAttribute.getPostProcessor());
}
}

View File

@@ -24,7 +24,6 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -38,7 +37,7 @@ import org.springframework.util.Assert;
* @since 5.8
*/
public final class PostAuthorizeReactiveAuthorizationManager
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
implements ReactiveAuthorizationManager<MethodInvocationResult> {
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
@@ -83,23 +82,15 @@ public final class PostAuthorizeReactiveAuthorizationManager
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return Mono.empty();
}
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
// @formatter:off
return authentication
.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
.cast(AuthorizationDecision.class);
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
.map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor()));
// @formatter:on
}
@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2024 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.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import org.springframework.util.Assert;
class PreAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision
implements MethodAuthorizationDeniedHandler {
private final MethodAuthorizationDeniedHandler handler;
PreAuthorizeAuthorizationDecision(boolean granted, Expression expression,
MethodAuthorizationDeniedHandler handler) {
super(granted, expression);
Assert.notNull(handler, "handler cannot be null");
this.handler = handler;
}
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
return this.handler.handle(methodInvocation, result);
}
}

View File

@@ -27,7 +27,6 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
/**
@@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication;
* @author Evgeniy Cheban
* @since 5.6
*/
public final class PreAuthorizeAuthorizationManager
implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
@@ -82,15 +80,11 @@ public final class PreAuthorizeAuthorizationManager
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
}
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PreAuthorizeExpressionAttribute postAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.getExpression(), ctx);
return new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(),
preAuthorizeAttribute.getHandler());
}
}

View File

@@ -24,7 +24,6 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -37,8 +36,7 @@ import org.springframework.util.Assert;
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PreAuthorizeReactiveAuthorizationManager
implements ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
@@ -81,19 +79,13 @@ public final class PreAuthorizeReactiveAuthorizationManager
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return Mono.empty();
}
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
// @formatter:off
return authentication
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi))
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
.cast(AuthorizationDecision.class);
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
.map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler()));
// @formatter:on
}
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
return preAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
}
}

View File

@@ -21,8 +21,6 @@ import reactor.core.publisher.Mono;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
/**
* For internal use only, as this contract is likely to change.
@@ -32,33 +30,6 @@ import org.springframework.security.authorization.ExpressionAuthorizationDecisio
*/
final class ReactiveExpressionUtils {
static Mono<AuthorizationResult> evaluate(Expression expr, EvaluationContext ctx) {
return Mono.defer(() -> {
Object value;
try {
value = expr.getValue(ctx);
}
catch (EvaluationException ex) {
return Mono.error(() -> new IllegalArgumentException(
"Failed to evaluate expression '" + expr.getExpressionString() + "'", ex));
}
if (value instanceof Mono<?> mono) {
return mono.flatMap((data) -> adapt(expr, data));
}
return adapt(expr, value);
});
}
private static Mono<AuthorizationResult> adapt(Expression expr, Object value) {
if (value instanceof Boolean granted) {
return Mono.just(new ExpressionAuthorizationDecision(granted, expr));
}
if (value instanceof AuthorizationResult decision) {
return Mono.just(decision);
}
return createInvalidReturnTypeMono(expr);
}
static Mono<Boolean> evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
return Mono.defer(() -> {
Object value;
@@ -85,9 +56,9 @@ final class ReactiveExpressionUtils {
});
}
private static <T> Mono<T> createInvalidReturnTypeMono(Expression expr) {
return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString()
+ "' must return boolean, Mono<Boolean>, AuthorizationResult, or Mono<AuthorizationResult>"));
private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
return Mono.error(() -> new IllegalStateException(
"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
}
private ReactiveExpressionUtils() {

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2002-2024 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.access.expression;
import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import static org.assertj.core.api.Assertions.assertThat;
public class ExpressionUtilsTests {
private final Object details = new Object();
@Test
public void evaluateWhenAuthorizationDecisionThenReturns() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root.returnDecision()");
StandardEvaluationContext context = new StandardEvaluationContext(this);
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(AuthorizationDecisionDetails.class)
.extracting("details")
.isEqualTo(this.details);
}
@Test
public void evaluateWhenBooleanThenReturnsExpressionAuthorizationDecision() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root.returnResult()");
StandardEvaluationContext context = new StandardEvaluationContext(this);
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class);
}
public AuthorizationDecision returnDecision() {
return new AuthorizationDecisionDetails(false, this.details);
}
public boolean returnResult() {
return false;
}
static final class AuthorizationDecisionDetails extends AuthorizationDecision {
final Object details;
AuthorizationDecisionDetails(boolean granted, Object details) {
super(granted);
this.details = details;
}
}
}

View File

@@ -19,15 +19,16 @@ package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
@@ -124,10 +125,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), any()))
.will((invocation) -> Mono.just(createDecision(new MaskingPostProcessor())));
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -143,16 +144,15 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(0);
if (!"john".equals(argument.getResult())) {
return monoMasking(invocation);
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), any())).willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(1);
if ("john".equals(argument.getResult())) {
return Mono.just(new AuthorizationDecision(true));
}
return Mono.just(argument.getResult());
return Mono.just(createDecision(new MaskingPostProcessor()));
});
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -168,10 +168,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
new LiteralExpression("1234"), new MaskingPostProcessor());
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -186,10 +187,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -204,10 +206,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
new LiteralExpression("1234"), new NullPostProcessor());
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -235,18 +238,34 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
verify(mockReactiveAuthorizationManager).check(any(), any());
}
private Object masking(InvocationOnMock invocation) {
MethodInvocationResult result = invocation.getArgument(0);
return result.getResult() + "-masked";
private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) {
return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor);
}
private Object monoMasking(InvocationOnMock invocation) {
MethodInvocationResult result = invocation.getArgument(0);
return Mono.just(result.getResult() + "-masked");
static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
@Override
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
return contextObject.getResult() + "-masked";
}
}
interface HandlingReactiveAuthorizationManager
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
@Override
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
return Mono.just(contextObject.getResult() + "-masked");
}
}
static class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor {
@Override
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
return null;
}
}

View File

@@ -23,10 +23,12 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
@@ -123,10 +125,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***");
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
new LiteralExpression("1234"), new MaskingPostProcessor());
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -141,10 +144,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -159,10 +163,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -209,8 +214,21 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
interface HandlingReactiveAuthorizationManager
extends ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
static class MaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
return "***";
}
}
static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
return Mono.just("***");
}
}