    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jexl3.introspection;
    019
    020import java.util.HashMap;
    021import java.util.HashSet;
    022import java.util.Map;
    023import java.util.Set;
    024import java.util.concurrent.ConcurrentHashMap;
    025
    026/**
    027 * A sandbox describes permissions on a class by explicitly allowing or forbidding
    028 * access to methods and properties through "allowlists" and "blocklists".
    029 *
    030 * <p>A <b>allowlist</b> explicitly allows methods/properties for a class;</p>
    031 *
    032 * <ul>
    033 *   <li>If a allowlist is empty and thus does not contain any names,
    034 *       all properties/methods are allowed for its class.</li>
    035 *   <li>If it is not empty, the only allowed properties/methods are the ones contained.</li>
    036 * </ul>
    037 *
    038 * <p>A <b>blocklist</b> explicitly forbids methods/properties for a class;</p>
    039 *
    040 * <ul>
    041 *   <li>If a blocklist is empty and thus does not contain any names,
    042 *       all properties/methods are forbidden for its class.</li>
    043 *   <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li>
    044 * </ul>
    045 *
    046 * <p>Permissions are composed of three lists, read, write, execute, each being
    047 * "allow" or "block":</p>
    048 *
    049 * <ul>
    050 *   <li><b>read</b> controls readable properties </li>
    051 *   <li><b>write</b> controls writable properties</li>
    052 *   <li><b>execute</b> controls executable methods and constructor</li>
    053 * </ul>
    054 *
    055 * <p>When specified, permissions - allow or block lists - can be created inheritable
    056 * on interfaces or classes and thus applicable to their implementations or derived
    057 * classes; the sandbox must be created with the 'inheritable' flag for this behavior
    058 * to be triggered. Note that even in this configuration, it is still possible to
    059 * add non-inheritable permissions.
    060 * Adding inheritable lists to a non inheritable sandbox has no added effect;
    061 * permissions only apply to their specified class.</p>
    062 *
    063 * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox
    064 * used to built it preventing permission changes after its instantiation.</p>
    065 *
    066 * @since 3.0
    067 */
    068public final class JexlSandbox {
    069    /**
    070     * A allow set of names.
    071     */
    072    static class AllowSet extends Names {
    073        /** The map of controlled names and aliases. */
    074        private Map<String, String> names;
    075
    076        @Override
    077        public boolean add(final String name) {
    078            if (names == null) {
    079                names = new HashMap<>();
    080            }
    081            return names.put(name, name) == null;
    082        }
    083
    084        @Override
    085        public boolean alias(final String name, final String alias) {
    086            if (names == null) {
    087                names = new HashMap<>();
    088            }
    089            return names.put(alias, name) == null;
    090        }
    091
    092        @Override
    093        protected Names copy() {
    094            final AllowSet copy = new AllowSet();
    095            copy.names = names == null ? null : new HashMap<>(names);
    096            return copy;
    097        }
    098
    099        @Override
    100        public String get(final String name) {
    101            if (names == null) {
    102                return name;
    103            }
    104            final String actual = names.get(name);
    105            // if null is not explicitly allowed, explicit null aka NULL
    106            if (name == null && actual == null && !names.containsKey(null)) {
    107                return JexlSandbox.NULL;
    108            }
    109            return actual;
    110        }
    111    }
    112    /**
    113     * @deprecated since 3.2, use {@link BlockSet}
    114     */
    115    @Deprecated
    116    public static final class BlackSet extends BlockSet {}
    117    /**
    118     * A block set of names.
    119     */
    120    static class BlockSet extends Names {
    121        /** The set of controlled names. */
    122        private Set<String> names;
    123
    124        @Override
    125        public boolean add(final String name) {
    126            if (names == null) {
    127                names = new HashSet<>();
    128            }
    129            return names.add(name);
    130        }
    131
    132        @Override
    133        protected Names copy() {
    134            final BlockSet copy = new BlockSet();
    135            copy.names = names == null ? null : new HashSet<>(names);
    136            return copy;
    137        }
    138
    139        @Override
    140        public String get(final String name) {
    141            // if name is null and contained in set, explicit null aka NULL
    142            return names != null && !names.contains(name) ? name : name != null ? null : NULL;
    143        }
    144    }
    145    /**
    146     * A base set of names.
    147     */
    148    public abstract static class Names {
    149
    150        /**
    151         * Adds a name to this set.
    152         *
    153         * @param name the name to add
    154         * @return  true if the name was really added, false if not
    155         */
    156        public abstract boolean add(String name);
    157
    158        /**
    159         * Adds an alias to a name to this set.
    160         * <p>This only has an effect on allow lists.</p>
    161         *
    162         * @param name the name to alias
    163         * @param alias the alias
    164         * @return  true if the alias was added, false if it was already present
    165         */
    166        public boolean alias(final String name, final String alias) {
    167            return false;
    168        }
    169
    170        /**
    171         * @return a copy of these Names
    172         */
    173        protected Names copy() {
    174            return this;
    175        }
    176
    177        /**
    178         * Whether a given name is allowed or not.
    179         *
    180         * @param name the method/property name to check
    181         * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise
    182         */
    183        public String get(final String name) {
    184            return name;
    185        }
    186    }
    187
    188    /**
    189     * Contains the allow or block lists for properties and methods for a given class.
    190     */
    191    public static final class Permissions {
    192        /** Whether these permissions are inheritable, ie can be used by derived classes. */
    193        private final boolean inheritable;
    194        /** The controlled readable properties. */
    195        private final Names read;
    196        /** The controlled  writable properties. */
    197        private final Names write;
    198        /** The controlled methods. */
    199        private final Names execute;
    200
    201        /**
    202         * Creates a new permissions instance.
    203         *
    204         * @param inherit whether these permissions are inheritable
    205         * @param readFlag whether the read property list is allow or block
    206         * @param writeFlag whether the write property list is allow or block
    207         * @param executeFlag whether the method list is allow of block
    208         */
    209        Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) {
    210            this(inherit,
    211                    readFlag ? new AllowSet() : new BlockSet(),
    212                    writeFlag ? new AllowSet() : new BlockSet(),
    213                    executeFlag ? new AllowSet() : new BlockSet());
    214        }
    215
    216        /**
    217         * Creates a new permissions instance.
    218         *
    219         * @param inherit whether these permissions are inheritable
    220         * @param nread the read set
    221         * @param nwrite the write set
    222         * @param nexecute the method set
    223         */
    224        Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) {
    225            this.read = nread != null ? nread : ALLOW_NAMES;
    226            this.write = nwrite != null ? nwrite : ALLOW_NAMES;
    227            this.execute = nexecute != null ? nexecute : ALLOW_NAMES;
    228            this.inheritable = inherit;
    229        }
    230
    231        /**
    232         * @return a copy of these permissions
    233         */
    234        Permissions copy() {
    235            return new Permissions(inheritable, read.copy(), write.copy(), execute.copy());
    236        }
    237
    238        /**
    239         * Gets the set of method names in these permissions.
    240         *
    241         * @return the set of method names
    242         */
    243        public Names execute() {
    244            return execute;
    245        }
    246
    247        /**
    248         * Adds a list of executable methods names to these permissions.
    249         * <p>The constructor is denoted as the empty-string, all other methods by their names.</p>
    250         *
    251         * @param methodNames the method names
    252         * @return this instance of permissions
    253         */
    254        public Permissions execute(final String... methodNames) {
    255            for (final String methodName : methodNames) {
    256                execute.add(methodName);
    257            }
    258            return this;
    259        }
    260
    261        /**
    262         * @return whether these permissions applies to derived classes.
    263         */
    264        public boolean isInheritable() {
    265            return inheritable;
    266        }
    267
    268        /**
    269         * Gets the set of readable property names in these permissions.
    270         *
    271         * @return the set of property names
    272         */
    273        public Names read() {
    274            return read;
    275        }
    276
    277        /**
    278         * Adds a list of readable property names to these permissions.
    279         *
    280         * @param propertyNames the property names
    281         * @return this instance of permissions
    282         */
    283        public Permissions read(final String... propertyNames) {
    284            for (final String propertyName : propertyNames) {
    285                read.add(propertyName);
    286            }
    287            return this;
    288        }
    289
    290        /**
    291         * Gets the set of writable property names in these permissions.
    292         *
    293         * @return the set of property names
    294         */
    295        public Names write() {
    296            return write;
    297        }
    298
    299        /**
    300         * Adds a list of writable property names to these permissions.
    301         *
    302         * @param propertyNames the property names
    303         * @return this instance of permissions
    304         */
    305        public Permissions write(final String... propertyNames) {
    306            for (final String propertyName : propertyNames) {
    307                write.add(propertyName);
    308            }
    309            return this;
    310        }
    311    }
    312
    313    /**
    314     * @deprecated since 3.2, use {@link AllowSet}
    315     */
    316    @Deprecated
    317    public static final class WhiteSet extends AllowSet {}
    318
    319    /**
    320     * The marker string for explicitly disallowed null properties.
    321     */
    322    public static final String NULL = "?";
    323
    324    /**
    325     * The pass-thru name set.
    326     */
    327    static final Names ALLOW_NAMES = new Names() {
    328        @Override
    329        public boolean add(final String name) {
    330            return false;
    331        }
    332
    333        @Override
    334        protected Names copy() {
    335            return this;
    336        }
    337    };
    338
    339    /**
    340     * The block-all name set.
    341     */
    342    private static final Names BLOCK_NAMES = new Names() {
    343        @Override
    344        public boolean add(final String name) {
    345            return false;
    346        }
    347
    348        @Override
    349        protected Names copy() {
    350            return this;
    351        }
    352
    353        @Override
    354        public String get(final String name) {
    355            return name == null ? NULL : null;
    356        }
    357    };
    358
    359    /**
    360     * The pass-thru permissions.
    361     */
    362    private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES);
    363
    364    /**
    365     * The block-all permissions.
    366     */
    367    private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES);
    368
    369    /**
    370     * Gets a class by name, crude mechanism for backwards (&lt;3.2 ) compatibility.
    371     * @param cname the class name
    372     * @return the class
    373     */
    374    static Class<?> forName(final String cname) {
    375        try {
    376            return Class.forName(cname);
    377        } catch (final Exception xany) {
    378            return null;
    379        }
    380    }
    381
    382    /**
    383     * The map from class names to permissions.
    384     */
    385    private final Map<String, Permissions> sandbox;
    386
    387    /**
    388     * Whether permissions can be inherited (through implementation or extension).
    389     */
    390    private final boolean inherit;
    391
    392    /**
    393     * Default behavior, block or allow.
    394     */
    395    private final boolean allow;
    396
    397    /**
    398     * Creates a new default sandbox.
    399     * <p>In the absence of explicit permissions on a class, the
    400     * sandbox is a allow-box, allow-listing that class for all permissions (read, write and execute).
    401     */
    402    public JexlSandbox() {
    403        this(true, false, null);
    404    }
    405
    406    /**
    407     * Creates a new default sandbox.
    408     * <p>A allow-box considers no permissions as &quot;everything is allowed&quot; when
    409     * a block-box considers no permissions as &quot;nothing is allowed&quot;.
    410     * @param ab whether this sandbox is allow (true) or block (false)
    411     * if no permission is explicitly defined for a class.
    412     * @since 3.1
    413     */
    414    public JexlSandbox(final boolean ab) {
    415        this(ab, false, null);
    416    }
    417
    418    /**
    419     * Creates a sandbox.
    420     * @param ab whether this sandbox is allow (true) or block (false)
    421     * @param inh whether permissions on interfaces and classes are inherited (true) or not (false)
    422     * @since 3.2
    423     */
    424    public JexlSandbox(final boolean ab, final boolean inh) {
    425        this(ab, inh, null);
    426    }
    427
    428    /**
    429     * Creates a sandbox based on an existing permissions map.
    430     * @param ab whether this sandbox is allow (true) or block (false)
    431     * @param inh whether permissions are inherited, default false
    432     * @param map the permissions map
    433     * @since 3.2
    434     */
    435    protected JexlSandbox(final boolean ab, final boolean inh, final Map<String, Permissions> map) {
    436        allow = ab;
    437        inherit = inh;
    438        sandbox = map != null ? map : new HashMap<>();
    439    }
    440
    441    /**
    442     * Creates a sandbox based on an existing permissions map.
    443     * @param ab whether this sandbox is allow (true) or block (false)
    444     * @param map the permissions map
    445     * @since 3.1
    446     */
    447    @Deprecated
    448    protected JexlSandbox(final boolean ab, final Map<String, Permissions> map) {
    449        this(ab, false, map);
    450    }
    451
    452    /**
    453     * Creates a sandbox based on an existing permissions map.
    454     * @param map the permissions map
    455     */
    456    @Deprecated
    457    protected JexlSandbox(final Map<String, Permissions> map) {
    458        this(true, false, map);
    459    }
    460
    461    /**
    462     * Creates a new set of permissions based on allow lists for methods and properties for a given class.
    463     * <p>The sandbox inheritance property will apply to the permissions created by this method
    464     *
    465     * @param clazz the allowed class name
    466     * @return the permissions instance
    467     */
    468    public Permissions allow(final String clazz) {
    469        return permissions(clazz, true, true, true);
    470    }
    471
    472    /**
    473     * Use block() instead.
    474     * @param clazz the allowed class name
    475     * @return the permissions instance
    476     */
    477    @Deprecated
    478    public Permissions black(final String clazz) {
    479        return block(clazz);
    480    }
    481
    482    /**
    483     * Creates a new set of permissions based on block lists for methods and properties for a given class.
    484     * <p>The sandbox inheritance property will apply to the permissions created by this method
    485     *
    486     * @param clazz the blocked class name
    487     * @return the permissions instance
    488     */
    489    public Permissions block(final String clazz) {
    490        return permissions(clazz, false, false, false);
    491    }
    492
    493    /**
    494     * @return a copy of this sandbox
    495     */
    496    public JexlSandbox copy() {
    497        // modified concurrently at runtime so...
    498        final Map<String, Permissions> map = new ConcurrentHashMap<>();
    499        for (final Map.Entry<String, Permissions> entry : sandbox.entrySet()) {
    500            map.put(entry.getKey(), entry.getValue().copy());
    501        }
    502        return new JexlSandbox(allow, inherit, map);
    503    }
    504
    505    /**
    506     * Gets the execute permission value for a given method of a class.
    507     *
    508     * @param clazz the class
    509     * @param name the method name
    510     * @return null if not allowed, the name of the method to use otherwise
    511     */
    512    public String execute(final Class<?> clazz, final String name) {
    513        final String m = get(clazz).execute().get(name);
    514        return "".equals(name) && m != null ? clazz.getName() : m;
    515    }
    516
    517    /**
    518     * Gets the execute permission value for a given method of a class.
    519     *
    520     * @param clazz the class name
    521     * @param name the method name
    522     * @return null if not allowed, the name of the method to use otherwise
    523     */
    524    @Deprecated
    525    public String execute(final String clazz, final String name) {
    526        final String m = get(clazz).execute().get(name);
    527        return "".equals(name) && m != null ? clazz : m;
    528    }
    529    /**
    530     * Gets the permissions associated to a class.
    531     * @param clazz the class
    532     * @return the permissions
    533     */
    534    @SuppressWarnings("null") // clazz can not be null since permissions would be not null and block;
    535    public Permissions get(final Class<?> clazz) {
    536        Permissions permissions = clazz == null ? BLOCK_ALL : sandbox.get(clazz.getName());
    537        if (permissions == null) {
    538            if (inherit) {
    539                // find first inherited interface that defines permissions
    540                for (final Class<?> inter : clazz.getInterfaces()) {
    541                    permissions = sandbox.get(inter.getName());
    542                    if (permissions != null) {
    543                        if (permissions.isInheritable()) {
    544                            break;
    545                        }
    546                        permissions = null;
    547                    }
    548                }
    549                // nothing defined yet, find first superclass that defines permissions
    550                if (permissions == null) {
    551                    // lets walk all super classes
    552                    Class<?> zuper = clazz.getSuperclass();
    553                    // walk all superclasses
    554                    while (zuper != null) {
    555                        permissions = sandbox.get(zuper.getName());
    556                        if (permissions != null) {
    557                            if (permissions.isInheritable()) {
    558                                break;
    559                            }
    560                            permissions = null;
    561                        }
    562                        zuper = zuper.getSuperclass();
    563                    }
    564                }
    565                // nothing was inheritable
    566                if (permissions == null) {
    567                    permissions = allow ? ALLOW_ALL : BLOCK_ALL;
    568                }
    569                // store the info to avoid doing this costly look up
    570                sandbox.put(clazz.getName(), permissions);
    571            } else {
    572                permissions = allow ? ALLOW_ALL : BLOCK_ALL;
    573            }
    574        }
    575        return permissions;
    576    }
    577
    578    /**
    579     * Gets the set of permissions associated to a class.
    580     *
    581     * @param clazz the class name
    582     * @return the defined permissions or an all-allow permission instance if none were defined
    583     */
    584    public Permissions get(final String clazz) {
    585        if (inherit) {
    586            return get(forName(clazz));
    587        }
    588        final Permissions permissions = sandbox.get(clazz);
    589        if (permissions == null) {
    590            return allow ? ALLOW_ALL : BLOCK_ALL;
    591        }
    592        return permissions;
    593    }
    594
    595    /**
    596     * Creates the set of permissions for a given class.
    597     * <p>The sandbox inheritance property will apply to the permissions created by this method
    598     *
    599     * @param clazz the class for which these permissions apply
    600     * @param readFlag whether the readable property list is allow - true - or block - false -
    601     * @param writeFlag whether the writable property list is allow - true - or block - false -
    602     * @param executeFlag whether the executable method list is allow - true - or block - false -
    603     * @return the set of permissions
    604     */
    605    public Permissions permissions(final String clazz,
    606                                   final boolean readFlag,
    607                                   final boolean writeFlag,
    608                                   final boolean executeFlag) {
    609        return permissions(clazz, inherit, readFlag, writeFlag, executeFlag);
    610    }
    611
    612    /**
    613     * Creates the set of permissions for a given class.
    614     *
    615     * @param clazz the class for which these permissions apply
    616     * @param inhf whether these permissions are inheritable
    617     * @param readf whether the readable property list is allow - true - or block - false -
    618     * @param writef whether the writable property list is allow - true - or block - false -
    619     * @param execf whether the executable method list is allow - true - or block - false -
    620     * @return the set of permissions
    621     */
    622    public Permissions permissions(final String clazz,
    623                                   final boolean inhf,
    624                                   final boolean readf,
    625                                   final boolean writef,
    626                                   final boolean execf) {
    627        final Permissions box = new Permissions(inhf, readf, writef, execf);
    628        sandbox.put(clazz, box);
    629        return box;
    630    }
    631    /**
    632     * Gets the read permission value for a given property of a class.
    633     *
    634     * @param clazz the class
    635     * @param name the property name
    636     * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise
    637     */
    638    public String read(final Class<?> clazz, final String name) {
    639        return get(clazz).read().get(name);
    640    }
    641
    642    /**
    643     * Gets the read permission value for a given property of a class.
    644     *
    645     * @param clazz the class name
    646     * @param name the property name
    647     * @return null if not allowed, the name of the property to use otherwise
    648     */
    649    @Deprecated
    650    public String read(final String clazz, final String name) {
    651        return get(clazz).read().get(name);
    652    }
    653
    654    /**
    655     * Use allow() instead.
    656     * @param clazz the allowed class name
    657     * @return the permissions instance
    658     */
    659    @Deprecated
    660    public Permissions white(final String clazz) {
    661        return allow(clazz);
    662    }
    663
    664    /**
    665     * Gets the write permission value for a given property of a class.
    666     *
    667     * @param clazz the class
    668     * @param name the property name
    669     * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise
    670     */
    671    public String write(final Class<?> clazz, final String name) {
    672        return get(clazz).write().get(name);
    673    }
    674
    675    /**
    676     * Gets the write permission value for a given property of a class.
    677     *
    678     * @param clazz the class name
    679     * @param name the property name
    680     * @return null if not allowed, the name of the property to use otherwise
    681     */
    682    @Deprecated
    683    public String write(final String clazz, final String name) {
    684        return get(clazz).write().get(name);
    685    }
    686
    687}