From fff0f40580987d29ff5b22dbc98deb52367472e5 Mon Sep 17 00:00:00 2001 From: Henri Biestro Date: Wed, 21 Aug 2024 12:15:43 +0200 Subject: [PATCH 1/2] JEXL: JexlSandbox clean up; - added test related to a StackOverflow question (testSortArray); --- .../jexl3/introspection/JexlSandbox.java | 1257 ++++++++--------- .../apache/commons/jexl3/Issues400Test.java | 38 + 2 files changed, 653 insertions(+), 642 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java index cae92e271..cc9354549 100644 --- a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java +++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java @@ -67,647 +67,620 @@ * @since 3.0 */ public final class JexlSandbox { - /** - * An allow set of names. - */ - static class AllowSet extends Names { - /** The map of controlled names and aliases. */ - private Map names; - - @Override - public boolean add(final String name) { - if (names == null) { - names = new HashMap<>(); - } - return names.put(name, name) == null; - } - - @Override - public boolean alias(final String name, final String alias) { - if (names == null) { - names = new HashMap<>(); - } - return names.put(alias, name) == null; - } - - @Override - protected Names copy() { - final AllowSet copy = new AllowSet(); - copy.names = names == null ? null : new HashMap<>(names); - return copy; - } - - @Override - public String get(final String name) { - if (names == null) { - return name; - } - final String actual = names.get(name); - // if null is not explicitly allowed, explicit null aka NULL - if (name == null && actual == null && !names.containsKey(null)) { - return JexlSandbox.NULL; - } - return actual; - } - - @Override - public String toString() { - return "allow{" + (names == null? "all" : Objects.toString(names.entrySet())) + "}"; - } - } - - /** - * @deprecated since 3.2, use {@link BlockSet} - */ - @Deprecated - public static final class BlackSet extends BlockSet {} - - /** - * A block set of names. - */ - static class BlockSet extends Names { - /** The set of controlled names. */ - private Set names; - - @Override - public boolean add(final String name) { - if (names == null) { - names = new HashSet<>(); - } - return names.add(name); - } - - @Override - protected Names copy() { - final BlockSet copy = new BlockSet(); - copy.names = names == null ? null : new HashSet<>(names); - return copy; - } - - @Override - public String get(final String name) { - // if name is null and contained in set, explicit null aka NULL - return names != null && !names.contains(name) ? name : name != null ? null : NULL; - } - - @Override - public String toString() { - return "block{" + (names == null? "all" :Objects.toString(names)) + "}"; - } - } - - /** - * A base set of names. - */ - public abstract static class Names { - /** - * Adds a name to this set. - * - * @param name the name to add - * @return true if the name was really added, false if not - */ - public abstract boolean add(String name); - - /** - * Adds an alias to a name to this set. - *

This only has an effect on allow lists.

- * - * @param name the name to alias - * @param alias the alias - * @return true if the alias was added, false if it was already present - */ - public boolean alias(final String name, final String alias) { - return false; - } - - /** - * @return a copy of these Names - */ - protected Names copy() { - return this; - } - - /** - * Whether a given name is allowed or not. - * - * @param name the method/property name to check - * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise - */ - public String get(final String name) { - return name; - } - } - - /** - * Contains the allow or block lists for properties and methods for a given class. - */ - public static final class Permissions { - /** Whether these permissions are inheritable, ie can be used by derived classes. */ - private final boolean inheritable; - /** The controlled readable properties. */ - private final Names read; - /** The controlled writable properties. */ - private final Names write; - /** The controlled methods. */ - private final Names execute; - - /** - * Creates a new permissions instance. - * - * @param inherit whether these permissions are inheritable - * @param readFlag whether the read property list is allow or block - * @param writeFlag whether the write property list is allow or block - * @param executeFlag whether the method list is allow of block - */ - Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) { - this(inherit, - readFlag ? new AllowSet() : new BlockSet(), - writeFlag ? new AllowSet() : new BlockSet(), - executeFlag ? new AllowSet() : new BlockSet()); - } - - /** - * Creates a new permissions instance. - * - * @param inherit whether these permissions are inheritable - * @param nread the read set - * @param nwrite the write set - * @param nexecute the method set - */ - Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) { - this.read = nread != null ? nread : ALLOW_NAMES; - this.write = nwrite != null ? nwrite : ALLOW_NAMES; - this.execute = nexecute != null ? nexecute : ALLOW_NAMES; - this.inheritable = inherit; - } - - /** - * @return a copy of these permissions - */ - Permissions copy() { - return new Permissions(inheritable, read.copy(), write.copy(), execute.copy()); - } - - /** - * Gets the set of method names in these permissions. - * - * @return the set of method names - */ - public Names execute() { - return execute; - } - - /** - * Adds a list of executable methods names to these permissions. - *

The constructor is denoted as the empty-string, all other methods by their names.

- * - * @param methodNames the method names - * @return this instance of permissions - */ - public Permissions execute(final String... methodNames) { - for (final String methodName : methodNames) { - execute.add(methodName); - } - return this; - } - - /** - * @return whether these permissions apply to derived classes. - */ - public boolean isInheritable() { - return inheritable; - } - - /** - * Gets the set of readable property names in these permissions. - * - * @return the set of property names - */ - public Names read() { - return read; - } - - /** - * Adds a list of readable property names to these permissions. - * - * @param propertyNames the property names - * @return this instance of permissions - */ - public Permissions read(final String... propertyNames) { - for (final String propertyName : propertyNames) { - read.add(propertyName); - } - return this; - } - - /** - * Gets the set of writable property names in these permissions. - * - * @return the set of property names - */ - public Names write() { - return write; - } - - /** - * Adds a list of writable property names to these permissions. - * - * @param propertyNames the property names - * @return this instance of permissions - */ - public Permissions write(final String... propertyNames) { - for (final String propertyName : propertyNames) { - write.add(propertyName); - } - return this; - } - } - - /** - * @deprecated since 3.2, use {@link AllowSet} - */ - @Deprecated - public static final class WhiteSet extends AllowSet {} - - /** - * The marker string for explicitly disallowed null properties. - */ - public static final String NULL = "?"; - - /** - * The pass-thru name set. - */ - static final Names ALLOW_NAMES = new Names() { - @Override - public boolean add(final String name) { - return false; - } - - @Override - public String toString() { - return "allowAll"; - } - }; - - /** - * The block-all name set. - */ - private static final Names BLOCK_NAMES = new Names() { - @Override - public boolean add(final String name) { - return false; - } - - @Override - public String get(final String name) { - return name == null ? NULL : null; - } - - @Override - public String toString() { - return "blockAll"; - } - }; - - /** - * The pass-thru permissions. - */ - private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES); - - /** - * The block-all permissions. - */ - private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES); - - /** - * Gets a class by name, crude mechanism for backwards (<3.2 ) compatibility. - * @param cname the class name - * @return the class - */ - static Class forName(final String cname) { - try { - return Class.forName(cname); - } catch (final Exception xany) { - return null; - } - } - - /** - * The map from class names to permissions. - */ - private final Map sandbox; - - /** - * Whether permissions can be inherited (through implementation or extension). - */ - private final boolean inherit; - - /** - * Default behavior, block or allow. - */ - private final boolean allow; - - /** - * Creates a new default sandbox. - *

In the absence of explicit permissions on a class, the - * sandbox is a allow-box, allow-listing that class for all permissions (read, write and execute). - */ - public JexlSandbox() { - this(true, false, null); - } - - /** - * Creates a new default sandbox. - *

A allow-box considers no permissions as "everything is allowed" when - * a block-box considers no permissions as "nothing is allowed". - * @param ab whether this sandbox is allow (true) or block (false) - * if no permission is explicitly defined for a class. - * @since 3.1 - */ - public JexlSandbox(final boolean ab) { - this(ab, false, null); - } - - /** - * Creates a sandbox. - * @param ab whether this sandbox is allow (true) or block (false) - * @param inh whether permissions on interfaces and classes are inherited (true) or not (false) - * @since 3.2 - */ - public JexlSandbox(final boolean ab, final boolean inh) { - this(ab, inh, null); - } - - /** - * Creates a sandbox based on an existing permissions map. - * @param ab whether this sandbox is allow (true) or block (false) - * @param inh whether permissions are inherited, default false - * @param map the permissions map - * @since 3.2 - */ - protected JexlSandbox(final boolean ab, final boolean inh, final Map map) { - allow = ab; - inherit = inh; - sandbox = map != null ? map : new HashMap<>(); - } - - /** - * Creates a sandbox based on an existing permissions map. - * @param ab whether this sandbox is allow (true) or block (false) - * @param map the permissions map - * @since 3.1 - */ - @Deprecated - protected JexlSandbox(final boolean ab, final Map map) { - this(ab, false, map); - } - - /** - * Creates a sandbox based on an existing permissions map. - * @param map the permissions map - */ - @Deprecated - protected JexlSandbox(final Map map) { - this(true, false, map); - } - - /** - * Creates a new set of permissions based on allow lists for methods and properties for a given class. - *

The sandbox inheritance property will apply to the permissions created by this method - * - * @param clazz the allowed class name - * @return the permissions instance - */ - public Permissions allow(final String clazz) { - return permissions(clazz, true, true, true); - } - - /** - * Use block() instead. - * @param clazz the allowed class name - * @return the permissions instance - */ - @Deprecated - public Permissions black(final String clazz) { - return block(clazz); - } - - /** - * Creates a new set of permissions based on block lists for methods and properties for a given class. - *

The sandbox inheritance property will apply to the permissions created by this method - * - * @param clazz the blocked class name - * @return the permissions instance - */ - public Permissions block(final String clazz) { - return permissions(clazz, false, false, false); - } - - /** - * @return a copy of this sandbox - */ - public JexlSandbox copy() { - // modified concurrently at runtime so... - final Map map = new ConcurrentHashMap<>(); - for (final Map.Entry entry : sandbox.entrySet()) { - map.put(entry.getKey(), entry.getValue().copy()); - } - return new JexlSandbox(allow, inherit, map); - } - - /** - * Gets the execute permission value for a given method of a class. - * - * @param clazz the class - * @param name the method name - * @return null if not allowed, the name of the method to use otherwise - */ - public String execute(final Class clazz, final String name) { - final String m = get(clazz).execute().get(name); - return "".equals(name) && m != null ? clazz.getName() : m; - } - - /** - * Gets the execute permission value for a given method of a class. - * - * @param clazz the class name - * @param name the method name - * @return null if not allowed, the name of the method to use otherwise - */ - @Deprecated - public String execute(final String clazz, final String name) { - final String m = get(clazz).execute().get(name); - return "".equals(name) && m != null ? clazz : m; - } - /** - * Gets the permissions associated to a class. - * @param clazz the class - * @return the permissions - */ - @SuppressWarnings("null") // clazz can not be null since permissions would be not null and block; - public Permissions get(final Class clazz) { - // we only store the result for classes we actively seek permissions for - return clazz == null ? BLOCK_ALL : compute(clazz, true); - } - - /** - * Computes and optionally the permissions associated to a class. - * @param clazz the class - * @param store whether the resulting permissions should be stored in the sandbox - * @return the permissions - */ - private Permissions compute(final Class clazz, boolean store) { - Permissions permissions = sandbox.get(clazz.getName()); - if (permissions == null) { - if (inherit) { - // find first inherited interface that defines permissions - for (final Class inter : clazz.getInterfaces()) { - permissions = compute(inter, false); - if (permissions != null) { - if (permissions.isInheritable()) { - break; - } - permissions = null; - } - } - // nothing defined yet, find first superclass that defines permissions - if (permissions == null) { - // let's walk all super classes - Class zuper = clazz.getSuperclass(); - // walk all superclasses - while (zuper != null) { - permissions = compute(zuper, false); - if (permissions != null) { - if (permissions.isInheritable()) { - break; - } - permissions = null; - } - zuper = zuper.getSuperclass(); - } - } - // nothing was inheritable - if (permissions == null) { - permissions = allow ? ALLOW_ALL : BLOCK_ALL; - } - // store the info to avoid doing this costly look-up - if (store) { - sandbox.put(clazz.getName(), permissions); - } - } else { - permissions = allow ? ALLOW_ALL : BLOCK_ALL; - } - } - return permissions; - } - - /** - * Gets the set of permissions associated to a class. - * - * @param clazz the class name - * @return the defined permissions or an all-allow permission instance if none were defined - */ - public Permissions get(final String clazz) { - if (inherit) { - return get(forName(clazz)); - } - final Permissions permissions = sandbox.get(clazz); - if (permissions == null) { - return allow ? ALLOW_ALL : BLOCK_ALL; - } - return permissions; - } - - /** - * Creates the set of permissions for a given class. - *

The sandbox inheritance property will apply to the permissions created by this method - * - * @param clazz the class for which these permissions apply - * @param readFlag whether the readable property list is allow - true - or block - false - - * @param writeFlag whether the writable property list is allow - true - or block - false - - * @param executeFlag whether the executable method list is allow - true - or block - false - - * @return the set of permissions - */ - public Permissions permissions(final String clazz, - final boolean readFlag, - final boolean writeFlag, - final boolean executeFlag) { - return permissions(clazz, inherit, readFlag, writeFlag, executeFlag); - } - - /** - * Creates the set of permissions for a given class. - * - * @param clazz the class for which these permissions apply - * @param inhf whether these permissions are inheritable - * @param readf whether the readable property list is allow - true - or block - false - - * @param writef whether the writable property list is allow - true - or block - false - - * @param execf whether the executable method list is allow - true - or block - false - - * @return the set of permissions - */ - public Permissions permissions(final String clazz, - final boolean inhf, - final boolean readf, - final boolean writef, - final boolean execf) { - final Permissions box = new Permissions(inhf, readf, writef, execf); - sandbox.put(clazz, box); - return box; - } - /** - * Gets the read permission value for a given property of a class. - * - * @param clazz the class - * @param name the property name - * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise - */ - public String read(final Class clazz, final String name) { - return get(clazz).read().get(name); - } - - /** - * Gets the read permission value for a given property of a class. - * - * @param clazz the class name - * @param name the property name - * @return null if not allowed, the name of the property to use otherwise - */ - @Deprecated - public String read(final String clazz, final String name) { - return get(clazz).read().get(name); - } - - /** - * Use allow() instead. - * @param clazz the allowed class name - * @return the permissions instance - */ - @Deprecated - public Permissions white(final String clazz) { - return allow(clazz); - } - - /** - * Gets the write permission value for a given property of a class. - * - * @param clazz the class - * @param name the property name - * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise - */ - public String write(final Class clazz, final String name) { - return get(clazz).write().get(name); - } - - /** - * Gets the write permission value for a given property of a class. - * - * @param clazz the class name - * @param name the property name - * @return null if not allowed, the name of the property to use otherwise - */ - @Deprecated - public String write(final String clazz, final String name) { - return get(clazz).write().get(name); - } + /** + * The marker string for explicitly disallowed null properties. + */ + public static final String NULL = "?"; + + /** + * The pass-thru name set. + */ + static final Names ALLOW_NAMES = new Names() { + @Override + public boolean add(final String name) { + return false; + } + + @Override + public String toString() { + return "allowAll"; + } + }; + + /** + * The block-all name set. + */ + private static final Names BLOCK_NAMES = new Names() { + @Override + public boolean add(final String name) { + return false; + } + + @Override + public String get(final String name) { + return name == null ? NULL : null; + } + + @Override + public String toString() { + return "blockAll"; + } + }; + + /** + * The block-all permissions. + */ + private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES); + /** + * The pass-thru permissions. + */ + private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES); + /** + * The map from class names to permissions. + */ + private final Map sandbox; + /** + * Whether permissions can be inherited (through implementation or extension). + */ + private final boolean inherit; + /** + * Default behavior, block or allow. + */ + private final boolean allow; + + /** + * Creates a new default sandbox. + *

In the absence of explicit permissions on a class, the + * sandbox is a allow-box, allow-listing that class for all permissions (read, write and execute). + */ + public JexlSandbox() { + this(true, false, null); + } + + /** + * Creates a new default sandbox. + *

A allow-box considers no permissions as "everything is allowed" when + * a block-box considers no permissions as "nothing is allowed". + * + * @param ab whether this sandbox is allow (true) or block (false) + * if no permission is explicitly defined for a class. + * @since 3.1 + */ + public JexlSandbox(final boolean ab) { + this(ab, false, null); + } + + /** + * Creates a sandbox. + * + * @param ab whether this sandbox is allow (true) or block (false) + * @param inh whether permissions on interfaces and classes are inherited (true) or not (false) + * @since 3.2 + */ + public JexlSandbox(final boolean ab, final boolean inh) { + this(ab, inh, null); + } + + /** + * Creates a sandbox based on an existing permissions map. + * + * @param ab whether this sandbox is allow (true) or block (false) + * @param inh whether permissions are inherited, default false + * @param map the permissions map + * @since 3.2 + */ + private JexlSandbox(final boolean ab, final boolean inh, final Map map) { + allow = ab; + inherit = inh; + sandbox = map != null ? map : new HashMap<>(); + } + + /** + * Gets a class by name, crude mechanism for backwards (<3.2 ) compatibility. + * + * @param cname the class name + * @return the class + */ + static Class forName(final String cname) { + try { + return Class.forName(cname); + } catch (final Exception xany) { + return null; + } + } + + /** + * Creates a new set of permissions based on allow lists for methods and properties for a given class. + *

The sandbox inheritance property will apply to the permissions created by this method + * + * @param clazz the allowed class name + * @return the permissions instance + */ + public Permissions allow(final String clazz) { + return permissions(clazz, true, true, true); + } + + /** + * Creates a new set of permissions based on block lists for methods and properties for a given class. + *

The sandbox inheritance property will apply to the permissions created by this method + * + * @param clazz the blocked class name + * @return the permissions instance + */ + public Permissions block(final String clazz) { + return permissions(clazz, false, false, false); + } + + /** + * @return a copy of this sandbox + */ + public JexlSandbox copy() { + // modified concurrently at runtime so... + final Map map = new ConcurrentHashMap<>(); + for (final Map.Entry entry : sandbox.entrySet()) { + map.put(entry.getKey(), entry.getValue().copy()); + } + return new JexlSandbox(allow, inherit, map); + } + + /** + * Gets the execute permission value for a given method of a class. + * + * @param clazz the class + * @param name the method name + * @return null if not allowed, the name of the method to use otherwise + */ + public String execute(final Class clazz, final String name) { + final String m = get(clazz).execute().get(name); + return "".equals(name) && m != null ? clazz.getName() : m; + } + + /** + * Gets the execute permission value for a given method of a class. + * + * @param clazz the class name + * @param name the method name + * @return null if not allowed, the name of the method to use otherwise + * @deprecated 3.3 + */ + @Deprecated + public String execute(final String clazz, final String name) { + final String m = get(clazz).execute().get(name); + return "".equals(name) && m != null ? clazz : m; + } + + /** + * Gets the permissions associated to a class. + * + * @param clazz the class + * @return the permissions + */ + @SuppressWarnings("null") + public Permissions get(final Class clazz) { + // clazz can not be null since permissions would be not null and block; + // we only store the result for classes we actively seek permissions for. + return clazz == null ? BLOCK_ALL : compute(clazz); + } + + /** + * Computes and optionally stores the permissions associated to a class. + * + * @param clazz the class + * @return the permissions + */ + private Permissions compute(final Class clazz) { + Permissions permissions = sandbox.get(clazz.getName()); + if (permissions == null) { + if (inherit) { + // find first inherited interface that defines permissions + Class[] interfaces = clazz.getInterfaces(); + for (int i = 0; permissions == null && i < interfaces.length; ++i) { + permissions = inheritable(sandbox.get(interfaces[i].getName())); + } + // nothing defined yet, find first superclass that defines permissions + if (permissions == null) { + // let's walk all super classes + for (Class zuper = clazz.getSuperclass(); + permissions == null && zuper != null; + zuper = zuper.getSuperclass()) { + permissions = inheritable(sandbox.get(zuper.getName())); + } + } + } + // nothing was determined through inheritance + if (permissions == null) { + permissions = allow ? ALLOW_ALL : BLOCK_ALL; + } + // store the info to avoid doing this costly look-up + sandbox.put(clazz.getName(), permissions); + } + return permissions; + } + + private Permissions inheritable(Permissions p) { + return p != null && p.isInheritable() ? p : null; + } + + /** + * Gets the set of permissions associated to a class. + * + * @param clazz the class name + * @return the defined permissions or an all-allow permission instance if none were defined + */ + public Permissions get(final String clazz) { + if (inherit) { + return get(forName(clazz)); + } + final Permissions permissions = sandbox.get(clazz); + if (permissions == null) { + return allow ? ALLOW_ALL : BLOCK_ALL; + } + return permissions; + } + + /** + * Creates the set of permissions for a given class. + *

The sandbox inheritance property will apply to the permissions created by this method + * + * @param clazz the class for which these permissions apply + * @param readFlag whether the readable property list is allow - true - or block - false - + * @param writeFlag whether the writable property list is allow - true - or block - false - + * @param executeFlag whether the executable method list is allow - true - or block - false - + * @return the set of permissions + */ + public Permissions permissions(final String clazz, + final boolean readFlag, + final boolean writeFlag, + final boolean executeFlag) { + return permissions(clazz, inherit, readFlag, writeFlag, executeFlag); + } + + /** + * Creates the set of permissions for a given class. + * + * @param clazz the class for which these permissions apply + * @param inhf whether these permissions are inheritable + * @param readf whether the readable property list is allow - true - or block - false - + * @param writef whether the writable property list is allow - true - or block - false - + * @param execf whether the executable method list is allow - true - or block - false - + * @return the set of permissions + */ + public Permissions permissions(final String clazz, + final boolean inhf, + final boolean readf, + final boolean writef, + final boolean execf) { + final Permissions box = new Permissions(inhf, readf, writef, execf); + sandbox.put(clazz, box); + return box; + } + + /** + * Gets the read permission value for a given property of a class. + * + * @param clazz the class + * @param name the property name + * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise + */ + public String read(final Class clazz, final String name) { + return get(clazz).read().get(name); + } + + /** + * Gets the read permission value for a given property of a class. + * + * @param clazz the class name + * @param name the property name + * @return null if not allowed, the name of the property to use otherwise + * @deprecated 3.3 + */ + @Deprecated + public String read(final String clazz, final String name) { + return get(clazz).read().get(name); + } + + /** + * Use allow() instead. + * + * @param clazz the allowed class name + * @return the permissions instance + * @deprecated 3.3 + */ + @Deprecated + public Permissions white(final String clazz) { + return allow(clazz); + } + + /** + * Gets the write permission value for a given property of a class. + * + * @param clazz the class + * @param name the property name + * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise + */ + public String write(final Class clazz, final String name) { + return get(clazz).write().get(name); + } + + /** + * Gets the write permission value for a given property of a class. + * + * @param clazz the class name + * @param name the property name + * @return null if not allowed, the name of the property to use otherwise + * @deprecated 3.3 + */ + @Deprecated + public String write(final String clazz, final String name) { + return get(clazz).write().get(name); + } + + /** + * An allow set of names. + */ + static class AllowSet extends Names { + /** + * The map of controlled names and aliases. + */ + private Map names; + + @Override + public boolean add(final String name) { + if (names == null) { + names = new HashMap<>(); + } + return names.put(name, name) == null; + } + + @Override + public boolean alias(final String name, final String alias) { + if (names == null) { + names = new HashMap<>(); + } + return names.put(alias, name) == null; + } + + @Override + protected Names copy() { + final AllowSet copy = new AllowSet(); + copy.names = names == null ? null : new HashMap<>(names); + return copy; + } + + @Override + public String get(final String name) { + if (names == null) { + return name; + } + final String actual = names.get(name); + // if null is not explicitly allowed, explicit null aka NULL + if (name == null && actual == null && !names.containsKey(null)) { + return JexlSandbox.NULL; + } + return actual; + } + + @Override + public String toString() { + return "allow{" + (names == null ? "all" : Objects.toString(names.entrySet())) + "}"; + } + } + + /** + * A block set of names. + */ + static class BlockSet extends Names { + /** + * The set of controlled names. + */ + private Set names; + + @Override + public boolean add(final String name) { + if (names == null) { + names = new HashSet<>(); + } + return names.add(name); + } + + @Override + protected Names copy() { + final BlockSet copy = new BlockSet(); + copy.names = names == null ? null : new HashSet<>(names); + return copy; + } + + @Override + public String get(final String name) { + // if name is null and contained in set, explicit null aka NULL + if (names != null && !names.contains(name)) { + return name; + } else if (name != null) { + return null; + } else { + return NULL; + } + } + + @Override + public String toString() { + return "block{" + (names == null ? "all" : Objects.toString(names)) + "}"; + } + } + + /** + * A base set of names. + */ + public abstract static class Names { + /** + * Adds a name to this set. + * + * @param name the name to add + * @return true if the name was really added, false if not + */ + public abstract boolean add(String name); + + /** + * Adds an alias to a name to this set. + *

This only has an effect on allow lists.

+ * + * @param name the name to alias + * @param alias the alias + * @return true if the alias was added, false if it was already present + */ + public boolean alias(final String name, final String alias) { + return false; + } + + /** + * @return a copy of these Names + */ + protected Names copy() { + return this; + } + + /** + * Whether a given name is allowed or not. + * + * @param name the method/property name to check + * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise + */ + public String get(final String name) { + return name; + } + } + + /** + * Contains the allow or block lists for properties and methods for a given class. + */ + public static final class Permissions { + /** + * Whether these permissions are inheritable, ie can be used by derived classes. + */ + private final boolean inheritable; + /** + * The controlled readable properties. + */ + private final Names read; + /** + * The controlled writable properties. + */ + private final Names write; + /** + * The controlled methods. + */ + private final Names execute; + + /** + * Creates a new permissions instance. + * + * @param inherit whether these permissions are inheritable + * @param readFlag whether the read property list is allow or block + * @param writeFlag whether the write property list is allow or block + * @param executeFlag whether the method list is allow of block + */ + Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) { + this(inherit, + readFlag ? new AllowSet() : new BlockSet(), + writeFlag ? new AllowSet() : new BlockSet(), + executeFlag ? new AllowSet() : new BlockSet()); + } + + /** + * Creates a new permissions instance. + * + * @param inherit whether these permissions are inheritable + * @param nread the read set + * @param nwrite the write set + * @param nexecute the method set + */ + Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) { + this.read = nread != null ? nread : ALLOW_NAMES; + this.write = nwrite != null ? nwrite : ALLOW_NAMES; + this.execute = nexecute != null ? nexecute : ALLOW_NAMES; + this.inheritable = inherit; + } + + /** + * @return a copy of these permissions + */ + Permissions copy() { + return new Permissions(inheritable, read.copy(), write.copy(), execute.copy()); + } + + /** + * Gets the set of method names in these permissions. + * + * @return the set of method names + */ + public Names execute() { + return execute; + } + + /** + * Adds a list of executable methods names to these permissions. + *

The constructor is denoted as the empty-string, all other methods by their names.

+ * + * @param methodNames the method names + * @return this instance of permissions + */ + public Permissions execute(final String... methodNames) { + for (final String methodName : methodNames) { + execute.add(methodName); + } + return this; + } + + /** + * @return whether these permissions apply to derived classes. + */ + public boolean isInheritable() { + return inheritable; + } + + /** + * Gets the set of readable property names in these permissions. + * + * @return the set of property names + */ + public Names read() { + return read; + } + + /** + * Adds a list of readable property names to these permissions. + * + * @param propertyNames the property names + * @return this instance of permissions + */ + public Permissions read(final String... propertyNames) { + for (final String propertyName : propertyNames) { + read.add(propertyName); + } + return this; + } + + /** + * Gets the set of writable property names in these permissions. + * + * @return the set of property names + */ + public Names write() { + return write; + } + + /** + * Adds a list of writable property names to these permissions. + * + * @param propertyNames the property names + * @return this instance of permissions + */ + public Permissions write(final String... propertyNames) { + for (final String propertyName : propertyNames) { + write.add(propertyName); + } + return this; + } + } } diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 812c1ca3b..98ca2d1a2 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -29,6 +29,7 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -443,4 +444,41 @@ public void testNamespaceVsTernary1() { assertNotNull(r); assertEquals(42, r); } + + public static class SortingContext extends MapContext { + /** + * Sorts an array using a script to evaluate the property used to compare elements. + * @param array the elements array + * @param expr the property evaluation lambda + */ + public void sort(Object[] array, JexlScript expr) { + final Comparator cmp = new Comparator() { + @Override + public int compare(Object o1, Object o2) { + Comparable left = (Comparable) expr.execute(SortingContext.this, o1); + Comparable right = (Comparable) expr.execute(SortingContext.this, o2); + return left.compareTo(right); + } + }; + Arrays.sort(array, cmp); + } + } + + @Test + public void testSortArray() { + final JexlEngine jexl = new JexlBuilder().safe(false).strict(true).silent(false).create(); + // test data, json like + String src = "[{'id':1,'name':'John','type':9},{'id':2,'name':'Doe','type':7},{'id':3,'name':'Doe','type':10}]"; + Object a = jexl.createExpression(src).evaluate(null); + assertNotNull(a); + // row 0 and 1 are not ordered + Map[] m = (Map[]) a; + assertEquals(9, m[0].get("type")); + assertEquals(7, m[1].get("type")); + // sort the elements on the type + jexl.createScript("array.sort( e -> e.type )", "array").execute(new SortingContext(), a); + // row 0 and 1 are now ordered + assertEquals(7, m[0].get("type")); + assertEquals(9, m[1].get("type")); + } } From 3d748e3ff7fbce780c622891dddfed1120e84490 Mon Sep 17 00:00:00 2001 From: Henrib Date: Wed, 21 Aug 2024 14:56:52 +0200 Subject: [PATCH 2/2] JEXL: JexlSandbox clean up; --- .../jexl3/introspection/JexlSandbox.java | 1230 ++++++++--------- 1 file changed, 615 insertions(+), 615 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java index cc9354549..3ab8e32fa 100644 --- a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java +++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java @@ -67,620 +67,620 @@ * @since 3.0 */ public final class JexlSandbox { - /** - * The marker string for explicitly disallowed null properties. - */ - public static final String NULL = "?"; - - /** - * The pass-thru name set. - */ - static final Names ALLOW_NAMES = new Names() { - @Override - public boolean add(final String name) { - return false; - } - - @Override - public String toString() { - return "allowAll"; - } - }; - - /** - * The block-all name set. - */ - private static final Names BLOCK_NAMES = new Names() { - @Override - public boolean add(final String name) { - return false; - } - - @Override - public String get(final String name) { - return name == null ? NULL : null; - } - - @Override - public String toString() { - return "blockAll"; - } - }; - - /** - * The block-all permissions. - */ - private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES); - /** - * The pass-thru permissions. - */ - private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES); - /** - * The map from class names to permissions. - */ - private final Map sandbox; - /** - * Whether permissions can be inherited (through implementation or extension). - */ - private final boolean inherit; - /** - * Default behavior, block or allow. - */ - private final boolean allow; - - /** - * Creates a new default sandbox. - *

In the absence of explicit permissions on a class, the - * sandbox is a allow-box, allow-listing that class for all permissions (read, write and execute). - */ - public JexlSandbox() { - this(true, false, null); - } - - /** - * Creates a new default sandbox. - *

A allow-box considers no permissions as "everything is allowed" when - * a block-box considers no permissions as "nothing is allowed". - * - * @param ab whether this sandbox is allow (true) or block (false) - * if no permission is explicitly defined for a class. - * @since 3.1 - */ - public JexlSandbox(final boolean ab) { - this(ab, false, null); - } - - /** - * Creates a sandbox. - * - * @param ab whether this sandbox is allow (true) or block (false) - * @param inh whether permissions on interfaces and classes are inherited (true) or not (false) - * @since 3.2 - */ - public JexlSandbox(final boolean ab, final boolean inh) { - this(ab, inh, null); - } - - /** - * Creates a sandbox based on an existing permissions map. - * - * @param ab whether this sandbox is allow (true) or block (false) - * @param inh whether permissions are inherited, default false - * @param map the permissions map - * @since 3.2 - */ - private JexlSandbox(final boolean ab, final boolean inh, final Map map) { - allow = ab; - inherit = inh; - sandbox = map != null ? map : new HashMap<>(); - } - - /** - * Gets a class by name, crude mechanism for backwards (<3.2 ) compatibility. - * - * @param cname the class name - * @return the class - */ - static Class forName(final String cname) { - try { - return Class.forName(cname); - } catch (final Exception xany) { - return null; - } - } - - /** - * Creates a new set of permissions based on allow lists for methods and properties for a given class. - *

The sandbox inheritance property will apply to the permissions created by this method - * - * @param clazz the allowed class name - * @return the permissions instance - */ - public Permissions allow(final String clazz) { - return permissions(clazz, true, true, true); - } - - /** - * Creates a new set of permissions based on block lists for methods and properties for a given class. - *

The sandbox inheritance property will apply to the permissions created by this method - * - * @param clazz the blocked class name - * @return the permissions instance - */ - public Permissions block(final String clazz) { - return permissions(clazz, false, false, false); - } - - /** - * @return a copy of this sandbox - */ - public JexlSandbox copy() { - // modified concurrently at runtime so... - final Map map = new ConcurrentHashMap<>(); - for (final Map.Entry entry : sandbox.entrySet()) { - map.put(entry.getKey(), entry.getValue().copy()); - } - return new JexlSandbox(allow, inherit, map); - } - - /** - * Gets the execute permission value for a given method of a class. - * - * @param clazz the class - * @param name the method name - * @return null if not allowed, the name of the method to use otherwise - */ - public String execute(final Class clazz, final String name) { - final String m = get(clazz).execute().get(name); - return "".equals(name) && m != null ? clazz.getName() : m; - } - - /** - * Gets the execute permission value for a given method of a class. - * - * @param clazz the class name - * @param name the method name - * @return null if not allowed, the name of the method to use otherwise - * @deprecated 3.3 - */ - @Deprecated - public String execute(final String clazz, final String name) { - final String m = get(clazz).execute().get(name); - return "".equals(name) && m != null ? clazz : m; - } - - /** - * Gets the permissions associated to a class. - * - * @param clazz the class - * @return the permissions - */ - @SuppressWarnings("null") - public Permissions get(final Class clazz) { - // clazz can not be null since permissions would be not null and block; - // we only store the result for classes we actively seek permissions for. - return clazz == null ? BLOCK_ALL : compute(clazz); - } - - /** - * Computes and optionally stores the permissions associated to a class. - * - * @param clazz the class - * @return the permissions - */ - private Permissions compute(final Class clazz) { - Permissions permissions = sandbox.get(clazz.getName()); - if (permissions == null) { - if (inherit) { - // find first inherited interface that defines permissions - Class[] interfaces = clazz.getInterfaces(); - for (int i = 0; permissions == null && i < interfaces.length; ++i) { - permissions = inheritable(sandbox.get(interfaces[i].getName())); - } - // nothing defined yet, find first superclass that defines permissions - if (permissions == null) { - // let's walk all super classes - for (Class zuper = clazz.getSuperclass(); - permissions == null && zuper != null; - zuper = zuper.getSuperclass()) { - permissions = inheritable(sandbox.get(zuper.getName())); - } - } - } - // nothing was determined through inheritance - if (permissions == null) { - permissions = allow ? ALLOW_ALL : BLOCK_ALL; - } - // store the info to avoid doing this costly look-up - sandbox.put(clazz.getName(), permissions); - } - return permissions; - } - - private Permissions inheritable(Permissions p) { - return p != null && p.isInheritable() ? p : null; - } - - /** - * Gets the set of permissions associated to a class. - * - * @param clazz the class name - * @return the defined permissions or an all-allow permission instance if none were defined - */ - public Permissions get(final String clazz) { - if (inherit) { - return get(forName(clazz)); - } - final Permissions permissions = sandbox.get(clazz); - if (permissions == null) { - return allow ? ALLOW_ALL : BLOCK_ALL; - } - return permissions; - } - - /** - * Creates the set of permissions for a given class. - *

The sandbox inheritance property will apply to the permissions created by this method - * - * @param clazz the class for which these permissions apply - * @param readFlag whether the readable property list is allow - true - or block - false - - * @param writeFlag whether the writable property list is allow - true - or block - false - - * @param executeFlag whether the executable method list is allow - true - or block - false - - * @return the set of permissions - */ - public Permissions permissions(final String clazz, - final boolean readFlag, - final boolean writeFlag, - final boolean executeFlag) { - return permissions(clazz, inherit, readFlag, writeFlag, executeFlag); - } - - /** - * Creates the set of permissions for a given class. - * - * @param clazz the class for which these permissions apply - * @param inhf whether these permissions are inheritable - * @param readf whether the readable property list is allow - true - or block - false - - * @param writef whether the writable property list is allow - true - or block - false - - * @param execf whether the executable method list is allow - true - or block - false - - * @return the set of permissions - */ - public Permissions permissions(final String clazz, - final boolean inhf, - final boolean readf, - final boolean writef, - final boolean execf) { - final Permissions box = new Permissions(inhf, readf, writef, execf); - sandbox.put(clazz, box); - return box; - } - - /** - * Gets the read permission value for a given property of a class. - * - * @param clazz the class - * @param name the property name - * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise - */ - public String read(final Class clazz, final String name) { - return get(clazz).read().get(name); - } - - /** - * Gets the read permission value for a given property of a class. - * - * @param clazz the class name - * @param name the property name - * @return null if not allowed, the name of the property to use otherwise - * @deprecated 3.3 - */ - @Deprecated - public String read(final String clazz, final String name) { - return get(clazz).read().get(name); - } - - /** - * Use allow() instead. - * - * @param clazz the allowed class name - * @return the permissions instance - * @deprecated 3.3 - */ - @Deprecated - public Permissions white(final String clazz) { - return allow(clazz); - } - - /** - * Gets the write permission value for a given property of a class. - * - * @param clazz the class - * @param name the property name - * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise - */ - public String write(final Class clazz, final String name) { - return get(clazz).write().get(name); - } - - /** - * Gets the write permission value for a given property of a class. - * - * @param clazz the class name - * @param name the property name - * @return null if not allowed, the name of the property to use otherwise - * @deprecated 3.3 - */ - @Deprecated - public String write(final String clazz, final String name) { - return get(clazz).write().get(name); - } - - /** - * An allow set of names. - */ - static class AllowSet extends Names { - /** - * The map of controlled names and aliases. - */ - private Map names; - - @Override - public boolean add(final String name) { - if (names == null) { - names = new HashMap<>(); - } - return names.put(name, name) == null; - } - - @Override - public boolean alias(final String name, final String alias) { - if (names == null) { - names = new HashMap<>(); - } - return names.put(alias, name) == null; - } - - @Override - protected Names copy() { - final AllowSet copy = new AllowSet(); - copy.names = names == null ? null : new HashMap<>(names); - return copy; - } - - @Override - public String get(final String name) { - if (names == null) { - return name; - } - final String actual = names.get(name); - // if null is not explicitly allowed, explicit null aka NULL - if (name == null && actual == null && !names.containsKey(null)) { - return JexlSandbox.NULL; - } - return actual; - } - - @Override - public String toString() { - return "allow{" + (names == null ? "all" : Objects.toString(names.entrySet())) + "}"; - } - } - - /** - * A block set of names. - */ - static class BlockSet extends Names { - /** - * The set of controlled names. - */ - private Set names; - - @Override - public boolean add(final String name) { - if (names == null) { - names = new HashSet<>(); - } - return names.add(name); - } - - @Override - protected Names copy() { - final BlockSet copy = new BlockSet(); - copy.names = names == null ? null : new HashSet<>(names); - return copy; - } - - @Override - public String get(final String name) { - // if name is null and contained in set, explicit null aka NULL - if (names != null && !names.contains(name)) { - return name; - } else if (name != null) { - return null; - } else { - return NULL; - } - } - - @Override - public String toString() { - return "block{" + (names == null ? "all" : Objects.toString(names)) + "}"; - } - } - - /** - * A base set of names. - */ - public abstract static class Names { - /** - * Adds a name to this set. - * - * @param name the name to add - * @return true if the name was really added, false if not - */ - public abstract boolean add(String name); - - /** - * Adds an alias to a name to this set. - *

This only has an effect on allow lists.

- * - * @param name the name to alias - * @param alias the alias - * @return true if the alias was added, false if it was already present - */ - public boolean alias(final String name, final String alias) { - return false; - } - - /** - * @return a copy of these Names - */ - protected Names copy() { - return this; - } - - /** - * Whether a given name is allowed or not. - * - * @param name the method/property name to check - * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise - */ - public String get(final String name) { - return name; - } - } - - /** - * Contains the allow or block lists for properties and methods for a given class. - */ - public static final class Permissions { - /** - * Whether these permissions are inheritable, ie can be used by derived classes. - */ - private final boolean inheritable; - /** - * The controlled readable properties. - */ - private final Names read; - /** - * The controlled writable properties. - */ - private final Names write; - /** - * The controlled methods. - */ - private final Names execute; - - /** - * Creates a new permissions instance. - * - * @param inherit whether these permissions are inheritable - * @param readFlag whether the read property list is allow or block - * @param writeFlag whether the write property list is allow or block - * @param executeFlag whether the method list is allow of block - */ - Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) { - this(inherit, - readFlag ? new AllowSet() : new BlockSet(), - writeFlag ? new AllowSet() : new BlockSet(), - executeFlag ? new AllowSet() : new BlockSet()); - } - - /** - * Creates a new permissions instance. - * - * @param inherit whether these permissions are inheritable - * @param nread the read set - * @param nwrite the write set - * @param nexecute the method set - */ - Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) { - this.read = nread != null ? nread : ALLOW_NAMES; - this.write = nwrite != null ? nwrite : ALLOW_NAMES; - this.execute = nexecute != null ? nexecute : ALLOW_NAMES; - this.inheritable = inherit; - } - - /** - * @return a copy of these permissions - */ - Permissions copy() { - return new Permissions(inheritable, read.copy(), write.copy(), execute.copy()); - } - - /** - * Gets the set of method names in these permissions. - * - * @return the set of method names - */ - public Names execute() { - return execute; - } - - /** - * Adds a list of executable methods names to these permissions. - *

The constructor is denoted as the empty-string, all other methods by their names.

- * - * @param methodNames the method names - * @return this instance of permissions - */ - public Permissions execute(final String... methodNames) { - for (final String methodName : methodNames) { - execute.add(methodName); - } - return this; - } - - /** - * @return whether these permissions apply to derived classes. - */ - public boolean isInheritable() { - return inheritable; - } - - /** - * Gets the set of readable property names in these permissions. - * - * @return the set of property names - */ - public Names read() { - return read; - } - - /** - * Adds a list of readable property names to these permissions. - * - * @param propertyNames the property names - * @return this instance of permissions - */ - public Permissions read(final String... propertyNames) { - for (final String propertyName : propertyNames) { - read.add(propertyName); - } - return this; - } - - /** - * Gets the set of writable property names in these permissions. - * - * @return the set of property names - */ - public Names write() { - return write; - } - - /** - * Adds a list of writable property names to these permissions. - * - * @param propertyNames the property names - * @return this instance of permissions - */ - public Permissions write(final String... propertyNames) { - for (final String propertyName : propertyNames) { - write.add(propertyName); - } - return this; - } - } + /** + * The marker string for explicitly disallowed null properties. + */ + public static final String NULL = "?"; + + /** + * The pass-thru name set. + */ + static final Names ALLOW_NAMES = new Names() { + @Override + public boolean add(final String name) { + return false; + } + + @Override + public String toString() { + return "allowAll"; + } + }; + + /** + * The block-all name set. + */ + private static final Names BLOCK_NAMES = new Names() { + @Override + public boolean add(final String name) { + return false; + } + + @Override + public String get(final String name) { + return name == null ? NULL : null; + } + + @Override + public String toString() { + return "blockAll"; + } + }; + + /** + * The block-all permissions. + */ + private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES); + /** + * The pass-thru permissions. + */ + private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES); + /** + * The map from class names to permissions. + */ + private final Map sandbox; + /** + * Whether permissions can be inherited (through implementation or extension). + */ + private final boolean inherit; + /** + * Default behavior, block or allow. + */ + private final boolean allow; + + /** + * Creates a new default sandbox. + *

In the absence of explicit permissions on a class, the + * sandbox is an allow-box, allow-listing that class for all permissions (read, write and execute). + */ + public JexlSandbox() { + this(true, false, null); + } + + /** + * Creates a new default sandbox. + *

A allow-box considers no permissions as "everything is allowed" when + * a block-box considers no permissions as "nothing is allowed". + * + * @param ab whether this sandbox is allow (true) or block (false) + * if no permission is explicitly defined for a class. + * @since 3.1 + */ + public JexlSandbox(final boolean ab) { + this(ab, false, null); + } + + /** + * Creates a sandbox. + * + * @param ab whether this sandbox is allow (true) or block (false) + * @param inh whether permissions on interfaces and classes are inherited (true) or not (false) + * @since 3.2 + */ + public JexlSandbox(final boolean ab, final boolean inh) { + this(ab, inh, null); + } + + /** + * Creates a sandbox based on an existing permissions map. + * + * @param ab whether this sandbox is allow (true) or block (false) + * @param inh whether permissions are inherited, default false + * @param map the permissions map + * @since 3.2 + */ + private JexlSandbox(final boolean ab, final boolean inh, final Map map) { + allow = ab; + inherit = inh; + sandbox = map != null ? map : new HashMap<>(); + } + + /** + * Gets a class by name, crude mechanism for backwards (<3.2 ) compatibility. + * + * @param cname the class name + * @return the class + */ + static Class forName(final String cname) { + try { + return Class.forName(cname); + } catch (final Exception xany) { + return null; + } + } + + /** + * Creates a new set of permissions based on allow lists for methods and properties for a given class. + *

The sandbox inheritance property will apply to the permissions created by this method + * + * @param clazz the allowed class name + * @return the permissions instance + */ + public Permissions allow(final String clazz) { + return permissions(clazz, true, true, true); + } + + /** + * Creates a new set of permissions based on block lists for methods and properties for a given class. + *

The sandbox inheritance property will apply to the permissions created by this method + * + * @param clazz the blocked class name + * @return the permissions instance + */ + public Permissions block(final String clazz) { + return permissions(clazz, false, false, false); + } + + /** + * @return a copy of this sandbox + */ + public JexlSandbox copy() { + // modified concurrently at runtime so... + final Map map = new ConcurrentHashMap<>(); + for (final Map.Entry entry : sandbox.entrySet()) { + map.put(entry.getKey(), entry.getValue().copy()); + } + return new JexlSandbox(allow, inherit, map); + } + + /** + * Gets the execute permission value for a given method of a class. + * + * @param clazz the class + * @param name the method name + * @return null if not allowed, the name of the method to use otherwise + */ + public String execute(final Class clazz, final String name) { + final String m = get(clazz).execute().get(name); + return "".equals(name) && m != null ? clazz.getName() : m; + } + + /** + * Gets the execute permission value for a given method of a class. + * + * @param clazz the class name + * @param name the method name + * @return null if not allowed, the name of the method to use otherwise + * @deprecated 3.3 + */ + @Deprecated + public String execute(final String clazz, final String name) { + final String m = get(clazz).execute().get(name); + return "".equals(name) && m != null ? clazz : m; + } + + /** + * Gets the permissions associated to a class. + * + * @param clazz the class + * @return the permissions + */ + @SuppressWarnings("null") + public Permissions get(final Class clazz) { + // argument clazz can not be null since permissions would be not null and block: + // we only store the result for classes we actively seek permissions for. + return clazz == null ? BLOCK_ALL : compute(clazz); + } + + /** + * Computes and optionally stores the permissions associated to a class. + * + * @param clazz the class + * @return the permissions + */ + private Permissions compute(final Class clazz) { + Permissions permissions = sandbox.get(clazz.getName()); + if (permissions == null) { + if (inherit) { + // find first inherited interface that defines permissions + Class[] interfaces = clazz.getInterfaces(); + for (int i = 0; permissions == null && i < interfaces.length; ++i) { + permissions = inheritable(sandbox.get(interfaces[i].getName())); + } + // nothing defined yet, find first superclass that defines permissions + if (permissions == null) { + // let's walk all super classes + for (Class zuper = clazz.getSuperclass(); + permissions == null && zuper != null; + zuper = zuper.getSuperclass()) { + permissions = inheritable(sandbox.get(zuper.getName())); + } + } + } + // nothing was determined through inheritance + if (permissions == null) { + permissions = allow ? ALLOW_ALL : BLOCK_ALL; + } + // store the info to avoid doing this costly look-up + sandbox.put(clazz.getName(), permissions); + } + return permissions; + } + + private Permissions inheritable(Permissions p) { + return p != null && p.isInheritable() ? p : null; + } + + /** + * Gets the set of permissions associated to a class. + * + * @param clazz the class name + * @return the defined permissions or an all-allow permission instance if none were defined + */ + public Permissions get(final String clazz) { + if (inherit) { + return get(forName(clazz)); + } + final Permissions permissions = sandbox.get(clazz); + if (permissions == null) { + return allow ? ALLOW_ALL : BLOCK_ALL; + } + return permissions; + } + + /** + * Creates the set of permissions for a given class. + *

The sandbox inheritance property will apply to the permissions created by this method + * + * @param clazz the class for which these permissions apply + * @param readFlag whether the readable property list is allow - true - or block - false - + * @param writeFlag whether the writable property list is allow - true - or block - false - + * @param executeFlag whether the executable method list is allow - true - or block - false - + * @return the set of permissions + */ + public Permissions permissions(final String clazz, + final boolean readFlag, + final boolean writeFlag, + final boolean executeFlag) { + return permissions(clazz, inherit, readFlag, writeFlag, executeFlag); + } + + /** + * Creates the set of permissions for a given class. + * + * @param clazz the class for which these permissions apply + * @param inhf whether these permissions are inheritable + * @param readf whether the readable property list is allow - true - or block - false - + * @param writef whether the writable property list is allow - true - or block - false - + * @param execf whether the executable method list is allow - true - or block - false - + * @return the set of permissions + */ + public Permissions permissions(final String clazz, + final boolean inhf, + final boolean readf, + final boolean writef, + final boolean execf) { + final Permissions box = new Permissions(inhf, readf, writef, execf); + sandbox.put(clazz, box); + return box; + } + + /** + * Gets the read permission value for a given property of a class. + * + * @param clazz the class + * @param name the property name + * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise + */ + public String read(final Class clazz, final String name) { + return get(clazz).read().get(name); + } + + /** + * Gets the read permission value for a given property of a class. + * + * @param clazz the class name + * @param name the property name + * @return null if not allowed, the name of the property to use otherwise + * @deprecated 3.3 + */ + @Deprecated + public String read(final String clazz, final String name) { + return get(clazz).read().get(name); + } + + /** + * Use allow() instead. + * + * @param clazz the allowed class name + * @return the permissions instance + * @deprecated 3.3 + */ + @Deprecated + public Permissions white(final String clazz) { + return allow(clazz); + } + + /** + * Gets the write permission value for a given property of a class. + * + * @param clazz the class + * @param name the property name + * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise + */ + public String write(final Class clazz, final String name) { + return get(clazz).write().get(name); + } + + /** + * Gets the write permission value for a given property of a class. + * + * @param clazz the class name + * @param name the property name + * @return null if not allowed, the name of the property to use otherwise + * @deprecated 3.3 + */ + @Deprecated + public String write(final String clazz, final String name) { + return get(clazz).write().get(name); + } + + /** + * An allow set of names. + */ + static class AllowSet extends Names { + /** + * The map of controlled names and aliases. + */ + private Map names; + + @Override + public boolean add(final String name) { + if (names == null) { + names = new HashMap<>(); + } + return names.put(name, name) == null; + } + + @Override + public boolean alias(final String name, final String alias) { + if (names == null) { + names = new HashMap<>(); + } + return names.put(alias, name) == null; + } + + @Override + protected Names copy() { + final AllowSet copy = new AllowSet(); + copy.names = names == null ? null : new HashMap<>(names); + return copy; + } + + @Override + public String get(final String name) { + if (names == null) { + return name; + } + final String actual = names.get(name); + // if null is not explicitly allowed, explicit null aka NULL + if (name == null && actual == null && !names.containsKey(null)) { + return JexlSandbox.NULL; + } + return actual; + } + + @Override + public String toString() { + return "allow{" + (names == null ? "all" : Objects.toString(names.entrySet())) + "}"; + } + } + + /** + * A block set of names. + */ + static class BlockSet extends Names { + /** + * The set of controlled names. + */ + private Set names; + + @Override + public boolean add(final String name) { + if (names == null) { + names = new HashSet<>(); + } + return names.add(name); + } + + @Override + protected Names copy() { + final BlockSet copy = new BlockSet(); + copy.names = names == null ? null : new HashSet<>(names); + return copy; + } + + @Override + public String get(final String name) { + // if name is null and contained in set, explicit null aka NULL + if (names != null && !names.contains(name)) { + return name; + } else if (name != null) { + return null; + } else { + return NULL; + } + } + + @Override + public String toString() { + return "block{" + (names == null ? "all" : Objects.toString(names)) + "}"; + } + } + + /** + * A base set of names. + */ + public abstract static class Names { + /** + * Adds a name to this set. + * + * @param name the name to add + * @return true if the name was really added, false if not + */ + public abstract boolean add(String name); + + /** + * Adds an alias to a name to this set. + *

This only has an effect on allow lists.

+ * + * @param name the name to alias + * @param alias the alias + * @return true if the alias was added, false if it was already present + */ + public boolean alias(final String name, final String alias) { + return false; + } + + /** + * @return a copy of these Names + */ + protected Names copy() { + return this; + } + + /** + * Whether a given name is allowed or not. + * + * @param name the method/property name to check + * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise + */ + public String get(final String name) { + return name; + } + } + + /** + * Contains the allow or block lists for properties and methods for a given class. + */ + public static final class Permissions { + /** + * Whether these permissions are inheritable, ie can be used by derived classes. + */ + private final boolean inheritable; + /** + * The controlled readable properties. + */ + private final Names read; + /** + * The controlled writable properties. + */ + private final Names write; + /** + * The controlled methods. + */ + private final Names execute; + + /** + * Creates a new permissions instance. + * + * @param inherit whether these permissions are inheritable + * @param readFlag whether the read property list is allow or block + * @param writeFlag whether the write property list is allow or block + * @param executeFlag whether the method list is allow of block + */ + Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) { + this(inherit, + readFlag ? new AllowSet() : new BlockSet(), + writeFlag ? new AllowSet() : new BlockSet(), + executeFlag ? new AllowSet() : new BlockSet()); + } + + /** + * Creates a new permissions instance. + * + * @param inherit whether these permissions are inheritable + * @param nread the read set + * @param nwrite the write set + * @param nexecute the method set + */ + Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) { + this.read = nread != null ? nread : ALLOW_NAMES; + this.write = nwrite != null ? nwrite : ALLOW_NAMES; + this.execute = nexecute != null ? nexecute : ALLOW_NAMES; + this.inheritable = inherit; + } + + /** + * @return a copy of these permissions + */ + Permissions copy() { + return new Permissions(inheritable, read.copy(), write.copy(), execute.copy()); + } + + /** + * Gets the set of method names in these permissions. + * + * @return the set of method names + */ + public Names execute() { + return execute; + } + + /** + * Adds a list of executable methods names to these permissions. + *

The constructor is denoted as the empty-string, all other methods by their names.

+ * + * @param methodNames the method names + * @return this instance of permissions + */ + public Permissions execute(final String... methodNames) { + for (final String methodName : methodNames) { + execute.add(methodName); + } + return this; + } + + /** + * @return whether these permissions apply to derived classes. + */ + public boolean isInheritable() { + return inheritable; + } + + /** + * Gets the set of readable property names in these permissions. + * + * @return the set of property names + */ + public Names read() { + return read; + } + + /** + * Adds a list of readable property names to these permissions. + * + * @param propertyNames the property names + * @return this instance of permissions + */ + public Permissions read(final String... propertyNames) { + for (final String propertyName : propertyNames) { + read.add(propertyName); + } + return this; + } + + /** + * Gets the set of writable property names in these permissions. + * + * @return the set of property names + */ + public Names write() { + return write; + } + + /** + * Adds a list of writable property names to these permissions. + * + * @param propertyNames the property names + * @return this instance of permissions + */ + public Permissions write(final String... propertyNames) { + for (final String propertyName : propertyNames) { + write.add(propertyName); + } + return this; + } + } }