Prechádzať zdrojové kódy

Add ExpressionTemplateValueProvider

Closes gh-17447

Signed-off-by: Mike Heath <michael.heath@familysearch.org>
Mike Heath 1 mesiac pred
rodič
commit
93cb01612b

+ 21 - 1
core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java

@@ -59,13 +59,18 @@ import org.springframework.util.PropertyPlaceholderHelper;
  * {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
  *
  * <p>
+ * Meta-annotations that use enum values can use {@link ExpressionTemplateValueProvider} to
+ * provide custom placeholder values.
+ *
+ * <p>
  * Since the process of synthesis is expensive, it is recommended to cache the synthesized
  * result to prevent multiple computations.
  *
  * @param <A> the annotation to search for and synthesize
  * @author Josh Cummings
  * @author DingHao
- * @since 6.4
+ * @author Mike Heath
+ * @since 7.0
  */
 final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
 		extends AbstractSecurityAnnotationScanner<A> {
@@ -74,6 +79,7 @@ final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
 
 	static {
 		conversionService.addConverter(new ClassToStringConverter());
+		conversionService.addConverter(new ExpressionTemplateValueProviderConverter());
 	}
 
 	private final Class<A> type;
@@ -162,4 +168,18 @@ final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
 
 	}
 
+	static class ExpressionTemplateValueProviderConverter implements GenericConverter {
+
+		@Override
+		public Set<ConvertiblePair> getConvertibleTypes() {
+			return Collections.singleton(new ConvertiblePair(ExpressionTemplateValueProvider.class, String.class));
+		}
+
+		@Override
+		public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+			return (source != null) ? ((ExpressionTemplateValueProvider)source).getExpressionTemplateValue() : null;
+		}
+
+	}
+
 }

+ 35 - 0
core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateValueProvider.java

@@ -0,0 +1,35 @@
+package org.springframework.security.core.annotation;
+
+/**
+ * Provides a mechanism for providing custom values from enum types used in security
+ * meta-annotation expressions. For example:
+ *
+ * <pre>
+ * enum Permission implements ExpressionTemplateValueProvider {
+ *   READ,
+ *   WRITE;
+ *
+ *   &#64;Override
+ *   public String getExpressionTemplateValue() {
+ *     return switch (this) {
+ *       case READ -> "user.permission-read";
+ *       case WRITE -> "user.permission-write";
+ *     }
+ *   }
+ *
+ * }
+ * </pre>
+ *
+ * @since 6.5
+ * @author Mike Heath
+ */
+public interface ExpressionTemplateValueProvider {
+
+	/**
+	 * Returns the value to be used in an expression template.
+	 *
+	 * @return the value to be used in an expression template
+	 */
+	String getExpressionTemplateValue();
+
+}

+ 40 - 0
core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java

@@ -54,6 +54,43 @@ public class ExpressionTemplateSecurityAnnotationScannerTests {
 		assertThat(preAuthorize.value()).isEqualTo("check(#name)");
 	}
 
+	@Test
+	void parseMetaSourceAnnotationWithEnumImplementingExpressionTemplateValueProvider() throws Exception {
+		Method method = MessageService.class.getDeclaredMethod("process");
+		PreAuthorize preAuthorize = this.scanner.scan(method, method.getDeclaringClass());
+		assertThat(preAuthorize.value()).isEqualTo("hasAnyAuthority('user.READ','user.WRITE')");
+	}
+
+	enum Permission implements ExpressionTemplateValueProvider {
+		READ,
+		WRITE;
+
+		@Override
+		public String getExpressionTemplateValue() {
+			return switch (this) {
+				case READ -> "'user.READ'";
+				case WRITE -> "'user.WRITE'";
+			};
+		}
+	}
+
+	@Documented
+	@Retention(RetentionPolicy.RUNTIME)
+	@Target({ ElementType.TYPE, ElementType.METHOD })
+	@PreAuthorize("hasAnyAuthority({permissions})")
+	@interface HasAnyCustomPermissions {
+
+		Permission[] permissions();
+
+	}
+
+	@Documented
+	@Retention(RetentionPolicy.RUNTIME)
+	@Target({ ElementType.TYPE, ElementType.METHOD })
+	@HasAnyCustomPermissions(permissions = { Permission.READ, Permission.WRITE })
+	@interface HasAllCustomPermissions {
+	}
+
 	@Documented
 	@Retention(RetentionPolicy.RUNTIME)
 	@Target({ ElementType.TYPE, ElementType.METHOD })
@@ -86,6 +123,9 @@ public class ExpressionTemplateSecurityAnnotationScannerTests {
 
 	private interface MessageService {
 
+		@HasAllCustomPermissions
+		void process();
+
 		@HasReadPermission("#name")
 		String sayHello(String name);