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 2b027b4676fbd08c98f37b7014d3a158ef2bade1 Author: henrib <hen...@apache.org> AuthorDate: Mon Nov 7 14:58:17 2022 +0100 JEXL-381: added import/namespace pragma feature to enable/disable syntax; - added JexlUberspect#getClassByName that verifies permissions, use it when resolving namespaces; - updated restricted permissions set based on Dmitri feedback; --- .../org/apache/commons/jexl3/JexlFeatures.java | 85 ++++++++++++++++++---- .../org/apache/commons/jexl3/internal/Engine.java | 29 +++----- .../jexl3/internal/introspection/Introspector.java | 12 +-- .../internal/introspection/SandboxUberspect.java | 5 ++ .../jexl3/introspection/JexlPermissions.java | 6 +- .../commons/jexl3/introspection/JexlUberspect.java | 24 ++++-- .../apache/commons/jexl3/parser/JexlParser.java | 16 +++- .../java/org/apache/commons/jexl3/PragmaTest.java | 33 ++++++++- .../internal/introspection/PermissionsTest.java | 5 +- .../commons/jexl3/jexl342/ReferenceUberspect.java | 4 + 10 files changed, 170 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java index 124468fb..2b692fd1 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java +++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java @@ -60,7 +60,7 @@ public final class JexlFeatures { "register", "reserved variable", "local variable", "assign/modify", "global assign/modify", "array reference", "create instance", "loop", "function", "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade", - "thin-arrow", "fat-arrow" + "thin-arrow", "fat-arrow", "namespace pragma", "import pragma" }; /** Registers feature ordinal. */ private static final int REGISTER = 0; @@ -98,24 +98,35 @@ public final class JexlFeatures { public static final int THIN_ARROW = 16; /** Fat-arrow lambda syntax. */ public static final int FAT_ARROW = 17; + /** Namespace pragma feature ordinal. */ + public static final int NS_PRAGMA = 18; + /** Import pragma feature ordinal. */ + public static final int IMPORT_PRAGMA = 19; + /** + * The default features flag mask. + */ + private static final long DEFAULT_FEATURES = + (1L << LOCAL_VAR) + | (1L << SIDE_EFFECT) + | (1L << SIDE_EFFECT_GLOBAL) + | (1L << ARRAY_REF_EXPR) + | (1L << NEW_INSTANCE) + | (1L << LOOP) + | (1L << LAMBDA) + | (1L << METHOD_CALL) + | (1L << STRUCTURED_LITERAL) + | (1L << PRAGMA) + | (1L << ANNOTATION) + | (1L << SCRIPT) + | (1L << THIN_ARROW) + | (1L << NS_PRAGMA) + | (1L << IMPORT_PRAGMA); /** * Creates an all-features-enabled instance. */ public JexlFeatures() { - flags = (1L << LOCAL_VAR) - | (1L << SIDE_EFFECT) - | (1L << SIDE_EFFECT_GLOBAL) - | (1L << ARRAY_REF_EXPR) - | (1L << NEW_INSTANCE) - | (1L << LOOP) - | (1L << LAMBDA) - | (1L << METHOD_CALL) - | (1L << STRUCTURED_LITERAL) - | (1L << PRAGMA) - | (1L << ANNOTATION) - | (1L << SCRIPT) - | (1L << THIN_ARROW); + flags = DEFAULT_FEATURES; reservedNames = Collections.emptySet(); nameSpaces = TEST_STR_FALSE; } @@ -489,16 +500,60 @@ public final class JexlFeatures { */ public JexlFeatures pragma(final boolean flag) { setFeature(PRAGMA, flag); + if (!flag) { + setFeature(NS_PRAGMA, false); + setFeature(IMPORT_PRAGMA, false); + } return this; } /** - * @return true if pragma are enabled, false otherwise + * @return true if namespace pragma are enabled, false otherwise */ public boolean supportsPragma() { return getFeature(PRAGMA); } + /** + * Sets whether namespace pragma constructs are enabled. + * <p> + * When disabled, parsing a script/expression using syntactic namespace pragma constructs + * (#pragma jexl.namespace....) will throw a parsing exception. + * @param flag true to enable, false to disable + * @return this features instance + */ + public JexlFeatures namespacePragma(final boolean flag) { + setFeature(NS_PRAGMA, flag); + return this; + } + + /** + * @return true if namespace pragma are enabled, false otherwise + */ + public boolean supportsNamespacePragma() { + return getFeature(NS_PRAGMA); + } + /** + * Sets whether import pragma constructs are enabled. + * <p> + * When disabled, parsing a script/expression using syntactic import pragma constructs + * (#pragma jexl.import....) will throw a parsing exception. + * @param flag true to enable, false to disable + * @return this features instance + */ + public JexlFeatures importPragma(final boolean flag) { + setFeature(IMPORT_PRAGMA, flag); + return this; + } + + /** + * @return true if import pragma are enabled, false otherwise + */ + public boolean supportsImportPragma() { + return getFeature(IMPORT_PRAGMA); + } + + /** * Sets whether annotation constructs are enabled. * <p> 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 54630f58..2e384983 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java @@ -56,6 +56,10 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; +import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_IMPORT; +import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_JEXLNS; +import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_OPTIONS; + /** * A JexlEngine implementation. * @since 2.0 @@ -79,18 +83,6 @@ public class Engine extends JexlEngine { /** Non-instantiable. */ private UberspectHolder() {} } - /** - * The name of the options pragma. - */ - protected static final String PRAGMA_OPTIONS = "jexl.options"; - /** - * The prefix of a namespace pragma. - */ - protected static final String PRAGMA_JEXLNS = "jexl.namespace."; - /** - * The prefix of an import pragma. - */ - protected static final String PRAGMA_IMPORT = "jexl.import"; /** * The Log to which all JexlEngine messages will be logged. */ @@ -353,7 +345,7 @@ public class Engine extends JexlEngine { /** * Extracts the engine evaluation options from context if available, the engine * options otherwise. - * <p>If the context is a options handle and the handled options shared instance flag + * <p>If the context is an options handle and the handled options shared instance flag * is false, this method creates a copy of the options making them immutable during execution. * @param context the context * @return the options if any @@ -457,15 +449,16 @@ public class Engine extends JexlEngine { if (value instanceof String) { // jexl.namespace.*** final String nsname = key.substring(PRAGMA_JEXLNS.length()); - if (nsname != null && !nsname.isEmpty()) { + if (!nsname.isEmpty()) { if (ns == null) { ns = new HashMap<>(functions); } final String nsclass = value.toString(); - try { - ns.put(nsname, uberspect.getClassLoader().loadClass(nsclass)); - } catch (final ClassNotFoundException e) { - ns.put(nsname, nsclass); + Class<?> clazz = uberspect.getClassByName(nsclass); + if (clazz == null) { + logger.warn(key + ": unable to find class " + nsclass); + } else { + ns.put(nsname, clazz); } } } 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 869c5405..761d1af2 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 @@ -62,7 +62,7 @@ public final class Introspector { /** * the logger. */ - protected final Log logger; + private final Log logger; /** * The class loader used to solve constructors if needed. */ @@ -78,15 +78,15 @@ public final class Introspector { /** * Holds the method maps for the classes we know about, keyed by Class. */ - private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>(); + private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<>(); /** * Holds the map of classes ctors we know about as well as unknown ones. */ - private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>(); + private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<>(); /** * Holds the set of classes we have introspected. */ - private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>(); + private final Map<String, Class<?>> constructibleClasses = new HashMap<>(); /** * Create the introspector. @@ -116,7 +116,8 @@ public final class Introspector { */ public Class<?> getClassByName(final String className) { try { - return Class.forName(className, false, loader); + Class<?> clazz = Class.forName(className, false, loader); + return permissions.allow(clazz)? clazz : null; } catch (final ClassNotFoundException xignore) { return null; } @@ -279,7 +280,6 @@ public final class Introspector { + cname + "." + key.debugString(), xnotfound); } - ctor = null; } catch (final MethodKey.AmbiguousException xambiguous) { if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) { logger.info("ambiguous constructor invocation: " diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java index c2a98ce5..314b8acb 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java +++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java @@ -68,6 +68,11 @@ public final class SandboxUberspect implements JexlUberspect { return uberspect.getVersion(); } + @Override + public Class<?> getClassByName(final String className) { + return uberspect.getClassByName(className); + } + @Override public JexlMethod getConstructor(final Object ctorHandle, final Object... args) { final String className; 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 97243680..ce9b95db 100644 --- a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java +++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java @@ -223,7 +223,9 @@ public interface JexlPermissions { "org.apache.commons.jexl3.*", "org.apache.commons.jexl3 { JexlBuilder {} }", "org.apache.commons.jexl3.internal { Engine {} }", - "java.lang { Runtime {} System {} ProcessBuilder {} Class {} }", + "java.lang { Runtime{} System{} ProcessBuilder{} Process{}" + + " RuntimePermission{} SecurityManager{}" + + " Thread{} ThreadGroup{} Class{} }", "java.lang.annotation {}", "java.lang.instrument {}", "java.lang.invoke {}", @@ -231,7 +233,7 @@ public interface JexlPermissions { "java.lang.ref {}", "java.lang.reflect {}", "java.net {}", - "java.io { File { } }", + "java.io { File{} FileDescriptor{} }", "java.nio { Path { } Paths { } Files { } }", "java.rmi" ); diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java index 08a9b02f..009e8149 100644 --- a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java +++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java @@ -37,7 +37,7 @@ public interface JexlUberspect { * Abstracts getting property setter and getter. * <p> * These are used through 'strategies' to solve properties; a strategy orders a list of resolver types, - * and each resolver type is tried in sequence; the first resolver that discovers a non null {s,g}etter + * and each resolver type is tried in sequence; the first resolver that discovers a non-null {s,g}etter * stops the search. * * @see JexlResolver @@ -115,7 +115,7 @@ public interface JexlUberspect { /** * A resolver types list tailored for POJOs, favors '.' over '[]'. */ - static final List<PropertyResolver> POJO = Collections.unmodifiableList(Arrays.asList( + List<PropertyResolver> POJO = Collections.unmodifiableList(Arrays.asList( JexlResolver.PROPERTY, JexlResolver.MAP, JexlResolver.LIST, @@ -128,7 +128,7 @@ public interface JexlUberspect { /** * A resolver types list tailored for Maps, favors '[]' over '.'. */ - static final List<PropertyResolver> MAP = Collections.unmodifiableList(Arrays.asList( + List<PropertyResolver> MAP = Collections.unmodifiableList(Arrays.asList( JexlResolver.MAP, JexlResolver.LIST, JexlResolver.DUCK, @@ -165,7 +165,7 @@ public interface JexlUberspect { * If the operator is '[]' or if the operator is null and the object is a map, use the MAP list of resolvers; * Other cases use the POJO list of resolvers. */ - static final ResolverStrategy JEXL_STRATEGY = (op, obj) -> { + ResolverStrategy JEXL_STRATEGY = (op, obj) -> { if (op == JexlOperator.ARRAY_GET) { return MAP; } @@ -184,7 +184,7 @@ public interface JexlUberspect { * <p>If the operator is '[]' or if the object is a map, use the MAP list of resolvers. * Otherwise, use the POJO list of resolvers.</p> */ - static final ResolverStrategy MAP_STRATEGY = (op, obj) -> { + ResolverStrategy MAP_STRATEGY = (op, obj) -> { if (op == JexlOperator.ARRAY_GET) { return MAP; } @@ -228,6 +228,20 @@ public interface JexlUberspect { */ int getVersion(); + /** + * Seeks a class by name using this uberspect class-loader. + * @param className the class name + * @return the class instance or null if the class cannot be located by this uberspect class loader or if + * permissions deny access to the class + */ + default Class<?> getClassByName(final String className) { + try { + return Class.forName(className, false, getClassLoader()); + } catch (ClassNotFoundException xignore) { + return null; + } + } + /** * Returns a class constructor. * diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java index 157ae9c2..a86119dc 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java +++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java @@ -489,10 +489,18 @@ public abstract class JexlParser extends StringParser { } } + /** + * The name of the options pragma. + */ + public static final String PRAGMA_OPTIONS = "jexl.options"; /** * The prefix of a namespace pragma. */ - protected static final String PRAGMA_JEXLNS = "jexl.namespace."; + public static final String PRAGMA_JEXLNS = "jexl.namespace."; + /** + * The import pragma. + */ + public static final String PRAGMA_IMPORT = "jexl.import"; /** * Adds a pragma declaration. @@ -503,12 +511,18 @@ public abstract class JexlParser extends StringParser { if (!getFeatures().supportsPragma()) { throwFeatureException(JexlFeatures.PRAGMA, getToken(0)); } + if (PRAGMA_IMPORT.equals(key) && !getFeatures().supportsImportPragma()) { + throwFeatureException(JexlFeatures.IMPORT_PRAGMA, getToken(0)); + } if (pragmas == null) { pragmas = new TreeMap<>(); } // declaring a namespace Predicate<String> ns = getFeatures().namespaceTest(); if (ns != null && key.startsWith(PRAGMA_JEXLNS)) { + if (!getFeatures().supportsNamespacePragma()) { + throwFeatureException(JexlFeatures.NS_PRAGMA, getToken(0)); + } // jexl.namespace.*** final String nsname = key.substring(PRAGMA_JEXLNS.length()); if (!nsname.isEmpty()) { diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java index c67fa067..4bd306aa 100644 --- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java +++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java @@ -140,7 +140,7 @@ public class PragmaTest extends JexlTestCase { } @Test - public void testNamespacePragmaValueSet() { + public void testImportPragmaValueSet() { String src = "#pragma jexl.import java.util\n"+ "#pragma jexl.import java.io\n"+ @@ -159,6 +159,37 @@ public class PragmaTest extends JexlTestCase { Assert.assertEquals(src, parsed); } + @Test + public void testImportPragmaDisabled() { + String src = + "#pragma jexl.import java.util\n"+ + "#pragma jexl.import java.io\n"+ + "#pragma jexl.import java.net\n"+ + "42"; + JexlFeatures features = new JexlFeatures(); + features.importPragma(false); + final JexlEngine jexl = new JexlBuilder().features(features).create(); + try { + final JexlScript script = jexl.createScript(src); + } catch(JexlException.Parsing xparse) { + Assert.assertTrue(xparse.getMessage().contains("import pragma")); + } + } + @Test + public void testNamespacePragmaDisabled() { + JexlFeatures features = new JexlFeatures(); + features.namespacePragma(false); + final JexlEngine jexl = new JexlBuilder().features(features).create(); + try { + final JexlScript src = jexl.createScript( + "#pragma jexl.namespace.sleeper " + StaticSleeper.class.getName() + "\n" + + "sleeper:sleep(100);" + + "42"); + Assert.fail("should have thrown syntax exception"); + } catch(JexlException.Parsing xparse) { + Assert.assertTrue(xparse.getMessage().contains("namespace pragma")); + } + } @Test @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes") public void testStaticNamespacePragma() { 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 6bf36179..da0eb7a8 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 @@ -26,6 +26,7 @@ import org.apache.commons.jexl3.JexlTestCase; import org.apache.commons.jexl3.MapContext; import org.apache.commons.jexl3.internal.introspection.nojexlpackage.Invisible; import org.apache.commons.jexl3.introspection.JexlPermissions; +import org.apache.commons.jexl3.introspection.JexlUberspect; import org.junit.Assert; import org.junit.Test; @@ -166,7 +167,7 @@ public class PermissionsTest { @Test public void testParsePermissions0() throws Exception { - String src = "java.lang { Runtime { exit(); exec(); } }"; + String src = "java.lang { Runtime { exit(); exec(); } }\njava.net { URL {} }"; Permissions p = (Permissions) JexlPermissions.parse(src); Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages(); Assert.assertNotNull(nojexlmap); @@ -178,6 +179,8 @@ public class PermissionsTest { Method exec = getMethod(java.lang.Runtime.class,"exec"); Assert.assertNotNull(exec); Assert.assertFalse(p.allow(exec)); + JexlUberspect uber = new Uberspect(null, null, p); + Assert.assertNull(uber.getClassByName("java.net.URL")); } public static class Outer { diff --git a/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java b/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java index 8c44dfd5..b674334d 100644 --- a/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java +++ b/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java @@ -51,6 +51,10 @@ public class ReferenceUberspect implements JexlUberspect { * The map resolver list strategy. */ private final List<PropertyResolver> mapStrategy; + @Override + public Class<?> getClassByName(final String className) { + return uberspect.getClassByName(className); + } /** * Constructor.