diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index 367eea5a7e3..223fc3f8fc2 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -998,6 +998,15 @@ public void adviseWhenPrePostEnabledThenEachInterceptorRunsExactlyOnce() { verify(expressionHandler, times(4)).createEvaluationContext(any(Supplier.class), any()); } + // gh-15721 + @Test + @WithMockUser(roles = "uid") + public void methodWhenMetaAnnotationPropertiesHasClassProperties() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.getIdPath("uid")).isEqualTo("uid"); + } + private static Consumer disallowBeanOverriding() { return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false); } @@ -1376,6 +1385,27 @@ List resultsContainDave(List list) { return list; } + @RestrictedAccess(entityClass = EntityClass.class) + String getIdPath(String id) { + return id; + } + + } + + @Retention(RetentionPolicy.RUNTIME) + @PreAuthorize("hasRole({idPath})") + @interface RestrictedAccess { + + String idPath() default "#id"; + + Class entityClass(); + + String[] recipes() default {}; + + } + + static class EntityClass { + } @Retention(RetentionPolicy.RUNTIME) diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java index de0e9c9111e..9387b7be506 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java @@ -19,9 +19,11 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import org.springframework.core.annotation.AnnotationConfigurationException; @@ -29,6 +31,8 @@ import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.RepeatableContainers; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.PropertyPlaceholderHelper; @@ -55,6 +59,12 @@ */ final class AuthorizationAnnotationUtils { + private static final DefaultConversionService conversionService = new DefaultConversionService(); + + static { + conversionService.addConverter(new ClassToStringConverter()); + } + static Function withDefaults(Class type, PrePostTemplateDefaults defaults) { Function, A> map = (mergedAnnotation) -> { @@ -70,7 +80,7 @@ static Function withDefaults(Class getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(Class.class, String.class)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + return (source != null) ? source.toString() : null; + } + + } + }