Test Reactive Method Security Exactly-One Invocation Semantics

Issue gh-15651
This commit is contained in:
Josh Cummings 2024-09-12 15:41:35 -06:00
parent 1aec571a81
commit 75fd84ce16
3 changed files with 47 additions and 0 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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;
}
}