diff --git a/src/main/java/com/hubspot/jinjava/el/ext/AllowlistMethodValidator.java b/src/main/java/com/hubspot/jinjava/el/ext/AllowlistMethodValidator.java index 7e19b70c1..44f67a700 100644 --- a/src/main/java/com/hubspot/jinjava/el/ext/AllowlistMethodValidator.java +++ b/src/main/java/com/hubspot/jinjava/el/ext/AllowlistMethodValidator.java @@ -51,6 +51,12 @@ public Method validateMethod(Method m) { m1 -> { Class clazz = m1.getDeclaringClass(); String canonicalClassName = clazz.getCanonicalName(); + if (canonicalClassName == null) { + Class superclass = clazz.getSuperclass(); + if (superclass != null && superclass.isEnum()) { + canonicalClassName = superclass.getCanonicalName(); + } + } if (canonicalClassName == null) { return false; } diff --git a/src/main/java/com/hubspot/jinjava/el/ext/AllowlistReturnTypeValidator.java b/src/main/java/com/hubspot/jinjava/el/ext/AllowlistReturnTypeValidator.java index e42d79888..58195170a 100644 --- a/src/main/java/com/hubspot/jinjava/el/ext/AllowlistReturnTypeValidator.java +++ b/src/main/java/com/hubspot/jinjava/el/ext/AllowlistReturnTypeValidator.java @@ -50,7 +50,7 @@ public Object validateReturnType(Object o) { if (o instanceof String || o instanceof Number || o instanceof Boolean) { return o; } - Class clazz = o.getClass(); + Class clazz = o instanceof Enum ? ((Enum) o).getDeclaringClass() : o.getClass(); if (clazz.isArray() && allowArrays) { return o; } diff --git a/src/test/java/com/hubspot/jinjava/el/ext/AllowlistEnumTest.java b/src/test/java/com/hubspot/jinjava/el/ext/AllowlistEnumTest.java new file mode 100644 index 000000000..39ff75357 --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/el/ext/AllowlistEnumTest.java @@ -0,0 +1,95 @@ +package com.hubspot.jinjava.el.ext; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hubspot.jinjava.BaseJinjavaTest; +import com.hubspot.jinjava.testobjects.OperationEnum; +import com.hubspot.jinjava.testobjects.SimpleColorEnum; +import java.lang.reflect.Method; +import java.time.Month; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +public class AllowlistEnumTest extends BaseJinjavaTest { + + private static final AllowlistReturnTypeValidator ENUM_RETURN_TYPE_VALIDATOR = + AllowlistReturnTypeValidator.create( + ReturnTypeValidatorConfig + .builder() + .addAllowedCanonicalClassNames( + SimpleColorEnum.class.getCanonicalName(), + OperationEnum.class.getCanonicalName() + ) + .build() + ); + + private static final AllowlistMethodValidator ENUM_METHOD_VALIDATOR = + AllowlistMethodValidator.create( + MethodValidatorConfig + .builder() + .addAllowedDeclaredMethodsFromCanonicalClassNames( + SimpleColorEnum.class.getCanonicalName(), + OperationEnum.class.getCanonicalName() + ) + .build() + ); + + @Test + public void itAllowsReturningSimpleEnumValue() { + assertThat(ENUM_RETURN_TYPE_VALIDATOR.validateReturnType(SimpleColorEnum.RED)) + .isEqualTo(SimpleColorEnum.RED); + } + + @Test + public void itAllowsReturningConstantSpecificBodyEnumValue() { + assertThat(ENUM_RETURN_TYPE_VALIDATOR.validateReturnType(OperationEnum.PLUS)) + .isEqualTo(OperationEnum.PLUS); + } + + @Test + public void itRejectsReturningNonAllowlistedEnumValue() { + assertThat(ENUM_RETURN_TYPE_VALIDATOR.validateReturnType(Month.JANUARY)).isNull(); + } + + @Test + public void itAllowsInvokingSimpleEnumGetter() throws NoSuchMethodException { + Method getLabel = SimpleColorEnum.class.getMethod("getLabel"); + assertThat(ENUM_METHOD_VALIDATOR.validateMethod(getLabel)).isEqualTo(getLabel); + } + + @Test + public void itAllowsInvokingConstantSpecificBodyEnumMethod() + throws NoSuchMethodException { + Method apply = OperationEnum.PLUS.getClass().getMethod("apply", int.class, int.class); + assertThat(apply.getDeclaringClass().getCanonicalName()).isNull(); + assertThat(ENUM_METHOD_VALIDATOR.validateMethod(apply)).isEqualTo(apply); + } + + @Test + public void itRejectsInvokingNonAllowlistedEnumMethod() throws NoSuchMethodException { + Method getValue = Month.class.getMethod("getValue"); + assertThat(ENUM_METHOD_VALIDATOR.validateMethod(getValue)).isNull(); + } + + @Test + public void itRendersSimpleEnumGetter() { + Map context = new HashMap<>(); + context.put("color", SimpleColorEnum.RED); + assertThat(jinjava.render("{{ color.label }}", context)).isEqualTo("red-label"); + } + + @Test + public void itRendersConstantSpecificBodyEnumMethodInvocation() { + Map context = new HashMap<>(); + context.put("op", OperationEnum.PLUS); + assertThat(jinjava.render("{{ op.apply(2, 3) }}", context)).isEqualTo("5"); + } + + @Test + public void itRendersEnumValueAsName() { + Map context = new HashMap<>(); + context.put("op", OperationEnum.TIMES); + assertThat(jinjava.render("{{ op }}", context)).isEqualTo("TIMES"); + } +} diff --git a/src/test/java/com/hubspot/jinjava/testobjects/OperationEnum.java b/src/test/java/com/hubspot/jinjava/testobjects/OperationEnum.java new file mode 100644 index 000000000..871b333c4 --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/testobjects/OperationEnum.java @@ -0,0 +1,18 @@ +package com.hubspot.jinjava.testobjects; + +public enum OperationEnum { + PLUS { + @Override + public int apply(int a, int b) { + return a + b; + } + }, + TIMES { + @Override + public int apply(int a, int b) { + return a * b; + } + }; + + public abstract int apply(int a, int b); +} diff --git a/src/test/java/com/hubspot/jinjava/testobjects/SimpleColorEnum.java b/src/test/java/com/hubspot/jinjava/testobjects/SimpleColorEnum.java new file mode 100644 index 000000000..048eccf21 --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/testobjects/SimpleColorEnum.java @@ -0,0 +1,16 @@ +package com.hubspot.jinjava.testobjects; + +public enum SimpleColorEnum { + RED("red-label"), + GREEN("green-label"); + + private final String label; + + SimpleColorEnum(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +}