Skip to content

Commit 3c12c94

Browse files
committed
Split MethodValidator from ReturnTypeValidator
1 parent 3be951e commit 3c12c94

19 files changed

Lines changed: 573 additions & 273 deletions

src/main/java/com/hubspot/jinjava/JinjavaConfig.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import com.hubspot.jinjava.el.JinjavaObjectUnwrapper;
2727
import com.hubspot.jinjava.el.JinjavaProcessors;
2828
import com.hubspot.jinjava.el.ObjectUnwrapper;
29-
import com.hubspot.jinjava.el.ext.MethodValidator;
29+
import com.hubspot.jinjava.el.ext.AllowlistMethodValidator;
30+
import com.hubspot.jinjava.el.ext.AllowlistReturnTypeValidator;
3031
import com.hubspot.jinjava.el.ext.MethodValidatorConfig;
32+
import com.hubspot.jinjava.el.ext.ReturnTypeValidatorConfig;
3133
import com.hubspot.jinjava.features.FeatureConfig;
3234
import com.hubspot.jinjava.features.Features;
3335
import com.hubspot.jinjava.interpret.Context.Library;
@@ -163,17 +165,25 @@ default TokenScannerSymbols getTokenScannerSymbols() {
163165
}
164166

165167
@Value.Default
166-
default MethodValidator getMethodValidator() {
167-
return MethodValidator.create(
168+
default AllowlistMethodValidator getMethodValidator() {
169+
return AllowlistMethodValidator.create(
168170
MethodValidatorConfig.builder().addDefaultAllowlistGroups().build()
169171
);
170172
}
171173

174+
@Value.Default
175+
default AllowlistReturnTypeValidator getReturnTypeValidator() {
176+
return AllowlistReturnTypeValidator.create(
177+
ReturnTypeValidatorConfig.builder().addDefaultAllowlistGroups().build()
178+
);
179+
}
180+
172181
@Value.Default
173182
default ELResolver getElResolver() {
174183
return JinjavaInterpreterResolver.createDefaultResolver(
175184
isDefaultReadOnlyResolver(),
176-
getMethodValidator()
185+
getMethodValidator(),
186+
getReturnTypeValidator()
177187
);
178188
}
179189

@@ -247,16 +257,7 @@ default boolean isIterateOverMapKeys() {
247257
return getLegacyOverrides().isIterateOverMapKeys();
248258
}
249259

250-
class Builder extends ImmutableJinjavaConfig.Builder {
251-
252-
public Builder withReadOnlyResolver(boolean readOnlyResolver) {
253-
return withElResolver(
254-
readOnlyResolver
255-
? JinjavaInterpreterResolver.DEFAULT_RESOLVER_READ_ONLY
256-
: JinjavaInterpreterResolver.DEFAULT_RESOLVER_READ_WRITE
257-
);
258-
}
259-
}
260+
class Builder extends ImmutableJinjavaConfig.Builder {}
260261

261262
static Builder builder() {
262263
return new Builder();

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
import com.google.common.collect.ImmutableMap;
66
import com.hubspot.jinjava.el.ext.AbstractCallableMethod;
7+
import com.hubspot.jinjava.el.ext.AllowlistMethodValidator;
8+
import com.hubspot.jinjava.el.ext.AllowlistReturnTypeValidator;
79
import com.hubspot.jinjava.el.ext.DeferredParsingException;
810
import com.hubspot.jinjava.el.ext.ExtendedParser;
911
import com.hubspot.jinjava.el.ext.JinjavaBeanELResolver;
1012
import com.hubspot.jinjava.el.ext.JinjavaListELResolver;
11-
import com.hubspot.jinjava.el.ext.MethodValidator;
1213
import com.hubspot.jinjava.el.ext.MethodValidatorConfig;
1314
import com.hubspot.jinjava.el.ext.NamedParameter;
15+
import com.hubspot.jinjava.el.ext.ReturnTypeValidatorConfig;
1416
import com.hubspot.jinjava.interpret.DeferredValueException;
1517
import com.hubspot.jinjava.interpret.DisabledException;
1618
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
@@ -23,6 +25,7 @@
2325
import com.hubspot.jinjava.objects.PyWrapper;
2426
import com.hubspot.jinjava.objects.collections.SizeLimitingPyList;
2527
import com.hubspot.jinjava.objects.collections.SizeLimitingPyMap;
28+
import com.hubspot.jinjava.objects.collections.SizeLimitingPySet;
2629
import com.hubspot.jinjava.objects.date.FormattedDate;
2730
import com.hubspot.jinjava.objects.date.InvalidDateFormatException;
2831
import com.hubspot.jinjava.objects.date.PyishDate;
@@ -41,6 +44,7 @@
4144
import java.util.Locale;
4245
import java.util.Map;
4346
import java.util.Objects;
47+
import java.util.Set;
4448
import javax.el.ArrayELResolver;
4549
import javax.el.CompositeELResolver;
4650
import javax.el.ELContext;
@@ -54,27 +58,36 @@ public class JinjavaInterpreterResolver extends SimpleResolver {
5458

5559
public static ELResolver createDefaultResolver(
5660
boolean readOnly,
57-
MethodValidator methodValidator
61+
AllowlistMethodValidator allowlistMethodValidator,
62+
AllowlistReturnTypeValidator allowlistReturnTypeValidator
5863
) {
5964
return new CompositeELResolver() {
6065
{
6166
add(new ArrayELResolver(readOnly));
6267
add(new JinjavaListELResolver(readOnly));
6368
add(new TypeConvertingMapELResolver(readOnly));
6469
add(new ResourceBundleELResolver());
65-
add(new JinjavaBeanELResolver(readOnly, methodValidator));
70+
add(
71+
new JinjavaBeanELResolver(
72+
readOnly,
73+
allowlistMethodValidator,
74+
allowlistReturnTypeValidator
75+
)
76+
);
6677
}
6778
};
6879
}
6980

7081
public static final ELResolver DEFAULT_RESOLVER_READ_ONLY = createDefaultResolver(
7182
true,
72-
MethodValidator.create(MethodValidatorConfig.of())
83+
AllowlistMethodValidator.create(MethodValidatorConfig.of()),
84+
AllowlistReturnTypeValidator.create(ReturnTypeValidatorConfig.of())
7385
);
7486

7587
public static final ELResolver DEFAULT_RESOLVER_READ_WRITE = createDefaultResolver(
7688
false,
77-
MethodValidator.create(MethodValidatorConfig.of())
89+
AllowlistMethodValidator.create(MethodValidatorConfig.of()),
90+
AllowlistReturnTypeValidator.create(ReturnTypeValidatorConfig.of())
7891
);
7992

8093
private final JinjavaInterpreter interpreter;
@@ -315,6 +328,12 @@ Object wrap(Object value) {
315328
interpreter.getConfig().getMaxListSize()
316329
);
317330
}
331+
if (Set.class.isAssignableFrom(value.getClass())) {
332+
return new SizeLimitingPySet(
333+
(Set<Object>) value,
334+
interpreter.getConfig().getMaxListSize()
335+
);
336+
}
318337
if (Map.class.isAssignableFrom(value.getClass())) {
319338
// FIXME: ensure keys are actually strings, if not, convert them
320339
return new SizeLimitingPyMap(
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package com.hubspot.jinjava.el.ext;
2+
3+
import com.google.common.collect.ForwardingCollection;
4+
import com.google.common.collect.ForwardingList;
5+
import com.google.common.collect.ForwardingMap;
6+
import com.google.common.collect.ForwardingSet;
7+
import com.hubspot.jinjava.lib.exptest.ExpTest;
8+
import com.hubspot.jinjava.lib.filter.Filter;
9+
import com.hubspot.jinjava.objects.DummyObject;
10+
import com.hubspot.jinjava.objects.Namespace;
11+
import com.hubspot.jinjava.objects.SafeString;
12+
import com.hubspot.jinjava.objects.collections.PyList;
13+
import com.hubspot.jinjava.objects.collections.PyMap;
14+
import com.hubspot.jinjava.objects.collections.PySet;
15+
import com.hubspot.jinjava.objects.collections.SizeLimitingPyList;
16+
import com.hubspot.jinjava.objects.collections.SizeLimitingPyMap;
17+
import com.hubspot.jinjava.objects.collections.SizeLimitingPySet;
18+
import com.hubspot.jinjava.objects.collections.SnakeCaseAccessibleMap;
19+
import com.hubspot.jinjava.objects.date.FormattedDate;
20+
import com.hubspot.jinjava.objects.date.PyishDate;
21+
import com.hubspot.jinjava.util.ForLoop;
22+
import java.lang.reflect.Method;
23+
import java.math.BigDecimal;
24+
import java.util.AbstractCollection;
25+
import java.util.ArrayList;
26+
import java.util.Map;
27+
28+
public enum AllowlistGroup {
29+
JavaPrimitives {
30+
private static final Class<?>[] ARRAY = {
31+
String.class,
32+
Long.class,
33+
Integer.class,
34+
Double.class,
35+
Byte.class,
36+
Character.class,
37+
Float.class,
38+
Boolean.class,
39+
Short.class,
40+
long.class,
41+
int.class,
42+
double.class,
43+
byte.class,
44+
char.class,
45+
float.class,
46+
boolean.class,
47+
short.class,
48+
BigDecimal.class,
49+
};
50+
51+
@Override
52+
Class<?>[] allowedReturnTypeClasses() {
53+
return ARRAY;
54+
}
55+
56+
@Override
57+
Class<?>[] allowedDeclaredMethodsFromClasses() {
58+
return ARRAY;
59+
}
60+
},
61+
JinjavaObjects {
62+
private static final Class<?>[] ARRAY = {
63+
PyList.class,
64+
PyMap.class,
65+
SizeLimitingPyMap.class,
66+
SizeLimitingPyList.class,
67+
SnakeCaseAccessibleMap.class,
68+
FormattedDate.class,
69+
PyishDate.class,
70+
DummyObject.class,
71+
Namespace.class,
72+
SafeString.class,
73+
};
74+
75+
@Override
76+
Class<?>[] allowedReturnTypeClasses() {
77+
return ARRAY;
78+
}
79+
80+
@Override
81+
Class<?>[] allowedDeclaredMethodsFromClasses() {
82+
return ARRAY;
83+
}
84+
},
85+
Collections {
86+
private static final Class<?>[] ARRAY = {
87+
Map.Entry.class,
88+
PyList.class,
89+
PyMap.class,
90+
PySet.class,
91+
SizeLimitingPyMap.class,
92+
SizeLimitingPyList.class,
93+
SizeLimitingPySet.class,
94+
ArrayList.class,
95+
ForwardingList.class,
96+
ForwardingMap.class,
97+
ForwardingSet.class,
98+
ForwardingCollection.class,
99+
AbstractCollection.class,
100+
};
101+
102+
@Override
103+
Class<?>[] allowedReturnTypeClasses() {
104+
return ARRAY;
105+
}
106+
107+
@Override
108+
Class<?>[] allowedDeclaredMethodsFromClasses() {
109+
return ARRAY;
110+
}
111+
},
112+
JinjavaTagConstructs {
113+
private static final Class<?>[] ARRAY = { ForLoop.class };
114+
115+
@Override
116+
Class<?>[] allowedReturnTypeClasses() {
117+
return ARRAY;
118+
}
119+
120+
@Override
121+
Class<?>[] allowedDeclaredMethodsFromClasses() {
122+
return ARRAY;
123+
}
124+
},
125+
JinjavaFilters {
126+
private static final String[] ARRAY = { Filter.class.getPackageName() };
127+
128+
@Override
129+
String[] allowedDeclaredMethodsFromCanonicalClassPrefixes() {
130+
return ARRAY;
131+
}
132+
},
133+
JinjavaFunctions,
134+
JinjavaExpTests {
135+
private static final String[] ARRAY = { ExpTest.class.getPackageName() };
136+
137+
@Override
138+
String[] allowedDeclaredMethodsFromCanonicalClassPrefixes() {
139+
return ARRAY;
140+
}
141+
};
142+
143+
Method[] allowMethods() {
144+
return new Method[0];
145+
}
146+
147+
String[] allowedDeclaredMethodsFromCanonicalClassPrefixes() {
148+
return new String[0];
149+
}
150+
151+
Class<?>[] allowedDeclaredMethodsFromClasses() {
152+
return new Class[0];
153+
}
154+
155+
String[] allowedReturnTypeCanonicalClassPrefixes() {
156+
return new String[0];
157+
}
158+
159+
Class<?>[] allowedReturnTypeClasses() {
160+
return new Class[0];
161+
}
162+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.hubspot.jinjava.el.ext;
2+
3+
import com.google.common.collect.ImmutableList;
4+
import com.google.common.collect.ImmutableSet;
5+
import java.lang.reflect.Method;
6+
7+
public final class AllowlistMethodValidator {
8+
9+
private final ImmutableSet<Method> allowedMethods;
10+
private final ImmutableSet<String> allowedDeclaredMethodsFromCanonicalClassPrefixes;
11+
private final ImmutableSet<String> allowedDeclaredMethodsFromCanonicalClassNames;
12+
private final ImmutableList<MethodValidator> additionalValidators;
13+
14+
public static AllowlistMethodValidator create(
15+
MethodValidatorConfig methodValidatorConfig,
16+
MethodValidator... additionalValidators
17+
) {
18+
return new AllowlistMethodValidator(
19+
methodValidatorConfig,
20+
ImmutableList.copyOf(additionalValidators)
21+
);
22+
}
23+
24+
private AllowlistMethodValidator(
25+
MethodValidatorConfig methodValidatorConfig,
26+
ImmutableList<MethodValidator> additionalValidators
27+
) {
28+
this.allowedMethods = methodValidatorConfig.allowedMethods();
29+
this.allowedDeclaredMethodsFromCanonicalClassPrefixes =
30+
methodValidatorConfig.allowedDeclaredMethodsFromCanonicalClassPrefixes();
31+
this.allowedDeclaredMethodsFromCanonicalClassNames =
32+
methodValidatorConfig.allowedDeclaredMethodsFromCanonicalClassNames();
33+
this.additionalValidators = additionalValidators;
34+
}
35+
36+
public Method validateMethod(Method m) {
37+
if (m == null) {
38+
return null;
39+
}
40+
Class<?> clazz = m.getDeclaringClass();
41+
String canonicalClassName = clazz.getCanonicalName();
42+
if (
43+
allowedMethods.contains(m) ||
44+
allowedDeclaredMethodsFromCanonicalClassNames.contains(canonicalClassName) ||
45+
allowedDeclaredMethodsFromCanonicalClassPrefixes
46+
.stream()
47+
.anyMatch(canonicalClassName::startsWith)
48+
) {
49+
for (MethodValidator v : additionalValidators) {
50+
m = v.validateMethod(m);
51+
if (m == null) {
52+
return null;
53+
}
54+
}
55+
return m;
56+
}
57+
return null;
58+
}
59+
}

0 commit comments

Comments
 (0)