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