Browse Source

Test Reactive Method Security Exactly-One Invocation Semantics

Issue gh-15651
Josh Cummings 11 months ago
parent
commit
75fd84ce16

+ 31 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java

@@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.method.configuration;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -58,6 +59,7 @@ import org.springframework.security.authorization.method.PrePostTemplateDefaults
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.Authentication;
 import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
 import org.springframework.security.test.context.support.WithMockUser;
@@ -71,6 +73,7 @@ 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.times;
 import static org.mockito.Mockito.verify;
 
@@ -449,6 +452,20 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
 					"postFilterAuthorizationMethodInterceptor", "authorizeReturnObjectMethodInterceptor");
 	}
 
+	// gh-15651
+	@Test
+	@WithMockUser(roles = "ADMIN")
+	public void adviseWhenPrePostEnabledThenEachInterceptorRunsExactlyOnce() {
+		this.spring
+			.register(MethodSecurityServiceEnabledConfig.class, CustomMethodSecurityExpressionHandlerConfig.class)
+			.autowire();
+		MethodSecurityExpressionHandler expressionHandler = this.spring.getContext()
+			.getBean(MethodSecurityExpressionHandler.class);
+		ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
+		service.manyAnnotations(Mono.just(new ArrayList<>(Arrays.asList("harold", "jonathan", "tim", "bo")))).block();
+		verify(expressionHandler, times(4)).createEvaluationContext(any(Authentication.class), any());
+	}
+
 	@Configuration
 	@EnableReactiveMethodSecurity
 	static class MethodSecurityServiceEnabledConfig {
@@ -460,6 +477,20 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
 
 	}
 
+	@Configuration
+	@EnableReactiveMethodSecurity
+	static class CustomMethodSecurityExpressionHandlerConfig {
+
+		private final MethodSecurityExpressionHandler expressionHandler = spy(
+				new DefaultMethodSecurityExpressionHandler());
+
+		@Bean
+		MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
+			return this.expressionHandler;
+		}
+
+	}
+
 	@Configuration
 	static class PermissionEvaluatorConfig {
 

+ 9 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

@@ -21,6 +21,7 @@ import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.List;
 
 import org.aopalliance.intercept.MethodInvocation;
 import reactor.core.publisher.Mono;
@@ -31,7 +32,9 @@ import org.springframework.expression.EvaluationContext;
 import org.springframework.expression.Expression;
 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.AuthorizationResult;
 import org.springframework.security.authorization.method.HandleAuthorizationDenied;
 import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
@@ -104,6 +107,12 @@ public interface ReactiveMethodSecurityService {
 	@PreAuthorize("hasPermission(#kgName, 'read')")
 	Mono<String> preAuthorizeHasPermission(String kgName);
 
+	@PreAuthorize("hasRole('ADMIN')")
+	@PostAuthorize("hasRole('ADMIN')")
+	@PreFilter("true")
+	@PostFilter("true")
+	Mono<List<String>> manyAnnotations(Mono<List<String>> array);
+
 	class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
 
 		@Override

+ 7 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java

@@ -16,6 +16,8 @@
 
 package org.springframework.security.config.annotation.method.configuration;
 
+import java.util.List;
+
 import reactor.core.publisher.Mono;
 
 import org.springframework.security.authorization.AuthorizationDecision;
@@ -93,4 +95,9 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
 		return Mono.just("ok");
 	}
 
+	@Override
+	public Mono<List<String>> manyAnnotations(Mono<List<String>> array) {
+		return array;
+	}
+
 }