Add AnnotationSythesizer API
Closes gh-13234 Closes gh-13490 Closes gh-15097
This commit is contained in:
parent
e3438aa36a
commit
c736e075c1
|
@ -47,6 +47,8 @@ public class PreAuthorizeAspectTests {
|
|||
|
||||
private PrePostSecured prePostSecured = new PrePostSecured();
|
||||
|
||||
private MultipleInterfaces multiple = new MultipleInterfaces();
|
||||
|
||||
@BeforeEach
|
||||
public final void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
@ -110,6 +112,12 @@ public class PreAuthorizeAspectTests {
|
|||
.isThrownBy(() -> this.secured.myObject().denyAllMethod());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleInterfacesPreAuthorizeAllows() {
|
||||
// aspectj doesn't inherit annotations
|
||||
this.multiple.securedMethod();
|
||||
}
|
||||
|
||||
interface SecuredInterface {
|
||||
|
||||
@PreAuthorize("hasRole('X')")
|
||||
|
@ -177,4 +185,19 @@ public class PreAuthorizeAspectTests {
|
|||
|
||||
}
|
||||
|
||||
interface AnotherSecuredInterface {
|
||||
|
||||
@PreAuthorize("hasRole('Y')")
|
||||
void securedMethod();
|
||||
|
||||
}
|
||||
|
||||
static class MultipleInterfaces implements SecuredInterface, AnotherSecuredInterface {
|
||||
|
||||
@Override
|
||||
public void securedMethod() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,12 +16,9 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
|
@ -43,8 +40,6 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
|
|||
|
||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
|
||||
private PrePostTemplateDefaults defaults;
|
||||
|
||||
/**
|
||||
* Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}.
|
||||
* @param mi the {@link MethodInvocation} to use
|
||||
|
@ -68,11 +63,6 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
|
|||
return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
|
||||
}
|
||||
|
||||
final <A extends Annotation> Function<AnnotatedElement, A> findUniqueAnnotation(Class<A> type) {
|
||||
return (this.defaults != null) ? AuthorizationAnnotationUtils.withDefaults(type, this.defaults)
|
||||
: AuthorizationAnnotationUtils.withDefaults(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodSecurityExpressionHandler}.
|
||||
* @return the {@link MethodSecurityExpressionHandler} to use
|
||||
|
@ -86,10 +76,6 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
|
|||
this.expressionHandler = expressionHandler;
|
||||
}
|
||||
|
||||
void setTemplateDefaults(PrePostTemplateDefaults defaults) {
|
||||
this.defaults = defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should implement this method to provide the non-null
|
||||
* {@link ExpressionAttribute} for the method and the target class.
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.authorization.method;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.annotation.RepeatableContainers;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.PropertyPlaceholderHelper;
|
||||
|
||||
/**
|
||||
* A collection of utility methods that check for, and error on, conflicting annotations.
|
||||
* This is specifically important for Spring Security annotations which are not designed
|
||||
* to be repeatable.
|
||||
*
|
||||
* <p>
|
||||
* There are numerous ways that two annotations of the same type may be attached to the
|
||||
* same method. For example, a class may implement a method defined in two separate
|
||||
* interfaces. If both of those interfaces have a {@code @PreAuthorize} annotation, then
|
||||
* it's unclear which {@code @PreAuthorize} expression Spring Security should use.
|
||||
*
|
||||
* <p>
|
||||
* Another way is when one of Spring Security's annotations is used as a meta-annotation.
|
||||
* In that case, two custom annotations can be declared, each with their own
|
||||
* {@code @PreAuthorize} declaration. If both custom annotations are used on the same
|
||||
* method, then it's unclear which {@code @PreAuthorize} expression Spring Security should
|
||||
* use.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
final class AuthorizationAnnotationUtils {
|
||||
|
||||
static <A extends Annotation> Function<AnnotatedElement, A> withDefaults(Class<A> type,
|
||||
PrePostTemplateDefaults defaults) {
|
||||
Function<MergedAnnotation<A>, A> map = (mergedAnnotation) -> {
|
||||
if (mergedAnnotation.getMetaSource() == null) {
|
||||
return mergedAnnotation.synthesize();
|
||||
}
|
||||
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("{", "}", null,
|
||||
defaults.isIgnoreUnknown());
|
||||
String expression = (String) mergedAnnotation.asMap().get("value");
|
||||
Map<String, Object> annotationProperties = mergedAnnotation.getMetaSource().asMap();
|
||||
Map<String, String> stringProperties = new HashMap<>();
|
||||
for (Map.Entry<String, Object> property : annotationProperties.entrySet()) {
|
||||
String key = property.getKey();
|
||||
Object value = property.getValue();
|
||||
String asString = (value instanceof String) ? (String) value
|
||||
: DefaultConversionService.getSharedInstance().convert(value, String.class);
|
||||
stringProperties.put(key, asString);
|
||||
}
|
||||
AnnotatedElement annotatedElement = (AnnotatedElement) mergedAnnotation.getSource();
|
||||
String value = helper.replacePlaceholders(expression, stringProperties::get);
|
||||
Map<String, Object> properties = new HashMap<>(mergedAnnotation.asMap());
|
||||
properties.put("value", value);
|
||||
return MergedAnnotation.of(annotatedElement, type, properties).synthesize();
|
||||
};
|
||||
return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, map);
|
||||
}
|
||||
|
||||
static <A extends Annotation> Function<AnnotatedElement, A> withDefaults(Class<A> type) {
|
||||
return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, MergedAnnotation::synthesize);
|
||||
}
|
||||
|
||||
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
|
||||
return findDistinctAnnotation(method, annotationType, MergedAnnotation::synthesize);
|
||||
}
|
||||
|
||||
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
|
||||
return findDistinctAnnotation(type, annotationType, MergedAnnotation::synthesize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an exhaustive search on the type hierarchy of the given {@link Method} for
|
||||
* the annotation of type {@code annotationType}, including any annotations using
|
||||
* {@code annotationType} as a meta-annotation.
|
||||
*
|
||||
* <p>
|
||||
* If more than one unique annotation is found, then throw an error.
|
||||
* @param method the method declaration to search from
|
||||
* @param annotationType the annotation type to search for
|
||||
* @return a unique instance of the annotation attributed to the method, {@code null}
|
||||
* otherwise
|
||||
* @throws AnnotationConfigurationException if more than one unique instance of the
|
||||
* annotation is found
|
||||
*/
|
||||
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType,
|
||||
Function<MergedAnnotation<A>, A> map) {
|
||||
return findDistinctAnnotation(method, annotationType, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an exhaustive search on the type hierarchy of the given {@link Class} for
|
||||
* the annotation of type {@code annotationType}, including any annotations using
|
||||
* {@code annotationType} as a meta-annotation.
|
||||
*
|
||||
* <p>
|
||||
* If more than one unique annotation is found, then throw an error.
|
||||
* @param type the type to search from
|
||||
* @param annotationType the annotation type to search for
|
||||
* @return a unique instance of the annotation attributed to the class, {@code null}
|
||||
* otherwise
|
||||
* @throws AnnotationConfigurationException if more than one unique instance of the
|
||||
* annotation is found
|
||||
*/
|
||||
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType,
|
||||
Function<MergedAnnotation<A>, A> map) {
|
||||
return findDistinctAnnotation(type, annotationType, map);
|
||||
}
|
||||
|
||||
private static <A extends Annotation> A findDistinctAnnotation(AnnotatedElement annotatedElement,
|
||||
Class<A> annotationType, Function<MergedAnnotation<A>, A> map) {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none());
|
||||
List<A> annotations = mergedAnnotations.stream(annotationType).map(map).distinct().toList();
|
||||
|
||||
return switch (annotations.size()) {
|
||||
case 0 -> null;
|
||||
case 1 -> annotations.get(0);
|
||||
default -> throw new AnnotationConfigurationException("""
|
||||
Please ensure there is one unique annotation of type @%s attributed to %s. \
|
||||
Found %d competing annotations: %s""".formatted(annotationType.getName(), annotatedElement,
|
||||
annotations.size(), annotations));
|
||||
};
|
||||
}
|
||||
|
||||
private AuthorizationAnnotationUtils() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -29,12 +30,13 @@ import jakarta.annotation.security.RolesAllowed;
|
|||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -49,14 +51,6 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
private static final Set<Class<? extends Annotation>> JSR250_ANNOTATIONS = new HashSet<>();
|
||||
|
||||
static {
|
||||
JSR250_ANNOTATIONS.add(DenyAll.class);
|
||||
JSR250_ANNOTATIONS.add(PermitAll.class);
|
||||
JSR250_ANNOTATIONS.add(RolesAllowed.class);
|
||||
}
|
||||
|
||||
private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();
|
||||
|
||||
private AuthorizationManager<Collection<String>> authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
|
||||
|
@ -102,6 +96,9 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
|
|||
|
||||
private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
|
||||
|
||||
private final AnnotationSynthesizer<?> synthesizer = AnnotationSynthesizers
|
||||
.requireUnique(List.of(DenyAll.class, PermitAll.class, RolesAllowed.class));
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
|
||||
|
@ -121,45 +118,8 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
|
|||
|
||||
private Annotation findJsr250Annotation(Method method, Class<?> targetClass) {
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
Annotation annotation = findAnnotation(specificMethod);
|
||||
return (annotation != null) ? annotation
|
||||
: findAnnotation((targetClass != null) ? targetClass : specificMethod.getDeclaringClass());
|
||||
}
|
||||
|
||||
private Annotation findAnnotation(Method method) {
|
||||
Set<Annotation> annotations = new HashSet<>();
|
||||
for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
|
||||
Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(method, annotationClass);
|
||||
if (annotation != null) {
|
||||
annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
if (annotations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (annotations.size() > 1) {
|
||||
throw new AnnotationConfigurationException(
|
||||
"The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same method.");
|
||||
}
|
||||
return annotations.iterator().next();
|
||||
}
|
||||
|
||||
private Annotation findAnnotation(Class<?> clazz) {
|
||||
Set<Annotation> annotations = new HashSet<>();
|
||||
for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
|
||||
Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, annotationClass);
|
||||
if (annotation != null) {
|
||||
annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
if (annotations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (annotations.size() > 1) {
|
||||
throw new AnnotationConfigurationException(
|
||||
"The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same class definition.");
|
||||
}
|
||||
return annotations.iterator().next();
|
||||
Class<?> targetClassToUse = (targetClass != null) ? targetClass : specificMethod.getDeclaringClass();
|
||||
return this.synthesizer.synthesize(specificMethod, targetClassToUse);
|
||||
}
|
||||
|
||||
private Set<String> getAllowedRolesWithPrefix(RolesAllowed rolesAllowed) {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
@ -27,6 +26,8 @@ import org.springframework.aop.support.AopUtils;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -40,8 +41,14 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private final AnnotationSynthesizer<HandleAuthorizationDenied> handleAuthorizationDeniedSynthesizer = AnnotationSynthesizers
|
||||
.requireUnique(HandleAuthorizationDenied.class);
|
||||
|
||||
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
|
||||
|
||||
private AnnotationSynthesizer<PostAuthorize> postAuthorizeSynthesizer = AnnotationSynthesizers
|
||||
.requireUnique(PostAuthorize.class);
|
||||
|
||||
PostAuthorizeExpressionAttributeRegistry() {
|
||||
this.handlerResolver = (clazz) -> this.defaultHandler;
|
||||
}
|
||||
|
@ -60,13 +67,9 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||
}
|
||||
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
|
||||
.withDefaults(HandleAuthorizationDenied.class);
|
||||
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
|
||||
if (deniedHandler != null) {
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
deniedHandler = lookup.apply(targetClass(method, targetClass));
|
||||
Class<?> targetClassToUse = targetClass(method, targetClass);
|
||||
HandleAuthorizationDenied deniedHandler = this.handleAuthorizationDeniedSynthesizer.synthesize(method,
|
||||
targetClassToUse);
|
||||
if (deniedHandler != null) {
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
|
@ -74,9 +77,8 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||
}
|
||||
|
||||
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
|
||||
PostAuthorize postAuthorize = lookup.apply(method);
|
||||
return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass));
|
||||
Class<?> targetClassToUse = targetClass(method, targetClass);
|
||||
return this.postAuthorizeSynthesizer.synthesize(method, targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,6 +91,10 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
|
||||
}
|
||||
|
||||
void setTemplateDefaults(PrePostTemplateDefaults templateDefaults) {
|
||||
this.postAuthorizeSynthesizer = AnnotationSynthesizers.requireUnique(PostAuthorize.class, templateDefaults);
|
||||
}
|
||||
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
|
||||
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
|
||||
if (handlerClass == this.defaultHandler.getClass()) {
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.access.prepost.PostFilter;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
|
||||
/**
|
||||
* For internal use only, as this contract is likely to change.
|
||||
|
@ -34,6 +34,8 @@ import org.springframework.security.access.prepost.PostFilter;
|
|||
*/
|
||||
final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
private AnnotationSynthesizer<PostFilter> synthesizer = AnnotationSynthesizers.requireUnique(PostFilter.class);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||
|
@ -47,10 +49,13 @@ final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttr
|
|||
return new ExpressionAttribute(postFilterExpression);
|
||||
}
|
||||
|
||||
void setTemplateDefaults(PrePostTemplateDefaults defaults) {
|
||||
this.synthesizer = AnnotationSynthesizers.requireUnique(PostFilter.class, defaults);
|
||||
}
|
||||
|
||||
private PostFilter findPostFilterAnnotation(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, PostFilter> lookup = findUniqueAnnotation(PostFilter.class);
|
||||
PostFilter postFilter = lookup.apply(method);
|
||||
return (postFilter != null) ? postFilter : lookup.apply(targetClass(method, targetClass));
|
||||
Class<?> targetClassToUse = targetClass(method, targetClass);
|
||||
return this.synthesizer.synthesize(method, targetClassToUse);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
@ -27,6 +26,8 @@ import org.springframework.aop.support.AopUtils;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -40,8 +41,14 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
|||
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private final AnnotationSynthesizer<HandleAuthorizationDenied> handleAuthorizationDeniedSynthesizer = AnnotationSynthesizers
|
||||
.requireUnique(HandleAuthorizationDenied.class);
|
||||
|
||||
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
|
||||
|
||||
private AnnotationSynthesizer<PreAuthorize> preAuthorizeSynthesizer = AnnotationSynthesizers
|
||||
.requireUnique(PreAuthorize.class);
|
||||
|
||||
PreAuthorizeExpressionAttributeRegistry() {
|
||||
this.handlerResolver = (clazz) -> this.defaultHandler;
|
||||
}
|
||||
|
@ -60,13 +67,9 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
|||
}
|
||||
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
|
||||
.withDefaults(HandleAuthorizationDenied.class);
|
||||
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
|
||||
if (deniedHandler != null) {
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
deniedHandler = lookup.apply(targetClass(method, targetClass));
|
||||
Class<?> targetClassToUse = targetClass(method, targetClass);
|
||||
HandleAuthorizationDenied deniedHandler = this.handleAuthorizationDeniedSynthesizer.synthesize(method,
|
||||
targetClassToUse);
|
||||
if (deniedHandler != null) {
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
|
@ -74,9 +77,8 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
|||
}
|
||||
|
||||
private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
|
||||
PreAuthorize preAuthorize = lookup.apply(method);
|
||||
return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass));
|
||||
Class<?> targetClassToUse = targetClass(method, targetClass);
|
||||
return this.preAuthorizeSynthesizer.synthesize(method, targetClassToUse);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,6 +91,10 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
|||
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
|
||||
}
|
||||
|
||||
void setTemplateDefaults(PrePostTemplateDefaults defaults) {
|
||||
this.preAuthorizeSynthesizer = AnnotationSynthesizers.requireUnique(PreAuthorize.class, defaults);
|
||||
}
|
||||
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
|
||||
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
|
||||
if (handlerClass == this.defaultHandler.getClass()) {
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.access.prepost.PreFilter;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
|
||||
/**
|
||||
* For internal use only, as this contract is likely to change.
|
||||
|
@ -35,6 +35,8 @@ import org.springframework.security.access.prepost.PreFilter;
|
|||
final class PreFilterExpressionAttributeRegistry
|
||||
extends AbstractExpressionAttributeRegistry<PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute> {
|
||||
|
||||
private AnnotationSynthesizer<PreFilter> synthesizer = AnnotationSynthesizers.requireUnique(PreFilter.class);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
PreFilterExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||
|
@ -48,10 +50,13 @@ final class PreFilterExpressionAttributeRegistry
|
|||
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
|
||||
}
|
||||
|
||||
void setTemplateDefaults(PrePostTemplateDefaults defaults) {
|
||||
this.synthesizer = AnnotationSynthesizers.requireUnique(PreFilter.class, defaults);
|
||||
}
|
||||
|
||||
private PreFilter findPreFilterAnnotation(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, PreFilter> lookup = findUniqueAnnotation(PreFilter.class);
|
||||
PreFilter preFilter = lookup.apply(method);
|
||||
return (preFilter != null) ? preFilter : lookup.apply(targetClass(method, targetClass));
|
||||
Class<?> targetClassToUse = targetClass(method, targetClass);
|
||||
return this.synthesizer.synthesize(method, targetClassToUse);
|
||||
}
|
||||
|
||||
static final class PreFilterExpressionAttribute extends ExpressionAttribute {
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
||||
|
||||
/**
|
||||
* A component for configuring various cross-cutting aspects of pre/post method security
|
||||
*
|
||||
|
@ -26,31 +28,6 @@ package org.springframework.security.authorization.method;
|
|||
* @see org.springframework.security.access.prepost.PreFilter
|
||||
* @see org.springframework.security.access.prepost.PostFilter
|
||||
*/
|
||||
public final class PrePostTemplateDefaults {
|
||||
|
||||
private boolean ignoreUnknown = true;
|
||||
|
||||
/**
|
||||
* Whether template resolution should ignore placeholders it doesn't recognize.
|
||||
* <p>
|
||||
* By default, this value is <code>true</code>.
|
||||
* @since 6.3
|
||||
*/
|
||||
public boolean isIgnoreUnknown() {
|
||||
return this.ignoreUnknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure template resolution to ignore unknown placeholders. When set to
|
||||
* <code>false</code>, template resolution will throw an exception for unknown
|
||||
* placeholders.
|
||||
* <p>
|
||||
* By default, this value is <code>true</code>.
|
||||
* @param ignoreUnknown - whether to ignore unknown placeholders parameters
|
||||
* @since 6.3
|
||||
*/
|
||||
public void setIgnoreUnknown(boolean ignoreUnknown) {
|
||||
this.ignoreUnknown = ignoreUnknown;
|
||||
}
|
||||
public final class PrePostTemplateDefaults extends AnnotationTemplateExpressionDefaults {
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import org.springframework.security.authorization.AuthoritiesAuthorizationManage
|
|||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -50,6 +52,8 @@ public final class SecuredAuthorizationManager implements AuthorizationManager<M
|
|||
|
||||
private final Map<MethodClassKey, Set<String>> cachedAuthorities = new ConcurrentHashMap<>();
|
||||
|
||||
private final AnnotationSynthesizer<Secured> synthesizer = AnnotationSynthesizers.requireUnique(Secured.class);
|
||||
|
||||
/**
|
||||
* Sets an {@link AuthorizationManager} that accepts a collection of authority
|
||||
* strings.
|
||||
|
@ -92,9 +96,8 @@ public final class SecuredAuthorizationManager implements AuthorizationManager<M
|
|||
}
|
||||
|
||||
private Secured findSecuredAnnotation(Method method, Class<?> targetClass) {
|
||||
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
|
||||
return (secured != null) ? secured : AuthorizationAnnotationUtils
|
||||
.findUniqueAnnotation((targetClass != null) ? targetClass : method.getDeclaringClass(), Secured.class);
|
||||
Class<?> targetClassToUse = (targetClass != null) ? targetClass : method.getDeclaringClass();
|
||||
return this.synthesizer.synthesize(method, targetClassToUse);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A strategy for synthesizing an annotation from an {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>
|
||||
* Synthesis generally refers to the process of taking an annotation's meta-annotations
|
||||
* and placeholders, resolving them, and then combining these elements into a facade of
|
||||
* the raw annotation instance.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Since the process of synthesizing an annotation can be expensive, it is recommended to
|
||||
* cache the synthesized annotation to prevent multiple computations.
|
||||
* </p>
|
||||
*
|
||||
* @param <A> the annotation type
|
||||
* @author Josh Cummings
|
||||
* @since 6.4
|
||||
* @see UniqueMergedAnnotationSynthesizer
|
||||
* @see ExpressionTemplateAnnotationSynthesizer
|
||||
*/
|
||||
public interface AnnotationSynthesizer<A extends Annotation> {
|
||||
|
||||
/**
|
||||
* Synthesize an annotation of type {@code A} from the given {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>
|
||||
* Implementations should fail if they encounter more than one annotation of that type
|
||||
* on the element.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Implementations should describe their strategy for searching the element and any
|
||||
* surrounding class, interfaces, or super-class.
|
||||
* </p>
|
||||
* @param element the element to search
|
||||
* @return the synthesized annotation or {@code null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
default A synthesize(AnnotatedElement element, Class<?> targetClass) {
|
||||
Assert.notNull(targetClass, "targetClass cannot be null");
|
||||
MergedAnnotation<A> annotation = merge(element, targetClass);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
return annotation.synthesize();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default A synthesize(AnnotatedElement element) {
|
||||
if (element instanceof Method method) {
|
||||
return synthesize(element, method.getDeclaringClass());
|
||||
}
|
||||
if (element instanceof Parameter parameter) {
|
||||
return synthesize(parameter, parameter.getDeclaringExecutable().getDeclaringClass());
|
||||
}
|
||||
throw new UnsupportedOperationException("Unsupported element of type " + element.getClass());
|
||||
}
|
||||
|
||||
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass);
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Factory for creating {@link AnnotationSynthesizer} instances.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.4
|
||||
*/
|
||||
public final class AnnotationSynthesizers {
|
||||
|
||||
private AnnotationSynthesizers() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AnnotationSynthesizer} that requires synthesized annotations to be
|
||||
* unique on the given {@link AnnotatedElement}.
|
||||
* @param type the annotation type
|
||||
* @param <A> the annotation type
|
||||
* @return the default {@link AnnotationSynthesizer}
|
||||
*/
|
||||
public static <A extends Annotation> AnnotationSynthesizer<A> requireUnique(Class<A> type) {
|
||||
return new UniqueMergedAnnotationSynthesizer<>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AnnotationSynthesizer} that requires synthesized annotations to be
|
||||
* unique on the given {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>
|
||||
* When a {@link AnnotationTemplateExpressionDefaults} is provided, it will return a
|
||||
* synthesizer that supports placeholders in the annotation's attributes in addition
|
||||
* to the meta-annotation synthesizing provided by {@link #requireUnique(Class)}.
|
||||
* @param type the annotation type
|
||||
* @param templateDefaults the defaults for resolving placeholders in the annotation's
|
||||
* attributes
|
||||
* @param <A> the annotation type
|
||||
* @return the default {@link AnnotationSynthesizer}
|
||||
*/
|
||||
public static <A extends Annotation> AnnotationSynthesizer<A> requireUnique(Class<A> type,
|
||||
AnnotationTemplateExpressionDefaults templateDefaults) {
|
||||
if (templateDefaults == null) {
|
||||
return new UniqueMergedAnnotationSynthesizer<>(type);
|
||||
}
|
||||
return new ExpressionTemplateAnnotationSynthesizer<>(type, templateDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AnnotationSynthesizer} that requires synthesized annotations to be
|
||||
* unique on the given {@link AnnotatedElement}. Supplying multiple types implies that
|
||||
* the synthesized annotation must be unique across all specified types.
|
||||
* @param types the annotation types
|
||||
* @return the default {@link AnnotationSynthesizer}
|
||||
*/
|
||||
public static AnnotationSynthesizer<Annotation> requireUnique(List<Class<? extends Annotation>> types) {
|
||||
List<Class<Annotation>> casted = new ArrayList<>();
|
||||
types.forEach((type) -> casted.add((Class<Annotation>) type));
|
||||
return new UniqueMergedAnnotationSynthesizer<>(casted);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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;
|
||||
|
||||
/**
|
||||
* A component for configuring the expression attribute template of the parsed Spring
|
||||
* Security annotation
|
||||
*
|
||||
* @author DingHao
|
||||
* @since 6.4
|
||||
* @see AuthenticationPrincipal
|
||||
* @see CurrentSecurityContext
|
||||
*/
|
||||
public class AnnotationTemplateExpressionDefaults {
|
||||
|
||||
private boolean ignoreUnknown = true;
|
||||
|
||||
/**
|
||||
* Whether template resolution should ignore placeholders it doesn't recognize.
|
||||
* <p>
|
||||
* By default, this value is <code>true</code>.
|
||||
*/
|
||||
public boolean isIgnoreUnknown() {
|
||||
return this.ignoreUnknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure template resolution to ignore unknown placeholders. When set to
|
||||
* <code>false</code>, template resolution will throw an exception for unknown
|
||||
* placeholders.
|
||||
* <p>
|
||||
* By default, this value is <code>true</code>.
|
||||
* @param ignoreUnknown - whether to ignore unknown placeholders parameters
|
||||
*/
|
||||
public void setIgnoreUnknown(boolean ignoreUnknown) {
|
||||
this.ignoreUnknown = ignoreUnknown;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PropertyPlaceholderHelper;
|
||||
|
||||
/**
|
||||
* A strategy for synthesizing an annotation from an {@link AnnotatedElement} that
|
||||
* supports meta-annotations with placeholders, like the following:
|
||||
*
|
||||
* <pre>
|
||||
* @PreAuthorize("hasRole({role})")
|
||||
* public @annotation HasRole {
|
||||
* String role();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* In that case, you could use an {@link ExpressionTemplateAnnotationSynthesizer} of type
|
||||
* {@link org.springframework.security.access.prepost.PreAuthorize} to synthesize any
|
||||
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>
|
||||
* Note that in all cases, Spring Security does not allow for repeatable annotations. So
|
||||
* this class delegates to {@link UniqueMergedAnnotationSynthesizer} in order to error if
|
||||
* a repeat is discovered.
|
||||
*
|
||||
* <p>
|
||||
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
|
||||
* result to prevent multiple computations.
|
||||
*
|
||||
* @param <A> the annotation type
|
||||
* @author Josh Cummings
|
||||
* @since 6.4
|
||||
*/
|
||||
final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> implements AnnotationSynthesizer<A> {
|
||||
|
||||
private final Class<A> type;
|
||||
|
||||
private final UniqueMergedAnnotationSynthesizer<A> unique;
|
||||
|
||||
private final AnnotationTemplateExpressionDefaults templateDefaults;
|
||||
|
||||
private final Map<Parameter, MergedAnnotation<A>> uniqueParameterAnnotationCache = new HashMap<>();
|
||||
|
||||
private final Map<MethodClassKey, MergedAnnotation<A>> uniqueMethodAnnotationCache = new HashMap<>();
|
||||
|
||||
ExpressionTemplateAnnotationSynthesizer(Class<A> type, AnnotationTemplateExpressionDefaults templateDefaults) {
|
||||
Assert.notNull(type, "type cannot be null");
|
||||
Assert.notNull(templateDefaults, "templateDefaults cannot be null");
|
||||
this.type = type;
|
||||
this.unique = new UniqueMergedAnnotationSynthesizer<>(type);
|
||||
this.templateDefaults = templateDefaults;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
|
||||
if (element instanceof Parameter parameter) {
|
||||
MergedAnnotation<A> annotation = this.uniqueParameterAnnotationCache.computeIfAbsent(parameter,
|
||||
(p) -> this.unique.merge(p, targetClass));
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
return resolvePlaceholders(annotation);
|
||||
}
|
||||
if (element instanceof Method method) {
|
||||
MethodClassKey key = new MethodClassKey(method, targetClass);
|
||||
MergedAnnotation<A> annotation = this.uniqueMethodAnnotationCache.computeIfAbsent(key,
|
||||
(k) -> this.unique.merge(method, targetClass));
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
return resolvePlaceholders(annotation);
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported element of type " + element.getClass());
|
||||
}
|
||||
|
||||
private MergedAnnotation<A> resolvePlaceholders(MergedAnnotation<A> mergedAnnotation) {
|
||||
if (this.templateDefaults == null) {
|
||||
return mergedAnnotation;
|
||||
}
|
||||
if (mergedAnnotation.getMetaSource() == null) {
|
||||
return mergedAnnotation;
|
||||
}
|
||||
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("{", "}", null, null,
|
||||
this.templateDefaults.isIgnoreUnknown());
|
||||
Map<String, Object> properties = new HashMap<>(mergedAnnotation.asMap());
|
||||
Map<String, Object> metaAnnotationProperties = mergedAnnotation.getMetaSource().asMap();
|
||||
Map<String, String> stringProperties = new HashMap<>();
|
||||
for (Map.Entry<String, Object> property : metaAnnotationProperties.entrySet()) {
|
||||
String key = property.getKey();
|
||||
Object value = property.getValue();
|
||||
String asString = (value instanceof String) ? (String) value
|
||||
: DefaultConversionService.getSharedInstance().convert(value, String.class);
|
||||
stringProperties.put(key, asString);
|
||||
}
|
||||
Map<String, Object> annotationProperties = mergedAnnotation.asMap();
|
||||
for (Map.Entry<String, Object> annotationProperty : annotationProperties.entrySet()) {
|
||||
if (!(annotationProperty.getValue() instanceof String)) {
|
||||
continue;
|
||||
}
|
||||
String expression = (String) annotationProperty.getValue();
|
||||
String value = helper.replacePlaceholders(expression, stringProperties::get);
|
||||
properties.put(annotationProperty.getKey(), value);
|
||||
}
|
||||
AnnotatedElement annotatedElement = (AnnotatedElement) mergedAnnotation.getSource();
|
||||
return MergedAnnotation.of(annotatedElement, this.type, properties);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.RepeatableContainers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A strategy for synthesizing an annotation from an {@link AnnotatedElement} that
|
||||
* supports meta-annotations, like the following:
|
||||
*
|
||||
* <pre>
|
||||
* @PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
* public @annotation HasRole {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* In that case, you can use an {@link UniqueMergedAnnotationSynthesizer} of type
|
||||
* {@link org.springframework.security.access.prepost.PreAuthorize} to synthesize any
|
||||
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>
|
||||
* Note that in all cases, Spring Security does not allow for repeatable annotations. As
|
||||
* such, this class errors if a repeat is discovered.
|
||||
*
|
||||
* <p>
|
||||
* If the given annotation can be applied to types, this class will search for annotations
|
||||
* across the entire {@link MergedAnnotations.SearchStrategy type hierarchy}; otherwise,
|
||||
* it will only look for annotations {@link MergedAnnotations.SearchStrategy directly}
|
||||
* attributed to the element.
|
||||
*
|
||||
* <p>
|
||||
* When traversing the type hierarchy, this class will first look for annotations on the
|
||||
* given method, then on any methods that method overrides. If no annotations are found,
|
||||
* it will then search for annotations on the given class, then on any classes that class
|
||||
* extends and on any interfaces that class implements.
|
||||
*
|
||||
* <p>
|
||||
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
|
||||
* result to prevent multiple computations.
|
||||
*
|
||||
* @param <A> the annotation type
|
||||
* @author Josh Cummings
|
||||
* @since 6.4
|
||||
*/
|
||||
final class UniqueMergedAnnotationSynthesizer<A extends Annotation> implements AnnotationSynthesizer<A> {
|
||||
|
||||
private final List<Class<A>> types;
|
||||
|
||||
UniqueMergedAnnotationSynthesizer(Class<A> type) {
|
||||
Assert.notNull(type, "type cannot be null");
|
||||
this.types = List.of(type);
|
||||
}
|
||||
|
||||
UniqueMergedAnnotationSynthesizer(List<Class<A>> types) {
|
||||
Assert.notNull(types, "types cannot be null");
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
|
||||
if (element instanceof Parameter parameter) {
|
||||
return handleParameterElement(parameter);
|
||||
}
|
||||
if (element instanceof Method method) {
|
||||
return handleMethodElement(method, targetClass);
|
||||
}
|
||||
throw new AnnotationConfigurationException("Unsupported element of type " + element.getClass());
|
||||
}
|
||||
|
||||
private MergedAnnotation<A> handleParameterElement(Parameter parameter) {
|
||||
List<MergedAnnotation<A>> annotations = findDirectAnnotations(parameter);
|
||||
return requireUnique(parameter, annotations);
|
||||
}
|
||||
|
||||
private MergedAnnotation<A> handleMethodElement(Method method, Class<?> targetClass) {
|
||||
List<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);
|
||||
return requireUnique(method, annotations);
|
||||
}
|
||||
|
||||
private MergedAnnotation<A> requireUnique(AnnotatedElement element, List<MergedAnnotation<A>> annotations) {
|
||||
return switch (annotations.size()) {
|
||||
case 0 -> null;
|
||||
case 1 -> annotations.get(0);
|
||||
default -> {
|
||||
List<Annotation> synthesized = new ArrayList<>();
|
||||
for (MergedAnnotation<A> annotation : annotations) {
|
||||
synthesized.add(annotation.synthesize());
|
||||
}
|
||||
throw new AnnotationConfigurationException("""
|
||||
Please ensure there is one unique annotation of type %s attributed to %s. \
|
||||
Found %d competing annotations: %s""".formatted(this.types, element, annotations.size(),
|
||||
synthesized));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) {
|
||||
List<MergedAnnotation<A>> annotations = findClosestMethodAnnotations(method, targetClass, new HashSet<>());
|
||||
if (!annotations.isEmpty()) {
|
||||
return annotations;
|
||||
}
|
||||
return findClosestClassAnnotations(targetClass, new HashSet<>());
|
||||
}
|
||||
|
||||
private List<MergedAnnotation<A>> findClosestMethodAnnotations(Method method, Class<?> targetClass,
|
||||
Set<Class<?>> classesToSkip) {
|
||||
if (targetClass == null || classesToSkip.contains(targetClass) || targetClass == Object.class) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
classesToSkip.add(targetClass);
|
||||
try {
|
||||
Method methodToUse = targetClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
|
||||
List<MergedAnnotation<A>> annotations = findDirectAnnotations(methodToUse);
|
||||
if (!annotations.isEmpty()) {
|
||||
return annotations;
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// move on
|
||||
}
|
||||
List<MergedAnnotation<A>> annotations = new ArrayList<>();
|
||||
annotations.addAll(findClosestMethodAnnotations(method, targetClass.getSuperclass(), classesToSkip));
|
||||
for (Class<?> inter : targetClass.getInterfaces()) {
|
||||
annotations.addAll(findClosestMethodAnnotations(method, inter, classesToSkip));
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private List<MergedAnnotation<A>> findClosestClassAnnotations(Class<?> targetClass, Set<Class<?>> classesToSkip) {
|
||||
if (targetClass == null || classesToSkip.contains(targetClass) || targetClass == Object.class) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
classesToSkip.add(targetClass);
|
||||
List<MergedAnnotation<A>> annotations = new ArrayList<>(findDirectAnnotations(targetClass));
|
||||
if (!annotations.isEmpty()) {
|
||||
return annotations;
|
||||
}
|
||||
annotations.addAll(findClosestClassAnnotations(targetClass.getSuperclass(), classesToSkip));
|
||||
for (Class<?> inter : targetClass.getInterfaces()) {
|
||||
annotations.addAll(findClosestClassAnnotations(inter, classesToSkip));
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private List<MergedAnnotation<A>> findDirectAnnotations(AnnotatedElement element) {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.DIRECT,
|
||||
RepeatableContainers.none());
|
||||
return mergedAnnotations.stream()
|
||||
.filter((annotation) -> this.types.contains(annotation.getType()))
|
||||
.map((annotation) -> (MergedAnnotation<A>) annotation)
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.authorization.method;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthorizationAnnotationUtils}.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class AuthorizationAnnotationUtilsTests {
|
||||
|
||||
@Test // gh-13132
|
||||
void annotationsOnSyntheticMethodsShouldNotTriggerAnnotationConfigurationException() throws NoSuchMethodException {
|
||||
StringRepository proxy = (StringRepository) Proxy.newProxyInstance(
|
||||
Thread.currentThread().getContextClassLoader(), new Class[] { StringRepository.class },
|
||||
(p, m, args) -> null);
|
||||
Method method = proxy.getClass().getDeclaredMethod("findAll");
|
||||
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
|
||||
}
|
||||
|
||||
@Test // gh-13625
|
||||
void annotationsFromSuperSuperInterfaceShouldNotTriggerAnnotationConfigurationException() throws Exception {
|
||||
Method method = HelloImpl.class.getDeclaredMethod("sayHello");
|
||||
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleIdenticalAnnotationsOnClassShouldNotTriggerAnnotationConfigurationException() {
|
||||
Class<?> clazz = MultipleIdenticalPreAuthorizeAnnotationsOnClass.class;
|
||||
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleIdenticalAnnotationsOnMethodShouldNotTriggerAnnotationConfigurationException() throws Exception {
|
||||
Method method = MultipleIdenticalPreAuthorizeAnnotationsOnMethod.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void competingAnnotationsOnClassShouldTriggerAnnotationConfigurationException() {
|
||||
Class<?> clazz = CompetingPreAuthorizeAnnotationsOnClass.class;
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class))
|
||||
.withMessageContainingAll("Found 2 competing annotations:", "someRole", "otherRole");
|
||||
}
|
||||
|
||||
@Test
|
||||
void competingAnnotationsOnMethodShouldTriggerAnnotationConfigurationException() throws Exception {
|
||||
Method method = CompetingPreAuthorizeAnnotationsOnMethod.class.getDeclaredMethod("method");
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class))
|
||||
.withMessageContainingAll("Found 2 competing annotations:", "someRole", "otherRole");
|
||||
}
|
||||
|
||||
@Test
|
||||
void composedMergedAnnotationsAreNotSupported() {
|
||||
Class<?> clazz = ComposedPreAuthAnnotationOnClass.class;
|
||||
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("hasRole('composedRole')");
|
||||
}
|
||||
|
||||
private interface BaseRepository<T> {
|
||||
|
||||
Iterable<T> findAll();
|
||||
|
||||
}
|
||||
|
||||
private interface StringRepository extends BaseRepository<String> {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
List<String> findAll();
|
||||
|
||||
}
|
||||
|
||||
private interface Hello {
|
||||
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
String sayHello();
|
||||
|
||||
}
|
||||
|
||||
private interface SayHello extends Hello {
|
||||
|
||||
}
|
||||
|
||||
private static class HelloImpl implements SayHello {
|
||||
|
||||
@Override
|
||||
public String sayHello() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
private @interface RequireSomeRole {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('otherRole')")
|
||||
private @interface RequireOtherRole {
|
||||
|
||||
}
|
||||
|
||||
@RequireSomeRole
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
private static class MultipleIdenticalPreAuthorizeAnnotationsOnClass {
|
||||
|
||||
}
|
||||
|
||||
private static class MultipleIdenticalPreAuthorizeAnnotationsOnMethod {
|
||||
|
||||
@RequireSomeRole
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
void method() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RequireOtherRole
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
private static class CompetingPreAuthorizeAnnotationsOnClass {
|
||||
|
||||
}
|
||||
|
||||
private static class CompetingPreAuthorizeAnnotationsOnMethod {
|
||||
|
||||
@RequireOtherRole
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
void method() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('metaRole')")
|
||||
private @interface ComposedPreAuth {
|
||||
|
||||
@AliasFor(annotation = PreAuthorize.class)
|
||||
String value();
|
||||
|
||||
}
|
||||
|
||||
@ComposedPreAuth("hasRole('composedRole')")
|
||||
private static class ComposedPreAuthAnnotationOnClass {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,549 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link UniqueMergedAnnotationSynthesizer}
|
||||
*/
|
||||
public class UniqueMergedAnnotationSynthesizerTests {
|
||||
|
||||
private UniqueMergedAnnotationSynthesizer<PreAuthorize> synthesizer = new UniqueMergedAnnotationSynthesizer<>(
|
||||
PreAuthorize.class);
|
||||
|
||||
@Test
|
||||
void synthesizeWhenAnnotationOnInterfaceThenResolves() throws Exception {
|
||||
Method method = AnnotationOnInterface.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("one");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenAnnotationOnMethodThenResolves() throws Exception {
|
||||
Method method = AnnotationOnInterfaceMethod.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("three");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenAnnotationOnClassThenResolves() throws Exception {
|
||||
Method method = AnnotationOnClass.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("five");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenAnnotationOnClassMethodThenResolves() throws Exception {
|
||||
Method method = AnnotationOnClassMethod.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("six");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenInterfaceOverridingAnnotationOnInterfaceThenResolves() throws Exception {
|
||||
Method method = InterfaceMethodOverridingAnnotationOnInterface.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("eight");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenInterfaceOverridingMultipleInterfaceInheritanceThenResolves() throws Exception {
|
||||
Method method = InterfaceOverridingMultipleInterfaceInheritance.class.getMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method,
|
||||
InterfaceOverridingMultipleInterfaceInheritance.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("ten");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenInterfaceMethodOverridingAnnotationOnInterfaceThenResolves() throws Exception {
|
||||
Method method = InterfaceMethodOverridingMultipleInterfaceInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("eleven");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassMultipleInheritanceThenException() throws Exception {
|
||||
Method method = ClassAttemptingMultipleInterfaceInheritance.class.getDeclaredMethod("method");
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.synthesizer.synthesize(method));
|
||||
}
|
||||
|
||||
// gh-15097
|
||||
@Test
|
||||
void synthesizeWhenClassOverridingMultipleInterfaceInheritanceThenResolves() throws Exception {
|
||||
Method method = ClassOverridingMultipleInterfaceInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("thirteen");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassMethodOverridingMultipleInterfaceInheritanceThenResolves() throws Exception {
|
||||
Method method = ClassMethodOverridingMultipleInterfaceInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("fourteen");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassInheritingInterfaceOverridingInterfaceAnnotationThenResolves() throws Exception {
|
||||
Method method = ClassInheritingInterfaceOverridingInterfaceAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("seven");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassOverridingGrandparentInterfaceAnnotationThenResolves() throws Exception {
|
||||
Method method = ClassOverridingGrandparentInterfaceAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("sixteen");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenMethodOverridingGrandparentInterfaceAnnotationThenResolves() throws Exception {
|
||||
Method method = MethodOverridingGrandparentInterfaceAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("seventeen");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassInheritingMethodOverriddenAnnotationThenResolves() throws Exception {
|
||||
Method method = ClassInheritingMethodOverriddenAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("eight");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassOverridingMethodOverriddenAnnotationThenResolves() throws Exception {
|
||||
Method method = ClassOverridingMethodOverriddenAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("eight");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenMethodOverridingMethodOverriddenAnnotationThenResolves() throws Exception {
|
||||
Method method = MethodOverridingMethodOverriddenAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twenty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassInheritingMultipleInheritanceThenException() throws Exception {
|
||||
Method method = ClassInheritingMultipleInheritance.class.getDeclaredMethod("method");
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.synthesizer.synthesize(method));
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassOverridingMultipleInheritanceThenResolves() throws Exception {
|
||||
Method method = ClassOverridingMultipleInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twentytwo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenMethodOverridingMultipleInheritanceThenResolves() throws Exception {
|
||||
Method method = MethodOverridingMultipleInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twentythree");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenInheritingInterfaceAndMethodAnnotationsThenResolves() throws Exception {
|
||||
Method method = InheritingInterfaceAndMethodAnnotations.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("three");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassOverridingInterfaceAndMethodInheritanceThenResolves() throws Exception {
|
||||
Method method = ClassOverridingInterfaceAndMethodInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("three");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenMethodOverridingInterfaceAndMethodInheritanceThenResolves() throws Exception {
|
||||
Method method = MethodOverridingInterfaceAndMethodInheritance.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twentysix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenMultipleMethodInheritanceThenException() throws Exception {
|
||||
Method method = MultipleMethodInheritance.class.getDeclaredMethod("method");
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.synthesizer.synthesize(method));
|
||||
}
|
||||
|
||||
// gh-13234
|
||||
@Test
|
||||
void synthesizeWhenClassInheritingGrandparentInterfaceAnnotationThenResolves() throws Exception {
|
||||
Method method = ClassInheritingGrandparentInterfaceAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("one");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenMethodInheritingMethodOverridingInterfaceAndMethodInheritanceThenResolves() throws Exception {
|
||||
Method method = MethodInheritingMethodOverridingInterfaceAndMethodInheritance.class.getMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twentysix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassOverridingMethodOverridingInterfaceAndMethodInheritanceThenResolves() throws Exception {
|
||||
Method method = ClassOverridingMethodOverridingInterfaceAndMethodInheritance.class.getMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method,
|
||||
ClassOverridingMethodOverridingInterfaceAndMethodInheritance.class);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twentysix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenInterfaceInheritingAnnotationsAtDifferentLevelsThenException() throws Exception {
|
||||
Method method = InterfaceInheritingAnnotationsAtDifferentLevels.class.getMethod("method");
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.synthesizer.synthesize(method));
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeWhenClassMethodOverridingAnnotationOnMethodThenResolves() throws Exception {
|
||||
Method method = ClassMethodOverridingAnnotationOnMethod.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("twentyeight");
|
||||
}
|
||||
|
||||
// gh-13490
|
||||
@Test
|
||||
void synthesizeWhenClassInheritingInterfaceInheritingInterfaceMethodAnnotationThenResolves() throws Exception {
|
||||
Method method = ClassInheritingInterfaceInheritingInterfaceMethodAnnotation.class.getDeclaredMethod("method");
|
||||
PreAuthorize preAuthorize = this.synthesizer.synthesize(method);
|
||||
assertThat(preAuthorize.value()).isEqualTo("three");
|
||||
}
|
||||
|
||||
@PreAuthorize("one")
|
||||
private interface AnnotationOnInterface {
|
||||
|
||||
String method();
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("two")
|
||||
private interface AlsoAnnotationOnInterface {
|
||||
|
||||
String method();
|
||||
|
||||
}
|
||||
|
||||
private interface AnnotationOnInterfaceMethod {
|
||||
|
||||
@PreAuthorize("three")
|
||||
String method();
|
||||
|
||||
}
|
||||
|
||||
private interface AlsoAnnotationOnInterfaceMethod {
|
||||
|
||||
@PreAuthorize("four")
|
||||
String method();
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("five")
|
||||
private static class AnnotationOnClass {
|
||||
|
||||
String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class AnnotationOnClassMethod {
|
||||
|
||||
@PreAuthorize("six")
|
||||
String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("seven")
|
||||
private interface InterfaceOverridingAnnotationOnInterface extends AnnotationOnInterface {
|
||||
|
||||
}
|
||||
|
||||
private interface InterfaceMethodOverridingAnnotationOnInterface extends AnnotationOnInterface {
|
||||
|
||||
@PreAuthorize("eight")
|
||||
String method();
|
||||
|
||||
}
|
||||
|
||||
private interface InterfaceAttemptingMultipleInterfaceInheritance
|
||||
extends AnnotationOnInterface, AlsoAnnotationOnInterface {
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("ten")
|
||||
private interface InterfaceOverridingMultipleInterfaceInheritance
|
||||
extends AnnotationOnInterface, AlsoAnnotationOnInterface {
|
||||
|
||||
}
|
||||
|
||||
private interface InterfaceMethodOverridingMultipleInterfaceInheritance
|
||||
extends AnnotationOnInterface, AlsoAnnotationOnInterface {
|
||||
|
||||
@PreAuthorize("eleven")
|
||||
String method();
|
||||
|
||||
}
|
||||
|
||||
private static class ClassAttemptingMultipleInterfaceInheritance
|
||||
implements AnnotationOnInterface, AlsoAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("thirteen")
|
||||
private static class ClassOverridingMultipleInterfaceInheritance
|
||||
implements AnnotationOnInterface, AlsoAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ClassMethodOverridingMultipleInterfaceInheritance
|
||||
implements AnnotationOnInterface, AlsoAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("fourteen")
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ClassInheritingInterfaceOverridingInterfaceAnnotation
|
||||
implements InterfaceOverridingAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("sixteen")
|
||||
private static class ClassOverridingGrandparentInterfaceAnnotation
|
||||
implements InterfaceOverridingAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MethodOverridingGrandparentInterfaceAnnotation
|
||||
implements InterfaceOverridingAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("seventeen")
|
||||
public String method() {
|
||||
return "ok";
|
||||
} // unambiguously seventeen
|
||||
|
||||
}
|
||||
|
||||
private static class ClassInheritingMethodOverriddenAnnotation
|
||||
implements InterfaceMethodOverridingAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("nineteen")
|
||||
private static class ClassOverridingMethodOverriddenAnnotation
|
||||
implements InterfaceMethodOverridingAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MethodOverridingMethodOverriddenAnnotation
|
||||
implements InterfaceMethodOverridingAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("twenty")
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ClassInheritingMultipleInheritance implements InterfaceAttemptingMultipleInterfaceInheritance {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("twentytwo")
|
||||
private static class ClassOverridingMultipleInheritance implements InterfaceAttemptingMultipleInterfaceInheritance {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MethodOverridingMultipleInheritance
|
||||
implements InterfaceAttemptingMultipleInterfaceInheritance {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("twentythree")
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class InheritingInterfaceAndMethodAnnotations
|
||||
implements AnnotationOnInterface, AnnotationOnInterfaceMethod {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("twentyfive")
|
||||
private static class ClassOverridingInterfaceAndMethodInheritance
|
||||
implements AnnotationOnInterface, AnnotationOnInterfaceMethod {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MethodOverridingInterfaceAndMethodInheritance
|
||||
implements AnnotationOnInterface, AnnotationOnInterfaceMethod {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("twentysix")
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MultipleMethodInheritance
|
||||
implements AnnotationOnInterfaceMethod, AlsoAnnotationOnInterfaceMethod {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface InterfaceInheritingInterfaceAnnotation extends AnnotationOnInterface {
|
||||
|
||||
}
|
||||
|
||||
private static class ClassInheritingGrandparentInterfaceAnnotation
|
||||
implements InterfaceInheritingInterfaceAnnotation {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MethodInheritingMethodOverridingInterfaceAndMethodInheritance
|
||||
extends MethodOverridingInterfaceAndMethodInheritance {
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("twentyseven")
|
||||
private static class ClassOverridingMethodOverridingInterfaceAndMethodInheritance
|
||||
extends MethodOverridingInterfaceAndMethodInheritance {
|
||||
|
||||
}
|
||||
|
||||
private static class InterfaceInheritingAnnotationsAtDifferentLevels
|
||||
implements InterfaceInheritingInterfaceAnnotation, AlsoAnnotationOnInterface {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ClassMethodOverridingAnnotationOnMethod implements AnnotationOnInterfaceMethod {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("twentyeight")
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface InterfaceInheritingInterfaceMethodAnnotation extends AnnotationOnInterfaceMethod {
|
||||
|
||||
}
|
||||
|
||||
private static class ClassInheritingInterfaceInheritingInterfaceMethodAnnotation
|
||||
implements InterfaceInheritingInterfaceMethodAnnotation {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue