|
17 | 17 | package org.springframework.security.config.annotation.method.configuration;
|
18 | 18 |
|
19 | 19 | import java.io.Serializable;
|
| 20 | +import java.lang.annotation.Retention; |
| 21 | +import java.lang.annotation.RetentionPolicy; |
20 | 22 | import java.util.ArrayList;
|
21 | 23 | import java.util.Arrays;
|
22 | 24 | import java.util.List;
|
|
49 | 51 | import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
50 | 52 | import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
51 | 53 | import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
| 54 | +import org.springframework.security.access.prepost.PostAuthorize; |
| 55 | +import org.springframework.security.access.prepost.PostFilter; |
| 56 | +import org.springframework.security.access.prepost.PreAuthorize; |
| 57 | +import org.springframework.security.access.prepost.PreFilter; |
52 | 58 | import org.springframework.security.authorization.AuthorizationDecision;
|
53 | 59 | import org.springframework.security.authorization.AuthorizationEventPublisher;
|
54 | 60 | import org.springframework.security.authorization.AuthorizationManager;
|
@@ -587,6 +593,74 @@ public void allAnnotationsWhenAdviceAfterAllOffsetThenReturnsFilteredList() {
|
587 | 593 | assertThat(filtered).containsExactly("DoNotDrop");
|
588 | 594 | }
|
589 | 595 |
|
| 596 | + @Test |
| 597 | + @WithMockUser |
| 598 | + public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses() { |
| 599 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 600 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 601 | + assertThat(service.hasRole("USER")).isTrue(); |
| 602 | + } |
| 603 | + |
| 604 | + @Test |
| 605 | + @WithMockUser |
| 606 | + public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses() { |
| 607 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 608 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 609 | + assertThat(service.hasUserRole()).isTrue(); |
| 610 | + } |
| 611 | + |
| 612 | + @Test |
| 613 | + public void methodWhenParameterizedAnnotationThenFails() { |
| 614 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 615 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 616 | + assertThatExceptionOfType(IllegalArgumentException.class) |
| 617 | + .isThrownBy(service::placeholdersOnlyResolvedByMetaAnnotations); |
| 618 | + } |
| 619 | + |
| 620 | + @Test |
| 621 | + @WithMockUser(authorities = "SCOPE_message:read") |
| 622 | + public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses() { |
| 623 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 624 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 625 | + assertThat(service.readMessage()).isEqualTo("message"); |
| 626 | + } |
| 627 | + |
| 628 | + @Test |
| 629 | + @WithMockUser(roles = "ADMIN") |
| 630 | + public void methodWhenMultiplePlaceholdersHasRoleThenPasses() { |
| 631 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 632 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 633 | + assertThat(service.readMessage()).isEqualTo("message"); |
| 634 | + } |
| 635 | + |
| 636 | + @Test |
| 637 | + @WithMockUser |
| 638 | + public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes() { |
| 639 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 640 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 641 | + service.startsWithDave("daveMatthews"); |
| 642 | + assertThatExceptionOfType(AccessDeniedException.class) |
| 643 | + .isThrownBy(() -> service.startsWithDave("jenniferHarper")); |
| 644 | + } |
| 645 | + |
| 646 | + @Test |
| 647 | + @WithMockUser |
| 648 | + public void methodWhenPreFilterMetaAnnotationThenFilters() { |
| 649 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 650 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 651 | + assertThat(service.parametersContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) |
| 652 | + .containsExactly("dave"); |
| 653 | + } |
| 654 | + |
| 655 | + @Test |
| 656 | + @WithMockUser |
| 657 | + public void methodWhenPostFilterMetaAnnotationThenFilters() { |
| 658 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 659 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 660 | + assertThat(service.resultsContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) |
| 661 | + .containsExactly("dave"); |
| 662 | + } |
| 663 | + |
590 | 664 | private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
591 | 665 | return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
592 | 666 | }
|
@@ -890,4 +964,95 @@ Authz authz() {
|
890 | 964 |
|
891 | 965 | }
|
892 | 966 |
|
| 967 | + @Configuration |
| 968 | + @EnableMethodSecurity(useAnnotationParameters = true) |
| 969 | + static class MetaAnnotationPlaceholderConfig { |
| 970 | + |
| 971 | + @Bean |
| 972 | + MetaAnnotationService methodSecurityService() { |
| 973 | + return new MetaAnnotationService(); |
| 974 | + } |
| 975 | + |
| 976 | + } |
| 977 | + |
| 978 | + static class MetaAnnotationService { |
| 979 | + |
| 980 | + @RequireRole(role = "#role") |
| 981 | + boolean hasRole(String role) { |
| 982 | + return true; |
| 983 | + } |
| 984 | + |
| 985 | + @RequireRole(role = "'USER'") |
| 986 | + boolean hasUserRole() { |
| 987 | + return true; |
| 988 | + } |
| 989 | + |
| 990 | + @PreAuthorize("hasRole({role})") |
| 991 | + void placeholdersOnlyResolvedByMetaAnnotations() { |
| 992 | + } |
| 993 | + |
| 994 | + @HasClaim(claim = "message:read", roles = { "'ADMIN'" }) |
| 995 | + String readMessage() { |
| 996 | + return "message"; |
| 997 | + } |
| 998 | + |
| 999 | + @ResultStartsWith("dave") |
| 1000 | + String startsWithDave(String value) { |
| 1001 | + return value; |
| 1002 | + } |
| 1003 | + |
| 1004 | + @ParameterContains("dave") |
| 1005 | + List<String> parametersContainDave(List<String> list) { |
| 1006 | + return list; |
| 1007 | + } |
| 1008 | + |
| 1009 | + @ResultContains("dave") |
| 1010 | + List<String> resultsContainDave(List<String> list) { |
| 1011 | + return list; |
| 1012 | + } |
| 1013 | + |
| 1014 | + } |
| 1015 | + |
| 1016 | + @Retention(RetentionPolicy.RUNTIME) |
| 1017 | + @PreAuthorize("hasRole({role})") |
| 1018 | + @interface RequireRole { |
| 1019 | + |
| 1020 | + String role(); |
| 1021 | + |
| 1022 | + } |
| 1023 | + |
| 1024 | + @Retention(RetentionPolicy.RUNTIME) |
| 1025 | + @PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})") |
| 1026 | + @interface HasClaim { |
| 1027 | + |
| 1028 | + String claim(); |
| 1029 | + |
| 1030 | + String[] roles() default {}; |
| 1031 | + |
| 1032 | + } |
| 1033 | + |
| 1034 | + @Retention(RetentionPolicy.RUNTIME) |
| 1035 | + @PostAuthorize("returnObject.startsWith('{value}')") |
| 1036 | + @interface ResultStartsWith { |
| 1037 | + |
| 1038 | + String value(); |
| 1039 | + |
| 1040 | + } |
| 1041 | + |
| 1042 | + @Retention(RetentionPolicy.RUNTIME) |
| 1043 | + @PreFilter("filterObject.contains('{value}')") |
| 1044 | + @interface ParameterContains { |
| 1045 | + |
| 1046 | + String value(); |
| 1047 | + |
| 1048 | + } |
| 1049 | + |
| 1050 | + @Retention(RetentionPolicy.RUNTIME) |
| 1051 | + @PostFilter("filterObject.contains('{value}')") |
| 1052 | + @interface ResultContains { |
| 1053 | + |
| 1054 | + String value(); |
| 1055 | + |
| 1056 | + } |
| 1057 | + |
893 | 1058 | }
|
0 commit comments