Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make restricted methods and properties to be configurable in JinjavaConfig #1076

Merged
merged 6 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/main/java/com/hubspot/jinjava/JinjavaConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.google.common.collect.ImmutableSet;
import com.hubspot.jinjava.el.JinjavaInterpreterResolver;
import com.hubspot.jinjava.el.JinjavaObjectUnwrapper;
import com.hubspot.jinjava.el.JinjavaProcessors;
Expand Down Expand Up @@ -63,7 +64,12 @@ public class JinjavaConfig {
private final boolean enableRecursiveMacroCalls;
private final int maxMacroRecursionDepth;

private final Map<Context.Library, Set<String>> disabled;
private final Map<Library, Set<String>> disabled;

private final Set<String> restrictedMethods;

private final Set<String> restrictedProperties;

private final boolean failOnUnknownTokens;
private final boolean nestedInterpretationEnabled;
private final RandomNumberGeneratorStrategy randomNumberGenerator;
Expand Down Expand Up @@ -120,6 +126,8 @@ private JinjavaConfig(Builder builder) {
timeZone = builder.timeZone;
maxRenderDepth = builder.maxRenderDepth;
disabled = builder.disabled;
restrictedMethods = builder.restrictedMethods;
restrictedProperties = builder.restrictedProperties;
trimBlocks = builder.trimBlocks;
lstripBlocks = builder.lstripBlocks;
enableRecursiveMacroCalls = builder.enableRecursiveMacroCalls;
Expand Down Expand Up @@ -217,6 +225,14 @@ public Map<Library, Set<String>> getDisabled() {
return disabled;
}

public Set<String> getRestrictedMethods() {
return restrictedMethods;
}

public Set<String> getRestrictedProperties() {
return restrictedProperties;
}

public boolean isFailOnUnknownTokens() {
return failOnUnknownTokens;
}
Expand Down Expand Up @@ -305,6 +321,9 @@ public static class Builder {
private long maxOutputSize = 0; // in bytes
private Map<Context.Library, Set<String>> disabled = new HashMap<>();

private Set<String> restrictedMethods = ImmutableSet.of();
private Set<String> restrictedProperties = ImmutableSet.of();

private boolean trimBlocks;
private boolean lstripBlocks;

Expand Down Expand Up @@ -355,6 +374,16 @@ public Builder withDisabled(Map<Context.Library, Set<String>> disabled) {
return this;
}

public Builder withRestrictedMethods(Set<String> restrictedMethods) {
this.restrictedMethods = ImmutableSet.copyOf(restrictedMethods);
return this;
}

public Builder withRestrictedProperties(Set<String> restrictedProperties) {
this.restrictedProperties = ImmutableSet.copyOf(restrictedProperties);
return this;
}

public Builder withMaxRenderDepth(int maxRenderDepth) {
this.maxRenderDepth = maxRenderDepth;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
* {@link BeanELResolver} supporting snake case property names.
*/
public class JinjavaBeanELResolver extends BeanELResolver {
private static final Set<String> RESTRICTED_PROPERTIES = ImmutableSet
private static final Set<String> DEFAULT_RESTRICTED_PROPERTIES = ImmutableSet
.<String>builder()
.add("class")
.build();

private static final Set<String> RESTRICTED_METHODS = ImmutableSet
private static final Set<String> DEFAULT_RESTRICTED_METHODS = ImmutableSet
.<String>builder()
.add("class")
.add("clone")
Expand Down Expand Up @@ -100,7 +100,16 @@ public Object invoke(
Class<?>[] paramTypes,
Object[] params
) {
if (method == null || RESTRICTED_METHODS.contains(method.toString())) {
JinjavaInterpreter interpreter = JinjavaInterpreter.getCurrent();

if (
method == null ||
DEFAULT_RESTRICTED_METHODS.contains(method.toString()) ||
(
interpreter != null &&
interpreter.getConfig().getRestrictedMethods().contains(method.toString())
)
) {
throw new MethodNotFoundException(
"Cannot find method '" + method + "' in " + base.getClass()
);
Expand Down Expand Up @@ -211,7 +220,15 @@ private static int pickMoreSpecificMethod(Method methodA, Method methodB) {
private String validatePropertyName(Object property) {
String propertyName = transformPropertyName(property);

if (RESTRICTED_PROPERTIES.contains(propertyName)) {
JinjavaInterpreter interpreter = JinjavaInterpreter.getCurrent();

if (
DEFAULT_RESTRICTED_PROPERTIES.contains(propertyName) ||
(
interpreter != null &&
interpreter.getConfig().getRestrictedProperties().contains(propertyName)
)
) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
package com.hubspot.jinjava.el.ext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.common.collect.ImmutableSet;
import com.hubspot.jinjava.JinjavaConfig;
import com.hubspot.jinjava.el.JinjavaELContext;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import javax.el.ELContext;
import javax.el.MethodNotFoundException;
import javax.el.PropertyNotFoundException;
import org.junit.Before;
import org.junit.Test;

public class JinjavaBeanELResolverTest {
private JinjavaBeanELResolver jinjavaBeanELResolver;
private ELContext elContext;

JinjavaInterpreter interpreter = mock(JinjavaInterpreter.class);
JinjavaConfig config = mock(JinjavaConfig.class);

@Before
public void setUp() throws Exception {
jinjavaBeanELResolver = new JinjavaBeanELResolver();
elContext = new JinjavaELContext();
when(interpreter.getConfig()).thenReturn(config);
manheychiu marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
Expand Down Expand Up @@ -142,4 +154,29 @@ public String getResult(Number a, int b) {
)
.isEqualTo("int Integer"); // should be "Number int", but we can't figure that out
}

@Test
public void itThrowsExceptionWhenMethodIsRestrictedFromConfig() {
JinjavaInterpreter.pushCurrent(interpreter);
when(config.getRestrictedMethods()).thenReturn(ImmutableSet.of("foo"));
assertThatThrownBy(
() ->
jinjavaBeanELResolver.invoke(elContext, "abcd", "foo", null, new Object[] { 1 })
)
.isInstanceOf(MethodNotFoundException.class)
.hasMessageStartingWith("Cannot find method 'foo'");
JinjavaInterpreter.popCurrent();
}

@Test
public void itThrowsExceptionWhenPropertyIsRestrictedFromConfig() {
JinjavaInterpreter.pushCurrent(interpreter);
when(config.getRestrictedProperties()).thenReturn(ImmutableSet.of("property1"));
assertThatThrownBy(
() -> jinjavaBeanELResolver.getValue(elContext, "abcd", "property1")
)
.isInstanceOf(PropertyNotFoundException.class)
.hasMessageStartingWith("Could not find property");
JinjavaInterpreter.popCurrent();
}
}