This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-381 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 9083d623de349fd72b308cf4c70a28a4d0721814 Author: henrib <hen...@apache.org> AuthorDate: Mon Oct 24 17:27:36 2022 +0200 JEXL-381: expose setting JexlEngine used by scripting; expose setting default JexlBuilder permissions; --- .../java/org/apache/commons/jexl3/JexlBuilder.java | 40 ++++++++---- .../commons/jexl3/scripting/JexlScriptEngine.java | 72 +++++++++++++++++----- .../jexl3/scripting/JexlScriptEngineTest.java | 41 ++++++++++++ 3 files changed, 128 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java index 52316bf2..a183d402 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java +++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java @@ -81,6 +81,21 @@ import java.nio.charset.Charset; * */ public class JexlBuilder { + /** + * The set of default permissions used when creating a new builder. + * <p>Static but modifiable so these default permissions can be changed to a purposeful set.</p> + * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p> + * <p>In JEXL 3.2, these were equivalent to {@link JexlPermissions#UNRESTRICTED}.</p> + */ + private static JexlPermissions PERMISSIONS = JexlPermissions.RESTRICTED; + + /** + * Sets the default permissions. + * @param permissions the permissions + */ + public static void setDefaultPermissions(JexlPermissions permissions) { + PERMISSIONS = permissions == null? JexlPermissions.RESTRICTED : permissions; + } /** The default maximum expression length to hit the expression cache. */ protected static final int CACHE_THRESHOLD = 64; @@ -88,11 +103,11 @@ public class JexlBuilder { /** The JexlUberspect instance. */ private JexlUberspect uberspect = null; - /** The strategy strategy. */ + /** The {@link JexlUberspect} resolver strategy. */ private JexlUberspect.ResolverStrategy strategy = null; /** The set of permissions. */ - private JexlPermissions permissions = JexlPermissions.RESTRICTED; + private JexlPermissions permissions; /** The sandbox. */ private JexlSandbox sandbox = null; @@ -137,7 +152,7 @@ public class JexlBuilder { * Default constructor. * <p> * As of JEXL 3.3, to reduce the security risks inherent to JEXL"s purpose, the builder will use a set of - * restricted permissions as a default to create the JexlEngine instance. This will greatly reduce which classes + * restricted permissions as a default to create the {@link JexlEngine} instance. This will greatly reduce which classes * and methods are visible to JEXL and usable in scripts using default implicit behaviors. * </p><p> * However, without mitigation, this change will likely break some scripts at runtime, especially those exposing @@ -148,16 +163,19 @@ public class JexlBuilder { * allowed and exposed to script authors and implement those through a set of {@link JexlPermissions}; * those are easily created using {@link JexlPermissions#parse(String...)}. * </p><p> - * In the urgent case of a strict 3.2 compatiblity, the simplest and fastest mitigation is to use the 'unrestricted' - * set of permissions. The builder must be explicit about it using - * <code>new JexlBuilder().permissions({@link JexlPermissions#UNRESTRICTED})</code>. + * In the urgent case of a strict 3.2 compatibility, the simplest and fastest mitigation is to use the 'unrestricted' + * set of permissions. The builder must be explicit about it either by setting the default permissions with a + * statement like <code>JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED);</code> or with a more precise + * one like <code>new JexlBuilder().permissions({@link JexlPermissions#UNRESTRICTED})</code>. * </p><p> - * Note that an explicit call to {@link #uberspect(JexlUberspect)} will supersede this behavior using the - * {@link JexlUberspect} provided instance in the {@link JexlEngine}. + * Note that an explicit call to {@link #uberspect(JexlUberspect)} will supersede any permissions related behavior + * by using the {@link JexlUberspect} provided as argument used as-is in the created {@link JexlEngine}. * </p> * @since 3.3 */ - public JexlBuilder() {} + public JexlBuilder() { + this.permissions = PERMISSIONS; + } /** * Sets the JexlUberspect instance the engine will use. @@ -192,7 +210,7 @@ public class JexlBuilder { } /** - * Sets the JexlUberspect strategy strategy the engine will use. + * Sets the JexlUberspect strategy the engine will use. * <p>This is ignored if the uberspect has been set. * * @param rs the strategy @@ -203,7 +221,7 @@ public class JexlBuilder { return this; } - /** @return the strategy strategy */ + /** @return the JexlUberspect strategy */ public JexlUberspect.ResolverStrategy strategy() { return this.strategy; } diff --git a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java index e1845974..3611afa5 100644 --- a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java +++ b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; import javax.script.AbstractScriptEngine; import javax.script.Bindings; @@ -39,6 +41,7 @@ import org.apache.commons.jexl3.JexlEngine; import org.apache.commons.jexl3.JexlException; import org.apache.commons.jexl3.JexlScript; +import org.apache.commons.jexl3.introspection.JexlPermissions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -63,6 +66,59 @@ import org.apache.commons.logging.LogFactory; * @since 2.0 */ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable { + /** + * The shared engine instance. + * <p>A single soft-reference JEXL engine and JexlUberspect is shared by all instances of JexlScriptEngine.</p> + */ + private static Reference<JexlEngine> ENGINE = null; + + /** + * Sets the shared instance used for the script engine. + * <p>This should be called early enough to have an effect, ie before any + * {@link javax.script.ScriptEngineManager} features.</p> + * <p>To restore 3.2 script permeability:</p> + * <code> + * JexlScriptEngine.setInstance(new JexlBuilder() + * .cache(512) + * .logger(LogFactory.getLog(JexlScriptEngine.class)) + * .permissions(JexlPermissions.UNRESTRICTED) + * .create()); + * </code> + * <p>Alternatively, setting the default {@link JexlBuilder#setDefaultPermissions(JexlPermissions)} using + * {@link org.apache.commons.jexl3.introspection.JexlPermissions#UNRESTRICTED} will also restore JEXL 3.2 + * behavior.</p> + * <code> + * JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED); + * </code> + * @param engine the JexlEngine instance to use + * @since 3.3 + */ + public static void setInstance(JexlEngine engine) { + ENGINE = new SoftReference<>(engine); + } + + /** + * @return the shared JexlEngine instance, create it if necessary + */ + private static JexlEngine getEngine() { + JexlEngine engine = ENGINE != null? ENGINE.get() : null; + if (engine == null) { + synchronized (JexlScriptEngineFactory.class) { + engine = ENGINE != null? ENGINE.get() : null; + if (engine == null) { + JexlBuilder builder = new JexlBuilder() + .strict(true) + .safe(false) + .logger(JexlScriptEngine.LOG) + .cache(JexlScriptEngine.CACHE_SIZE); + engine = builder.create(); + ENGINE = new SoftReference<>(engine); + } + } + } + return engine; + } + /** The logger. */ static final Log LOG = LogFactory.getLog(JexlScriptEngine.class); @@ -197,7 +253,7 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable throw new NullPointerException("ScriptEngineFactory must not be null"); } parentFactory = factory; - jexlEngine = EngineSingletonHolder.DEFAULT_ENGINE; + jexlEngine = getEngine(); jexlObject = new JexlScriptObject(); } @@ -307,19 +363,7 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable private FactorySingletonHolder() {} /** The engine factory singleton instance. */ - static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory(); - } - - /** - * Holds singleton JexlScriptEngine (IODH). - * <p>A single JEXL engine and JexlUberspect is shared by all instances of JexlScriptEngine.</p> - */ - private static class EngineSingletonHolder { - /** non instantiable. */ - private EngineSingletonHolder() {} - - /** The JEXL engine singleton instance. */ - static final JexlEngine DEFAULT_ENGINE = new JexlBuilder().logger(LOG).cache(CACHE_SIZE).create(); + private static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory(); } /** diff --git a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java index 91e5d78b..e119eb65 100644 --- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java +++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java @@ -29,8 +29,13 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import org.apache.commons.jexl3.JexlBuilder; import org.apache.commons.jexl3.JexlException; +import org.apache.commons.jexl3.introspection.JexlPermissions; +import org.apache.commons.logging.LogFactory; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; public class JexlScriptEngineTest { @@ -42,6 +47,16 @@ public class JexlScriptEngineTest { "application/x-jexl2", "application/x-jexl3"); + @Before + public void setUp() { + } + + @After + public void tearDown() { + JexlBuilder.setDefaultPermissions(null); + JexlScriptEngine.setInstance(null); + } + @Test public void testScriptEngineFactory() throws Exception { final JexlScriptEngineFactory factory = new JexlScriptEngineFactory(); @@ -108,6 +123,32 @@ public class JexlScriptEngineTest { Assert.assertEquals(System.class,engine.eval("JEXL.System")); } + @Test + public void testScriptingInstance0() throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + JexlScriptEngine.setInstance(new JexlBuilder() + .cache(512) + .logger(LogFactory.getLog(JexlScriptEngine.class)) + .permissions(JexlPermissions.UNRESTRICTED) + .create()); + final ScriptEngine engine = manager.getEngineByName("jexl3"); + Long time2 = (Long) engine.eval( + "sys=context.class.forName(\"java.lang.System\");" + + "now=sys.currentTimeMillis();"); + Assert.assertTrue(time2 <= System.currentTimeMillis()); + } + + @Test + public void testScriptingPermissions1() throws Exception { + JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED); + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("jexl3"); + Long time2 = (Long) engine.eval( + "sys=context.class.forName(\"java.lang.System\");" + + "now=sys.currentTimeMillis();"); + Assert.assertTrue(time2 <= System.currentTimeMillis()); + } + @Test public void testNulls() throws Exception { final ScriptEngineManager manager = new ScriptEngineManager();