38 changed files with 3394 additions and 191 deletions
@ -0,0 +1,87 @@ |
|||||||
|
/* |
||||||
|
* 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) { |
||||||
|
return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
||||||
|
AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor( |
||||||
|
MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager( |
||||||
|
expressionHandler); |
||||||
|
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
||||||
|
PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor( |
||||||
|
MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
||||||
|
AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor( |
||||||
|
MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager( |
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,484 @@ |
|||||||
|
/* |
||||||
|
* 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#useAuthorizationManager()} 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 (for example, a Mono or Flux) 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(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPostFilterWhenFilteringThenWorks() { |
||||||
|
Flux<String> flux = this.messageService.fluxPostFilter(Flux.just("harold", "jonathan", "michael", "pete", "bo")) |
||||||
|
.contextWrite(this.withAdmin); |
||||||
|
StepVerifier.create(flux).expectNext("harold", "jonathan", "michael").verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
// 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(useAuthorizationManager = true) |
||||||
|
static class Config { |
||||||
|
|
||||||
|
ReactiveMessageService delegate = mock(ReactiveMessageService.class); |
||||||
|
|
||||||
|
@Bean |
||||||
|
DelegatingReactiveMessageService defaultMessageService() { |
||||||
|
return new DelegatingReactiveMessageService(this.delegate); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
Authz authz() { |
||||||
|
return new Authz(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,153 @@ |
|||||||
|
/* |
||||||
|
* 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 java.util.function.Function; |
||||||
|
|
||||||
|
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.core.Ordered; |
||||||
|
import org.springframework.core.ReactiveAdapter; |
||||||
|
import org.springframework.core.ReactiveAdapterRegistry; |
||||||
|
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 { |
||||||
|
|
||||||
|
private final Pointcut pointcut; |
||||||
|
|
||||||
|
private final ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager; |
||||||
|
|
||||||
|
private int order = AuthorizationInterceptorsOrder.LAST.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) { |
||||||
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( |
||||||
|
AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager); |
||||||
|
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder()); |
||||||
|
return interceptor; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 { |
||||||
|
Method method = mi.getMethod(); |
||||||
|
Class<?> type = method.getReturnType(); |
||||||
|
Assert.state(Publisher.class.isAssignableFrom(type), |
||||||
|
() -> String.format("The returnType %s on %s must return an instance of org.reactivestreams.Publisher " |
||||||
|
+ "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); |
||||||
|
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication(); |
||||||
|
Function<Object, Mono<?>> postAuthorize = (result) -> postAuthorize(authentication, mi, result); |
||||||
|
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); |
||||||
|
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi); |
||||||
|
if (isMultiValue(type, adapter)) { |
||||||
|
Flux<?> flux = Flux.from(publisher).flatMap(postAuthorize); |
||||||
|
return (adapter != null) ? adapter.fromPublisher(flux) : flux; |
||||||
|
} |
||||||
|
Mono<?> mono = Mono.from(publisher).flatMap(postAuthorize); |
||||||
|
return (adapter != null) ? adapter.fromPublisher(mono) : mono; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) { |
||||||
|
if (Flux.class.isAssignableFrom(returnType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return adapter == null || adapter.isMultiValue(); |
||||||
|
} |
||||||
|
|
||||||
|
private Mono<?> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) { |
||||||
|
return this.authorizationManager.verify(authentication, new MethodInvocationResult(mi, result)) |
||||||
|
.thenReturn(result); |
||||||
|
} |
||||||
|
|
||||||
|
@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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,149 @@ |
|||||||
|
/* |
||||||
|
* 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.core.Ordered; |
||||||
|
import org.springframework.core.ReactiveAdapter; |
||||||
|
import org.springframework.core.ReactiveAdapterRegistry; |
||||||
|
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 |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.8 |
||||||
|
*/ |
||||||
|
public final class AuthorizationManagerBeforeReactiveMethodInterceptor |
||||||
|
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { |
||||||
|
|
||||||
|
private final Pointcut pointcut; |
||||||
|
|
||||||
|
private final ReactiveAuthorizationManager<MethodInvocation> authorizationManager; |
||||||
|
|
||||||
|
private int order = AuthorizationInterceptorsOrder.FIRST.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) { |
||||||
|
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( |
||||||
|
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager); |
||||||
|
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder()); |
||||||
|
return interceptor; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 { |
||||||
|
Method method = mi.getMethod(); |
||||||
|
Class<?> type = method.getReturnType(); |
||||||
|
Assert.state(Publisher.class.isAssignableFrom(type), |
||||||
|
() -> String.format("The returnType %s on %s must return an instance of org.reactivestreams.Publisher " |
||||||
|
+ "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); |
||||||
|
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication(); |
||||||
|
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); |
||||||
|
Mono<Void> preAuthorize = this.authorizationManager.verify(authentication, mi); |
||||||
|
if (isMultiValue(type, adapter)) { |
||||||
|
Publisher<?> publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); |
||||||
|
Flux<?> result = preAuthorize.thenMany(publisher); |
||||||
|
return (adapter != null) ? adapter.fromPublisher(result) : result; |
||||||
|
} |
||||||
|
Mono<?> publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); |
||||||
|
Mono<?> result = preAuthorize.then(publisher); |
||||||
|
return (adapter != null) ? adapter.fromPublisher(result) : result; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) { |
||||||
|
if (Flux.class.isAssignableFrom(returnType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return adapter == null || adapter.isMultiValue(); |
||||||
|
} |
||||||
|
|
||||||
|
@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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
* 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 final MethodSecurityExpressionHandler expressionHandler; |
||||||
|
|
||||||
|
PostAuthorizeExpressionAttributeRegistry() { |
||||||
|
this(new DefaultMethodSecurityExpressionHandler()); |
||||||
|
} |
||||||
|
|
||||||
|
PostAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.expressionHandler = expressionHandler; |
||||||
|
} |
||||||
|
|
||||||
|
MethodSecurityExpressionHandler getExpressionHandler() { |
||||||
|
return this.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,78 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2022 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.security.authorization.method; |
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.prepost.PostAuthorize; |
||||||
|
import org.springframework.security.authorization.AuthorizationDecision; |
||||||
|
import org.springframework.security.authorization.ReactiveAuthorizationManager; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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; |
||||||
|
|
||||||
|
public PostAuthorizeReactiveAuthorizationManager() { |
||||||
|
this(new DefaultMethodSecurityExpressionHandler()); |
||||||
|
} |
||||||
|
|
||||||
|
public PostAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.registry = new PostAuthorizeExpressionAttributeRegistry(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
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,153 @@ |
|||||||
|
/* |
||||||
|
* 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.core.Ordered; |
||||||
|
import org.springframework.core.ReactiveAdapter; |
||||||
|
import org.springframework.core.ReactiveAdapterRegistry; |
||||||
|
import org.springframework.expression.EvaluationContext; |
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; |
||||||
|
import org.springframework.security.access.prepost.PostFilter; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 { |
||||||
|
|
||||||
|
private final PostFilterExpressionAttributeRegistry registry; |
||||||
|
|
||||||
|
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class); |
||||||
|
|
||||||
|
private int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an instance. |
||||||
|
*/ |
||||||
|
public PostFilterAuthorizationReactiveMethodInterceptor() { |
||||||
|
this(new DefaultMethodSecurityExpressionHandler()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an instance. |
||||||
|
*/ |
||||||
|
public PostFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.registry = new PostFilterExpressionAttributeRegistry(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 { |
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(mi); |
||||||
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { |
||||||
|
return ReactiveMethodInvocationUtils.proceed(mi); |
||||||
|
} |
||||||
|
Mono<EvaluationContext> toInvoke = ReactiveAuthenticationUtils.getAuthentication() |
||||||
|
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi)); |
||||||
|
Method method = mi.getMethod(); |
||||||
|
Class<?> type = method.getReturnType(); |
||||||
|
Assert.state(Publisher.class.isAssignableFrom(type), |
||||||
|
() -> String.format("The parameter type %s on %s must be an instance of org.reactivestreams.Publisher " |
||||||
|
+ "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); |
||||||
|
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); |
||||||
|
if (isMultiValue(type, adapter)) { |
||||||
|
Publisher<?> publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); |
||||||
|
Flux<?> flux = toInvoke.flatMapMany((ctx) -> filterMultiValue(publisher, ctx, attribute)); |
||||||
|
return (adapter != null) ? adapter.fromPublisher(flux) : flux; |
||||||
|
} |
||||||
|
Publisher<?> publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); |
||||||
|
Mono<?> mono = toInvoke.flatMap((ctx) -> filterSingleValue(publisher, ctx, attribute)); |
||||||
|
return (adapter != null) ? adapter.fromPublisher(mono) : mono; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) { |
||||||
|
if (Flux.class.isAssignableFrom(returnType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return adapter == null || adapter.isMultiValue(); |
||||||
|
} |
||||||
|
|
||||||
|
private Mono<?> filterSingleValue(Publisher<?> publisher, EvaluationContext ctx, ExpressionAttribute attribute) { |
||||||
|
return Mono.from(publisher).doOnNext((result) -> setFilterObject(ctx, result)) |
||||||
|
.flatMap((result) -> postFilter(ctx, result, attribute)); |
||||||
|
} |
||||||
|
|
||||||
|
private Flux<?> filterMultiValue(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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
/* |
||||||
|
* 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 final MethodSecurityExpressionHandler expressionHandler; |
||||||
|
|
||||||
|
PostFilterExpressionAttributeRegistry() { |
||||||
|
this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); |
||||||
|
} |
||||||
|
|
||||||
|
PostFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.expressionHandler = expressionHandler; |
||||||
|
} |
||||||
|
|
||||||
|
MethodSecurityExpressionHandler getExpressionHandler() { |
||||||
|
return this.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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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 final MethodSecurityExpressionHandler expressionHandler; |
||||||
|
|
||||||
|
PreAuthorizeExpressionAttributeRegistry() { |
||||||
|
this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); |
||||||
|
} |
||||||
|
|
||||||
|
PreAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.expressionHandler = expressionHandler; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link MethodSecurityExpressionHandler}. |
||||||
|
* @return the {@link MethodSecurityExpressionHandler} to use |
||||||
|
*/ |
||||||
|
MethodSecurityExpressionHandler getExpressionHandler() { |
||||||
|
return this.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,73 @@ |
|||||||
|
/* |
||||||
|
* 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.DefaultMethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||||
|
import org.springframework.security.authorization.AuthorizationDecision; |
||||||
|
import org.springframework.security.authorization.ReactiveAuthorizationManager; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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; |
||||||
|
|
||||||
|
public PreAuthorizeReactiveAuthorizationManager() { |
||||||
|
this(new DefaultMethodSecurityExpressionHandler()); |
||||||
|
} |
||||||
|
|
||||||
|
public PreAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.registry = new PreAuthorizeExpressionAttributeRegistry(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
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,213 @@ |
|||||||
|
/* |
||||||
|
* 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.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.DefaultMethodSecurityExpressionHandler; |
||||||
|
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 { |
||||||
|
|
||||||
|
private final PreFilterExpressionAttributeRegistry registry; |
||||||
|
|
||||||
|
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class); |
||||||
|
|
||||||
|
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer(); |
||||||
|
|
||||||
|
private int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder(); |
||||||
|
|
||||||
|
public PreFilterAuthorizationReactiveMethodInterceptor() { |
||||||
|
this(new DefaultMethodSecurityExpressionHandler()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an instance. |
||||||
|
*/ |
||||||
|
public PreFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.registry = new PreFilterExpressionAttributeRegistry(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.proceed(mi); |
||||||
|
} |
||||||
|
FilterTarget filterTarget = findFilterTarget(attribute.getFilterTarget(), mi); |
||||||
|
Mono<EvaluationContext> toInvoke = ReactiveAuthenticationUtils.getAuthentication() |
||||||
|
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi)); |
||||||
|
Method method = mi.getMethod(); |
||||||
|
Class<?> type = filterTarget.value.getClass(); |
||||||
|
Assert.state(Publisher.class.isAssignableFrom(type), |
||||||
|
() -> String.format("The parameter type %s on %s must be an instance of org.reactivestreams.Publisher " |
||||||
|
+ "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); |
||||||
|
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); |
||||||
|
if (isMultiValue(type, adapter)) { |
||||||
|
Flux<?> result = toInvoke |
||||||
|
.flatMapMany((ctx) -> filterMultiValue(filterTarget.value, attribute.getExpression(), ctx)); |
||||||
|
mi.getArguments()[filterTarget.index] = (adapter != null) ? adapter.fromPublisher(result) : result; |
||||||
|
} |
||||||
|
else { |
||||||
|
Mono<?> result = toInvoke |
||||||
|
.flatMap((ctx) -> filterSingleValue(filterTarget.value, attribute.getExpression(), ctx)); |
||||||
|
mi.getArguments()[filterTarget.index] = (adapter != null) ? adapter.fromPublisher(result) : result; |
||||||
|
} |
||||||
|
return ReactiveMethodInvocationUtils.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 boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) { |
||||||
|
if (Flux.class.isAssignableFrom(returnType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return adapter == null || adapter.isMultiValue(); |
||||||
|
} |
||||||
|
|
||||||
|
private Mono<?> filterSingleValue(Publisher<?> filterTarget, Expression filterExpression, EvaluationContext ctx) { |
||||||
|
MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject() |
||||||
|
.getValue(); |
||||||
|
return Mono.from(filterTarget).filterWhen((filterObject) -> { |
||||||
|
rootObject.setFilterObject(filterObject); |
||||||
|
return ReactiveExpressionUtils.evaluateAsBoolean(filterExpression, ctx); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private Flux<?> filterMultiValue(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; |
||||||
|
} |
||||||
|
|
||||||
|
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,89 @@ |
|||||||
|
/* |
||||||
|
* 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 final MethodSecurityExpressionHandler expressionHandler; |
||||||
|
|
||||||
|
PreFilterExpressionAttributeRegistry() { |
||||||
|
this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); |
||||||
|
} |
||||||
|
|
||||||
|
PreFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { |
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null"); |
||||||
|
this.expressionHandler = expressionHandler; |
||||||
|
} |
||||||
|
|
||||||
|
MethodSecurityExpressionHandler getExpressionHandler() { |
||||||
|
return this.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,124 @@ |
|||||||
|
/* |
||||||
|
* 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.access.intercept.method.MockMethodInvocation; |
||||||
|
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.spy; |
||||||
|
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 = spy( |
||||||
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); |
||||||
|
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 = spy( |
||||||
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); |
||||||
|
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); |
||||||
|
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock( |
||||||
|
ReactiveAuthorizationManager.class); |
||||||
|
given(mockReactiveAuthorizationManager.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 = spy( |
||||||
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); |
||||||
|
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()); |
||||||
|
} |
||||||
|
|
||||||
|
class Sample { |
||||||
|
|
||||||
|
Mono<String> mono() { |
||||||
|
return Mono.just("john"); |
||||||
|
} |
||||||
|
|
||||||
|
Flux<String> flux() { |
||||||
|
return Flux.just("john", "bob"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,125 @@ |
|||||||
|
/* |
||||||
|
* 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.access.intercept.method.MockMethodInvocation; |
||||||
|
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.spy; |
||||||
|
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 = spy( |
||||||
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); |
||||||
|
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 = spy( |
||||||
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); |
||||||
|
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); |
||||||
|
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock( |
||||||
|
ReactiveAuthorizationManager.class); |
||||||
|
given(mockReactiveAuthorizationManager.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 = spy( |
||||||
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); |
||||||
|
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)); |
||||||
|
} |
||||||
|
|
||||||
|
class Sample { |
||||||
|
|
||||||
|
Mono<String> mono() { |
||||||
|
return Mono.just("john"); |
||||||
|
} |
||||||
|
|
||||||
|
Flux<String> flux() { |
||||||
|
return Flux.just("john", "bob"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,246 @@ |
|||||||
|
/* |
||||||
|
* 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( |
||||||
|
expressionHandler); |
||||||
|
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void setExpressionHandlerWhenNullThenException() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new PostAuthorizeReactiveAuthorizationManager(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 { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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( |
||||||
|
expressionHandler); |
||||||
|
assertThat(interceptor).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void setExpressionHandlerWhenNullThenException() { |
||||||
|
assertThatIllegalArgumentException() |
||||||
|
.isThrownBy(() -> new PostFilterAuthorizationReactiveMethodInterceptor(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 { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,210 @@ |
|||||||
|
/* |
||||||
|
* 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( |
||||||
|
expressionHandler); |
||||||
|
assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void setExpressionHandlerWhenNullThenException() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new PreAuthorizeReactiveAuthorizationManager(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 { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,236 @@ |
|||||||
|
/* |
||||||
|
* 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( |
||||||
|
expressionHandler); |
||||||
|
assertThat(interceptor).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void setExpressionHandlerWhenNullThenException() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new PreFilterAuthorizationReactiveMethodInterceptor(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