ReactiveAuthorizationManager + Reactive Method Security
Closes gh-9401
This commit is contained in:
parent
4ff0724c87
commit
cbb4f40f0c
|
@ -26,6 +26,7 @@ import org.springframework.context.annotation.AdviceMode;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -69,4 +70,11 @@ public @interface EnableReactiveMethodSecurity {
|
||||||
*/
|
*/
|
||||||
int order() default Ordered.LOWEST_PRECEDENCE;
|
int order() default Ordered.LOWEST_PRECEDENCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether {@link ReactiveAuthorizationManager} based Method Security to be
|
||||||
|
* used.
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
boolean authorizationManager() default false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* 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.config.annotation.method.configuration;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Role;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
|
import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
|
||||||
|
import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
|
||||||
|
import org.springframework.security.authorization.method.PostAuthorizeReactiveAuthorizationManager;
|
||||||
|
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
|
||||||
|
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
|
||||||
|
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
|
||||||
|
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for a {@link ReactiveAuthenticationManager} based Method Security.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
|
||||||
|
MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
PreFilterAuthorizationReactiveMethodInterceptor preFilter = new PreFilterAuthorizationReactiveMethodInterceptor();
|
||||||
|
preFilter.setExpressionHandler(expressionHandler);
|
||||||
|
return preFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
|
||||||
|
MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager();
|
||||||
|
authorizationManager.setExpressionHandler(expressionHandler);
|
||||||
|
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
|
||||||
|
MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
PostFilterAuthorizationReactiveMethodInterceptor postFilter = new PostFilterAuthorizationReactiveMethodInterceptor();
|
||||||
|
postFilter.setExpressionHandler(expressionHandler);
|
||||||
|
return postFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
|
||||||
|
MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager();
|
||||||
|
authorizationManager.setExpressionHandler(expressionHandler);
|
||||||
|
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
|
||||||
|
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
|
||||||
|
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
if (grantedAuthorityDefaults != null) {
|
||||||
|
handler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||||
|
}
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,37 +17,55 @@
|
||||||
package org.springframework.security.config.annotation.method.configuration;
|
package org.springframework.security.config.annotation.method.configuration;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.context.annotation.AdviceMode;
|
import org.springframework.context.annotation.AdviceMode;
|
||||||
import org.springframework.context.annotation.AdviceModeImportSelector;
|
import org.springframework.context.annotation.AdviceModeImportSelector;
|
||||||
import org.springframework.context.annotation.AutoProxyRegistrar;
|
import org.springframework.context.annotation.AutoProxyRegistrar;
|
||||||
|
import org.springframework.context.annotation.ImportSelector;
|
||||||
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Evgeniy Cheban
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
class ReactiveMethodSecuritySelector extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
|
class ReactiveMethodSecuritySelector implements ImportSelector {
|
||||||
|
|
||||||
|
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] selectImports(AdviceMode adviceMode) {
|
public String[] selectImports(AnnotationMetadata importMetadata) {
|
||||||
|
if (!importMetadata.hasAnnotation(EnableReactiveMethodSecurity.class.getName())) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
EnableReactiveMethodSecurity annotation = importMetadata.getAnnotations()
|
||||||
|
.get(EnableReactiveMethodSecurity.class).synthesize();
|
||||||
|
List<String> imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata)));
|
||||||
|
if (annotation.authorizationManager()) {
|
||||||
|
imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
|
||||||
|
}
|
||||||
|
return imports.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class AutoProxyRegistrarSelector
|
||||||
|
extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
|
||||||
|
|
||||||
|
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
|
||||||
if (adviceMode == AdviceMode.PROXY) {
|
if (adviceMode == AdviceMode.PROXY) {
|
||||||
return getProxyImports();
|
return IMPORTS;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported");
|
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the imports to use if the {@link AdviceMode} is set to
|
|
||||||
* {@link AdviceMode#PROXY}.
|
|
||||||
* <p>
|
|
||||||
* Take care of adding the necessary JSR-107 import if it is available.
|
|
||||||
*/
|
|
||||||
private String[] getProxyImports() {
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
result.add(AutoProxyRegistrar.class.getName());
|
|
||||||
result.add(ReactiveMethodSecurityConfiguration.class.getName());
|
|
||||||
return result.toArray(new String[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,11 +16,14 @@
|
||||||
|
|
||||||
package org.springframework.security.config.annotation.method.configuration;
|
package org.springframework.security.config.annotation.method.configuration;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Evgeniy Cheban
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
|
@ -34,6 +37,10 @@ public class Authz {
|
||||||
return id % 2 == 0;
|
return id % 2 == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Mono<Boolean> checkReactive(long id) {
|
||||||
|
return Mono.defer(() -> Mono.just(id % 2 == 0));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean check(Authentication authentication, String message) {
|
public boolean check(Authentication authentication, String message) {
|
||||||
return message != null && message.contains(authentication.getName());
|
return message != null && message.contains(authentication.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,9 @@ import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
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.PreAuthorize;
|
||||||
|
import org.springframework.security.access.prepost.PreFilter;
|
||||||
|
|
||||||
public class DelegatingReactiveMessageService implements ReactiveMessageService {
|
public class DelegatingReactiveMessageService implements ReactiveMessageService {
|
||||||
|
|
||||||
|
@ -60,6 +62,12 @@ public class DelegatingReactiveMessageService implements ReactiveMessageService
|
||||||
return this.delegate.monoPreAuthorizeBeanFindById(id);
|
return this.delegate.monoPreAuthorizeBeanFindById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PreAuthorize("@authz.checkReactive(#id)")
|
||||||
|
public Mono<String> monoPreAuthorizeBeanFindByIdReactiveExpression(long id) {
|
||||||
|
return this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(id);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostAuthorize("@authz.check(authentication, returnObject)")
|
@PostAuthorize("@authz.check(authentication, returnObject)")
|
||||||
public Mono<String> monoPostAuthorizeBeanFindById(long id) {
|
public Mono<String> monoPostAuthorizeBeanFindById(long id) {
|
||||||
|
@ -95,6 +103,15 @@ public class DelegatingReactiveMessageService implements ReactiveMessageService
|
||||||
return this.delegate.fluxPostAuthorizeBeanFindById(id);
|
return this.delegate.fluxPostAuthorizeBeanFindById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreFilter("filterObject.length > 3")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@PostFilter("filterObject.length > 5")
|
||||||
|
@PostAuthorize("returnObject == 'harold' or returnObject == 'jonathan'")
|
||||||
|
@Override
|
||||||
|
public Flux<String> fluxManyAnnotations(Flux<String> flux) {
|
||||||
|
return flux;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Publisher<String> publisherFindById(long id) {
|
public Publisher<String> publisherFindById(long id) {
|
||||||
return this.delegate.publisherFindById(id);
|
return this.delegate.publisherFindById(id);
|
||||||
|
|
|
@ -0,0 +1,478 @@
|
||||||
|
/*
|
||||||
|
* 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.config.annotation.method.configuration;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
import reactor.test.publisher.TestPublisher;
|
||||||
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link EnableReactiveMethodSecurity} with the
|
||||||
|
* {@link EnableReactiveMethodSecurity#authorizationManager()} flag set to true.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
public class EnableAuthorizationManagerReactiveMethodSecurityTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ReactiveMessageService messageService;
|
||||||
|
|
||||||
|
ReactiveMessageService delegate;
|
||||||
|
|
||||||
|
TestPublisher<String> result = TestPublisher.create();
|
||||||
|
|
||||||
|
Context withAdmin = ReactiveSecurityContextHolder
|
||||||
|
.withAuthentication(new TestingAuthenticationToken("admin", "password", "ROLE_USER", "ROLE_ADMIN"));
|
||||||
|
|
||||||
|
Context withUser = ReactiveSecurityContextHolder
|
||||||
|
.withAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void cleanup() {
|
||||||
|
reset(this.delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setConfig(Config config) {
|
||||||
|
this.delegate = config.delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notPublisherPreAuthorizeFindByIdThenThrowsIllegalStateException() {
|
||||||
|
assertThatIllegalStateException().isThrownBy(() -> this.messageService.notPublisherPreAuthorizeFindById(1L))
|
||||||
|
.withMessage("The returnType class java.lang.String on public abstract java.lang.String "
|
||||||
|
+ "org.springframework.security.config.annotation.method.configuration.ReactiveMessageService"
|
||||||
|
+ ".notPublisherPreAuthorizeFindById(long) 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoWhenPermitAllThenAopDoesNotSubscribe() {
|
||||||
|
given(this.delegate.monoFindById(1L)).willReturn(Mono.from(this.result));
|
||||||
|
this.delegate.monoFindById(1L);
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoWhenPermitAllThenSuccess() {
|
||||||
|
given(this.delegate.monoFindById(1L)).willReturn(Mono.just("success"));
|
||||||
|
StepVerifier.create(this.delegate.monoFindById(1L)).expectNext("success").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeHasRoleWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.monoPreAuthorizeHasRoleFindById(1L)).willReturn(Mono.just("result"));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeHasRoleFindById(1L).contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeHasRoleWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.monoPreAuthorizeHasRoleFindById(1L)).willReturn(Mono.from(this.result));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeHasRoleFindById(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeHasRoleWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.monoPreAuthorizeHasRoleFindById(1L)).willReturn(Mono.from(this.result));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeHasRoleFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindById(2L)).willReturn(Mono.just("result"));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(2L).contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanWhenNotAuthenticatedAndGrantedThenSuccess() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindById(2L)).willReturn(Mono.just("result"));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindById(1L)).willReturn(Mono.from(this.result));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindById(1L)).willReturn(Mono.from(this.result));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanReactiveExpressionWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(2L)).willReturn(Mono.just("result"));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindByIdReactiveExpression(2L)
|
||||||
|
.contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanReactiveExpressionWhenNotAuthenticatedAndGrantedThenSuccess() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(2L)).willReturn(Mono.just("result"));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindByIdReactiveExpression(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanReactiveExpressionWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(1L)).willReturn(Mono.from(this.result));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindByIdReactiveExpression(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPreAuthorizeBeanReactiveExpressionWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(1L)).willReturn(Mono.from(this.result));
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindByIdReactiveExpression(1L)
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPostAuthorizeWhenAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.monoPostAuthorizeFindById(1L)).willReturn(Mono.just("user"));
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectNext("user").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPostAuthorizeWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.monoPostAuthorizeBeanFindById(1L)).willReturn(Mono.just("not-authorized"));
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPostAuthorizeWhenBeanAndAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.monoPostAuthorizeBeanFindById(2L)).willReturn(Mono.just("user"));
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(2L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectNext("user").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPostAuthorizeWhenBeanAndNotAuthenticatedAndAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.monoPostAuthorizeBeanFindById(2L)).willReturn(Mono.just("anonymous"));
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("anonymous").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monoPostAuthorizeWhenBeanAndNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.monoPostAuthorizeBeanFindById(1L)).willReturn(Mono.just("not-authorized"));
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flux tests
|
||||||
|
@Test
|
||||||
|
public void fluxWhenPermitAllThenAopDoesNotSubscribe() {
|
||||||
|
given(this.delegate.fluxFindById(1L)).willReturn(Flux.from(this.result));
|
||||||
|
this.delegate.fluxFindById(1L);
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxWhenPermitAllThenSuccess() {
|
||||||
|
given(this.delegate.fluxFindById(1L)).willReturn(Flux.just("success"));
|
||||||
|
StepVerifier.create(this.delegate.fluxFindById(1L)).expectNext("success").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeHasRoleWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeHasRoleFindById(1L)).willReturn(Flux.just("result"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeHasRoleFindById(1L).contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).consumeNextWith((s) -> assertThat(s).isEqualTo("result")).verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeHasRoleWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeHasRoleFindById(1L)).willReturn(Flux.from(this.result));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeHasRoleFindById(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeHasRoleWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeHasRoleFindById(1L)).willReturn(Flux.from(this.result));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeHasRoleFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeBeanWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeBeanFindById(2L)).willReturn(Flux.just("result"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(2L).contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeBeanWhenNotAuthenticatedAndGrantedThenSuccess() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeBeanFindById(2L)).willReturn(Flux.just("result"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeBeanWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeBeanFindById(1L)).willReturn(Flux.from(this.result));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPreAuthorizeBeanWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.fluxPreAuthorizeBeanFindById(1L)).willReturn(Flux.from(this.result));
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPostAuthorizeWhenAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.fluxPostAuthorizeFindById(1L)).willReturn(Flux.just("user"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectNext("user").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPostAuthorizeWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.fluxPostAuthorizeBeanFindById(1L)).willReturn(Flux.just("not-authorized"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPostAuthorizeWhenBeanAndAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.fluxPostAuthorizeBeanFindById(2L)).willReturn(Flux.just("user"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(2L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectNext("user").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPostAuthorizeWhenBeanAndNotAuthenticatedAndAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.fluxPostAuthorizeBeanFindById(2L)).willReturn(Flux.just("anonymous"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("anonymous").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxPostAuthorizeWhenBeanAndNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.fluxPostAuthorizeBeanFindById(1L)).willReturn(Flux.just("not-authorized"));
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(1L).contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxManyAnnotationsWhenMeetsConditionsThenReturnsFilteredFlux() {
|
||||||
|
Flux<String> flux = this.messageService.fluxManyAnnotations(Flux.just("harold", "jonathan", "pete", "bo"))
|
||||||
|
.contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(flux).expectNext("harold", "jonathan").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxManyAnnotationsWhenUserThenFails() {
|
||||||
|
Flux<String> flux = this.messageService.fluxManyAnnotations(Flux.just("harold", "jonathan", "pete", "bo"))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(flux).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fluxManyAnnotationsWhenNameNotAllowedThenFails() {
|
||||||
|
Flux<String> flux = this.messageService
|
||||||
|
.fluxManyAnnotations(Flux.just("harold", "jonathan", "michael", "pete", "bo"))
|
||||||
|
.contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(flux).expectNext("harold", "jonathan").expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publisher tests
|
||||||
|
@Test
|
||||||
|
public void publisherWhenPermitAllThenAopDoesNotSubscribe() {
|
||||||
|
given(this.delegate.publisherFindById(1L)).willReturn(this.result);
|
||||||
|
this.delegate.publisherFindById(1L);
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherWhenPermitAllThenSuccess() {
|
||||||
|
given(this.delegate.publisherFindById(1L)).willReturn(publisherJust("success"));
|
||||||
|
StepVerifier.create(this.delegate.publisherFindById(1L)).expectNext("success").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeHasRoleWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeHasRoleFindById(1L)).willReturn(publisherJust("result"));
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeHasRoleFindById(1L))
|
||||||
|
.contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).consumeNextWith((s) -> assertThat(s).isEqualTo("result")).verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeHasRoleWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeHasRoleFindById(1L)).willReturn(this.result);
|
||||||
|
Publisher<String> findById = this.messageService.publisherPreAuthorizeHasRoleFindById(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeHasRoleWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeHasRoleFindById(1L)).willReturn(this.result);
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeHasRoleFindById(1L))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeBeanWhenGrantedThenSuccess() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeBeanFindById(2L)).willReturn(publisherJust("result"));
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeBeanFindById(2L))
|
||||||
|
.contextWrite(this.withAdmin);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeBeanWhenNotAuthenticatedAndGrantedThenSuccess() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeBeanFindById(2L)).willReturn(publisherJust("result"));
|
||||||
|
Publisher<String> findById = this.messageService.publisherPreAuthorizeBeanFindById(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("result").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeBeanWhenNoAuthenticationThenDenied() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeBeanFindById(1L)).willReturn(this.result);
|
||||||
|
Publisher<String> findById = this.messageService.publisherPreAuthorizeBeanFindById(1L);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPreAuthorizeBeanWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.publisherPreAuthorizeBeanFindById(1L)).willReturn(this.result);
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeBeanFindById(1L))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
this.result.assertNoSubscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPostAuthorizeWhenAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.publisherPostAuthorizeFindById(1L)).willReturn(publisherJust("user"));
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeFindById(1L))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectNext("user").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPostAuthorizeWhenNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.publisherPostAuthorizeBeanFindById(1L)).willReturn(publisherJust("not-authorized"));
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeBeanFindById(1L))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPostAuthorizeWhenBeanAndAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.publisherPostAuthorizeBeanFindById(2L)).willReturn(publisherJust("user"));
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeBeanFindById(2L))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectNext("user").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPostAuthorizeWhenBeanAndNotAuthenticatedAndAuthorizedThenSuccess() {
|
||||||
|
given(this.delegate.publisherPostAuthorizeBeanFindById(2L)).willReturn(publisherJust("anonymous"));
|
||||||
|
Publisher<String> findById = this.messageService.publisherPostAuthorizeBeanFindById(2L);
|
||||||
|
StepVerifier.create(findById).expectNext("anonymous").verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publisherPostAuthorizeWhenBeanAndNotAuthorizedThenDenied() {
|
||||||
|
given(this.delegate.publisherPostAuthorizeBeanFindById(1L)).willReturn(publisherJust("not-authorized"));
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeBeanFindById(1L))
|
||||||
|
.contextWrite(this.withUser);
|
||||||
|
StepVerifier.create(findById).expectError(AccessDeniedException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Publisher<T> publisher(Flux<T> flux) {
|
||||||
|
return (subscriber) -> flux.subscribe(subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Publisher<T> publisherJust(T... data) {
|
||||||
|
return publisher(Flux.just(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableReactiveMethodSecurity(authorizationManager = true)
|
||||||
|
static class Config {
|
||||||
|
|
||||||
|
ReactiveMessageService delegate = mock(ReactiveMessageService.class);
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
DelegatingReactiveMessageService defaultMessageService() {
|
||||||
|
return new DelegatingReactiveMessageService(this.delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Authz authz() {
|
||||||
|
return new Authz();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -32,6 +32,8 @@ public interface ReactiveMessageService {
|
||||||
|
|
||||||
Mono<String> monoPreAuthorizeBeanFindById(long id);
|
Mono<String> monoPreAuthorizeBeanFindById(long id);
|
||||||
|
|
||||||
|
Mono<String> monoPreAuthorizeBeanFindByIdReactiveExpression(long id);
|
||||||
|
|
||||||
Mono<String> monoPostAuthorizeBeanFindById(long id);
|
Mono<String> monoPostAuthorizeBeanFindById(long id);
|
||||||
|
|
||||||
Flux<String> fluxFindById(long id);
|
Flux<String> fluxFindById(long id);
|
||||||
|
@ -44,6 +46,8 @@ public interface ReactiveMessageService {
|
||||||
|
|
||||||
Flux<String> fluxPostAuthorizeBeanFindById(long id);
|
Flux<String> fluxPostAuthorizeBeanFindById(long id);
|
||||||
|
|
||||||
|
Flux<String> fluxManyAnnotations(Flux<String> flux);
|
||||||
|
|
||||||
Publisher<String> publisherFindById(long id);
|
Publisher<String> publisherFindById(long id);
|
||||||
|
|
||||||
Publisher<String> publisherPreAuthorizeHasRoleFindById(long id);
|
Publisher<String> publisherPreAuthorizeHasRoleFindById(long id);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.ComposablePointcut;
|
||||||
import org.springframework.aop.support.Pointcuts;
|
import org.springframework.aop.support.Pointcuts;
|
||||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
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 Josh Cummings
|
||||||
|
* @author Evgeniy Cheban
|
||||||
*/
|
*/
|
||||||
final class AuthorizationMethodPointcuts {
|
final class AuthorizationMethodPointcuts {
|
||||||
|
|
||||||
|
static Pointcut forAllAnnotations() {
|
||||||
|
return forAnnotations(PreFilter.class, PreAuthorize.class, PostFilter.class, PostAuthorize.class);
|
||||||
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
|
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
|
||||||
ComposablePointcut pointcut = null;
|
ComposablePointcut pointcut = null;
|
||||||
|
|
|
@ -16,24 +16,18 @@
|
||||||
|
|
||||||
package org.springframework.security.authorization.method;
|
package org.springframework.security.authorization.method;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
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.EvaluationContext;
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.security.access.expression.ExpressionUtils;
|
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.expression.method.MethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
|
* 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 final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this the {@link MethodSecurityExpressionHandler}.
|
* Use this the {@link MethodSecurityExpressionHandler}.
|
||||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
*/
|
*/
|
||||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
this.registry.setExpressionHandler(expressionHandler);
|
||||||
this.expressionHandler = expressionHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,36 +64,11 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication,
|
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||||
mi.getMethodInvocation());
|
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
|
||||||
this.expressionHandler.setReturnObject(mi.getResult(), ctx);
|
expressionHandler.setReturnObject(mi.getResult(), ctx);
|
||||||
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||||
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
|
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;
|
package org.springframework.security.authorization.method;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.aopalliance.aop.Advice;
|
import org.aopalliance.aop.Advice;
|
||||||
|
@ -26,19 +25,14 @@ import org.aopalliance.intercept.MethodInvocation;
|
||||||
import org.springframework.aop.Pointcut;
|
import org.springframework.aop.Pointcut;
|
||||||
import org.springframework.aop.PointcutAdvisor;
|
import org.springframework.aop.PointcutAdvisor;
|
||||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||||
import org.springframework.aop.support.AopUtils;
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.expression.EvaluationContext;
|
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.expression.method.MethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.prepost.PostFilter;
|
import org.springframework.security.access.prepost.PostFilter;
|
||||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MethodInterceptor} which filters a {@code returnedObject} from the
|
* A {@link MethodInterceptor} which filters a {@code returnedObject} from the
|
||||||
|
@ -61,8 +55,6 @@ public final class PostFilterAuthorizationMethodInterceptor
|
||||||
|
|
||||||
private final Pointcut pointcut;
|
private final Pointcut pointcut;
|
||||||
|
|
||||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link PostFilterAuthorizationMethodInterceptor} using the provided
|
* Creates a {@link PostFilterAuthorizationMethodInterceptor} using the provided
|
||||||
* parameters
|
* parameters
|
||||||
|
@ -76,8 +68,7 @@ public final class PostFilterAuthorizationMethodInterceptor
|
||||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
*/
|
*/
|
||||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
this.registry.setExpressionHandler(expressionHandler);
|
||||||
this.expressionHandler = expressionHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,8 +124,9 @@ public final class PostFilterAuthorizationMethodInterceptor
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return returnedObject;
|
return returnedObject;
|
||||||
}
|
}
|
||||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(this.authentication, mi);
|
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||||
return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
|
EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
|
||||||
|
return expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Supplier<Authentication> getAuthentication(SecurityContextHolderStrategy strategy) {
|
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;
|
package org.springframework.security.authorization.method;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
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.EvaluationContext;
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.security.access.expression.ExpressionUtils;
|
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.expression.method.MethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
|
* 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 final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link MethodSecurityExpressionHandler}.
|
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
*/
|
*/
|
||||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
this.registry.setExpressionHandler(expressionHandler);
|
||||||
this.expressionHandler = expressionHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,33 +64,9 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
|
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
|
||||||
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||||
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
|
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;
|
package org.springframework.security.authorization.method;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.aopalliance.aop.Advice;
|
import org.aopalliance.aop.Advice;
|
||||||
|
@ -26,12 +25,8 @@ import org.aopalliance.intercept.MethodInvocation;
|
||||||
import org.springframework.aop.Pointcut;
|
import org.springframework.aop.Pointcut;
|
||||||
import org.springframework.aop.PointcutAdvisor;
|
import org.springframework.aop.PointcutAdvisor;
|
||||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||||
import org.springframework.aop.support.AopUtils;
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.expression.EvaluationContext;
|
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.expression.method.MethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.prepost.PreFilter;
|
import org.springframework.security.access.prepost.PreFilter;
|
||||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
|
@ -61,8 +56,6 @@ public final class PreFilterAuthorizationMethodInterceptor
|
||||||
|
|
||||||
private final Pointcut pointcut;
|
private final Pointcut pointcut;
|
||||||
|
|
||||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link PreFilterAuthorizationMethodInterceptor} using the provided
|
* Creates a {@link PreFilterAuthorizationMethodInterceptor} using the provided
|
||||||
* parameters
|
* parameters
|
||||||
|
@ -76,8 +69,7 @@ public final class PreFilterAuthorizationMethodInterceptor
|
||||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
*/
|
*/
|
||||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
this.registry.setExpressionHandler(expressionHandler);
|
||||||
this.expressionHandler = expressionHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,13 +119,14 @@ public final class PreFilterAuthorizationMethodInterceptor
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||||
PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
|
PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
|
||||||
if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return mi.proceed();
|
return mi.proceed();
|
||||||
}
|
}
|
||||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(this.authentication, mi);
|
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||||
Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
|
EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
|
||||||
this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
|
Object filterTarget = findFilterTarget(attribute.getFilterTarget(), ctx, mi);
|
||||||
|
expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
|
||||||
return mi.proceed();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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();
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
manager.setExpressionHandler(expressionHandler);
|
manager.setExpressionHandler(expressionHandler);
|
||||||
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
|
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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();
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||||
advice.setExpressionHandler(expressionHandler);
|
advice.setExpressionHandler(expressionHandler);
|
||||||
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
|
assertThat(advice).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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();
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||||
manager.setExpressionHandler(expressionHandler);
|
manager.setExpressionHandler(expressionHandler);
|
||||||
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
|
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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();
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||||
advice.setExpressionHandler(expressionHandler);
|
advice.setExpressionHandler(expressionHandler);
|
||||||
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
|
assertThat(advice).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue