Skip to content

Commit 0b963d3

Browse files
committed
Add ExpressionTemplateValueProvider
Closes gh-17447
1 parent ca557a9 commit 0b963d3

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed

core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,18 @@
5959
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
6060
*
6161
* <p>
62+
* Meta-annotations that use enum values can use {@link ExpressionTemplateValueProvider} to
63+
* provide custom placeholder values.
64+
*
65+
* <p>
6266
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
6367
* result to prevent multiple computations.
6468
*
6569
* @param <A> the annotation to search for and synthesize
6670
* @author Josh Cummings
6771
* @author DingHao
68-
* @since 6.4
72+
* @author Mike Heath
73+
* @since 7.0
6974
*/
7075
final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
7176
extends AbstractSecurityAnnotationScanner<A> {
@@ -74,6 +79,7 @@ final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
7479

7580
static {
7681
conversionService.addConverter(new ClassToStringConverter());
82+
conversionService.addConverter(new ExpressionTemplateValueProviderConverter());
7783
}
7884

7985
private final Class<A> type;
@@ -162,4 +168,18 @@ public Set<ConvertiblePair> getConvertibleTypes() {
162168

163169
}
164170

171+
static class ExpressionTemplateValueProviderConverter implements GenericConverter {
172+
173+
@Override
174+
public Set<ConvertiblePair> getConvertibleTypes() {
175+
return Collections.singleton(new ConvertiblePair(ExpressionTemplateValueProvider.class, String.class));
176+
}
177+
178+
@Override
179+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
180+
return (source != null) ? ((ExpressionTemplateValueProvider)source).getExpressionTemplateValue() : null;
181+
}
182+
183+
}
184+
165185
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.springframework.security.core.annotation;
2+
3+
/**
4+
* Provides a mechanism for providing custom values from enum types used in security
5+
* meta-annotation expressions. For example:
6+
*
7+
* <pre>
8+
* enum Permission implements ExpressionTemplateValueProvider {
9+
* READ,
10+
* WRITE;
11+
*
12+
* &#64;Override
13+
* public String getExpressionTemplateValue() {
14+
* return switch (this) {
15+
* case READ -> "user.permission-read";
16+
* case WRITE -> "user.permission-write";
17+
* }
18+
* }
19+
*
20+
* }
21+
* </pre>
22+
*
23+
* @since 6.5
24+
* @author Mike Heath
25+
*/
26+
public interface ExpressionTemplateValueProvider {
27+
28+
/**
29+
* Returns the value to be used in an expression template.
30+
*
31+
* @return the value to be used in an expression template
32+
*/
33+
String getExpressionTemplateValue();
34+
35+
}

core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,43 @@ void parseMultipleMetaSourceAnnotationParameterWithAliasFor() throws Exception {
5454
assertThat(preAuthorize.value()).isEqualTo("check(#name)");
5555
}
5656

57+
@Test
58+
void parseMetaSourceAnnotationWithEnumImplementingExpressionTemplateValueProvider() throws Exception {
59+
Method method = MessageService.class.getDeclaredMethod("process");
60+
PreAuthorize preAuthorize = this.scanner.scan(method, method.getDeclaringClass());
61+
assertThat(preAuthorize.value()).isEqualTo("hasAnyAuthority('user.READ','user.WRITE')");
62+
}
63+
64+
enum Permission implements ExpressionTemplateValueProvider {
65+
READ,
66+
WRITE;
67+
68+
@Override
69+
public String getExpressionTemplateValue() {
70+
return switch (this) {
71+
case READ -> "'user.READ'";
72+
case WRITE -> "'user.WRITE'";
73+
};
74+
}
75+
}
76+
77+
@Documented
78+
@Retention(RetentionPolicy.RUNTIME)
79+
@Target({ ElementType.TYPE, ElementType.METHOD })
80+
@PreAuthorize("hasAnyAuthority({permissions})")
81+
@interface HasAnyCustomPermissions {
82+
83+
Permission[] permissions();
84+
85+
}
86+
87+
@Documented
88+
@Retention(RetentionPolicy.RUNTIME)
89+
@Target({ ElementType.TYPE, ElementType.METHOD })
90+
@HasAnyCustomPermissions(permissions = { Permission.READ, Permission.WRITE })
91+
@interface HasAllCustomPermissions {
92+
}
93+
5794
@Documented
5895
@Retention(RetentionPolicy.RUNTIME)
5996
@Target({ ElementType.TYPE, ElementType.METHOD })
@@ -86,6 +123,9 @@ void parseMultipleMetaSourceAnnotationParameterWithAliasFor() throws Exception {
86123

87124
private interface MessageService {
88125

126+
@HasAllCustomPermissions
127+
void process();
128+
89129
@HasReadPermission("#name")
90130
String sayHello(String name);
91131

0 commit comments

Comments
 (0)