Add Authorization Denied Handlers for Method Security
Closes gh-14601
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* 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.
|
||||
@@ -23,6 +23,9 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||
|
||||
/**
|
||||
* Annotation for specifying a method access-control expression which will be evaluated
|
||||
* after a method has been invoked.
|
||||
@@ -42,4 +45,10 @@ public @interface PostAuthorize {
|
||||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* @return the {@link MethodAuthorizationDeniedPostProcessor} class used to
|
||||
* post-process access denied
|
||||
*/
|
||||
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* 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.
|
||||
@@ -23,6 +23,9 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||
|
||||
/**
|
||||
* Annotation for specifying a method access-control expression which will be evaluated to
|
||||
* decide whether a method invocation is allowed or not.
|
||||
@@ -42,4 +45,10 @@ public @interface PreAuthorize {
|
||||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* @return the {@link MethodAuthorizationDeniedHandler} class used to handle access
|
||||
* denied
|
||||
*/
|
||||
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AccessDeniedException} that contains the {@link AuthorizationResult}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
*/
|
||||
public class AuthorizationDeniedException extends AccessDeniedException {
|
||||
|
||||
private final AuthorizationResult result;
|
||||
|
||||
public AuthorizationDeniedException(String msg, AuthorizationResult authorizationResult) {
|
||||
super(msg);
|
||||
Assert.notNull(authorizationResult, "authorizationResult cannot be null");
|
||||
Assert.isTrue(!authorizationResult.isGranted(), "Granted authorization results are not supported");
|
||||
this.result = authorizationResult;
|
||||
}
|
||||
|
||||
public AuthorizationResult getAuthorizationResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -55,6 +55,8 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
||||
|
||||
private final AuthorizationManager<MethodInvocationResult> authorizationManager;
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
private int order;
|
||||
|
||||
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish;
|
||||
@@ -116,8 +118,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
Object result = mi.proceed();
|
||||
attemptAuthorization(mi, result);
|
||||
return result;
|
||||
return attemptAuthorization(mi, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,7 +169,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
||||
this.securityContextHolderStrategy = () -> strategy;
|
||||
}
|
||||
|
||||
private void attemptAuthorization(MethodInvocation mi, Object result) {
|
||||
private Object attemptAuthorization(MethodInvocation mi, Object result) {
|
||||
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
|
||||
MethodInvocationResult object = new MethodInvocationResult(mi, result);
|
||||
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
|
||||
@@ -176,9 +177,17 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
||||
if (decision != null && !decision.isGranted()) {
|
||||
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
|
||||
+ this.authorizationManager + " and decision " + decision));
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
return postProcess(object, decision);
|
||||
}
|
||||
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
|
||||
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
return postProcessableDecision.postProcessResult(mi, decision);
|
||||
}
|
||||
return this.defaultPostProcessor.postProcessResult(mi, decision);
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -57,6 +58,8 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
||||
|
||||
private int order = AuthorizationInterceptorsOrder.LAST.getOrder();
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
/**
|
||||
* Creates an instance for the {@link PostAuthorize} annotation.
|
||||
* @return the {@link AuthorizationManagerAfterReactiveMethodInterceptor} to use
|
||||
@@ -144,9 +147,28 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
||||
return adapter != null && adapter.isMultiValue();
|
||||
}
|
||||
|
||||
private Mono<?> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) {
|
||||
return this.authorizationManager.verify(authentication, new MethodInvocationResult(mi, result))
|
||||
.thenReturn(result);
|
||||
private Mono<Object> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) {
|
||||
MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result);
|
||||
return this.authorizationManager.check(authentication, invocationResult)
|
||||
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
|
||||
.flatMap((decision) -> postProcess(decision, invocationResult));
|
||||
}
|
||||
|
||||
private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocationResult methodInvocationResult) {
|
||||
if (decision.isGranted()) {
|
||||
return Mono.just(methodInvocationResult.getResult());
|
||||
}
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
|
||||
}
|
||||
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);
|
||||
}).flatMap((processedResult) -> {
|
||||
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
|
||||
return (Mono<?>) processedResult;
|
||||
}
|
||||
return Mono.justOrEmpty(processedResult);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -59,6 +59,8 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
|
||||
|
||||
private final AuthorizationManager<MethodInvocation> authorizationManager;
|
||||
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
|
||||
|
||||
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish;
|
||||
@@ -190,8 +192,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
attemptAuthorization(mi);
|
||||
return mi.proceed();
|
||||
return attemptAuthorization(mi);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -242,16 +243,24 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
|
||||
this.securityContextHolderStrategy = () -> securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
private void attemptAuthorization(MethodInvocation mi) {
|
||||
private Object attemptAuthorization(MethodInvocation mi) throws Throwable {
|
||||
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
|
||||
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, mi);
|
||||
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, decision);
|
||||
if (decision != null && !decision.isGranted()) {
|
||||
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
|
||||
+ this.authorizationManager + " and decision " + decision));
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
return handle(mi, decision);
|
||||
}
|
||||
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
|
||||
return mi.proceed();
|
||||
}
|
||||
|
||||
private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
|
||||
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handle(mi, decision);
|
||||
}
|
||||
return this.defaultHandler.handle(mi, decision);
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -57,6 +58,8 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
|
||||
|
||||
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
|
||||
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
/**
|
||||
* Creates an instance for the {@link PreAuthorize} annotation.
|
||||
* @return the {@link AuthorizationManagerBeforeReactiveMethodInterceptor} to use
|
||||
@@ -112,31 +115,65 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
|
||||
+ " must return an instance of org.reactivestreams.Publisher "
|
||||
+ "(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
|
||||
+ "in order to support Reactor Context");
|
||||
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
|
||||
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type);
|
||||
Mono<Void> preAuthorize = this.authorizationManager.verify(authentication, mi);
|
||||
if (hasFlowReturnType) {
|
||||
if (isSuspendingFunction) {
|
||||
return preAuthorize.thenMany(Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
|
||||
return preAuthorized(mi, Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
|
||||
}
|
||||
else {
|
||||
Assert.state(adapter != null, () -> "The returnType " + type + " on " + method
|
||||
+ " must have a org.springframework.core.ReactiveAdapter registered");
|
||||
Flux<?> response = preAuthorize
|
||||
.thenMany(Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))));
|
||||
Flux<Object> response = preAuthorized(mi,
|
||||
Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))));
|
||||
return KotlinDelegate.asFlow(response);
|
||||
}
|
||||
}
|
||||
if (isMultiValue(type, adapter)) {
|
||||
Publisher<?> publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi));
|
||||
Flux<?> result = preAuthorize.thenMany(publisher);
|
||||
Flux<?> result = preAuthorized(mi, Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
|
||||
return (adapter != null) ? adapter.fromPublisher(result) : result;
|
||||
}
|
||||
Mono<?> publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi));
|
||||
Mono<?> result = preAuthorize.then(publisher);
|
||||
Mono<?> result = preAuthorized(mi, Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
|
||||
return (adapter != null) ? adapter.fromPublisher(result) : result;
|
||||
}
|
||||
|
||||
private Flux<Object> preAuthorized(MethodInvocation mi, Flux<Object> mapping) {
|
||||
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
|
||||
return this.authorizationManager.check(authentication, mi)
|
||||
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
|
||||
.flatMapMany((decision) -> {
|
||||
if (decision.isGranted()) {
|
||||
return mapping;
|
||||
}
|
||||
return postProcess(decision, mi);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Object> preAuthorized(MethodInvocation mi, Mono<Object> mapping) {
|
||||
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
|
||||
return this.authorizationManager.check(authentication, mi)
|
||||
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
|
||||
.flatMap((decision) -> {
|
||||
if (decision.isGranted()) {
|
||||
return mapping;
|
||||
}
|
||||
return postProcess(decision, mi);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocation mi) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handle(mi, decision);
|
||||
}
|
||||
return this.defaultHandler.handle(mi, decision);
|
||||
}).flatMap((result) -> {
|
||||
if (Mono.class.isAssignableFrom(result.getClass())) {
|
||||
return (Mono<?>) result;
|
||||
}
|
||||
return Mono.justOrEmpty(result);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) {
|
||||
if (Flux.class.isAssignableFrom(returnType)) {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.lang.Nullable;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
/**
|
||||
* An interface used to define a strategy to handle denied method invocations
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
* @see org.springframework.security.access.prepost.PreAuthorize
|
||||
*/
|
||||
public interface MethodAuthorizationDeniedHandler {
|
||||
|
||||
/**
|
||||
* Handle denied method invocations, implementations might either throw an
|
||||
* {@link org.springframework.security.access.AccessDeniedException} or a replacement
|
||||
* result instead of invoking the method, e.g. a masked value.
|
||||
* @param methodInvocation the {@link MethodInvocation} related to the authorization
|
||||
* denied
|
||||
* @param authorizationResult the authorization denied result
|
||||
* @return a replacement result for the denied method invocation, or null, or a
|
||||
* {@link reactor.core.publisher.Mono} for reactive applications
|
||||
*/
|
||||
@Nullable
|
||||
Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.lang.Nullable;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
/**
|
||||
* An interface to define a strategy to handle denied method invocation results
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
* @see org.springframework.security.access.prepost.PostAuthorize
|
||||
*/
|
||||
public interface MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
/**
|
||||
* Post-process the denied result produced by a method invocation, implementations
|
||||
* might either throw an
|
||||
* {@link org.springframework.security.access.AccessDeniedException} or return a
|
||||
* replacement result instead of the denied result, e.g. a masked value.
|
||||
* @param methodInvocationResult the object containing the method invocation and the
|
||||
* result produced
|
||||
* @param authorizationResult the {@link AuthorizationResult} containing the
|
||||
* authorization denied details
|
||||
* @return a replacement result for the denied result, or null, or a
|
||||
* {@link reactor.core.publisher.Mono} for reactive applications
|
||||
*/
|
||||
@Nullable
|
||||
Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,13 +20,13 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.security.access.expression.ExpressionUtils;
|
||||
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.AuthorizationManager;
|
||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,18 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
||||
this.registry.setTemplateDefaults(defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes
|
||||
* {@link PostAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)}
|
||||
* with the provided {@link ApplicationContext}.
|
||||
* @param context the {@link ApplicationContext}
|
||||
* @since 6.3
|
||||
* @see PreAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)
|
||||
*/
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.registry.setApplicationContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an {@link Authentication} has access to the returned object by
|
||||
* evaluating the {@link PostAuthorize} annotation that the {@link MethodInvocation}
|
||||
@@ -76,11 +88,13 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
||||
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);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.getExpression(), ctx);
|
||||
return new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(),
|
||||
postAuthorizeAttribute.getPostProcessor());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link ExpressionAttribute} that carries additional properties for
|
||||
* {@code @PostAuthorize}.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
*/
|
||||
class PostAuthorizeExpressionAttribute extends ExpressionAttribute {
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor postProcessor;
|
||||
|
||||
PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedPostProcessor postProcessor) {
|
||||
super(expression);
|
||||
Assert.notNull(postProcessor, "postProcessor cannot be null");
|
||||
this.postProcessor = postProcessor;
|
||||
}
|
||||
|
||||
MethodAuthorizationDeniedPostProcessor getPostProcessor() {
|
||||
return this.postProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,13 +18,16 @@ package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.util.annotation.NonNull;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* For internal use only, as this contract is likely to change.
|
||||
@@ -35,6 +38,14 @@ import org.springframework.security.access.prepost.PostAuthorize;
|
||||
*/
|
||||
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
private Function<Class<? extends MethodAuthorizationDeniedPostProcessor>, MethodAuthorizationDeniedPostProcessor> postProcessorResolver;
|
||||
|
||||
PostAuthorizeExpressionAttributeRegistry() {
|
||||
this.postProcessorResolver = (clazz) -> this.defaultPostProcessor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||
@@ -44,7 +55,9 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
||||
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||
}
|
||||
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
|
||||
return new ExpressionAttribute(expression);
|
||||
MethodAuthorizationDeniedPostProcessor postProcessor = this.postProcessorResolver
|
||||
.apply(postAuthorize.postProcessorClass());
|
||||
return new PostAuthorizeExpressionAttribute(expression, postProcessor);
|
||||
}
|
||||
|
||||
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||
@@ -53,4 +66,30 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
||||
return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided {@link ApplicationContext} to resolve the
|
||||
* {@link MethodAuthorizationDeniedPostProcessor} from {@link PostAuthorize}
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
*/
|
||||
void setApplicationContext(ApplicationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
this.postProcessorResolver = (postProcessorClass) -> resolvePostProcessor(context, postProcessorClass);
|
||||
}
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(ApplicationContext context,
|
||||
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass) {
|
||||
if (postProcessorClass == this.defaultPostProcessor.getClass()) {
|
||||
return this.defaultPostProcessor;
|
||||
}
|
||||
String[] beanNames = context.getBeanNamesForType(postProcessorClass);
|
||||
if (beanNames.length == 0) {
|
||||
throw new IllegalStateException("Could not find a bean of type " + postProcessorClass.getName());
|
||||
}
|
||||
if (beanNames.length > 1) {
|
||||
throw new IllegalStateException("Expected to find a single bean of type " + postProcessorClass.getName()
|
||||
+ " but found " + Arrays.toString(beanNames));
|
||||
}
|
||||
return context.getBean(beanNames[0], postProcessorClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.security.authorization.method;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
@@ -61,6 +62,10 @@ public final class PostAuthorizeReactiveAuthorizationManager
|
||||
this.registry.setTemplateDefaults(defaults);
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.registry.setApplicationContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an {@link Authentication} has access to the returned object from the
|
||||
* {@link MethodInvocation} by evaluating an expression from the {@link PostAuthorize}
|
||||
@@ -77,13 +82,14 @@ 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.evaluateAsBoolean(attribute.getExpression(), ctx))
|
||||
.map((granted) -> new ExpressionAttributeAuthorizationDecision(granted, attribute));
|
||||
.map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor()));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,13 +20,13 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.security.access.expression.ExpressionUtils;
|
||||
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.AuthorizationManager;
|
||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,10 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
||||
this.registry.setTemplateDefaults(defaults);
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.registry.setApplicationContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an {@link Authentication} has access to a method by evaluating an
|
||||
* expression from the {@link PreAuthorize} annotation that the
|
||||
@@ -76,9 +80,11 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return null;
|
||||
}
|
||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.getExpression(), ctx);
|
||||
return new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(),
|
||||
preAuthorizeAttribute.getHandler());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link ExpressionAttribute} that carries additional properties for
|
||||
* {@code @PreAuthorize}.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
*/
|
||||
class PreAuthorizeExpressionAttribute extends ExpressionAttribute {
|
||||
|
||||
private final MethodAuthorizationDeniedHandler handler;
|
||||
|
||||
PreAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) {
|
||||
super(expression);
|
||||
Assert.notNull(handler, "handler cannot be null");
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
MethodAuthorizationDeniedHandler getHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,13 +18,16 @@ package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.util.annotation.NonNull;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* For internal use only, as this contract is likely to change.
|
||||
@@ -35,6 +38,14 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
*/
|
||||
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
|
||||
|
||||
PreAuthorizeExpressionAttributeRegistry() {
|
||||
this.handlerResolver = (clazz) -> this.defaultHandler;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||
@@ -44,7 +55,8 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
||||
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||
}
|
||||
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
|
||||
return new ExpressionAttribute(expression);
|
||||
MethodAuthorizationDeniedHandler handler = this.handlerResolver.apply(preAuthorize.handlerClass());
|
||||
return new PreAuthorizeExpressionAttribute(expression, handler);
|
||||
}
|
||||
|
||||
private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||
@@ -53,4 +65,30 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
||||
return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided {@link ApplicationContext} to resolve the
|
||||
* {@link MethodAuthorizationDeniedHandler} from {@link PreAuthorize}.
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
*/
|
||||
void setApplicationContext(ApplicationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
|
||||
}
|
||||
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
|
||||
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
|
||||
if (handlerClass == this.defaultHandler.getClass()) {
|
||||
return this.defaultHandler;
|
||||
}
|
||||
String[] beanNames = context.getBeanNamesForType(handlerClass);
|
||||
if (beanNames.length == 0) {
|
||||
throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName());
|
||||
}
|
||||
if (beanNames.length > 1) {
|
||||
throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName()
|
||||
+ " but found " + Arrays.toString(beanNames));
|
||||
}
|
||||
return context.getBean(beanNames[0], handlerClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.security.authorization.method;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -60,6 +61,10 @@ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveA
|
||||
this.registry.setTemplateDefaults(defaults);
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.registry.setApplicationContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||
* by evaluating an expression from the {@link PreAuthorize} annotation.
|
||||
@@ -74,11 +79,12 @@ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveA
|
||||
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.evaluateAsBoolean(attribute.getExpression(), ctx))
|
||||
.map((granted) -> new ExpressionAttributeAuthorizationDecision(granted, attribute));
|
||||
.map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler()));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
/**
|
||||
* An implementation of {@link MethodAuthorizationDeniedHandler} that throws
|
||||
* {@link AuthorizationDeniedException}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
*/
|
||||
public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
throw new AuthorizationDeniedException("Access Denied", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
/**
|
||||
* An implementation of {@link MethodAuthorizationDeniedPostProcessor} that throws
|
||||
* {@link AuthorizationDeniedException}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
*/
|
||||
public final class ThrowingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) {
|
||||
throw new AuthorizationDeniedException("Access Denied", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -23,8 +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;
|
||||
@@ -66,14 +70,15 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.check(any(), any()))
|
||||
.willReturn(Mono.just(new AuthorizationDecision(true)));
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo("john");
|
||||
verify(mockReactiveAuthorizationManager).verify(any(), any());
|
||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,7 +88,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.check(any(), any()))
|
||||
.willReturn(Mono.just(new AuthorizationDecision(true)));
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
@@ -91,7 +97,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||
.extracting(Flux::collectList)
|
||||
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
|
||||
.containsExactly("john", "bob");
|
||||
verify(mockReactiveAuthorizationManager, times(2)).verify(any(), any());
|
||||
verify(mockReactiveAuthorizationManager, times(2)).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -101,8 +107,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.verify(any(), any()))
|
||||
.willReturn(Mono.error(new AccessDeniedException("Access Denied")));
|
||||
given(mockReactiveAuthorizationManager.check(any(), any()))
|
||||
.willReturn(Mono.just(new AuthorizationDecision(false)));
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
@@ -110,7 +116,157 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block))
|
||||
.withMessage("Access Denied");
|
||||
verify(mockReactiveAuthorizationManager).verify(any(), any());
|
||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeFluxWhenAllValuesDeniedAndPostProcessorThenPostProcessorAppliedToEachValueEmitted()
|
||||
throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
|
||||
.extracting(Flux::collectList)
|
||||
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
|
||||
.containsExactly("john-masked", "bob-masked");
|
||||
verify(mockReactiveAuthorizationManager, times(2)).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeFluxWhenOneValueDeniedAndPostProcessorThenPostProcessorAppliedToDeniedValue() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
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(createDecision(new MaskingPostProcessor()));
|
||||
});
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
|
||||
.extracting(Flux::collectList)
|
||||
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
|
||||
.containsExactly("john", "bob-masked");
|
||||
verify(mockReactiveAuthorizationManager, times(2)).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenPostProcessableDecisionThenPostProcess() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo("john-masked");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenPostProcessableDecisionAndPostProcessResultIsMonoThenPostProcessWorks() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo("john-masked");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenPostProcessableDecisionAndPostProcessResultIsNullThenPostProcessWorks() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo(null);
|
||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenEmptyDecisionThenUseDefaultPostProcessor() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
assertThatExceptionOfType(AuthorizationDeniedException.class)
|
||||
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block))
|
||||
.withMessage("Access Denied");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) {
|
||||
return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor);
|
||||
}
|
||||
|
||||
static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
return contextObject.getResult() + "-masked";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Sample {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -23,8 +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;
|
||||
@@ -67,14 +71,15 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation)))
|
||||
.willReturn(Mono.just(new AuthorizationDecision(true)));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo("john");
|
||||
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -84,7 +89,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation)))
|
||||
.willReturn(Mono.just(new AuthorizationDecision((true))));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
@@ -92,7 +98,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||
.extracting(Flux::collectList)
|
||||
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
|
||||
.containsExactly("john", "bob");
|
||||
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,8 +108,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation)))
|
||||
.willReturn(Mono.error(new AccessDeniedException("Access Denied")));
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation)))
|
||||
.willReturn(Mono.just(new AuthorizationDecision(false)));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
@@ -111,7 +117,119 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block))
|
||||
.withMessage("Access Denied");
|
||||
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenDeniedAndPostProcessorThenInvokePostProcessor() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo("***");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenDeniedAndMonoPostProcessorThenInvokePostProcessor() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block)
|
||||
.isEqualTo("***");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeFluxWhenDeniedAndPostProcessorThenInvokePostProcessor() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
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);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
|
||||
.extracting(Flux::collectList)
|
||||
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
|
||||
.containsExactly("***");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMonoWhenEmptyDecisionThenInvokeDefaultPostProcessor() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
assertThatExceptionOfType(AuthorizationDeniedException.class)
|
||||
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
|
||||
.extracting(Mono::block))
|
||||
.withMessage("Access Denied");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeFluxWhenEmptyDecisionThenInvokeDefaultPostProcessor() throws Throwable {
|
||||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
assertThatExceptionOfType(AuthorizationDeniedException.class)
|
||||
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
|
||||
.extracting(Flux::blockFirst))
|
||||
.withMessage("Access Denied");
|
||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
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("***");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Sample {
|
||||
|
||||
Reference in New Issue
Block a user