Test meta-annotation parameter support in Reactive
Issue gh-14480
This commit is contained in:
		
							parent
							
								
									1760e7fac8
								
							
						
					
					
						commit
						fc2ad34e5d
					
				| 
						 | 
					@ -16,23 +16,39 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package org.springframework.security.config.annotation.method.configuration;
 | 
					package org.springframework.security.config.annotation.method.configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
import org.junit.jupiter.api.extension.ExtendWith;
 | 
					import org.junit.jupiter.api.extension.ExtendWith;
 | 
				
			||||||
 | 
					import org.junit.jupiter.params.ParameterizedTest;
 | 
				
			||||||
 | 
					import org.junit.jupiter.params.provider.ValueSource;
 | 
				
			||||||
 | 
					import reactor.core.publisher.Flux;
 | 
				
			||||||
 | 
					import reactor.core.publisher.Mono;
 | 
				
			||||||
import reactor.test.StepVerifier;
 | 
					import reactor.test.StepVerifier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.config.BeanDefinition;
 | 
					import org.springframework.beans.factory.config.BeanDefinition;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.context.annotation.Configuration;
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
import org.springframework.context.annotation.Role;
 | 
					import org.springframework.context.annotation.Role;
 | 
				
			||||||
 | 
					import org.springframework.security.access.AccessDeniedException;
 | 
				
			||||||
import org.springframework.security.access.PermissionEvaluator;
 | 
					import org.springframework.security.access.PermissionEvaluator;
 | 
				
			||||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
 | 
					import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
 | 
				
			||||||
 | 
					import org.springframework.security.access.prepost.PostAuthorize;
 | 
				
			||||||
 | 
					import org.springframework.security.access.prepost.PostFilter;
 | 
				
			||||||
 | 
					import org.springframework.security.access.prepost.PreAuthorize;
 | 
				
			||||||
 | 
					import org.springframework.security.access.prepost.PreFilter;
 | 
				
			||||||
import org.springframework.security.authorization.AuthorizationDeniedException;
 | 
					import org.springframework.security.authorization.AuthorizationDeniedException;
 | 
				
			||||||
 | 
					import org.springframework.security.authorization.method.PrePostTemplateDefaults;
 | 
				
			||||||
import org.springframework.security.config.test.SpringTestContext;
 | 
					import org.springframework.security.config.test.SpringTestContext;
 | 
				
			||||||
import org.springframework.security.config.test.SpringTestContextExtension;
 | 
					import org.springframework.security.config.test.SpringTestContextExtension;
 | 
				
			||||||
 | 
					import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 | 
				
			||||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
 | 
					import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
 | 
				
			||||||
import org.springframework.security.test.context.support.WithMockUser;
 | 
					import org.springframework.security.test.context.support.WithMockUser;
 | 
				
			||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
 | 
					import org.springframework.test.context.junit.jupiter.SpringExtension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
 | 
					import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 | 
				
			||||||
import static org.mockito.ArgumentMatchers.any;
 | 
					import static org.mockito.ArgumentMatchers.any;
 | 
				
			||||||
import static org.mockito.ArgumentMatchers.eq;
 | 
					import static org.mockito.ArgumentMatchers.eq;
 | 
				
			||||||
import static org.mockito.BDDMockito.given;
 | 
					import static org.mockito.BDDMockito.given;
 | 
				
			||||||
