1
0
mirror of synced 2026-05-22 21:33:16 +00:00

ReactiveAuthorizationManager + Reactive Method Security

Closes gh-9401
This commit is contained in:
Evgeniy Cheban
2021-06-04 03:02:47 +03:00
committed by Josh Cummings
parent 4ff0724c87
commit cbb4f40f0c
38 changed files with 3231 additions and 185 deletions
@@ -0,0 +1,76 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
/**
* A {@link MethodInterceptor} that wraps a {@link Mono} or a {@link Flux} using
* <code>deffer</code> call.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class AuthorizationAfterReactiveMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAllAnnotations();
private final int order = AuthorizationInterceptorsOrder.LAST.getOrder();
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Method method = mi.getMethod();
Class<?> returnType = method.getReturnType();
if (Mono.class.isAssignableFrom(returnType)) {
return Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi));
}
return Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi));
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public int getOrder() {
return this.order;
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
/**
* Adds {@link AuthorizationBeforeReactiveMethodInterceptor} and
* {@link AuthorizationAfterReactiveMethodInterceptor} bean definitions to the
* {@link BeanDefinitionRegistry} if they have not already been added.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class AuthorizationBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
private static final String BEFORE_INTERCEPTOR_BEAN_NAME = "org.springframework.security.authorization.method.authorizationBeforeReactiveMethodInterceptor";
private static final String AFTER_INTERCEPTOR_BEAN_NAME = "org.springframework.security.authorization.method.authorizationAfterReactiveMethodInterceptor";
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEFORE_INTERCEPTOR_BEAN_NAME)) {
RootBeanDefinition beforeInterceptor = new RootBeanDefinition(
AuthorizationBeforeReactiveMethodInterceptor.class);
beforeInterceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEFORE_INTERCEPTOR_BEAN_NAME, beforeInterceptor);
}
if (!registry.containsBeanDefinition(AFTER_INTERCEPTOR_BEAN_NAME)) {
RootBeanDefinition afterInterceptor = new RootBeanDefinition(
AuthorizationAfterReactiveMethodInterceptor.class);
afterInterceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AFTER_INTERCEPTOR_BEAN_NAME, afterInterceptor);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} which validates and transforms the return type for methods
* that return a {@link Publisher}.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class AuthorizationBeforeReactiveMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAllAnnotations();
private final int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Method method = mi.getMethod();
Class<?> returnType = method.getReturnType();
Assert.state(Publisher.class.isAssignableFrom(returnType),
() -> "The returnType " + returnType + " on " + method
+ " must return an instance of org.reactivestreams.Publisher "
+ "(i.e. Mono / Flux) or the function must be a Kotlin coroutine "
+ "function in order to support Reactor Context");
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(returnType);
return (adapter != null) ? adapter.fromPublisher(publisher) : publisher;
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public int getOrder() {
return this.order;
}
}
@@ -0,0 +1,146 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} which can determine if an {@link Authentication} has access
* to the returned object from the {@link MethodInvocation} using the configured
* {@link ReactiveAuthorizationManager}.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class AuthorizationManagerAfterReactiveMethodInterceptor implements Ordered, MethodInterceptor,
PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor {
private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor();
private final Pointcut pointcut;
private final ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager;
private int order = AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder();
/**
* Creates an instance for the {@link PostAuthorize} annotation.
* @return the {@link AuthorizationManagerAfterReactiveMethodInterceptor} to use
*/
public static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorize() {
return postAuthorize(new PostAuthorizeReactiveAuthorizationManager());
}
/**
* Creates an instance for the {@link PostAuthorize} annotation.
* @param authorizationManager the {@link ReactiveAuthorizationManager} to use
* @return the {@link AuthorizationManagerAfterReactiveMethodInterceptor} to use
*/
public static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorize(
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager) {
return new AuthorizationManagerAfterReactiveMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager);
}
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
* @param authorizationManager the {@link ReactiveAuthorizationManager} to use
*/
public AuthorizationManagerAfterReactiveMethodInterceptor(Pointcut pointcut,
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager) {
Assert.notNull(pointcut, "pointcut cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.pointcut = pointcut;
this.authorizationManager = authorizationManager;
}
/**
* Determines if an {@link Authentication} has access to the returned object from the
* {@link MethodInvocation} using the configured {@link ReactiveAuthorizationManager}.
* @param mi the {@link MethodInvocation} to use
* @return the {@link Publisher} from the {@link MethodInvocation} or a
* {@link Publisher} error if access is denied
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
if (publisher instanceof Mono<?>) {
Mono<?> mono = (Mono<?>) publisher;
return mono.flatMap((result) -> postAuthorize(authentication, mi, result));
}
return Flux.from(publisher).flatMap((result) -> postAuthorize(authentication, mi, result));
}
private Mono<?> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) {
return this.authorizationManager.verify(authentication, new MethodInvocationResult(mi, result))
.thenReturn(result);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
}
}
@@ -0,0 +1,140 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} which can determine if an {@link Authentication} has access
* to the {@link MethodInvocation} using the configured
* {@link ReactiveAuthorizationManager}.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class AuthorizationManagerBeforeReactiveMethodInterceptor implements Ordered, MethodInterceptor,
PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor {
private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor();
private final Pointcut pointcut;
private final ReactiveAuthorizationManager<MethodInvocation> authorizationManager;
private int order = AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder();
/**
* Creates an instance for the {@link PreAuthorize} annotation.
* @return the {@link AuthorizationManagerBeforeReactiveMethodInterceptor} to use
*/
public static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorize() {
return preAuthorize(new PreAuthorizeReactiveAuthorizationManager());
}
/**
* Creates an instance for the {@link PreAuthorize} annotation.
* @param authorizationManager the {@link ReactiveAuthorizationManager} to use
* @return the {@link AuthorizationManagerBeforeReactiveMethodInterceptor} to use
*/
public static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorize(
ReactiveAuthorizationManager<MethodInvocation> authorizationManager) {
return new AuthorizationManagerBeforeReactiveMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
}
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
* @param authorizationManager the {@link ReactiveAuthorizationManager} to use
*/
public AuthorizationManagerBeforeReactiveMethodInterceptor(Pointcut pointcut,
ReactiveAuthorizationManager<MethodInvocation> authorizationManager) {
Assert.notNull(pointcut, "pointcut cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.pointcut = pointcut;
this.authorizationManager = authorizationManager;
}
/**
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
* using the configured {@link ReactiveAuthorizationManager}.
* @param mi the {@link MethodInvocation} to use
* @return the {@link Publisher} from the {@link MethodInvocation} or a
* {@link Publisher} error if access is denied
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
Mono<Void> preAuthorize = this.authorizationManager.verify(authentication, mi);
if (publisher instanceof Mono<?>) {
return preAuthorize.then((Mono<?>) publisher);
}
return preAuthorize.thenMany(publisher);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,12 +22,21 @@ import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.Pointcuts;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
/**
* @author Josh Cummings
* @author Evgeniy Cheban
*/
final class AuthorizationMethodPointcuts {
static Pointcut forAllAnnotations() {
return forAnnotations(PreFilter.class, PreAuthorize.class, PostFilter.class, PostAuthorize.class);
}
@SafeVarargs
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
ComposablePointcut pointcut = null;
@@ -16,24 +16,18 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
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;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
@@ -47,15 +41,12 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Use this the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
this.registry.setExpressionHandler(expressionHandler);
}
/**
@@ -73,36 +64,11 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication,
mi.getMethodInvocation());
this.expressionHandler.setReturnObject(mi.getResult(), ctx);
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());
}
private final class PostAuthorizeExpressionAttributeRegistry
extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
if (postAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postAuthorizeExpression = PostAuthorizeAuthorizationManager.this.expressionHandler
.getExpressionParser().parseExpression(postAuthorize.value());
return new ExpressionAttribute(postAuthorizeExpression);
}
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method,
PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils
.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
if (postAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser()
.parseExpression(postAuthorize.value());
return new ExpressionAttribute(postAuthorizeExpression);
}
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
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.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
/**
* A {@link ReactiveAuthorizationManager} which can determine if an {@link Authentication}
* has access to the returned object from the {@link MethodInvocation} by evaluating an
* expression from the {@link PostAuthorize} annotation.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PostAuthorizeReactiveAuthorizationManager
implements ReactiveAuthorizationManager<MethodInvocationResult> {
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Determines if an {@link Authentication} has access to the returned object from the
* {@link MethodInvocation} by evaluating an expression from the {@link PostAuthorize}
* annotation.
* @param authentication the {@link Mono} of the {@link Authentication} to check
* @param result the {@link MethodInvocationResult} to check
* @return a Mono of the {@link AuthorizationDecision} or an empty {@link Mono} if the
* {@link PostAuthorize} annotation is not present
*/
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, MethodInvocationResult result) {
MethodInvocation mi = result.getMethodInvocation();
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return Mono.empty();
}
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));
// @formatter:on
}
}
@@ -16,7 +16,6 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
@@ -26,19 +25,14 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} which filters a {@code returnedObject} from the
@@ -61,8 +55,6 @@ public final class PostFilterAuthorizationMethodInterceptor
private final Pointcut pointcut;
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Creates a {@link PostFilterAuthorizationMethodInterceptor} using the provided
* parameters
@@ -76,8 +68,7 @@ public final class PostFilterAuthorizationMethodInterceptor
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
this.registry.setExpressionHandler(expressionHandler);
}
/**
@@ -133,8 +124,9 @@ public final class PostFilterAuthorizationMethodInterceptor
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return returnedObject;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(this.authentication, mi);
return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
return expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
}
private Supplier<Authentication> getAuthentication(SecurityContextHolderStrategy strategy) {
@@ -148,28 +140,4 @@ public final class PostFilterAuthorizationMethodInterceptor
};
}
private final class PostFilterExpressionAttributeRegistry
extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostFilter postFilter = findPostFilterAnnotation(specificMethod);
if (postFilter == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postFilterExpression = PostFilterAuthorizationMethodInterceptor.this.expressionHandler
.getExpressionParser().parseExpression(postFilter.value());
return new ExpressionAttribute(postFilterExpression);
}
private PostFilter findPostFilterAnnotation(Method method) {
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
return (postFilter != null) ? postFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
}
}
}
@@ -0,0 +1,146 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.access.prepost.PostFilter;
/**
* A {@link MethodInterceptor} which filters the returned object from the
* {@link MethodInvocation} by evaluating an expression from the {@link PostFilter}
* annotation.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PostFilterAuthorizationReactiveMethodInterceptor implements Ordered, MethodInterceptor,
PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor {
private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor();
private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();
private final Pointcut pointcut;
private int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder();
/**
* Creates an instance.
*/
public PostFilterAuthorizationReactiveMethodInterceptor() {
this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);
}
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Filters the returned object from the {@link MethodInvocation} by evaluating an
* expression from the {@link PostFilter} annotation.
* @param mi the {@link MethodInvocation} to use
* @return the {@link Publisher} to use
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return publisher;
}
Mono<EvaluationContext> toInvoke = ReactiveAuthenticationUtils.getAuthentication()
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi));
if (publisher instanceof Mono<?>) {
return toInvoke.flatMap((ctx) -> filterMono((Mono<?>) publisher, ctx, attribute));
}
return toInvoke.flatMapMany((ctx) -> filterPublisher(publisher, ctx, attribute));
}
private Mono<?> filterMono(Mono<?> mono, EvaluationContext ctx, ExpressionAttribute attribute) {
return mono.doOnNext((result) -> setFilterObject(ctx, result))
.flatMap((result) -> postFilter(ctx, result, attribute));
}
private Flux<?> filterPublisher(Publisher<?> publisher, EvaluationContext ctx, ExpressionAttribute attribute) {
return Flux.from(publisher).doOnNext((result) -> setFilterObject(ctx, result))
.flatMap((result) -> postFilter(ctx, result, attribute));
}
private void setFilterObject(EvaluationContext ctx, Object result) {
((MethodSecurityExpressionOperations) ctx.getRootObject().getValue()).setFilterObject(result);
}
private Mono<?> postFilter(EvaluationContext ctx, Object result, ExpressionAttribute attribute) {
return ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx)
.flatMap((granted) -> granted ? Mono.just(result) : Mono.empty());
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostFilter postFilter = findPostFilterAnnotation(specificMethod);
if (postFilter == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postFilterExpression = this.expressionHandler.getExpressionParser()
.parseExpression(postFilter.value());
return new ExpressionAttribute(postFilterExpression);
}
private PostFilter findPostFilterAnnotation(Method method) {
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
return (postFilter != null) ? postFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
}
}
@@ -16,24 +16,18 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
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;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
@@ -47,15 +41,12 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
this.registry.setExpressionHandler(expressionHandler);
}
/**
@@ -73,33 +64,9 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
}
private final class PreAuthorizeExpressionAttributeRegistry
extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
if (preAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preAuthorizeExpression = PreAuthorizeAuthorizationManager.this.expressionHandler
.getExpressionParser().parseExpression(preAuthorize.value());
return new ExpressionAttribute(preAuthorizeExpression);
}
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
return (preAuthorize != null) ? preAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
}
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Returns the {@link MethodSecurityExpressionHandler}.
* @return the {@link MethodSecurityExpressionHandler} to use
*/
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
if (preAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
.parseExpression(preAuthorize.value());
return new ExpressionAttribute(preAuthorizeExpression);
}
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
return (preAuthorize != null) ? preAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
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.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
/**
* A {@link ReactiveAuthorizationManager} which can determine if an {@link Authentication}
* has access to the {@link MethodInvocation} by evaluating an expression from the
* {@link PreAuthorize} annotation.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
* by evaluating an expression from the {@link PreAuthorize} annotation.
* @param authentication the {@link Mono} of the {@link Authentication} to check
* @param mi the {@link MethodInvocation} to check
* @return a {@link Mono} of the {@link AuthorizationDecision} or an empty
* {@link Mono} if the {@link PreAuthorize} annotation is not present
*/
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, MethodInvocation mi) {
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return Mono.empty();
}
// @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));
// @formatter:on
}
}
@@ -16,7 +16,6 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
@@ -26,12 +25,8 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
@@ -61,8 +56,6 @@ public final class PreFilterAuthorizationMethodInterceptor
private final Pointcut pointcut;
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Creates a {@link PreFilterAuthorizationMethodInterceptor} using the provided
* parameters
@@ -76,8 +69,7 @@ public final class PreFilterAuthorizationMethodInterceptor
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
this.registry.setExpressionHandler(expressionHandler);
}
/**
@@ -127,13 +119,14 @@ public final class PreFilterAuthorizationMethodInterceptor
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
return mi.proceed();
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(this.authentication, mi);
Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
Object filterTarget = findFilterTarget(attribute.getFilterTarget(), ctx, mi);
expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
return mi.proceed();
}
@@ -168,41 +161,4 @@ public final class PreFilterAuthorizationMethodInterceptor
};
}
private final class PreFilterExpressionAttributeRegistry
extends AbstractExpressionAttributeRegistry<PreFilterExpressionAttribute> {
@NonNull
@Override
PreFilterExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreFilter preFilter = findPreFilterAnnotation(specificMethod);
if (preFilter == null) {
return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preFilterExpression = PreFilterAuthorizationMethodInterceptor.this.expressionHandler
.getExpressionParser().parseExpression(preFilter.value());
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
}
private PreFilter findPreFilterAnnotation(Method method) {
PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class);
return (preFilter != null) ? preFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class);
}
}
private static final class PreFilterExpressionAttribute extends ExpressionAttribute {
private static final PreFilterExpressionAttribute NULL_ATTRIBUTE = new PreFilterExpressionAttribute(null, null);
private final String filterTarget;
private PreFilterExpressionAttribute(Expression expression, String filterTarget) {
super(expression);
this.filterTarget = filterTarget;
}
}
}
@@ -0,0 +1,211 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.core.parameters.DefaultSecurityParameterNameDiscoverer;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link MethodInterceptor} which filters a reactive method argument by evaluating an
* expression from the {@link PreFilter} annotation.
*
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PreFilterAuthorizationReactiveMethodInterceptor implements Ordered, MethodInterceptor,
PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor {
private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor();
private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry();
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer();
private int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Sets the {@link ParameterNameDiscoverer}.
* @param parameterNameDiscoverer the {@link ParameterNameDiscoverer} to use
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
Assert.notNull(parameterNameDiscoverer, "parameterNameDiscoverer cannot be null");
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Filters a reactive method argument by evaluating an expression from the
* {@link PreFilter} annotation.
* @param mi the {@link MethodInvocation} to use
* @return the {@link Publisher} to use
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
return ReactiveMethodInvocationUtils.<Publisher<?>>proceed(mi);
}
FilterTarget filterTarget = findFilterTarget(attribute.getFilterTarget(), mi);
Mono<EvaluationContext> toInvoke = ReactiveAuthenticationUtils.getAuthentication()
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi));
if (filterTarget.value instanceof Mono<?>) {
mi.getArguments()[filterTarget.index] = toInvoke
.flatMap((ctx) -> filterMono((Mono<?>) filterTarget.value, attribute.getExpression(), ctx));
}
else {
Flux<?> result = toInvoke
.flatMapMany((ctx) -> filterPublisher(filterTarget.value, attribute.getExpression(), ctx));
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance()
.getAdapter(filterTarget.value.getClass());
mi.getArguments()[filterTarget.index] = (adapter != null) ? adapter.fromPublisher(result) : result;
}
return ReactiveMethodInvocationUtils.<Publisher<?>>proceed(mi);
}
private FilterTarget findFilterTarget(String name, MethodInvocation mi) {
Object value = null;
int index = 0;
if (StringUtils.hasText(name)) {
Object target = mi.getThis();
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
Method specificMethod = AopUtils.getMostSpecificMethod(mi.getMethod(), targetClass);
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(specificMethod);
if (parameterNames != null && parameterNames.length > 0) {
Object[] arguments = mi.getArguments();
for (index = 0; index < parameterNames.length; index++) {
if (name.equals(parameterNames[index])) {
value = arguments[index];
break;
}
}
Assert.notNull(value,
"Filter target was null, or no argument with name '" + name + "' found in method.");
}
}
else {
Object[] arguments = mi.getArguments();
Assert.state(arguments.length == 1,
"Unable to determine the method argument for filtering. Specify the filter target.");
value = arguments[0];
Assert.notNull(value,
"Filter target was null. Make sure you passing the correct value in the method argument.");
}
Assert.state(value instanceof Publisher<?>, "Filter target must be an instance of Publisher.");
return new FilterTarget((Publisher<?>) value, index);
}
private Mono<?> filterMono(Mono<?> filterTarget, Expression filterExpression, EvaluationContext ctx) {
MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject()
.getValue();
return filterTarget.filterWhen((filterObject) -> {
rootObject.setFilterObject(filterObject);
return ReactiveExpressionUtils.evaluateAsBoolean(filterExpression, ctx);
});
}
private Flux<?> filterPublisher(Publisher<?> filterTarget, Expression filterExpression, EvaluationContext ctx) {
MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject()
.getValue();
return Flux.from(filterTarget).filterWhen((filterObject) -> {
rootObject.setFilterObject(filterObject);
return ReactiveExpressionUtils.evaluateAsBoolean(filterExpression, ctx);
});
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
}
private static final class FilterTarget {
private final Publisher<?> value;
private final int index;
private FilterTarget(Publisher<?> value, int index) {
this.value = value;
this.index = index;
}
}
}
@@ -0,0 +1,85 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class PreFilterExpressionAttributeRegistry
extends AbstractExpressionAttributeRegistry<PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute> {
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
@NonNull
@Override
PreFilterExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreFilter preFilter = findPreFilterAnnotation(specificMethod);
if (preFilter == null) {
return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preFilterExpression = this.expressionHandler.getExpressionParser()
.parseExpression(preFilter.value());
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
}
private PreFilter findPreFilterAnnotation(Method method) {
PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class);
return (preFilter != null) ? preFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class);
}
static final class PreFilterExpressionAttribute extends ExpressionAttribute {
static final PreFilterExpressionAttribute NULL_ATTRIBUTE = new PreFilterExpressionAttribute(null, null);
private final String filterTarget;
private PreFilterExpressionAttribute(Expression expression, String filterTarget) {
super(expression);
this.filterTarget = filterTarget;
}
String getFilterTarget() {
return this.filterTarget;
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import reactor.core.publisher.Mono;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class ReactiveAuthenticationUtils {
private static final Authentication ANONYMOUS = new AnonymousAuthenticationToken("key", "anonymous",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
static Mono<Authentication> getAuthentication() {
return ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication)
.defaultIfEmpty(ANONYMOUS);
}
private ReactiveAuthenticationUtils() {
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import reactor.core.publisher.Mono;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class ReactiveExpressionUtils {
static Mono<Boolean> evaluateAsBoolean(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 Boolean) {
return Mono.just((Boolean) value);
}
if (value instanceof Mono<?>) {
Mono<?> monoValue = (Mono<?>) value;
// @formatter:off
return monoValue
.filter(Boolean.class::isInstance)
.map(Boolean.class::cast)
.switchIfEmpty(createInvalidReturnTypeMono(expr));
// @formatter:on
}
return createInvalidReturnTypeMono(expr);
});
}
private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
return Mono.error(() -> new IllegalStateException(
"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
}
private ReactiveExpressionUtils() {
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.Exceptions;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @since 5.8
*/
final class ReactiveMethodInvocationUtils {
static <T> T proceed(MethodInvocation mi) {
try {
return (T) mi.proceed();
}
catch (Throwable ex) {
throw Exceptions.propagate(ex);
}
}
private ReactiveMethodInvocationUtils() {
}
}
@@ -0,0 +1,107 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AuthorizationManagerAfterReactiveMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@Test
public void instantiateWhenPointcutNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerAfterReactiveMethodInterceptor(null,
mock(ReactiveAuthorizationManager.class)))
.withMessage("pointcut cannot be null");
}
@Test
public void instantiateWhenAuthorizationManagerNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerAfterReactiveMethodInterceptor(mock(Pointcut.class), null))
.withMessage("authorizationManager cannot be null");
}
@Test
public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty());
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());
}
@Test
public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty());
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");
verify(mockReactiveAuthorizationManager, times(2)).verify(any(), any());
}
@Test
public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedException() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
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")));
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> assertThat(result)
.asInstanceOf(InstanceOfAssertFactories.type(Mono.class)).extracting(Mono::block))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).verify(any(), any());
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AuthorizationManagerBeforeReactiveMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@Test
public void instantiateWhenPointcutNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerBeforeReactiveMethodInterceptor(null,
mock(ReactiveAuthorizationManager.class)))
.withMessage("pointcut cannot be null");
}
@Test
public void instantiateWhenAuthorizationManagerNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerBeforeReactiveMethodInterceptor(mock(Pointcut.class), null))
.withMessage("authorizationManager cannot be null");
}
@Test
public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
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));
}
@Test
public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
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("john", "bob");
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
}
@Test
public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedException() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
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")));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> assertThat(result)
.asInstanceOf(InstanceOfAssertFactories.type(Mono.class)).extracting(Mono::block))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ public class PostAuthorizeAuthorizationManagerTests {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
manager.setExpressionHandler(expressionHandler);
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
@@ -0,0 +1,247 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PostAuthorizeReactiveAuthorizationManager}.
*
* @author Evgeniy Cheban
*/
public class PostAuthorizeReactiveAuthorizationManagerTests {
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
manager.setExpressionHandler(expressionHandler);
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething", new Class[] {}, new Object[] {});
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
AuthorizationDecision decision = manager.check(ReactiveAuthenticationUtils.getAuthentication(), result).block();
assertThat(decision).isNull();
}
@Test
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
AuthorizationDecision decision = manager.check(ReactiveAuthenticationUtils.getAuthentication(), result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(ReactiveAuthenticationUtils.getAuthentication(), result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkDoSomethingListWhenReturnObjectContainsGrantThenGrantedDecision() throws Exception {
List<String> list = Arrays.asList("grant", "deny");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingList", new Class[] { List.class }, new Object[] { list });
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(ReactiveAuthenticationUtils.getAuthentication(), result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDoSomethingListWhenReturnObjectNotContainsGrantThenDeniedDecision() throws Exception {
List<String> list = Collections.singletonList("deny");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingList", new Class[] { List.class }, new Object[] { list });
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(ReactiveAuthenticationUtils.getAuthentication(), result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedAdmin");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
authentication = Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"));
decision = manager.check(authentication, result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedUser");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
authentication = Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"));
decision = manager.check(authentication, result).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, result));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "inheritedAnnotations");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, result));
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {
}
@PostAuthorize("#s == 'grant'")
public String doSomethingString(String s) {
return s;
}
@PostAuthorize("returnObject.contains('grant')")
public List<String> doSomethingList(List<String> list) {
return list;
}
@Override
public void inheritedAnnotations() {
}
}
@PostAuthorize("hasRole('USER')")
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
@PostAuthorize("hasRole('ADMIN')")
public void securedAdmin() {
}
public void securedUser() {
}
@Override
@PostAuthorize("hasRole('ADMIN')")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PostAuthorize("hasRole('ADMIN')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PostAuthorize("hasRole('USER')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPostAuthorize
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("hasRole('USER')")
public @interface MyPostAuthorize {
}
}
@@ -67,7 +67,7 @@ public class PostFilterAuthorizationMethodInterceptorTests {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
advice.setExpressionHandler(expressionHandler);
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
assertThat(advice).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
@@ -0,0 +1,191 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PostFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PostFilterAuthorizationReactiveMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class PostFilterAuthorizationReactiveMethodInterceptorTests {
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
interceptor.setExpressionHandler(expressionHandler);
assertThat(interceptor).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> interceptor.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception {
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
assertThat(interceptor.getPointcut().getMethodMatcher()
.matches(NoPostFilterClass.class.getMethod("doSomething"), NoPostFilterClass.class)).isFalse();
}
@Test
public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception {
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
assertThat(interceptor.getPointcut().getMethodMatcher()
.matches(TestClass.class.getMethod("doSomethingFlux", Flux.class), TestClass.class)).isTrue();
}
@Test
public void invokeWhenMonoThenFilteredMono() throws Throwable {
Mono<String> mono = Mono.just("bob");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingMono", new Class[] { Mono.class }, new Object[] { mono }) {
@Override
public Object proceed() {
return mono;
}
};
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
Object result = interceptor.invoke(methodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)).extracting(Mono::block).isNull();
}
@Test
public void invokeWhenFluxThenFilteredFlux() throws Throwable {
Flux<String> flux = Flux.just("john", "bob");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingFluxClassLevel", new Class[] { Flux.class }, new Object[] { flux }) {
@Override
public Object proceed() {
return flux;
}
};
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
Object result = interceptor.invoke(methodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class)).extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class)).containsOnly("john");
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> interceptor.invoke(methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(),
ConflictingAnnotations.class, "inheritedAnnotations");
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> interceptor.invoke(methodInvocation));
}
@PostFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
@PostFilter("filterObject == 'john'")
public Flux<String> doSomethingFlux(Flux<String> flux) {
return flux;
}
public Flux<String> doSomethingFluxClassLevel(Flux<String> flux) {
return flux;
}
@PostFilter("filterObject == 'john'")
public Mono<String> doSomethingMono(Mono<String> mono) {
return mono;
}
@Override
public void inheritedAnnotations() {
}
}
public static class NoPostFilterClass {
public void doSomething() {
}
}
public static class ConflictingAnnotations implements InterfaceAnnotationsThree {
@Override
@PostFilter("filterObject == 'jack'")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PostFilter("filterObject == 'jim'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PostFilter("filterObject == 'jane'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPostFilter
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PostFilter("filterObject == 'john'")
public @interface MyPostFilter {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@ public class PreAuthorizeAuthorizationManagerTests {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
manager.setExpressionHandler(expressionHandler);
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
@@ -0,0 +1,211 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PreAuthorizeReactiveAuthorizationManager}.
*
* @author Evgeniy Cheban
*/
public class PreAuthorizeReactiveAuthorizationManagerTests {
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
manager.setExpressionHandler(expressionHandler);
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething", new Class[] {}, new Object[] {});
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager
.check(ReactiveAuthenticationUtils.getAuthentication(), methodInvocation).block();
assertThat(decision).isNull();
}
@Test
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager
.check(ReactiveAuthenticationUtils.getAuthentication(), methodInvocation).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager
.check(ReactiveAuthenticationUtils.getAuthentication(), methodInvocation).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedAdmin");
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
authentication = Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"));
decision = manager.check(authentication, methodInvocation).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedUser");
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
authentication = Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"));
decision = manager.check(authentication, methodInvocation).block();
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
Mono<Authentication> authentication = Mono
.just(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "inheritedAnnotations");
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {
}
@PreAuthorize("#s == 'grant'")
public String doSomethingString(String s) {
return s;
}
@Override
public void inheritedAnnotations() {
}
}
@PreAuthorize("hasRole('USER')")
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
@PreAuthorize("hasRole('ADMIN')")
public void securedAdmin() {
}
public void securedUser() {
}
@Override
@PreAuthorize("hasRole('ADMIN')")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PreAuthorize("hasRole('ADMIN')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PreAuthorize("hasRole('USER')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPreAuthorize
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
public @interface MyPreAuthorize {
}
}
@@ -69,7 +69,7 @@ public class PreFilterAuthorizationMethodInterceptorTests {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
advice.setExpressionHandler(expressionHandler);
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
assertThat(advice).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
@@ -0,0 +1,237 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.core.parameters.DefaultSecurityParameterNameDiscoverer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PreFilterAuthorizationReactiveMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class PreFilterAuthorizationReactiveMethodInterceptorTests {
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
interceptor.setExpressionHandler(expressionHandler);
assertThat(interceptor).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> interceptor.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void setParameterNameDiscovererWhenNotNullThenSetsParameterNameDiscoverer() {
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer();
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
interceptor.setParameterNameDiscoverer(parameterNameDiscoverer);
assertThat(interceptor).extracting("parameterNameDiscoverer").isEqualTo(parameterNameDiscoverer);
}
@Test
public void setParameterNameDiscovererWhenNullThenException() {
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> interceptor.setParameterNameDiscoverer(null))
.withMessage("parameterNameDiscoverer cannot be null");
}
@Test
public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception {
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThat(interceptor.getPointcut().getMethodMatcher().matches(NoPreFilterClass.class.getMethod("doSomething"),
NoPreFilterClass.class)).isFalse();
}
@Test
public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception {
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThat(interceptor.getPointcut().getMethodMatcher()
.matches(TestClass.class.getMethod("doSomethingFluxFilterTargetMatch", Flux.class), TestClass.class))
.isTrue();
}
@Test
public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingFluxFilterTargetNotMatch", new Class[] { Flux.class }, new Object[] { Flux.empty() });
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> interceptor.invoke(methodInvocation)).withMessage(
"Filter target was null, or no argument with name 'filterTargetNotMatch' found in method.");
}
@Test
public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingFluxFilterTargetMatch", new Class[] { Flux.class }, new Object[] { null });
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> interceptor.invoke(methodInvocation))
.withMessage("Filter target was null, or no argument with name 'flux' found in method.");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgMonoThenFiltersMono() throws Throwable {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingMonoFilterTargetNotProvided", new Class[] { Mono.class },
new Object[] { Mono.just("bob") }) {
@Override
public Object proceed() {
return getArguments()[0];
}
};
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
Object result = interceptor.invoke(methodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)).extracting(Mono::block).isNull();
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgFluxThenFiltersFlux() throws Throwable {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingFluxFilterTargetNotProvided", new Class[] { Flux.class },
new Object[] { Flux.just("john", "bob") }) {
@Override
public Object proceed() {
return getArguments()[0];
}
};
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
Object result = interceptor.invoke(methodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class)).extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class)).containsOnly("john");
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> interceptor.invoke(methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(),
ConflictingAnnotations.class, "inheritedAnnotations");
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> interceptor.invoke(methodInvocation));
}
@PreFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
@PreFilter(value = "filterObject == 'john'", filterTarget = "filterTargetNotMatch")
public Flux<String> doSomethingFluxFilterTargetNotMatch(Flux<String> flux) {
return flux;
}
@PreFilter(value = "filterObject == 'john'", filterTarget = "flux")
public Flux<String> doSomethingFluxFilterTargetMatch(Flux<String> flux) {
return flux;
}
@PreFilter("filterObject == 'john'")
public Flux<String> doSomethingFluxFilterTargetNotProvided(Flux<String> flux) {
return flux;
}
@PreFilter("filterObject == 'john'")
public Mono<String> doSomethingMonoFilterTargetNotProvided(Mono<String> mono) {
return mono;
}
public Flux<String> doSomethingTwoArgsFilterTargetNotProvided(String s, Flux<String> flux) {
return flux;
}
@Override
public void inheritedAnnotations() {
}
}
public static class NoPreFilterClass {
public void doSomething() {
}
}
public static class ConflictingAnnotations implements InterfaceAnnotationsThree {
@Override
@PreFilter("filterObject == 'jack'")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PreFilter("filterObject == 'jim'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PreFilter("filterObject == 'jane'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPreFilter
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PreFilter("filterObject == 'john'")
public @interface MyPreFilter {
}
}