    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    package org.apache.commons.jexl2.introspection;
    018
    019    import java.util.HashMap;
    020    import java.util.HashSet;
    021    import java.util.Map;
    022    import java.util.Set;
    023
    024    /**
    025     * A sandbox describes permissions on a class by explicitly allowing or forbidding access to methods and properties
    026     * through "whitelists" and "blacklists".
    027     * <p>
    028     * A <b>whitelist</b> explicitly allows methods/properties for a class;
    029     * <ul>
    030     * <li>
    031     * If a whitelist is empty and thus does not contain any names, all properties/methods are allowed for its class.
    032     * </li>
    033     * <li>
    034     * If it is not empty, the only allowed properties/methods are the ones contained.
    035     * </li>
    036     * </ul>
    037     * </p>
    038     * <p>
    039     * A <b>blacklist</b> explicitly forbids methods/properties for a class;
    040     * <ul>
    041     * <li>
    042     * If a blacklist is empty and thus does not contain any names, all properties/methods are forbidden for its class.
    043     * </li>
    044     * <li>
    045     * If it is not empty, the only forbidden properties/methods are the ones contained.
    046     * </li>
    047     * </ul>
    048     * <p>
    049     * Permissions are composed of three lists, read, write, execute, each being "white" or "black":
    050     * <ul>
    051     * <li><b>read</b> controls readable properties </li>
    052     * <li><b>write</b> controls writeable properties</li>
    053     * <li><b>execute</b> controls executable methods and constructor</li>
    054     * </ul>
    055     * </p>
    056     * @since 2.1
    057     */
    058    public final class Sandbox {
    059        /**
    060         * The map from class names to permissions.
    061         */
    062        private final Map<String, Permissions> sandbox;
    063
    064        /**
    065         * Creates a new default sandbox.
    066         */
    067        public Sandbox() {
    068            this(new HashMap<String, Permissions>());
    069        }
    070
    071        /**
    072         * Creates a sandbox based on an existing permissions map.
    073         * @param map the permissions map
    074         */
    075        protected Sandbox(Map<String, Permissions> map) {
    076            sandbox = map;
    077        }
    078
    079        /**
    080         * Gets the read permission value for a given property of a class.
    081         * @param clazz the class
    082         * @param name the property name
    083         * @return null if not allowed, the name of the property to use otherwise
    084         */
    085        public String read(Class<?> clazz, String name) {
    086            return read(clazz.getName(), name);
    087        }
    088
    089        /**
    090         * Gets the read permission value for a given property of a class.
    091         * @param clazz the class name
    092         * @param name the property name
    093         * @return null if not allowed, the name of the property to use otherwise
    094         */
    095        public String read(String clazz, String name) {
    096            Permissions permissions = sandbox.get(clazz);
    097            if (permissions == null) {
    098                return name;
    099            } else {
    100                return permissions.read().get(name);
    101            }
    102        }
    103
    104        /**
    105         * Gets the write permission value for a given property of a class.
    106         * @param clazz the class
    107         * @param name the property name
    108         * @return null if not allowed, the name of the property to use otherwise
    109         */
    110        public String write(Class<?> clazz, String name) {
    111            return write(clazz.getName(), name);
    112        }
    113
    114        /**
    115         * Gets the write permission value for a given property of a class.
    116         * @param clazz the class name
    117         * @param name the property name
    118         * @return null if not allowed, the name of the property to use otherwise
    119         */
    120        public String write(String clazz, String name) {
    121            Permissions permissions = sandbox.get(clazz);
    122            if (permissions == null) {
    123                return name;
    124            } else {
    125                return permissions.write().get(name);
    126            }
    127        }
    128
    129        /**
    130         * Gets the execute permission value for a given method of a class.
    131         * @param clazz the class
    132         * @param name the method name
    133         * @return null if not allowed, the name of the method to use otherwise
    134         */
    135        public String execute(Class<?> clazz, String name) {
    136            return execute(clazz.getName(), name);
    137        }
    138
    139        /**
    140         * Gets the execute permission value for a given method of a class.
    141         * @param clazz the class name
    142         * @param name the method name
    143         * @return null if not allowed, the name of the method to use otherwise
    144         */
    145        public String execute(String clazz, String name) {
    146            Permissions permissions = sandbox.get(clazz);
    147            if (permissions == null) {
    148                return name;
    149            } else {
    150                return permissions.execute().get(name);
    151            }
    152        }
    153
    154        /**
    155         * A base set of names.
    156         */
    157        public abstract static class Names {
    158            /**
    159             * Adds a name to this set.
    160             * @param name the name to add
    161             * @return  true if the name was really added, false if not
    162             */
    163            public abstract boolean add(String name);
    164
    165            /**
    166             * Adds an alias to a name to this set.
    167             * <p>This only has an effect on white lists.</p>
    168             * @param name the name to alias
    169             * @param alias the alias
    170             * @return  true if the alias was added, false if it was already present
    171             */
    172            public boolean alias(String name, String alias) {
    173                return false;
    174            }
    175
    176            /**
    177             * Whether a given name is allowed or not.
    178             * @param name the method/property name to check
    179             * @return null if not allowed, the actual name to use otherwise
    180             */
    181            public String get(String name) {
    182                return name;
    183            }
    184        }
    185        /**
    186         * The pass-thru name set.
    187         */
    188        private static final Names WHITE_NAMES = new Names() {
    189            @Override
    190            public boolean add(String name) {
    191                return false;
    192            }
    193        };
    194
    195        /**
    196         * A white set of names.
    197         */
    198        public static final class WhiteSet extends Names {
    199            /** The map of controlled names and aliases. */
    200            private Map<String, String> names = null;
    201
    202            @Override
    203            public boolean add(String name) {
    204                if (names == null) {
    205                    names = new HashMap<String, String>();
    206                }
    207                return names.put(name, name) == null;
    208            }
    209
    210            @Override
    211            public boolean alias(String name, String alias) {
    212                if (names == null) {
    213                    names = new HashMap<String, String>();
    214                }
    215                return names.put(alias, name) == null;
    216            }
    217
    218            @Override
    219            public String get(String name) {
    220                if (names == null) {
    221                    return name;
    222                } else {
    223                    return names.get(name);
    224                }
    225            }
    226        }
    227
    228        /**
    229         * A black set of names.
    230         */
    231        public static final class BlackSet extends Names {
    232            /** The set of controlled names. */
    233            private Set<String> names = null;
    234
    235            @Override
    236            public boolean add(String name) {
    237                if (names == null) {
    238                    names = new HashSet<String>();
    239                }
    240                return names.add(name);
    241            }
    242
    243            @Override
    244            public String get(String name) {
    245                return names != null && !names.contains(name) ? name : null;
    246            }
    247        }
    248
    249        /**
    250         * Contains the white or black lists for properties and methods for a given class.
    251         */
    252        public static final class Permissions {
    253            /** The controlled readable properties. */
    254            private final Names read;
    255            /** The controlled  writeable properties. */
    256            private final Names write;
    257            /** The controlled methods. */
    258            private final Names execute;
    259
    260            /**
    261             * Creates a new permissions instance.
    262             * @param readFlag whether the read property list is white or black
    263             * @param writeFlag whether the write property list is white or black
    264             * @param executeFlag whether the method list is white of black
    265             */
    266            Permissions(boolean readFlag, boolean writeFlag, boolean executeFlag) {
    267                this(readFlag ? new WhiteSet() : new BlackSet(),
    268                        writeFlag ? new WhiteSet() : new BlackSet(),
    269                        executeFlag ? new WhiteSet() : new BlackSet());
    270            }
    271
    272            /**
    273             * Creates a new permissions instance.
    274             * @param nread the read set
    275             * @param nwrite the write set
    276             * @param nexecute the method set
    277             */
    278            Permissions(Names nread, Names nwrite, Names nexecute) {
    279                this.read = nread != null ? nread : WHITE_NAMES;
    280                this.write = nwrite != null ? nwrite : WHITE_NAMES;
    281                this.execute = nexecute != null ? nexecute : WHITE_NAMES;
    282            }
    283
    284            /**
    285             * Adds a list of readable property names to these permissions.
    286             * @param pnames the property names
    287             * @return this instance of permissions
    288             */
    289            public Permissions read(String... pnames) {
    290                for (String pname : pnames) {
    291                    read.add(pname);
    292                }
    293                return this;
    294            }
    295
    296            /**
    297             * Adds a list of writeable property names to these permissions.
    298             * @param pnames the property names
    299             * @return this instance of permissions
    300             */
    301            public Permissions write(String... pnames) {
    302                for (String pname : pnames) {
    303                    write.add(pname);
    304                }
    305                return this;
    306            }
    307
    308            /**
    309             * Adds a list of executable methods names to these permissions.
    310             * <p>The constructor is denoted as the empty-string, all other methods by their names.</p>
    311             * @param mnames the method names
    312             * @return this instance of permissions
    313             */
    314            public Permissions execute(String... mnames) {
    315                for (String mname : mnames) {
    316                    execute.add(mname);
    317                }
    318                return this;
    319            }
    320
    321            /**
    322             * Gets the set of readable property names in these permissions.
    323             * @return the set of property names
    324             */
    325            public Names read() {
    326                return read;
    327            }
    328
    329            /**
    330             * Gets the set of writeable property names in these permissions.
    331             * @return the set of property names
    332             */
    333            public Names write() {
    334                return write;
    335            }
    336
    337            /**
    338             * Gets the set of method names in these permissions.
    339             * @return the set of method names
    340             */
    341            public Names execute() {
    342                return execute;
    343            }
    344        }
    345
    346        /**
    347         * The pass-thru permissions.
    348         */
    349        private static final Permissions ALL_WHITE = new Permissions(WHITE_NAMES, WHITE_NAMES, WHITE_NAMES);
    350
    351        /**
    352         * Creates the set of permissions for a given class.
    353         * @param clazz the class for which these permissions apply
    354         * @param readFlag whether the readable property list is white - true - or black - false -
    355         * @param writeFlag whether the writeable property list is white - true - or black - false -
    356         * @param executeFlag whether the executable method list is white white - true - or black - false -
    357         * @return the set of permissions
    358         */
    359        public Permissions permissions(String clazz, boolean readFlag, boolean writeFlag, boolean executeFlag) {
    360            Permissions box = new Permissions(readFlag, writeFlag, executeFlag);
    361            sandbox.put(clazz, box);
    362            return box;
    363        }
    364
    365        /**
    366         * Creates a new set of permissions based on white lists for methods and properties for a given class.
    367         * @param clazz the whitened class name
    368         * @return the permissions instance
    369         */
    370        public Permissions white(String clazz) {
    371            return permissions(clazz, true, true, true);
    372        }
    373
    374        /**
    375         * Creates a new set of permissions based on black lists for methods and properties for a given class.
    376         * @param clazz the blackened class name
    377         * @return the permissions instance
    378         */
    379        public Permissions black(String clazz) {
    380            return permissions(clazz, false, false, false);
    381        }
    382
    383        /**
    384         * Gets the set of permissions associated to a class.
    385         * @param clazz the class name
    386         * @return the defined permissions or an all-white permission instance if none were defined
    387         */
    388        public Permissions get(String clazz) {
    389            Permissions permissions = sandbox.get(clazz);
    390            if (permissions == null) {
    391                return ALL_WHITE;
    392            } else {
    393                return permissions;
    394            }
    395        }
    396    }