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 a42411b5c4acdf838054a0ef8b91ab0314c47242
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&quot;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();

Reply via email to