Skip to content

Add @AuthenticationPrincipal/@CurrentSecurityContext Interface Support for Expression Templates #16201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
Expand Down Expand Up @@ -83,6 +84,7 @@
*
* @param <A> the annotation to search for and synthesize
* @author Josh Cummings
* @author DingHao
* @since 6.4
*/
final class UniqueSecurityAnnotationScanner<A extends Annotation> extends AbstractSecurityAnnotationScanner<A> {
Expand All @@ -107,7 +109,7 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
if (element instanceof Parameter parameter) {
return this.uniqueParameterAnnotationCache.computeIfAbsent(parameter, (p) -> {
List<MergedAnnotation<A>> annotations = findDirectAnnotations(p);
List<MergedAnnotation<A>> annotations = findParameterAnnotations(p);
return requireUnique(p, annotations);
});
}
Expand Down Expand Up @@ -137,6 +139,56 @@ private MergedAnnotation<A> requireUnique(AnnotatedElement element, List<MergedA
};
}

private List<MergedAnnotation<A>> findParameterAnnotations(Parameter current) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(current);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
Executable executable = current.getDeclaringExecutable();
if (executable instanceof Method method) {
Class<?> clazz = method.getDeclaringClass();
Set<Class<?>> visited = new HashSet<>();
while (clazz != null && clazz != Object.class) {
directAnnotations = findClosestParameterAnnotations(method, clazz, current, visited);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
clazz = clazz.getSuperclass();
}
}
return Collections.emptyList();
}

private List<MergedAnnotation<A>> findClosestParameterAnnotations(Method method, Class<?> clazz, Parameter current,
Set<Class<?>> visited) {
if (!visited.add(clazz)) {
return Collections.emptyList();
}
List<MergedAnnotation<A>> annotations = new ArrayList<>(findDirectParameterAnnotations(method, clazz, current));
for (Class<?> ifc : clazz.getInterfaces()) {
annotations.addAll(findClosestParameterAnnotations(method, ifc, current, visited));
}
return annotations;
}

private List<MergedAnnotation<A>> findDirectParameterAnnotations(Method method, Class<?> clazz, Parameter current) {
try {
Method methodToUse = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
for (Parameter parameter : methodToUse.getParameters()) {
if (parameter.getName().equals(current.getName())) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(parameter);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
}
}
}
catch (NoSuchMethodException ex) {
// move on
}
return Collections.emptyList();
}

private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) {
// The method may be on an interface, but we need attributes from the target
// class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@

package org.springframework.security.core.annotation;

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 java.lang.reflect.Parameter;
import java.util.List;

import org.junit.jupiter.api.Test;

Expand All @@ -34,6 +40,9 @@ public class UniqueSecurityAnnotationScannerTests {
private UniqueSecurityAnnotationScanner<PreAuthorize> scanner = new UniqueSecurityAnnotationScanner<>(
PreAuthorize.class);

private UniqueSecurityAnnotationScanner<CustomParameterAnnotation> parameterScanner = new UniqueSecurityAnnotationScanner<>(
CustomParameterAnnotation.class);

@Test
void scanWhenAnnotationOnInterfaceThenResolves() throws Exception {
Method method = AnnotationOnInterface.class.getDeclaredMethod("method");
Expand Down Expand Up @@ -251,6 +260,101 @@ void scanWhenClassInheritingAbstractClassNoAnnotationsThenNoAnnotation() throws
assertThat(preAuthorize).isNull();
}

@Test
void scanParameterAnnotationWhenAnnotationOnInterface() throws Exception {
Parameter parameter = UserService.class.getDeclaredMethod("add", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("one");
}

@Test
void scanParameterAnnotationWhenClassInheritingInterfaceAnnotation() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("add", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("one");
}

@Test
void scanParameterAnnotationWhenClassOverridingMethodOverridingInterface() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("get", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("five");
}

@Test
void scanParameterAnnotationWhenMultipleMethodInheritanceThenException() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("list", String.class).getParameters()[0];
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> this.parameterScanner.scan(parameter));
}

@Test
void scanParameterAnnotationWhenInterfaceNoAnnotationsThenException() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("delete", String.class).getParameters()[0];
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> this.parameterScanner.scan(parameter));
}

interface UserService {

void add(@CustomParameterAnnotation("one") String user);

List<String> list(@CustomParameterAnnotation("two") String user);

String get(@CustomParameterAnnotation("three") String user);

void delete(@CustomParameterAnnotation("five") String user);

}

interface OtherUserService {

List<String> list(@CustomParameterAnnotation("four") String user);

}

interface ThirdPartyUserService {

void delete(@CustomParameterAnnotation("five") String user);

}

interface RemoteUserService extends ThirdPartyUserService {

}

static class UserServiceImpl implements UserService, OtherUserService, RemoteUserService {

@Override
public void add(String user) {

}

@Override
public List<String> list(String user) {
return List.of(user);
}

@Override
public String get(@CustomParameterAnnotation("five") String user) {
return user;
}

@Override
public void delete(String user) {

}

}

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@interface CustomParameterAnnotation {

String value();

}

@PreAuthorize("one")
private interface AnnotationOnInterface {

Expand Down
Loading