Skip to content

Commit fc2ad34

Browse files
committed
Test meta-annotation parameter support in Reactive
Issue gh-14480
1 parent 1760e7f commit fc2ad34

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java

+226
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,39 @@
1616

1717
package org.springframework.security.config.annotation.method.configuration;
1818

19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
1922
import org.junit.jupiter.api.Test;
2023
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.ValueSource;
26+
import reactor.core.publisher.Flux;
27+
import reactor.core.publisher.Mono;
2128
import reactor.test.StepVerifier;
2229

2330
import org.springframework.beans.factory.config.BeanDefinition;
2431
import org.springframework.context.annotation.Bean;
2532
import org.springframework.context.annotation.Configuration;
2633
import org.springframework.context.annotation.Role;
34+
import org.springframework.security.access.AccessDeniedException;
2735
import org.springframework.security.access.PermissionEvaluator;
2836
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
37+
import org.springframework.security.access.prepost.PostAuthorize;
38+
import org.springframework.security.access.prepost.PostFilter;
39+
import org.springframework.security.access.prepost.PreAuthorize;
40+
import org.springframework.security.access.prepost.PreFilter;
2941
import org.springframework.security.authorization.AuthorizationDeniedException;
42+
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
3043
import org.springframework.security.config.test.SpringTestContext;
3144
import org.springframework.security.config.test.SpringTestContextExtension;
45+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
3246
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
3347
import org.springframework.security.test.context.support.WithMockUser;
3448
import org.springframework.test.context.junit.jupiter.SpringExtension;
3549

50+
import static org.assertj.core.api.Assertions.assertThat;
51+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3652
import static org.mockito.ArgumentMatchers.any;
3753
import static org.mockito.ArgumentMatchers.eq;
3854
import static org.mockito.BDDMockito.given;
@@ -228,6 +244,82 @@ public void preAuthorizeWhenCustomMethodSecurityExpressionHandlerThenUses() {
228244
verify(permissionEvaluator, times(2)).hasPermission(any(), any(), any());
229245
}
230246

247+
@ParameterizedTest
248+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
249+
@WithMockUser
250+
public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses(Class<?> config) {
251+
this.spring.register(config).autowire();
252+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
253+
assertThat(service.hasRole("USER").block()).isTrue();
254+
}
255+
256+
@ParameterizedTest
257+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
258+
@WithMockUser
259+
public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses(Class<?> config) {
260+
this.spring.register(config).autowire();
261+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
262+
assertThat(service.hasUserRole().block()).isTrue();
263+
}
264+
265+
@ParameterizedTest
266+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
267+
public void methodWhenParameterizedAnnotationThenFails(Class<?> config) {
268+
this.spring.register(config).autowire();
269+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
270+
assertThatExceptionOfType(IllegalArgumentException.class)
271+
.isThrownBy(() -> service.placeholdersOnlyResolvedByMetaAnnotations().block());
272+
}
273+
274+
@ParameterizedTest
275+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
276+
@WithMockUser(authorities = "SCOPE_message:read")
277+
public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses(Class<?> config) {
278+
this.spring.register(config).autowire();
279+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
280+
assertThat(service.readMessage().block()).isEqualTo("message");
281+
}
282+
283+
@ParameterizedTest
284+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
285+
@WithMockUser(roles = "ADMIN")
286+
public void methodWhenMultiplePlaceholdersHasRoleThenPasses(Class<?> config) {
287+
this.spring.register(config).autowire();
288+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
289+
assertThat(service.readMessage().block()).isEqualTo("message");
290+
}
291+
292+
@ParameterizedTest
293+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
294+
@WithMockUser
295+
public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes(Class<?> config) {
296+
this.spring.register(config).autowire();
297+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
298+
service.startsWithDave("daveMatthews");
299+
assertThatExceptionOfType(AccessDeniedException.class)
300+
.isThrownBy(() -> service.startsWithDave("jenniferHarper").block());
301+
}
302+
303+
@ParameterizedTest
304+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
305+
@WithMockUser
306+
public void methodWhenPreFilterMetaAnnotationThenFilters(Class<?> config) {
307+
this.spring.register(config).autowire();
308+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
309+
assertThat(service.parametersContainDave(Flux.just("dave", "carla", "vanessa", "paul")).collectList().block())
310+
.containsExactly("dave");
311+
}
312+
313+
@ParameterizedTest
314+
@ValueSource(classes = { LegacyMetaAnnotationPlaceholderConfig.class, MetaAnnotationPlaceholderConfig.class })
315+
@WithMockUser
316+
public void methodWhenPostFilterMetaAnnotationThenFilters(Class<?> config) {
317+
this.spring.register(config).autowire();
318+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
319+
assertThat(service.resultsContainDave(Flux.just("dave", "carla", "vanessa", "paul")).collectList().block())
320+
.containsExactly("dave");
321+
}
322+
231323
@Configuration
232324
@EnableReactiveMethodSecurity
233325
static class MethodSecurityServiceEnabledConfig {
@@ -258,4 +350,138 @@ static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
258350

259351
}
260352

353+
@Configuration
354+
@EnableReactiveMethodSecurity
355+
static class LegacyMetaAnnotationPlaceholderConfig {
356+
357+
@Bean
358+
PrePostTemplateDefaults methodSecurityDefaults() {
359+
return new PrePostTemplateDefaults();
360+
}
361+
362+
@Bean
363+
MetaAnnotationService metaAnnotationService() {
364+
return new MetaAnnotationService();
365+
}
366+
367+
}
368+
369+
@Configuration
370+
@EnableReactiveMethodSecurity
371+
static class MetaAnnotationPlaceholderConfig {
372+
373+
@Bean
374+
AnnotationTemplateExpressionDefaults methodSecurityDefaults() {
375+
return new AnnotationTemplateExpressionDefaults();
376+
}
377+
378+
@Bean
379+
MetaAnnotationService metaAnnotationService() {
380+
return new MetaAnnotationService();
381+
}
382+
383+
}
384+
385+
static class MetaAnnotationService {
386+
387+
@RequireRole(role = "#role")
388+
Mono<Boolean> hasRole(String role) {
389+
return Mono.just(true);
390+
}
391+
392+
@RequireRole(role = "'USER'")
393+
Mono<Boolean> hasUserRole() {
394+
return Mono.just(true);
395+
}
396+
397+
@PreAuthorize("hasRole({role})")
398+
Mono<Void> placeholdersOnlyResolvedByMetaAnnotations() {
399+
return Mono.empty();
400+
}
401+
402+
@HasClaim(claim = "message:read", roles = { "'ADMIN'" })
403+
Mono<String> readMessage() {
404+
return Mono.just("message");
405+
}
406+
407+
@ResultStartsWith("dave")
408+
Mono<String> startsWithDave(String value) {
409+
return Mono.just(value);
410+
}
411+
412+
@ParameterContains("dave")
413+
Flux<String> parametersContainDave(Flux<String> list) {
414+
return list;
415+
}
416+
417+
@ResultContains("dave")
418+
Flux<String> resultsContainDave(Flux<String> list) {
419+
return list;
420+
}
421+
422+
@RestrictedAccess(entityClass = EntityClass.class)
423+
Mono<String> getIdPath(String id) {
424+
return Mono.just(id);
425+
}
426+
427+
}
428+
429+
@Retention(RetentionPolicy.RUNTIME)
430+
@PreAuthorize("hasRole({idPath})")
431+
@interface RestrictedAccess {
432+
433+
String idPath() default "#id";
434+
435+
Class<?> entityClass();
436+
437+
String[] recipes() default {};
438+
439+
}
440+
441+
static class EntityClass {
442+
443+
}
444+
445+
@Retention(RetentionPolicy.RUNTIME)
446+
@PreAuthorize("hasRole({role})")
447+
@interface RequireRole {
448+
449+
String role();
450+
451+
}
452+
453+
@Retention(RetentionPolicy.RUNTIME)
454+
@PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})")
455+
@interface HasClaim {
456+
457+
String claim();
458+
459+
String[] roles() default {};
460+
461+
}
462+
463+
@Retention(RetentionPolicy.RUNTIME)
464+
@PostAuthorize("returnObject.startsWith('{value}')")
465+
@interface ResultStartsWith {
466+
467+
String value();
468+
469+
}
470+
471+
@Retention(RetentionPolicy.RUNTIME)
472+
@PreFilter("filterObject.contains('{value}')")
473+
@interface ParameterContains {
474+
475+
String value();
476+
477+
}
478+
479+
@Retention(RetentionPolicy.RUNTIME)
480+
@PostFilter("filterObject.contains('{value}')")
481+
@interface ResultContains {
482+
483+
String value();
484+
485+
}
486+
261487
}

0 commit comments

Comments
 (0)