| 
						 | 
					@ -228,6 +244,82 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
 | 
				
			||||||
		verify(permissionEvaluator, times(2)).hasPermission(any(), any(), any());
 | 
							verify(permissionEvaluator, times(2)).hasPermission(any(), any(), any());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser
 | 
				
			||||||
 | 
						public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThat(service.hasRole("USER").block()).isTrue();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser
 | 
				
			||||||
 | 
						public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThat(service.hasUserRole().block()).isTrue();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						public void methodWhenParameterizedAnnotationThenFails(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThatExceptionOfType(IllegalArgumentException.class)
 | 
				
			||||||
 | 
								.isThrownBy(() -> service.placeholdersOnlyResolvedByMetaAnnotations().block());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser(authorities = "SCOPE_message:read")
 | 
				
			||||||
 | 
						public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThat(service.readMessage().block()).isEqualTo("message");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser(roles = "ADMIN")
 | 
				
			||||||
 | 
						public void methodWhenMultiplePlaceholdersHasRoleThenPasses(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThat(service.readMessage().block()).isEqualTo("message");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser
 | 
				
			||||||
 | 
						public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							service.startsWithDave("daveMatthews");
 | 
				
			||||||
 | 
							assertThatExceptionOfType(AccessDeniedException.class)
 | 
				
			||||||
 | 
								.isThrownBy(() -> service.startsWithDave("jenniferHarper").block());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser
 | 
				
			||||||
 | 
						public void methodWhenPreFilterMetaAnnotationThenFilters(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThat(service.parametersContainDave(Flux.just("dave", "carla", "vanessa", "paul")).collectList().block())
 | 
				
			||||||
 | 
								.containsExactly("dave");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ParameterizedTest
 | 
				
			||||||
 | 
						@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
 | 
				
			||||||
 | 
						@WithMockUser
 | 
				
			||||||
 | 
						public void methodWhenPostFilterMetaAnnotationThenFilters(Class<?> config) {
 | 
				
			||||||
 | 
							this.spring.register(config).autowire();
 | 
				
			||||||
 | 
							MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
 | 
				
			||||||
 | 
							assertThat(service.resultsContainDave(Flux.just("dave", "carla", "vanessa", "paul")).collectList().block())
 | 
				
			||||||
 | 
								.containsExactly("dave");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Configuration
 | 
						@Configuration
 | 
				
			||||||
	@EnableReactiveMethodSecurity
 | 
						@EnableReactiveMethodSecurity
 | 
				
			||||||
	static class MethodSecurityServiceEnabledConfig {
 | 
						static class MethodSecurityServiceEnabledConfig {
 | 
				
			||||||
| 
						 | 
					@ -258,4 +350,138 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Configuration
 | 
				
			||||||
 | 
						@EnableReactiveMethodSecurity
 | 
				
			||||||
 | 
						static class LegacyMetaAnnotationPlaceholderConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							PrePostTemplateDefaults methodSecurityDefaults() {
 | 
				
			||||||
 | 
								return new PrePostTemplateDefaults();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							MetaAnnotationService metaAnnotationService() {
 | 
				
			||||||
 | 
								return new MetaAnnotationService();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Configuration
 | 
				
			||||||
 | 
						@EnableReactiveMethodSecurity
 | 
				
			||||||
 | 
						static class MetaAnnotationPlaceholderConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							AnnotationTemplateExpressionDefaults methodSecurityDefaults() {
 | 
				
			||||||
 | 
								return new AnnotationTemplateExpressionDefaults();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							MetaAnnotationService metaAnnotationService() {
 | 
				
			||||||
 | 
								return new MetaAnnotationService();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static class MetaAnnotationService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@RequireRole(role = "#role")
 | 
				
			||||||
 | 
							Mono<Boolean> hasRole(String role) {
 | 
				
			||||||
 | 
								return Mono.just(true);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@RequireRole(role = "'USER'")
 | 
				
			||||||
 | 
							Mono<Boolean> hasUserRole() {
 | 
				
			||||||
 | 
								return Mono.just(true);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@PreAuthorize("hasRole({role})")
 | 
				
			||||||
 | 
							Mono<Void> placeholdersOnlyResolvedByMetaAnnotations() {
 | 
				
			||||||
 | 
								return Mono.empty();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@HasClaim(claim = "message:read", roles = { "'ADMIN'" })
 | 
				
			||||||
 | 
							Mono<String> readMessage() {
 | 
				
			||||||
 | 
								return Mono.just("message");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@ResultStartsWith("dave")
 | 
				
			||||||
 | 
							Mono<String> startsWithDave(String value) {
 | 
				
			||||||
 | 
								return Mono.just(value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@ParameterContains("dave")
 | 
				
			||||||
 | 
							Flux<String> parametersContainDave(Flux<String> list) {
 | 
				
			||||||
 | 
								return list;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@ResultContains("dave")
 | 
				
			||||||
 | 
							Flux<String> resultsContainDave(Flux<String> list) {
 | 
				
			||||||
 | 
								return list;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@RestrictedAccess(entityClass = EntityClass.class)
 | 
				
			||||||
 | 
							Mono<String> getIdPath(String id) {
 | 
				
			||||||
 | 
								return Mono.just(id);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
						@PreAuthorize("hasRole({idPath})")
 | 
				
			||||||
 | 
						@interface RestrictedAccess {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String idPath() default "#id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Class<?> entityClass();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String[] recipes() default {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static class EntityClass {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
						@PreAuthorize("hasRole({role})")
 | 
				
			||||||
 | 
						@interface RequireRole {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String role();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
						@PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})")
 | 
				
			||||||
 | 
						@interface HasClaim {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String claim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String[] roles() default {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
						@PostAuthorize("returnObject.startsWith('{value}')")
 | 
				
			||||||
 | 
						@interface ResultStartsWith {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String value();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
						@PreFilter("filterObject.contains('{value}')")
 | 
				
			||||||
 | 
						@interface ParameterContains {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String value();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
						@PostFilter("filterObject.contains('{value}')")
 | 
				
			||||||
 | 
						@interface ResultContains {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							String value();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue