Merge branch 'spel-context-fix' into 3.1.x

This commit is contained in:
spencergibb
2022-02-16 13:28:48 -05:00
3 changed files with 220 additions and 3 deletions

View File

@@ -25,10 +25,23 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.env.Environment;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -54,8 +67,7 @@ public interface ShortcutConfigurable {
}
if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
// assume it's spel
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory);
Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
value = expression.getValue(context);
}
@@ -148,4 +160,92 @@ public interface ShortcutConfigurable {
}
class GatewayEvaluationContext implements EvaluationContext {
private final BeanFactoryResolver beanFactoryResolver;
private final SimpleEvaluationContext delegate;
public GatewayEvaluationContext(BeanFactory beanFactory) {
this.beanFactoryResolver = new BeanFactoryResolver(beanFactory);
Environment env = beanFactory.getBean(Environment.class);
boolean restrictive = env.getProperty("spring.cloud.gateway.restrictive-property-accessor.enabled",
Boolean.class, true);
if (restrictive) {
delegate = SimpleEvaluationContext.forPropertyAccessors(new RestrictivePropertyAccessor())
.withMethodResolvers((context, targetObject, name, argumentTypes) -> null).build();
}
else {
delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build();
}
}
@Override
public TypedValue getRootObject() {
return delegate.getRootObject();
}
@Override
public List<PropertyAccessor> getPropertyAccessors() {
return delegate.getPropertyAccessors();
}
@Override
public List<ConstructorResolver> getConstructorResolvers() {
return delegate.getConstructorResolvers();
}
@Override
public List<MethodResolver> getMethodResolvers() {
return delegate.getMethodResolvers();
}
@Override
@Nullable
public BeanResolver getBeanResolver() {
return this.beanFactoryResolver;
}
@Override
public TypeLocator getTypeLocator() {
return delegate.getTypeLocator();
}
@Override
public TypeConverter getTypeConverter() {
return delegate.getTypeConverter();
}
@Override
public TypeComparator getTypeComparator() {
return delegate.getTypeComparator();
}
@Override
public OperatorOverloader getOperatorOverloader() {
return delegate.getOperatorOverloader();
}
@Override
public void setVariable(String name, Object value) {
delegate.setVariable(name, value);
}
@Override
@Nullable
public Object lookupVariable(String name) {
return delegate.lookupVariable(name);
}
}
class RestrictivePropertyAccessor extends ReflectivePropertyAccessor {
@Override
public boolean canRead(EvaluationContext context, Object target, String name) {
return false;
}
}
}

View File

@@ -371,6 +371,12 @@
"type": "java.lang.Integer",
"description": "The order of RoutePredicateHandlerMapping.",
"defaultValue": "1"
},
{
"name": "spring.cloud.gateway.restrictive-property-accessor.enabled",
"type": "java.lang.Boolean",
"description": "Restricts method and property access in SpEL.",
"defaultValue": "true"
}
]
}

View File

@@ -28,12 +28,16 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@RunWith(SpringRunner.class)
@SpringBootTest
@@ -42,8 +46,64 @@ public class ShortcutConfigurableTests {
@Autowired
BeanFactory beanFactory;
@Autowired
ConfigurableEnvironment env;
private SpelExpressionParser parser;
@Test
public void testNormalizeDefaultTypeWithSpelAndInvalidInputFails() {
parser = new SpelExpressionParser();
ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() {
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("bean", "#{T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/x\")}");
args.put("arg1", "val1");
assertThatThrownBy(() -> {
ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory);
}).isInstanceOf(SpelEvaluationException.class);
}
@Test
public void testNormalizeDefaultTypeWithSpelAndInvalidPropertyReferenceFails() {
TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=true").applyTo(env);
parser = new SpelExpressionParser();
ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() {
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("barproperty", "#{@bar.getInt}");
args.put("arg1", "val1");
assertThatThrownBy(() -> {
ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory);
}).isInstanceOf(SpelEvaluationException.class);
}
@Test
public void testNormalizeDefaultTypeWithSpelAndInvalidMethodReferenceFails() {
TestPropertyValues.of("ispring.cloud.gateway.restrictve-property-accessor.enabled=true").applyTo(env);
parser = new SpelExpressionParser();
ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() {
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("barmethod", "#{@bar.myMethod}");
args.put("arg1", "val1");
assertThatThrownBy(() -> {
ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory);
}).isInstanceOf(SpelEvaluationException.class);
}
@Test
public void testNormalizeDefaultTypeWithSpel() {
parser = new SpelExpressionParser();
@@ -60,6 +120,40 @@ public class ShortcutConfigurableTests {
assertThat(map).isNotNull().containsEntry("bean", 42).containsEntry("arg1", "val1");
}
@Test
public void testNormalizeDefaultTypeWithSpelAndPropertyReferenceEnabled() {
TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=false").applyTo(env);
parser = new SpelExpressionParser();
ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() {
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("barproperty", "#{@bar.getInt}");
args.put("arg1", "val1");
Map<String, Object> map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory);
assertThat(map).isNotNull().containsEntry("barproperty", 42).containsEntry("arg1", "val1");
}
@Test
public void testNormalizeDefaultTypeWithSpelAndMethodReferenceEnabled() {
TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=false").applyTo(env);
parser = new SpelExpressionParser();
ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() {
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("barmethod", "#{@bar.myMethod}");
args.put("arg1", "val1");
Map<String, Object> map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory);
assertThat(map).isNotNull().containsEntry("barmethod", 42).containsEntry("arg1", "val1");
}
@Test
@SuppressWarnings("unchecked")
public void testNormalizeGatherListTypeWithSpel() {
@@ -136,6 +230,23 @@ public class ShortcutConfigurableTests {
return 42;
}
@Bean
public Bar bar() {
return new Bar();
}
}
protected static class Bar {
public int getInt() {
return 42;
}
public int myMethod() {
return 42;
}
}
}