diff --git a/pom.xml b/pom.xml
index a2dfc67d7..58b1ccfd0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -134,6 +134,9 @@
Ales Dolecek
+
+ Nick Müller
+
diff --git a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java
index 643e37360..75f08de51 100644
--- a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java
@@ -74,11 +74,21 @@ public Evaluator getEvaluator(final SCXML document) {
/** The internal JexlEngine instance to use. */
private transient volatile JexlEngine jexlEngine;
+ /** Optional: saves user defined packages, which JEXL should allow or deny for evaluation */
+ private String[] customJexlPermissions;
+
/** Constructor. */
public JexlEvaluator() {
jexlEngine = getJexlEngine();
}
+ /** Constructor with a given builder to set up specific options */
+ public JexlEvaluator(JexlEvaluatorBuilder builder) {
+ customJexlPermissions = builder.getJexlPermissions();
+ jexlEngine = getJexlEngine();
+ }
+
+
@Override
public String getSupportedDatamodel() {
return SUPPORTED_DATA_MODEL;
@@ -176,7 +186,7 @@ public Context newContext(final Context parent) {
/**
* Create the internal JexlEngine member during the initialization.
- * This method can be overriden to specify more detailed options
+ * This method can be overridden to specify more detailed options
* into the JexlEngine.
* @return new JexlEngine instance
*/
@@ -185,7 +195,13 @@ protected JexlEngine createJexlEngine() {
// See javadoc of org.apache.commons.jexl2.JexlEngine#setFunctions(Map funcs) for detail.
final Map funcs = new HashMap<>();
funcs.put(null, JexlBuiltin.class);
+
JexlPermissions permissions = JexlPermissions.RESTRICTED.compose("org.apache.commons.scxml2.*");
+
+ if (customJexlPermissions != null && customJexlPermissions.length > 0) {
+ permissions = permissions.compose(customJexlPermissions);
+ }
+
return new JexlBuilder().permissions(permissions).namespaces(funcs).cache(256).create();
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorBuilder.java b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorBuilder.java
new file mode 100644
index 000000000..dd62f87bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.scxml2.env.jexl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class JexlEvaluatorBuilder {
+
+ private final List allowedPackages = new ArrayList<>();
+ private final List deniedClasses = new ArrayList<>();
+
+ /**
+ * Adds a whole package to the JEXL permissions and allows to use this classes in JEXL expressions.
+ *
+ * @param fullQualifiedPackageName expects the complete path f.e. "org.apache.commons.logging"
+ */
+ public JexlEvaluatorBuilder addAllowedPackage(String fullQualifiedPackageName) {
+ allowedPackages.add(fullQualifiedPackageName);
+ return this;
+ }
+
+ /**
+ * Adds a specific class, which is not allowed to be used in JEXL expressions.
+ *
+ * @param fullQualifiedClassName expects a complete path f.e. "org.apache.commons.logging.Logger"
+ */
+ public JexlEvaluatorBuilder addDeniedClass(String fullQualifiedClassName) {
+ deniedClasses.add(fullQualifiedClassName);
+ return this;
+ }
+
+ /**
+ * Creates a JexlEvaluator by the defined options.
+ */
+ public JexlEvaluator build() {
+ return new JexlEvaluator(this);
+ }
+
+ /**
+ * Package-private: converts the user given information to JEXL understandable src-list
+ */
+ String[] getJexlPermissions() {
+ List permissions = new ArrayList<>();
+
+ for (String allowedPackage : allowedPackages) {
+ permissions.add(allowedPackage.replace(".*", "") + ".*");
+ }
+
+ for (String deniedClass : deniedClasses) {
+ final int lastDot = deniedClass.lastIndexOf('.');
+ String packageName = deniedClass.substring(0, lastDot);
+ String className = deniedClass.substring(lastDot + 1);
+ permissions.add(packageName + " { " + className + " {} }");
+ }
+
+ return permissions.toArray(new String[]{});
+ }
+}
+
diff --git a/src/test/java/com/custom/Payload.java b/src/test/java/com/custom/Payload.java
new file mode 100644
index 000000000..d32ca832f
--- /dev/null
+++ b/src/test/java/com/custom/Payload.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.custom;
+
+public class Payload {
+
+ private final int id;
+ private final String someString;
+
+ public Payload(int id, String someString) {
+ this.id = id;
+ this.someString = someString;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getSomeString() {
+ return someString;
+ }
+}
diff --git a/src/test/java/org/apache/commons/scxml2/SCXMLExecutorTest.java b/src/test/java/org/apache/commons/scxml2/SCXMLExecutorTest.java
index 90b2cf6cf..af14aebc7 100644
--- a/src/test/java/org/apache/commons/scxml2/SCXMLExecutorTest.java
+++ b/src/test/java/org/apache/commons/scxml2/SCXMLExecutorTest.java
@@ -21,6 +21,9 @@
import java.util.Map;
import java.util.Set;
+import com.custom.Payload;
+import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
+import org.apache.commons.scxml2.env.jexl.JexlEvaluatorBuilder;
import org.apache.commons.scxml2.model.EnterableState;
import org.apache.commons.scxml2.model.TransitionTarget;
import org.junit.jupiter.api.Assertions;
@@ -289,6 +292,20 @@ public void testSCXMLExecutorFinalDoneData() throws Exception {
Assertions.assertEquals("done", exec.getFinalDoneData());
}
+ @Test
+ public void testSCXMLExecutorWithExternalPayloadObject() throws Exception {
+ final SCXMLExecutor exec = SCXMLTestHelper.getExecutor("org/apache/commons/scxml2/external-payload.xml");
+ final JexlEvaluator evaluator = new JexlEvaluatorBuilder()
+ .addAllowedPackage("com.custom")
+ .build();
+
+ exec.setEvaluator(evaluator);
+
+ exec.go();
+ SCXMLTestHelper.assertPostTriggerState(exec, "done", new Payload(1, "someString"), "end");
+ Assertions.assertEquals(1, exec.getGlobalContext().getVars().get("idFromEventPayload"));
+ }
+
private void checkMicrowave01Sample(final SCXMLExecutor exec) throws Exception {
final Set currentStates = SCXMLTestHelper.fireEvent(exec, "turn_on");
Assertions.assertEquals(1, currentStates.size());
diff --git a/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorBuilderTest.java b/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorBuilderTest.java
new file mode 100644
index 000000000..514c031dc
--- /dev/null
+++ b/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorBuilderTest.java
@@ -0,0 +1,35 @@
+package org.apache.commons.scxml2.env.jexl;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class JexlEvaluatorBuilderTest {
+
+ @Test
+ public void testAddAllowedPackages() {
+
+ final String[] jexlPermissions = new JexlEvaluatorBuilder()
+ .addAllowedPackage("org.apache.commons.scxml2")
+ .addAllowedPackage("org.apache.commons.logging")
+ .getJexlPermissions();
+
+ Assertions.assertEquals(2, jexlPermissions.length);
+ Assertions.assertEquals("org.apache.commons.scxml2.*", jexlPermissions[0]);
+ Assertions.assertEquals("org.apache.commons.logging.*", jexlPermissions[1]);
+ }
+
+ @Test
+ public void testAllowedPackagesAndDeniedClasses() {
+
+ final String[] jexlPermissions = new JexlEvaluatorBuilder()
+ .addAllowedPackage("org.apache.commons.scxml2")
+ .addDeniedClass("org.apache.commons.scxml2.Builtin")
+ .addDeniedClass("org.apache.commons.logging.Logger")
+ .getJexlPermissions();
+
+ Assertions.assertEquals(3, jexlPermissions.length);
+ Assertions.assertEquals("org.apache.commons.scxml2.*", jexlPermissions[0]);
+ Assertions.assertEquals("org.apache.commons.scxml2 { Builtin {} }", jexlPermissions[1]);
+ Assertions.assertEquals("org.apache.commons.logging { Logger {} }", jexlPermissions[2]);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorTest.java b/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorTest.java
index 2c0c01495..8b462b805 100644
--- a/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorTest.java
+++ b/src/test/java/org/apache/commons/scxml2/env/jexl/JexlEvaluatorTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.scxml2.env.jexl;
+import com.custom.Payload;
import org.apache.commons.scxml2.Context;
import org.apache.commons.scxml2.Evaluator;
import org.apache.commons.scxml2.SCXMLExpressionException;
@@ -60,4 +61,20 @@ public void testErrorMessage() {
"JexlEvaluator: Incorrect error message");
}
+ @Test
+ void testEvalInCustomClass() throws SCXMLExpressionException {
+
+ // Arrange
+ final JexlEvaluator eval = new JexlEvaluatorBuilder()
+ .addAllowedPackage("com.custom")
+ .build();
+
+ ctx.set("payload", new Payload(1, "hello"));
+
+ // Act
+ final Object result = eval.evalScript(ctx, "payload.getId()");
+
+ // Assert
+ Assertions.assertEquals(1, result);
+ }
}
diff --git a/src/test/java/org/apache/commons/scxml2/external-payload.xml b/src/test/java/org/apache/commons/scxml2/external-payload.xml
new file mode 100644
index 000000000..47cd63dd7
--- /dev/null
+++ b/src/test/java/org/apache/commons/scxml2/external-payload.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+