You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/02/07 16:43:57 UTC
[commons-jexl] branch master updated: JEXL-357: added and updated Javadoc; - refined code to reduce clutter; - refined tests to improve coverage;
This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/master by this push:
new d4e0976 JEXL-357: added and updated Javadoc; - refined code to reduce clutter; - refined tests to improve coverage;
d4e0976 is described below
commit d4e09765d3f044ded3b9d8502c9a0eefbeab0ff2
Author: henrib <he...@apache.org>
AuthorDate: Mon Feb 7 17:43:51 2022 +0100
JEXL-357: added and updated Javadoc;
- refined code to reduce clutter;
- refined tests to improve coverage;
---
RELEASE-NOTES.txt | 12 +++
src/changes/changes.xml | 9 ++
.../java/org/apache/commons/jexl3/JexlBuilder.java | 57 ++++++++--
.../java/org/apache/commons/jexl3/JexlOptions.java | 7 +-
.../apache/commons/jexl3/annotations/NoJexl.java | 4 +-
.../org/apache/commons/jexl3/internal/Engine.java | 34 +++++-
.../jexl3/internal/introspection/Introspector.java | 2 +-
.../jexl3/internal/introspection/Permissions.java | 51 +++------
.../internal/introspection/PermissionsParser.java | 4 +-
.../jexl3/internal/introspection/Uberspect.java | 5 +-
.../jexl3/introspection/JexlPermissions.java | 115 +++++++++++++++++++--
.../org/apache/commons/jexl3/JexlTestCase.java | 30 +++++-
.../internal/introspection/PermissionsTest.java | 52 ++++++++--
13 files changed, 308 insertions(+), 74 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 74b2e63..be26679 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -28,6 +28,18 @@ Compatibility with previous releases
====================================
Version 3.3 is source and binary compatible with 3.2.
+What's new in 3.3:
+==================
+JEXL 3.3 brings the ability to configure permissions on libraries in the manner pioneered
+with the @NoJexl annotation on source code. This is achieved through a crude but light mechanism akin to
+a security manager that controls what JEXL can introspect and thus expose to scripts.
+Used in conjunction with options (JexlOptions) and features (JexlFeatures), the permissions (JexlPermissions)
+allow fine-tuning the scripting integration into any project.
+
+New Features in 3.3:
+====================
+* JEXL-357: Configure accessible packages/classes/methods/fields
+
Bugs Fixed in 3.3:
==================
* JEXL-354: #pragma does not handle negative integer or real literals
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 6e81e57..74640cc 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -27,6 +27,15 @@
<body>
<release version="3.3" date="YYYY-MM-DD">
<!-- UPDATE -->
+ <action dev="henrib" type="add" issue="JEXL-357" >
+ Configure accessible packages/classes/methods/fields
+ </action>
+ <action dev="henrib" type="fix" issue="JEXL-354" due-to="William Price">
+ #pragma does not handle negative integer or real literals
+ </action>
+ <action dev="henrib" type="fix" issue="JEXL-353" due-to="Mr.Z">
+ Documentation error for not-in/not-match operator
+ </action>
<action dev="ggregory" type="update" due-to="Dependabot">
Bump checkstyle from 9.2 to 9.2.1 #72.
</action>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index df19317..c9e7f93 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -18,6 +18,7 @@
package org.apache.commons.jexl3;
import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.apache.commons.jexl3.introspection.JexlSandbox;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.logging.Log;
@@ -26,13 +27,27 @@ import java.util.Map;
import java.nio.charset.Charset;
/**
- * Configure and builds a JexlEngine.
+ * Configures and builds a JexlEngine.
*
- * <p>The <code>setSilent</code> and <code>setStrict</code> methods allow to fine-tune an engine instance behavior
- * according to various error control needs. The strict flag tells the engine when and if null as operand is
- * considered an error, the silent flag tells the engine what to do with the error
- * (log as warning or throw exception).</p>
+ * <p>The builder allow fine-tuning an engine instance behavior according to various control needs.</p>
*
+ * <p>Broad configurations elements are controlled through the features ({@link JexlFeatures}) that can restrict JEXL
+ * syntax - for instance, only expressions with no-side effects - and permissions ({@link JexlPermissions}) that control
+ * the visible set of objects - for instance, avoiding access to any object in java.rmi.* -. </p>
+ *
+ * <p>Fine error control and runtime-overridable behaviors are implemented through options ({@link JexlOptions}). Most
+ * common flags accessible from the builder are reflected in its options ({@link #options()}).
+ * <p>The <code>silent</code> flag tells the engine what to do with the error; when true, errors are logged as
+ * warning, when false, they throw {@link JexlException} exceptions.</p>
+ * <p>The <code>strict</code> flag tells the engine when and if null as operand is considered an error. The <code>safe</code>
+ * flog determines if safe-navigation is used. Safe-navigation allows an evaluation shortcut and return null in expressions
+ * that attempts dereferencing null, typically a method call or accessing a property.</p>
+ * <p>The <code>lexical</code> and <code>lexicalShade</code> flags can be used to enforce a lexical scope for
+ * variables and parameters. The <code>lexicalShade</code> can be used to further ensure no global variable can be
+ * used with the same name as a local one even after it goes out of scope. The corresponding feature flags should be
+ * preferred since they will detect violations at parsing time. (see {@link JexlFeatures})</p>
+ *
+ * <p>The following rules apply on silent and strict flags:</p>
* <ul>
* <li>When "silent" & "not-strict":
* <p> 0 & null should be indicators of "default" values so that even in an case of error,
@@ -55,6 +70,7 @@ import java.nio.charset.Charset;
* </p>
* </li>
* </ul>
+ *
*/
public class JexlBuilder {
@@ -67,6 +83,9 @@ public class JexlBuilder {
/** The strategy strategy. */
private JexlUberspect.ResolverStrategy strategy = null;
+ /** The set of permissions. */
+ private JexlPermissions permissions = null;
+
/** The sandbox. */
private JexlSandbox sandbox = null;
@@ -123,6 +142,22 @@ public class JexlBuilder {
}
/**
+ * Sets the JexlPermissions instance the engine will use.
+ *
+ * @param p the permissions
+ * @return this builder
+ */
+ public JexlBuilder permissions(final JexlPermissions p) {
+ this.permissions = p;
+ return this;
+ }
+
+ /** @return the permissions */
+ public JexlPermissions permissions() {
+ return this.permissions;
+ }
+
+ /**
* Sets the JexlUberspect strategy strategy the engine will use.
* <p>This is ignored if the uberspect has been set.
*
@@ -319,7 +354,9 @@ public class JexlBuilder {
/**
* Sets whether the engine will throw JexlException during evaluation when an error is triggered.
- *
+ * <p>When <em>not</em> silent, the engine throws an exception when the evaluation triggers an exception or an
+ * error.</p>
+ * <p>It is recommended to use <em>silent(true)</em> as an explicit default.</p>
* @param flag true means no JexlException will occur, false allows them
* @return this builder
*/
@@ -336,6 +373,9 @@ public class JexlBuilder {
/**
* Sets whether the engine considers unknown variables, methods, functions and constructors as errors or
* evaluates them as null.
+ * <p>When <em>not</em> strict, operators or functions using null operands return null on evaluation. When
+ * strict, those raise exceptions.</p>
+ * <p>It is recommended to use <em>strict(true)</em> as an explicit default.</p>
*
* @param flag true means strict error reporting, false allows them to be evaluated as null
* @return this builder
@@ -352,9 +392,10 @@ public class JexlBuilder {
/**
* Sets whether the engine considers dereferencing null in navigation expressions
- * as errors or evaluates them as null.
+ * as null or triggers an error.
* <p><code>x.y()</code> if x is null throws an exception when not safe,
- * return null and warns if it is.<p>
+ * return null and warns if it is.</p>
+ * <p>It is recommended to use <em>safe(false)</em> as an explicit default.</p>
*
* @param flag true means safe navigation, false throws exception when dereferencing null
* @return this builder
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index 1c12b1e..ab9e573 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -27,7 +27,7 @@ import org.apache.commons.jexl3.internal.Engine;
* The flags, briefly explained, are the following:
* <ul>
* <li>silent: whether errors throw exception</li>
- * <li>safe: whether navigation through null is an error</li>
+ * <li>safe: whether navigation through null is <em>not</em>an error</li>
* <li>cancellable: whether thread interruption is an error</li>
* <li>lexical: whether redefining local variables is an error</li>
* <li>lexicalShade: whether local variables shade global ones even outside their scope</li>
@@ -312,8 +312,11 @@ public final class JexlOptions {
}
/**
- * Sets whether the engine considers null in navigation expression as errors
+ * Sets whether the engine considers null in navigation expression as null or as errors
* during evaluation.
+ * <p>If safe, encountering null during a navigation expression - dereferencing a method or a field through a null
+ * object or property - will <em>not</em> be considered an error but evaluated as <em>null</em>. It is recommended
+ * to use <em>setSafe(false)</em> as an explicit default.</p>
* @param flag true if safe, false otherwise
*/
public void setSafe(final boolean flag) {
diff --git a/src/main/java/org/apache/commons/jexl3/annotations/NoJexl.java b/src/main/java/org/apache/commons/jexl3/annotations/NoJexl.java
index 14ce643..7c2c21d 100644
--- a/src/main/java/org/apache/commons/jexl3/annotations/NoJexl.java
+++ b/src/main/java/org/apache/commons/jexl3/annotations/NoJexl.java
@@ -29,7 +29,9 @@ import java.lang.annotation.Target;
* This allows to completely hide a package, class, interface, constructor, method or field from
* JEXL; a NoJexl annotated element will not be usable through any kind of JEXL expression or script.
* </p>
- * See {@link org.apache.commons.jexl3.introspection.JexlSandbox} for another way to restrict JEXL access.
+ * See {@link org.apache.commons.jexl3.introspection.JexlPermissions} for the general mechanism to restrict
+ * what JEXL allows in scripts.
+ * See {@link org.apache.commons.jexl3.introspection.JexlSandbox} for another way to further constrain JEXL access.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index 61de330..11d6338 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -25,9 +25,11 @@ import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlOptions;
import org.apache.commons.jexl3.JexlScript;
+import org.apache.commons.jexl3.internal.introspection.Permissions;
import org.apache.commons.jexl3.internal.introspection.SandboxUberspect;
import org.apache.commons.jexl3.internal.introspection.Uberspect;
import org.apache.commons.jexl3.introspection.JexlMethod;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.apache.commons.jexl3.introspection.JexlSandbox;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.jexl3.parser.ASTArrayAccess;
@@ -73,7 +75,9 @@ public class Engine extends JexlEngine {
private static final class UberspectHolder {
/** The default uberspector that handles all introspection patterns. */
private static final Uberspect UBERSPECT =
- new Uberspect(LogFactory.getLog(JexlEngine.class), JexlUberspect.JEXL_STRATEGY);
+ new Uberspect(LogFactory.getLog(JexlEngine.class),
+ JexlUberspect.JEXL_STRATEGY,
+ JexlPermissions.parse());
/** Non-instantiable. */
private UberspectHolder() {}
@@ -194,7 +198,9 @@ public class Engine extends JexlEngine {
this.collectMode = conf.collectMode();
this.stackOverflow = conf.stackOverflow() > 0? conf.stackOverflow() : Integer.MAX_VALUE;
// core properties:
- final JexlUberspect uber = conf.uberspect() == null ? getUberspect(conf.logger(), conf.strategy()) : conf.uberspect();
+ final JexlUberspect uber = conf.uberspect() == null
+ ? getUberspect(conf.logger(), conf.strategy(), conf.permissions())
+ : conf.uberspect();
final ClassLoader loader = conf.loader();
if (loader != null) {
uber.setClassLoader(loader);
@@ -238,14 +244,32 @@ public class Engine extends JexlEngine {
* instead of the default one.</p>
* @param logger the logger to use for the underlying Uberspect
* @param strategy the property resolver strategy
+ * @param permissions the introspection permissions
* @return Uberspect the default uberspector instance.
+ * @since 3.3
*/
- public static Uberspect getUberspect(final Log logger, final JexlUberspect.ResolverStrategy strategy) {
+ public static Uberspect getUberspect(
+ final Log logger,
+ final JexlUberspect.ResolverStrategy strategy,
+ final JexlPermissions permissions) {
if ((logger == null || logger.equals(LogFactory.getLog(JexlEngine.class)))
- && (strategy == null || strategy == JexlUberspect.JEXL_STRATEGY)) {
+ && (strategy == null || strategy == JexlUberspect.JEXL_STRATEGY)
+ && permissions == null || permissions == Permissions.DEFAULT) {
return UberspectHolder.UBERSPECT;
}
- return new Uberspect(logger, strategy);
+ return new Uberspect(logger, strategy, permissions);
+ }
+
+ /**
+ * Use {@link Engine#getUberspect(Log, JexlUberspect.ResolverStrategy, JexlPermissions)}.
+ * @deprecated 3.3
+ * @param logger the logger
+ * @param strategy the strategy
+ * @return an Uberspect instance
+ */
+ @Deprecated
+ public static Uberspect getUberspect(final Log logger, final JexlUberspect.ResolverStrategy strategy) {
+ return getUberspect(logger, strategy, null);
}
@Override
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index ee534da..08b5290 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -106,7 +106,7 @@ public final class Introspector {
public Introspector(final Log log, final ClassLoader cloader, final JexlPermissions perms) {
this.logger = log;
this.loader = cloader;
- this.permissions = perms != null? perms : Permissions.SECURE;
+ this.permissions = perms;
}
/**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
index 7c6ad9f..911f395 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
@@ -33,19 +33,21 @@ import org.apache.commons.jexl3.introspection.JexlPermissions;
/**
* Checks whether an element (ctor, field or method) is visible by JEXL introspection.
- * Default implementation does this by checking if element has been annotated with NoJexl.
+ * <p>Default implementation does this by checking if element has been annotated with NoJexl.</p>
*
- * The NoJexl annotation allows a fine grain permissions on executable objects (methods, fields, constructors).
- * NoJexl of a package implies all classes (including derived classes) and all interfaces
- * of that package are invisible to Jexl;
- * NoJexl on a class implies this class and all its derived classes are invisible to Jexl;
- * NoJexl on a (public) field makes it not visible as a property to Jexl;
- * NoJexl on a constructor prevents that constructor to be used to instantiate through 'new';
- * NoJexl on a method prevents that method and any of its overrides to be visible to Jexl;
- * NoJexl on an interface prevents all methods of that interface and their overrides to be visible to Jexl.
- *
- * It is possible to further refine permissions on classes used through libraries where source code form can
- * not be altered. The @link(PermissionsParser).
+ * <p>The NoJexl annotation allows a fine grain permissions on executable objects (methods, fields, constructors).
+ * </p>
+ * <ul>
+ * <li>NoJexl of a package implies all classes (including derived classes) and all interfaces
+ * of that package are invisible to JEXL.</li>
+ * <li>NoJexl on a class implies this class and all its derived classes are invisible to JEXL.</li>
+ * <li>NoJexl on a (public) field makes it not visible as a property to JEXL.</li>
+ * <li>NoJexl on a constructor prevents that constructor to be used to instantiate through 'new'.</li>
+ * <li>NoJexl on a method prevents that method and any of its overrides to be visible to JEXL.</li>
+ * <li>NoJexl on an interface prevents all methods of that interface and their overrides to be visible to JEXL.</li>
+ * </ul>
+ * <p> It is possible to further refine permissions on classes used through libraries where source code form can
+ * not be altered using an instance of permissions using {@link JexlPermissions#parse(String...)}.</p>
*/
public class Permissions implements JexlPermissions {
@@ -235,31 +237,6 @@ public class Permissions implements JexlPermissions {
public static final Permissions DEFAULT = new Permissions();
/**
- * A very secure singleton.
- */
- public static final Permissions SECURE = new PermissionsParser().parse(
- "# Secure Uberspect Permissions",
- "java.nio.*",
- "java.io.*",
- "java.lang.*",
- "java.math.*",
- "java.text.*",
- "java.util.*",
- "org.w3c.dom.*",
- "org.apache.commons.jexl3.*",
- "java.lang { Runtime {} System {} ProcessBuilder {} Class {} }",
- "java.lang.annotation {}",
- "java.lang.instrument {}",
- "java.lang.invoke {}",
- "java.lang.management {}",
- "java.lang.ref {}",
- "java.lang.reflect {}",
- "java.net {}",
- "java.io { File { } }",
- "java.nio { Path { } Paths { } Files { } }"
- );
-
- /**
* @return the packages
*/
Map<String, NoJexlPackage> getPackages() {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java
index 8f05737..165aef3 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java
@@ -151,6 +151,7 @@ public class PermissionsParser {
*/
private int readIdentifier(StringBuilder id, int offset, boolean dot, boolean star) {
int begin = -1;
+ boolean starf = star;
int i = offset;
char c = 0;
while (i < size) {
@@ -167,8 +168,9 @@ public class PermissionsParser {
}
id.append('.');
begin = -1;
- } else if (star && c == '*') {
+ } else if (starf && c == '*') {
id.append('*');
+ starf = false; // only one star
} else {
break;
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index 64bf0d6..589a84d 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -20,6 +20,7 @@ import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.introspection.JexlMethod;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
import org.apache.commons.jexl3.introspection.JexlPropertySet;
import org.apache.commons.jexl3.introspection.JexlUberspect;
@@ -58,7 +59,7 @@ public class Uberspect implements JexlUberspect {
/** The resolver strategy. */
private final JexlUberspect.ResolverStrategy strategy;
/** The permissions. */
- private final Permissions permissions;
+ private final JexlPermissions permissions;
/** The introspector version. */
private final AtomicInteger version;
/** The soft reference to the introspector currently in use. */
@@ -88,7 +89,7 @@ public class Uberspect implements JexlUberspect {
* @param sty the resolver strategy
* @param perms the introspector permissions
*/
- public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty, final Permissions perms) {
+ public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty, final JexlPermissions perms) {
logger = runtimeLogger == null? LogFactory.getLog(JexlEngine.class) : runtimeLogger;
strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty;
permissions = perms == null? Permissions.DEFAULT : perms;
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
index 910bf53..f0cad14 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.jexl3.introspection;
+import org.apache.commons.jexl3.internal.introspection.Permissions;
import org.apache.commons.jexl3.internal.introspection.PermissionsParser;
import java.lang.reflect.Constructor;
@@ -23,54 +24,148 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
- * A JEXL dedicated 'security manager' that constraints which packages/classes/constructors/fields/methods
- * are made visible to JEXL scripts.
+ * This interface describes permissions used by JEXL introspection that constrain which
+ * packages/classes/constructors/fields/methods are made visible to JEXL scripts.
+ * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated
+ * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control
+ * over what can be executed. JEXL introspection mechanism will check whether it is permitted to
+ * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions
+ * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}.
+ * </p>
+ * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated -
+ * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl}
+ * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments;
+ * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be
+ * processed.</p>
+ * <p>A simple textual configuration can be used to create user defined permissions using
+ * {@link JexlPermissions#parse(String...)}.</p>
+ *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
+ * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
+ * be to instantiate a {@link JexlUberspect} with those permissions and call
+ * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
+ * @since 3.3
*/
public interface JexlPermissions {
/**
- * Checks whether a package explicitly disallows JEXL introspection.
+ * Checks whether a package allows JEXL introspection.
+ * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
+ * to JEXL and can not be used in scripts or expression.</p>
* @param pack the package
* @return true if JEXL is allowed to introspect, false otherwise
+ * @since 3.3
*/
boolean allow(final Package pack);
/**
- * Checks whether a class or one of its super-classes or implemented interfaces
- * explicitly disallows JEXL introspection.
+ * Checks whether a class allows JEXL introspection.
+ * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
+ * as well as derived classes are visible to JEXL and can not be used in scripts or expressions.
+ * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
+ * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
* @param clazz the class to check
* @return true if JEXL is allowed to introspect, false otherwise
+ * @since 3.3
*/
boolean allow(final Class<?> clazz);
/**
- * Checks whether a constructor explicitly disallows JEXL introspection.
+ * Checks whether a constructor allows JEXL introspection.
+ * <p>If a constructor is not allowed, the new operator can not be used to instantiate its declared class
+ * in scripts or expressions.</p>
* @param ctor the constructor to check
* @return true if JEXL is allowed to introspect, false otherwise
+ * @since 3.3
*/
boolean allow(final Constructor<?> ctor);
/**
- * Checks whether a method explicitly disallows JEXL introspection.
- * <p>Since methods can be overridden, this also checks that no superclass or interface
+ * Checks whether a method allows JEXL introspection.
+ * <p>If a method is not allowed, it can not resolved and called in scripts or expressions.</p>
+ * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface
* explicitly disallows this methods.</p>
* @param method the method to check
* @return true if JEXL is allowed to introspect, false otherwise
+ * @since 3.3
*/
boolean allow(final Method method);
/**
* Checks whether a field explicitly disallows JEXL introspection.
+ * <p>If a field is not allowed, it can not resolved and accessed in scripts or expressions.</p>
* @param field the field to check
* @return true if JEXL is allowed to introspect, false otherwise
+ * @since 3.3
*/
boolean allow(final Field field);
/**
* Parses a set of permissions.
- * @param src the permissions source
+ * <p>
+ * In JEXL 3.3, the syntax recognizes 2 types of permissions:
+ * </p>
+ * <ul>
+ * <li>Allowing access to a wildcard restricted set of packages. </li>
+ * <li>Denying access to packages, classes (and inner classes), methods and fields</li>
+ * </ul>
+ * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be
+ * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards.
+ * These allow composing the volume of what is allowed by addition.</p>
+ * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation;
+ * they can restrict access to package, class, inner-class, methods and fields.
+ * These allow refining the volume of what is allowed by extrusion.</p>
+ * An example of a tight environment that would not allow scripts to wander could be:
+ * <pre>
+ * # allow a very restricted set of base classes
+ * java.math.*
+ * java.text.*
+ * java.util.*
+ * # deny classes that could pose a security risk
+ * java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
+ * org.apache.commons.jexl3 { JexlBuilder {} }
+ * </pre>
+ * <p>
+ * Syntax for wildcards is the name of the package suffixed by <code>.*</code>. Syntax for restrictions is
+ * a list of package restrictions. A package restriction is a package name followed by a block
+ * (as in curly-bracket block {}) that contains a list of class restrictions. A class restriction is a class name
+ * followed by a block of member restrictions. A member restriction can be a class restriction - to restrict
+ * nested classes -, a field which is the Java field name suffixed with <code>;</code>, a method composed of
+ * its Java name suffixed with <code>();</code>. Constructor restrictions are specified like methods using the
+ * class name as method name.
+ * </p>
+ * <p>
+ * All overrides and overloads of a constructors or method are allowed or restricted at the same time,
+ * the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation.
+ * </p>
+ * <pre>
+ * # some wildcards
+ * java.lang.*; # java.lang is pretty much a must have
+ * my.allowed.package0.*
+ * another.allowed.package1.*
+ * # nojexl like restrictions
+ * my.package.internal {} # the whole package is hidden
+ * my.package {
+ * class0 {
+ * class1 {} # the whole class1 is hidden
+ * class2 {
+ * class2(); # class2 constructors can not be invoked
+ * class3 {
+ * aMethod(); # aMethod can not be called
+ * aField; # aField can not be accessed
+ * }
+ * } # end of class2
+ * class0(); # class0 constructors can not be invoked
+ * method(); # method can not be called
+ * field; # field can not be accessed
+ * } # end class0
+ * } # end package my.package
+ * </pre>
+ *
+ * @param src the permissions source, the default (NoJexl aware) permissions if null
* @return the permissions instance
+ * @since 3.3
*/
static JexlPermissions parse(String... src) {
- return new PermissionsParser().parse(src);
+ return src == null || src.length == 0? Permissions.DEFAULT : new PermissionsParser().parse(src);
}
+
}
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
index d9f515a..56d031c 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Method;
import org.apache.commons.jexl3.internal.Util;
import org.apache.commons.jexl3.internal.introspection.Permissions;
import org.apache.commons.jexl3.internal.introspection.Uberspect;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.junit.After;
import org.junit.Assert;
@@ -68,9 +69,36 @@ public class JexlTestCase {
return new JexlBuilder().create();
}
+ /**
+ * A very secure singleton.
+ */
+ public static final JexlPermissions SECURE = JexlPermissions.parse(
+ "# Secure Uberspect Permissions",
+ "java.nio.*",
+ "java.io.*",
+ "java.lang.*",
+ "java.math.*",
+ "java.text.*",
+ "java.util.*",
+ "org.w3c.dom.*",
+ "org.apache.commons.jexl3.*",
+ "org.apache.commons.jexl3 { JexlBuilder {} }",
+ "org.apache.commons.jexl3.internal { Engine {} }",
+ "java.lang { Runtime {} System {} ProcessBuilder {} Class {} }",
+ "java.lang.annotation {}",
+ "java.lang.instrument {}",
+ "java.lang.invoke {}",
+ "java.lang.management {}",
+ "java.lang.ref {}",
+ "java.lang.reflect {}",
+ "java.net {}",
+ "java.io { File { } }",
+ "java.nio { Path { } Paths { } Files { } }"
+ );
+
public static JexlEngine createEngine(final boolean lenient) {
return new JexlBuilder()
- .uberspect(new Uberspect(null, null, Permissions.SECURE))
+ .uberspect(new Uberspect(null, null, SECURE))
.arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
}
diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
index 0f8161c..f9fdf4d 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
@@ -16,6 +16,11 @@
*/
package org.apache.commons.jexl3.internal.introspection;
+import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlScript;
+import org.apache.commons.jexl3.JexlTestCase;
import org.apache.commons.jexl3.internal.introspection.nojexlpackage.Invisible;
import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.junit.Assert;
@@ -24,15 +29,12 @@ import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
-import static org.apache.commons.jexl3.internal.introspection.Permissions.SECURE;
/**
* Checks the CacheMap.MethodKey implementation
@@ -40,6 +42,7 @@ import static org.apache.commons.jexl3.internal.introspection.Permissions.SECURE
public class PermissionsTest {
+
public static class A {
public int i;
public A() {}
@@ -205,6 +208,8 @@ public class PermissionsTest {
Set<String> wildcards = p.getWildcards();
Assert.assertEquals(4, wildcards.size());
+ JexlEngine jexl = new JexlBuilder().permissions(p).safe(false).lexical(true).create();
+
Method exit = getMethod(java.lang.Runtime.class,"exit");
Assert.assertNotNull(exit);
Assert.assertFalse(p.allow(exit));
@@ -214,10 +219,24 @@ public class PermissionsTest {
Method callMeNot = getMethod(Outer.Inner.class, "callMeNot");
Assert.assertNotNull(callMeNot);
Assert.assertFalse(p.allow(callMeNot));
+ JexlScript script = jexl.createScript("o.callMeNot()", "o");
+ try {
+ Object result = script.execute(null, new Outer.Inner());
+ Assert.fail("callMeNot should be uncallable");
+ } catch(JexlException.Method xany) {
+ Assert.assertEquals("callMeNot", xany.getMethod());
+ }
Method uncallable = getMethod(Invisible.class, "uncallable");
Assert.assertFalse(p.allow(uncallable));
Package ip = Invisible.class.getPackage();
Assert.assertFalse(p.allow(ip));
+ script = jexl.createScript("o.uncallable()", "o");
+ try {
+ Object result = script.execute(null, new Invisible());
+ Assert.fail("uncallable should be uncallable");
+ } catch(JexlException.Method xany) {
+ Assert.assertEquals("uncallable", xany.getMethod());
+ }
}
@Test
@@ -233,7 +252,7 @@ public class PermissionsTest {
@Test
public void testSecurePermissions() {
- Assert.assertNotNull(SECURE);
+ Assert.assertNotNull(JexlTestCase.SECURE);
List<Class<?>> acs = Arrays.asList(
java.lang.Runtime.class,
java.math.BigDecimal.class,
@@ -242,7 +261,7 @@ public class PermissionsTest {
for(Class<?> ac: acs) {
Package p = ac.getPackage();
Assert.assertNotNull(ac.getName(), p);
- Assert.assertTrue(ac.getName(), SECURE.allow(p));
+ Assert.assertTrue(ac.getName(), JexlTestCase.SECURE.allow(p));
}
List<Class<?>> nacs = Arrays.asList(
java.lang.annotation.ElementType.class,
@@ -254,8 +273,29 @@ public class PermissionsTest {
for(Class<?> nac : nacs) {
Package p = nac.getPackage();
Assert.assertNotNull(nac.getName(), p);
- Assert.assertFalse(nac.getName(), SECURE.allow(p));
+ Assert.assertFalse(nac.getName(), JexlTestCase.SECURE.allow(p));
}
}
+
+ @Test
+ public void testParsePermissionsFailures() {
+ String[] srcs = new String[]{
+ "java.lang.*.*",
+ "java.math.*.",
+ "java.text.*;",
+ "java.lang {{ Runtime {} }",
+ "java.rmi {}}",
+ "java.io { Text File {} }",
+ "java.io { File { m.x } }"
+ };
+ for(String src : srcs) {
+ try {
+ Permissions p = (Permissions) JexlPermissions.parse(src);
+ Assert.fail(src);
+ } catch(IllegalStateException xill) {
+ Assert.assertNotNull(xill);
+ }
+ }
+ }
}