From d7132b43d5c492f765792627ec930ab2aaaaaa53 Mon Sep 17 00:00:00 2001 From: DingHao Date: Fri, 7 Feb 2025 16:33:23 +0800 Subject: [PATCH] Method Security templates support use deep non-aliased attributes Closes gh-16498 Signed-off-by: DingHao --- ...sionTemplateSecurityAnnotationScanner.java | 35 +++++---- ...emplateSecurityAnnotationScannerTests.java | 76 +++++++++++++++++++ 2 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java diff --git a/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java b/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java index 76be0842789..7ee6e58d74f 100644 --- a/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java +++ b/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,7 @@ * * @param the annotation to search for and synthesize * @author Josh Cummings + * @author DingHao * @since 6.4 */ final class ExpressionTemplateSecurityAnnotationScanner @@ -116,27 +117,35 @@ private MergedAnnotation resolvePlaceholders(MergedAnnotation mergedAnnota PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("{", "}", null, null, this.templateDefaults.isIgnoreUnknown()); Map properties = new HashMap<>(mergedAnnotation.asMap()); - Map metaAnnotationProperties = mergedAnnotation.getMetaSource().asMap(); - Map stringProperties = new HashMap<>(); - for (Map.Entry property : metaAnnotationProperties.entrySet()) { - String key = property.getKey(); - Object value = property.getValue(); - String asString = (value instanceof String) ? (String) value - : conversionService.convert(value, String.class); - stringProperties.put(key, asString); - } - Map annotationProperties = mergedAnnotation.asMap(); - for (Map.Entry annotationProperty : annotationProperties.entrySet()) { + Map metaAnnotationProperties = extractMetaAnnotationProperties(mergedAnnotation); + for (Map.Entry annotationProperty : mergedAnnotation.asMap().entrySet()) { if (!(annotationProperty.getValue() instanceof String expression)) { continue; } - String value = helper.replacePlaceholders(expression, stringProperties::get); + String value = helper.replacePlaceholders(expression, metaAnnotationProperties::get); properties.put(annotationProperty.getKey(), value); } AnnotatedElement annotatedElement = (AnnotatedElement) mergedAnnotation.getSource(); return MergedAnnotation.of(annotatedElement, this.type, properties); } + private Map extractMetaAnnotationProperties(MergedAnnotation mergedAnnotation) { + Map stringProperties = new HashMap<>(); + Map metaAnnotationProperties = new HashMap<>(); + MergedAnnotation metaSource = mergedAnnotation.getMetaSource(); + while (metaSource != null) { + metaAnnotationProperties.putAll(metaSource.asMap()); + metaSource = metaSource.getMetaSource(); + } + for (Map.Entry property : metaAnnotationProperties.entrySet()) { + Object value = property.getValue(); + String valueString = (value instanceof String) ? (String) value + : conversionService.convert(value, String.class); + stringProperties.put(property.getKey(), valueString); + } + return stringProperties; + } + static class ClassToStringConverter implements GenericConverter { @Override diff --git a/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java b/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java new file mode 100644 index 00000000000..c6be904ecbb --- /dev/null +++ b/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.core.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.access.prepost.PreAuthorize; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ExpressionTemplateSecurityAnnotationScanner} + * + * @author DingHao + */ +public class ExpressionTemplateSecurityAnnotationScannerTests { + + private ExpressionTemplateSecurityAnnotationScanner scanner = new ExpressionTemplateSecurityAnnotationScanner<>( + PreAuthorize.class, new AnnotationTemplateExpressionDefaults()); + + @Test + void parseMultipleMetaSourceAnnotationParameter() throws Exception { + Method method = MessageService.class.getDeclaredMethod("sayHello", String.class); + PreAuthorize preAuthorize = this.scanner.scan(method, method.getDeclaringClass()); + assertThat(preAuthorize.value()).isEqualTo("check(#name)"); + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @PreAuthorize("check({object})") + @interface HasPermission { + + String object(); + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @HasPermission(object = "{value}") + @interface HasReadPermission { + + String value(); + + } + + private interface MessageService { + + @HasReadPermission("#name") + String sayHello(String name); + + } + +}