| /* |
| * 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.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". |
| * |
| * <p>A <b>allowlist</b> explicitly allows methods/properties for a class;</p> |
| * |
| * <ul> |
| * <li>If a allowlist is empty and thus does not contain any names, |
| * all properties/methods are allowed for its class.</li> |
| * <li>If it is not empty, the only allowed properties/methods are the ones contained.</li> |
| * </ul> |
| * |
| * <p>A <b>blocklist</b> explicitly forbids methods/properties for a class;</p> |
| * |
| * <ul> |
| * <li>If a blocklist is empty and thus does not contain any names, |
| * all properties/methods are forbidden for its class.</li> |
| * <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li> |
| * </ul> |
| * |
| * <p>Permissions are composed of three lists, read, write, execute, each being |
| * "allow" or "block":</p> |
| * |
| * <ul> |
| * <li><b>read</b> controls readable properties </li> |
| * <li><b>write</b> controls writable properties</li> |
| * <li><b>execute</b> controls executable methods and constructor</li> |
| * </ul> |
| * |
| * <p>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.</p> |
| * |
| * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox |
| * used to built it preventing permission changes after its instantiation.</p> |
| * |
| * @since 3.0 |
| */ |
| public final class JexlSandbox { |
| /** |
| * The map from class names to permissions. |
| */ |
| private final Map<String, Permissions> 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. |
| * <p>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. |
| * <p>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 map the permissions map |
| */ |
| @Deprecated |
| protected JexlSandbox(final Map<String, Permissions> map) { |
| this(true, false, map); |
| } |
| |
| /** |
| * 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<String, Permissions> map) { |
| this(ab, false, map); |
| } |
| |
| /** |
| * 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<String, Permissions> map) { |
| allow = ab; |
| inherit = inh; |
| sandbox = map != null? map : new HashMap<>(); |
| } |
| |
| /** |
| * @return a copy of this sandbox |
| */ |
| public JexlSandbox copy() { |
| // modified concurently at runtime so... |
| final Map<String, Permissions> map = new ConcurrentHashMap<>(); |
| for (final Map.Entry<String, Permissions> entry : sandbox.entrySet()) { |
| map.put(entry.getKey(), entry.getValue().copy()); |
| } |
| return new JexlSandbox(allow, inherit, map); |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * Gets the read permission value for a given property of a class. |
| * |
| * @param clazz the class |
| * @param name the property name |
| * @return 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); |
| } |
| |
| /** |
| * Gets the write permission value for a given property of a class. |
| * |
| * @param clazz the class |
| * @param name the property name |
| * @return 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); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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. |
| * <p>This only has an effect on allow lists.</p> |
| * |
| * @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; |
| } |
| |
| /** |
| * Whether a given name is allowed or not. |
| * |
| * @param name the method/property name to check |
| * @return null if not allowed, the actual name to use otherwise |
| */ |
| public String get(final String name) { |
| return name; |
| } |
| |
| /** |
| * @return a copy of these Names |
| */ |
| protected Names copy() { |
| return this; |
| } |
| } |
| |
| /** |
| * The pass-thru name set. |
| */ |
| private static final Names ALLOW_NAMES = new Names() { |
| @Override |
| public boolean add(final String name) { |
| return false; |
| } |
| |
| @Override |
| protected Names copy() { |
| return this; |
| } |
| }; |
| |
| /** |
| * The block-all name set. |
| */ |
| private static final Names BLOCK_NAMES = new Names() { |
| @Override |
| public boolean add(final String name) { |
| return false; |
| } |
| |
| @Override |
| protected Names copy() { |
| return this; |
| } |
| |
| @Override |
| public String get(final String name) { |
| return null; |
| } |
| }; |
| |
| /** |
| * A allow set of names. |
| */ |
| static class AllowSet extends Names { |
| /** The map of controlled names and aliases. */ |
| private Map<String, String> names = null; |
| |
| @Override |
| protected Names copy() { |
| final AllowSet copy = new AllowSet(); |
| copy.names = names == null ? null : new HashMap<>(names); |
| return copy; |
| } |
| |
| @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 |
| public String get(final String name) { |
| return names == null ? name : names.get(name); |
| } |
| } |
| |
| /** |
| * A block set of names. |
| */ |
| static class BlockSet extends Names { |
| /** The set of controlled names. */ |
| private Set<String> names = null; |
| |
| @Override |
| protected Names copy() { |
| final BlockSet copy = new BlockSet(); |
| copy.names = names == null ? null : new HashSet<>(names); |
| return copy; |
| } |
| |
| @Override |
| public boolean add(final String name) { |
| if (names == null) { |
| names = new HashSet<>(); |
| } |
| return names.add(name); |
| } |
| |
| @Override |
| public String get(final String name) { |
| return names != null && !names.contains(name) ? name : null; |
| } |
| } |
| |
| /** |
| * Unused. |
| */ |
| @Deprecated |
| public static final class WhiteSet extends AllowSet {} |
| |
| /** |
| * Unused. |
| */ |
| @Deprecated |
| public static final class BlackSet extends BlockSet {} |
| |
| /** |
| * 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()); |
| } |
| |
| /** |
| * @return whether these permissions applies to derived classes. |
| */ |
| public boolean isInheritable() { |
| return inheritable; |
| } |
| |
| /** |
| * Adds a list of readable property names to these permissions. |
| * |
| * @param pnames the property names |
| * @return this instance of permissions |
| */ |
| public Permissions read(final String... pnames) { |
| for (final String pname : pnames) { |
| read.add(pname); |
| } |
| return this; |
| } |
| |
| /** |
| * Adds a list of writable property names to these permissions. |
| * |
| * @param pnames the property names |
| * @return this instance of permissions |
| */ |
| public Permissions write(final String... pnames) { |
| for (final String pname : pnames) { |
| write.add(pname); |
| } |
| return this; |
| } |
| |
| /** |
| * Adds a list of executable methods names to these permissions. |
| * <p>The constructor is denoted as the empty-string, all other methods by their names.</p> |
| * |
| * @param mnames the method names |
| * @return this instance of permissions |
| */ |
| public Permissions execute(final String... mnames) { |
| for (final String mname : mnames) { |
| execute.add(mname); |
| } |
| return this; |
| } |
| |
| /** |
| * Gets the set of readable property names in these permissions. |
| * |
| * @return the set of property names |
| */ |
| public Names read() { |
| return read; |
| } |
| |
| /** |
| * Gets the set of writable property names in these permissions. |
| * |
| * @return the set of property names |
| */ |
| public Names write() { |
| return write; |
| } |
| |
| /** |
| * Gets the set of method names in these permissions. |
| * |
| * @return the set of method names |
| */ |
| public Names execute() { |
| return execute; |
| } |
| } |
| |
| /** |
| * 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); |
| |
| /** |
| * Creates the set of permissions for a given class. |
| * <p>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 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 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; |
| } |
| |
| /** |
| * Creates a new set of permissions based on allow lists for methods and properties for a given class. |
| * <p>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 allow() instead. |
| * @param clazz the allowed class name |
| * @return the permissions instance |
| */ |
| @Deprecated |
| public Permissions white(final String clazz) { |
| return allow(clazz); |
| } |
| |
| /** |
| * Creates a new set of permissions based on block lists for methods and properties for a given class. |
| * <p>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); |
| } |
| |
| /** |
| * Use block() instead. |
| * @param clazz the allowed class name |
| * @return the permissions instance |
| */ |
| @Deprecated |
| public Permissions black(final String clazz) { |
| return block(clazz); |
| } |
| |
| /** |
| * 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; |
| } else { |
| return permissions; |
| } |
| } |
| |
| /** |
| * Get 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) { |
| Permissions permissions = clazz == null ? BLOCK_ALL : sandbox.get(clazz.getName()); |
| if (permissions == null) { |
| if (inherit) { |
| // find first inherited interface that defines permissions |
| for (final Class<?> inter : clazz.getInterfaces()) { |
| permissions = sandbox.get(inter.getName()); |
| if (permissions != null && permissions.isInheritable()) { |
| break; |
| } |
| } |
| // nothing defined yet, find first superclass that defines permissions |
| if (permissions == null) { |
| // lets walk all super classes |
| Class<?> zuper = clazz.getSuperclass(); |
| // walk all superclasses |
| while (zuper != null) { |
| permissions = sandbox.get(zuper.getName()); |
| if (permissions != null && permissions.isInheritable()) { |
| break; |
| } |
| 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 |
| sandbox.put(clazz.getName(), permissions); |
| } else { |
| permissions = allow ? ALLOW_ALL : BLOCK_ALL; |
| } |
| } |
| return permissions; |
| } |
| |
| } |