Skip to content

Commit 49c9aee

Browse files
committed
Validate ReturnTypes at the top-level so that accessing values of
arrays, lists, maps have their return values validated
1 parent 46c6dc3 commit 49c9aee

6 files changed

Lines changed: 90 additions & 75 deletions

File tree

src/main/java/com/hubspot/jinjava/el/ExpressionResolver.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class ExpressionResolver {
4141

4242
private final JinjavaInterpreter interpreter;
4343
private final ExpressionFactory expressionFactory;
44-
private final JinjavaInterpreterResolver resolver;
44+
private final ReturnTypeValidatingJinjavaInterpreterResolver resolver;
4545
private final JinjavaELContext elContext;
4646
private final ObjectUnwrapper objectUnwrapper;
4747

@@ -55,7 +55,11 @@ public ExpressionResolver(JinjavaInterpreter interpreter, Jinjava jinjava) {
5555
? jinjava.getEagerExpressionFactory()
5656
: jinjava.getExpressionFactory();
5757

58-
this.resolver = new JinjavaInterpreterResolver(interpreter);
58+
this.resolver =
59+
new ReturnTypeValidatingJinjavaInterpreterResolver(
60+
interpreter.getConfig().getReturnTypeValidator(),
61+
new JinjavaInterpreterResolver(interpreter)
62+
);
5963
this.elContext = new JinjavaELContext(interpreter, resolver);
6064
for (ELFunctionDefinition fn : jinjava.getGlobalContext().getAllFunctions()) {
6165
this.elContext.setFunction(fn.getNamespace(), fn.getLocalName(), fn.getMethod());
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.hubspot.jinjava.el;
2+
3+
import com.hubspot.jinjava.el.ext.AllowlistReturnTypeValidator;
4+
import java.beans.FeatureDescriptor;
5+
import java.util.Iterator;
6+
import javax.el.ELContext;
7+
import javax.el.ELResolver;
8+
9+
class ReturnTypeValidatingJinjavaInterpreterResolver extends ELResolver {
10+
11+
private final AllowlistReturnTypeValidator returnTypeValidator;
12+
private final JinjavaInterpreterResolver delegate;
13+
14+
ReturnTypeValidatingJinjavaInterpreterResolver(
15+
AllowlistReturnTypeValidator returnTypeValidator,
16+
JinjavaInterpreterResolver delegate
17+
) {
18+
this.returnTypeValidator = returnTypeValidator;
19+
this.delegate = delegate;
20+
}
21+
22+
@Override
23+
public Class<?> getCommonPropertyType(ELContext context, Object base) {
24+
return delegate.getCommonPropertyType(context, base);
25+
}
26+
27+
@Override
28+
public Iterator<FeatureDescriptor> getFeatureDescriptors(
29+
ELContext context,
30+
Object base
31+
) {
32+
return delegate.getFeatureDescriptors(context, base);
33+
}
34+
35+
@Override
36+
public Class<?> getType(ELContext context, Object base, Object property) {
37+
return delegate.getType(context, base, property);
38+
}
39+
40+
@Override
41+
public Object getValue(ELContext context, Object base, Object property) {
42+
return returnTypeValidator.validateReturnType(
43+
delegate.getValue(context, base, property)
44+
);
45+
}
46+
47+
@Override
48+
public boolean isReadOnly(ELContext context, Object base, Object property) {
49+
return delegate.isReadOnly(context, base, property);
50+
}
51+
52+
@Override
53+
public void setValue(ELContext context, Object base, Object property, Object value) {
54+
delegate.setValue(context, base, property, value);
55+
}
56+
57+
@Override
58+
public Object invoke(
59+
ELContext context,
60+
Object base,
61+
Object method,
62+
Class<?>[] paramTypes,
63+
Object[] params
64+
) {
65+
return returnTypeValidator.validateReturnType(
66+
delegate.invoke(context, base, method, paramTypes, params)
67+
);
68+
}
69+
}

src/main/java/com/hubspot/jinjava/el/ext/JinjavaBeanELResolver.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,7 @@ public Class<?> getType(ELContext context, Object base, Object property) {
7070

7171
@Override
7272
public Object getValue(ELContext context, Object base, Object property) {
73-
return getJinjavaConfig()
74-
.getReturnTypeValidator()
75-
.validateReturnType(super.getValue(context, base, transformPropertyName(property)));
73+
return super.getValue(context, base, transformPropertyName(property));
7674
}
7775

7876
@Override
@@ -111,9 +109,7 @@ public Object invoke(
111109
);
112110
}
113111

114-
return getJinjavaConfig()
115-
.getReturnTypeValidator()
116-
.validateReturnType(super.invoke(context, base, method, paramTypes, params));
112+
return super.invoke(context, base, method, paramTypes, params);
117113
}
118114

119115
@Override
@@ -181,7 +177,7 @@ private static JinjavaConfig getJinjavaConfig() {
181177
return Objects
182178
.requireNonNull(
183179
JinjavaInterpreter.getCurrent(),
184-
"JinjavaInterpreter.closeablePushCurrent must be used if using a JinjavaInterpreter directly"
180+
"JinjavaInterpreter.closeablePushCurrent must be used if using a JinjavaBeanELResolver directly"
185181
)
186182
.getConfig();
187183
}

src/test/java/com/hubspot/jinjava/el/ext/JinjavaBeanELResolverTest.java

Lines changed: 6 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,25 @@
11
package com.hubspot.jinjava.el.ext;
22

33
import static org.assertj.core.api.Assertions.assertThat;
4-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5-
import static org.mockito.Mockito.mock;
6-
import static org.mockito.Mockito.when;
74

8-
import com.hubspot.jinjava.JinjavaConfig;
5+
import com.hubspot.jinjava.Jinjava;
96
import com.hubspot.jinjava.el.JinjavaELContext;
10-
import com.hubspot.jinjava.interpret.AutoCloseableSupplier;
117
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
128
import javax.el.ELContext;
13-
import javax.el.MethodNotFoundException;
14-
import javax.el.PropertyNotFoundException;
159
import org.junit.Before;
1610
import org.junit.Test;
1711

1812
public class JinjavaBeanELResolverTest {
1913

2014
private JinjavaBeanELResolver jinjavaBeanELResolver;
2115
private ELContext elContext;
22-
23-
JinjavaInterpreter interpreter = mock(JinjavaInterpreter.class);
24-
JinjavaConfig config = mock(JinjavaConfig.class);
16+
private Jinjava jinjava;
2517

2618
@Before
2719
public void setUp() throws Exception {
28-
jinjavaBeanELResolver =
29-
new JinjavaBeanELResolver(
30-
AllowlistMethodValidator.create(
31-
MethodValidatorConfig
32-
.builder()
33-
.addDefaultAllowlistGroups()
34-
.addAllowedDeclaredMethodsFromCanonicalClassPrefixes(
35-
JinjavaBeanELResolverTest.class.getCanonicalName()
36-
)
37-
.build()
38-
),
39-
AllowlistReturnTypeValidator.create(
40-
ReturnTypeValidatorConfig
41-
.builder()
42-
.addDefaultAllowlistGroups()
43-
.addAllowedCanonicalClassPrefixes(
44-
JinjavaBeanELResolverTest.class.getCanonicalName()
45-
)
46-
.build()
47-
)
48-
);
20+
jinjavaBeanELResolver = new JinjavaBeanELResolver();
4921
elContext = new JinjavaELContext();
50-
when(interpreter.getConfig()).thenReturn(config);
22+
jinjava = new Jinjava();
5123
}
5224

5325
@Test
@@ -139,40 +111,10 @@ public void itPrefersPrimitives() {
139111
.isEqualTo("int Integer"); // should be "Number int", but we can't figure that out
140112
}
141113

142-
@Test
143-
public void itThrowsExceptionWhenMethodIsRestrictedFromConfig() {
144-
JinjavaInterpreter.pushCurrent(interpreter);
145-
// when(config.getRestrictedMethods()).thenReturn(ImmutableSet.of("foo"));
146-
assertThatThrownBy(() ->
147-
jinjavaBeanELResolver.invoke(elContext, "abcd", "foo", null, new Object[] { 1 })
148-
)
149-
.isInstanceOf(MethodNotFoundException.class)
150-
.hasMessageStartingWith("Cannot find method 'foo'");
151-
JinjavaInterpreter.popCurrent();
152-
}
153-
154-
@Test
155-
public void itThrowsExceptionWhenPropertyIsRestrictedFromConfig() {
156-
JinjavaInterpreter.pushCurrent(interpreter);
157-
// when(config.getRestrictedProperties()).thenReturn(ImmutableSet.of("property1"));
158-
assertThatThrownBy(() ->
159-
jinjavaBeanELResolver.getValue(elContext, "abcd", "property1")
160-
)
161-
.isInstanceOf(PropertyNotFoundException.class)
162-
.hasMessageStartingWith("Could not find property");
163-
JinjavaInterpreter.popCurrent();
164-
}
165-
166114
@Test
167115
public void itDoesNotAllowAccessingPropertiesOfInterpreter() {
168-
try (
169-
AutoCloseableSupplier.AutoCloseableImpl<JinjavaInterpreter> c = JinjavaInterpreter
170-
.closeablePushCurrent(interpreter)
171-
.get()
172-
) {
173-
assertThat(jinjavaBeanELResolver.getValue(elContext, interpreter, "config"))
174-
.isNull();
175-
}
116+
JinjavaInterpreter interpreter = jinjava.newInterpreter();
117+
assertThat(jinjavaBeanELResolver.getValue(elContext, interpreter, "config")).isNull();
176118
}
177119

178120
private static class TempItInvokesBestMethodWithSingleParam {

src/test/java/com/hubspot/jinjava/el/ext/eager/EagerAstDotTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@ public void setup() {
5656

5757
@Test
5858
public void itDefersWhenDotThrowsDeferredValueException() {
59-
interpreter.getContext().put("foo", new Foo());
60-
assertThat(interpreter.render("{{ foo.deferred }}")).isEqualTo("{{ foo.deferred }}");
59+
try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) {
60+
interpreter.getContext().put("foo", new Foo());
61+
assertThat(interpreter.render("{{ foo.deferred }}"))
62+
.isEqualTo("{{ foo.deferred }}");
63+
}
6164
}
6265

6366
@Test

src/test/java/com/hubspot/jinjava/util/EagerExpressionResolverTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ public void itSplitsAndIndexesOnNonWords() {
273273

274274
@Test
275275
public void itSupportsOrderOfOperations() {
276+
eagerResolveExpression("['a','b'][1]");
276277
EagerExpressionResult eagerExpressionResult = eagerResolveExpression(
277278
"[0,1]|reverse + deferred"
278279
);

0 commit comments

Comments
 (0)