Skip to content
Permalink
Browse files
JEXL-357: added and updated Javadoc;
- refined code to reduce clutter;
- refined tests to improve coverage;
  • Loading branch information
henrib committed Feb 7, 2022
1 parent d6df444 commit d4e09765d3f044ded3b9d8502c9a0eefbeab0ff2
Showing 13 changed files with 308 additions and 74 deletions.
@@ -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
@@ -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>
@@ -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.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" &amp; "not-strict":
* <p> 0 &amp; null should be indicators of "default" values so that even in an case of error,
@@ -55,6 +70,7 @@
* </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;

@@ -122,6 +141,22 @@ public JexlUberspect uberspect() {
return this.uberspect;
}

/**
* 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 boolean lexicalShade() {

/**
* 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 Boolean silent() {
/**
* 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 Boolean strict() {

/**
* 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
@@ -27,7 +27,7 @@
* 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 void setMathScale(final int mscale) {
}

/**
* 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) {
@@ -29,7 +29,9 @@
* 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)
@@ -25,9 +25,11 @@
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 Engine(final JexlBuilder conf) {
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 Engine(final JexlBuilder conf) {
* 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
@@ -106,7 +106,7 @@ public Introspector(final Log log, final ClassLoader cloader) {
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;
}

/**
@@ -33,19 +33,21 @@

/**
* 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 {

@@ -234,31 +236,6 @@ protected Permissions(Set<String> perimeter, Map<String, NoJexlPackage> nojexl)
*/
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
*/
@@ -151,6 +151,7 @@ private int readIdentifier(StringBuilder id, int offset) {
*/
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 @@ private int readIdentifier(StringBuilder id, int offset, boolean dot, boolean st
}
id.append('.');
begin = -1;
} else if (star && c == '*') {
} else if (starf && c == '*') {
id.append('*');
starf = false; // only one star
} else {
break;
}
@@ -20,6 +20,7 @@
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 Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy s
* @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;

0 comments on commit d4e0976

Please sign in to comment.