All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.jexl3.introspection.JexlSandbox Maven / Gradle / Ivy

Go to download

The Apache Commons JEXL library is an implementation of the JSTL Expression Language with extensions.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.jexl3.introspection;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A sandbox describes permissions on a class by explicitly allowing or forbidding
 * access to methods and properties through "allowlists" and "blocklists".
 *
 * 

A allowlist explicitly allows methods/properties for a class;

* *
    *
  • If a allowlist is empty and thus does not contain any names, * all properties/methods are allowed for its class.
  • *
  • If it is not empty, the only allowed properties/methods are the ones contained.
  • *
* *

A blocklist explicitly forbids methods/properties for a class;

* *
    *
  • If a blocklist is empty and thus does not contain any names, * all properties/methods are forbidden for its class.
  • *
  • If it is not empty, the only forbidden properties/methods are the ones contained.
  • *
* *

Permissions are composed of three lists, read, write, execute, each being * "allow" or "block":

* *
    *
  • read controls readable properties
  • *
  • write controls writable properties
  • *
  • execute controls executable methods and constructor
  • *
* *

When specified, permissions - allow or block lists - can be created inheritable * on interfaces or classes and thus applicable to their implementations or derived * classes; the sandbox must be created with the 'inheritable' flag for this behavior * to be triggered. Note that even in this configuration, it is still possible to * add non-inheritable permissions. * Adding inheritable lists to a non inheritable sandbox has no added effect; * permissions only apply to their specified class.

* *

Note that a JexlUberspect always uses a copy of the JexlSandbox * used to built it preventing permission changes after its instantiation.

* * @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 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); } /** * Gets a copy of this sandbox * @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 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) { return get(forName(clazz)); } /** * Gets the permissions associated to a class. * * @param clazz the class * @return the permissions */ @SuppressWarnings("null") public Permissions get(final Class clazz) { // argument clazz cannot be null since permissions would be not null and block: // we only store the result for classes we actively seek permissions for. return compute(clazz, true); } private static Permissions inheritable(final Permissions p) { return p != null && p.isInheritable() ? p : null; } /** * Computes and optionally stores the permissions associated to a class. * * @param clazz the class * @param store whether the computed permissions should be stored in the sandbox * @return the permissions */ private Permissions compute(final Class clazz, final boolean store) { // belt and suspender; recursion should not lead here if (clazz == null) { return BLOCK_ALL; } final String className = clazz.getName(); Permissions permissions = sandbox.get(className); if (permissions == null) { if (inherit) { // find first inherited interface that defines permissions final Class[] interfaces = clazz.getInterfaces(); for (int i = 0; permissions == null && i < interfaces.length; ++i) { permissions = inheritable(compute(interfaces[i], false)); } // nothing defined yet, find first superclass that defines permissions if (permissions == null) { // let's recurse on super classes final Class superClazz = clazz.getSuperclass(); if (Object.class != superClazz) { permissions = inheritable(compute(superClazz, false)); } } } // 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(className, permissions); } } 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 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; } if (name != null) { return null; } return NULL; } @Override public String toString() { return "block{" + (names == null ? "all" : Objects.toString(names)) + "}"; } } /** * A base set of names. */ public abstract static class Names { /** Default constructor */ public Names() {} // Keep Javadoc happy /** * 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; } /** * Gets a copy of these Names * @return a copy of these Names */ protected Names copy() { return this; } /** * Gets 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; } /** * Do these permissions apply to derived classes? * @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 BlockSet} */ @Deprecated public static final class BlackSet extends BlockSet { /** Default constructor */ public BlackSet() { } // Keep Javadoc happy } /** * @deprecated since 3.2, use {@link AllowSet} */ @Deprecated public static final class WhiteSet extends AllowSet { /** Default constructor */ public WhiteSet() { } // Keep Javadoc happy } /** * Use block() instead. * * @param clazz the blocked class name * @return the permissions instance * @deprecated 3.3 */ @Deprecated public Permissions black(final String clazz) { return block(clazz); } /** * 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); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy