Source code
Description: source: package: org.apache.commons.jexl3.introspection, interface: JexlPermissions, class: Delegate

    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 *      https://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 */
    017package org.apache.commons.jexl3.introspection;
    018
    019import java.lang.reflect.Constructor;
    020import java.lang.reflect.Field;
    021import java.lang.reflect.Method;
    022import java.lang.reflect.Modifier;
    023import java.util.Arrays;
    024import java.util.Collection;
    025import java.util.HashSet;
    026import java.util.Objects;
    027import java.util.Set;
    028import java.util.concurrent.ConcurrentHashMap;
    029import java.util.stream.Collectors;
    030
    031import org.apache.commons.jexl3.internal.introspection.PermissionsParser;
    032import org.apache.commons.logging.Log;
    033import org.apache.commons.logging.LogFactory;
    034
    035/**
    036 * This interface describes permissions used by JEXL introspection that constrain which
    037 * packages/classes/constructors/fields/methods are made visible to JEXL scripts.
    038 * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated
    039 * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control
    040 * over what can be executed. JEXL introspection mechanism will check whether it is permitted to
    041 * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions
    042 * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}.
    043 * </p>
    044 * <p><strong>Security disclaimer.</strong> Neither {@link #RESTRICTED} nor {@link #SECURE} is exhaustive, and neither
    045 * must be considered completely safe or sufficient on its own for executing untrusted user input. They are hardened
    046 * baselines, not guarantees. Any application that evaluates untrusted scripts <em>must</em> define its own tailored,
    047 * strict whitelist of exactly the classes, methods and fields its scripts legitimately need - ideally by composing on
    048 * top of {@link #NONE} (which denies everything) via {@link #create(String...)} / {@link #compose(String...)} - and
    049 * audit the result with {@link #logging()}.</p>
    050 * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated -
    051 * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl}
    052 * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments;
    053 * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be
    054 * processed.</p>
    055 * <p>A simple textual configuration can be used to create user-defined permissions using
    056 * {@link JexlPermissions#parse(String...)}. The permission syntax supports both positive (+) and negative (-)
    057 * declarations:</p>
    058 * <ul>
    059 * <li><b>Negative restrictions ({@code -})</b>: By default or when prefixed with {@code -}, class restrictions
    060 * explicitly <b>deny</b> access to the specified members (or the entire class if the block is empty).
    061 * This is the default mode and works like {@link org.apache.commons.jexl3.annotations.NoJexl}.</li>
    062 * <li><b>Positive restrictions ({@code +})</b>: When prefixed with {@code +}, class restrictions
    063 * explicitly <b>allow only</b> the specified members (or the entire class if the block is empty), denying
    064 * all others. This provides a whitelist approach where you must explicitly list what is permitted.</li>
    065 * </ul>
    066 * <p>For example:</p>
    067 * <pre>
    068 * // Deny specific methods in a class (negative restriction - default)
    069 * java.lang { System { exit(); } }  // or -System { exit(); }
    070 *
    071 * // Allow only specific methods in a class (positive restriction)
    072 * java.lang { +System { currentTimeMillis(); nanoTime(); } }
    073 *
    074 * // Allow entire class (positive restriction with empty block)
    075 * java.io -{ +PrintWriter{} +Writer{} }
    076 * </pre>
    077 *
    078 * <p>To build a policy from scratch, start from {@link #NONE} (or {@link #create(String...)}), which denies
    079 * everything, and compose only what scripts need on top - the closed-world, deny-by-default approach. This is the
    080 * opposite of {@link #UNRESTRICTED} (the empty {@link #parse(String...)}), which allows everything.</p>
    081 *
    082 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
    083 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
    084 * be to instantiate a {@link JexlUberspect} with those permissions and call
    085 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
    086 *
    087 * <p>
    088 *     To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior
    089 *     by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with
    090 *     {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance.
    091 * </p>
    092 * <p>
    093 *     For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to
    094 *     JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)}
    095 *     with {@link #UNRESTRICTED} as parameter.
    096 * </p>
    097 *
    098 * @since 3.3
    099 */
    100public interface JexlPermissions {
    101
    102    /**
    103     * A permission delegation that augments the RESTRICTED permission with an explicit
    104     * set of classes.
    105     * <p>A typical use case is to deny access to a package - and thus all its classes - but allow
    106     * a few specific classes.</p>
    107     * <p>Note that the newer positive restriction syntax is preferable as in:
    108     * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
    109     */
    110    final class ClassPermissions extends JexlPermissions.Delegate {
    111      /**
    112       * The set of explicitly allowed classes, overriding the delegate permissions.
    113       */
    114      private final Set<String> allowedClasses;
    115
    116      /**
    117       * Creates permissions based on the RESTRICTED set but allowing an explicit set.
    118       *
    119       * @param allow the set of allowed classes
    120       */
    121      public ClassPermissions(final Class<?>... allow) {
    122        this(JexlPermissions.RESTRICTED, allow);
    123      }
    124
    125      /**
    126       * Creates permissions by augmenting an existing set with an explicit set of allowed classes.
    127       * @param permissions the base permissions to augment
    128       * @param allow the set of allowed classes
    129       */
    130      public ClassPermissions(final JexlPermissions permissions, final Class<?>... allow) {
    131        this(permissions, Arrays.stream(Objects.requireNonNull(allow)).map(Class::getCanonicalName).collect(Collectors.toList()));
    132      }
    133
    134      /**
    135       * Creates permissions by augmenting an existing set with an explicit set of allowed canonical class names.
    136       *
    137       * @param delegate the base to delegate to
    138       * @param allow    the list of class canonical names
    139       */
    140      public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) {
    141        super(Objects.requireNonNull(delegate));
    142        allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
    143      }
    144
    145      @Override
    146      public boolean allow(final Constructor<?> constructor) {
    147        return validate(constructor) &&
    148            (allowedClasses.contains(constructor.getDeclaringClass().getCanonicalName()) || super.allow(constructor));
    149      }
    150
    151      @Override
    152      public boolean allow(final Class<?> clazz) {
    153        return validate(clazz) &&
    154            (allowedClasses.contains(clazz.getCanonicalName()) || super.allow(clazz));
    155      }
    156
    157      @Override
    158      public boolean allow(final Class<?> clazz, final Field field) {
    159        if (!validate(field)) {
    160          return false;
    161        }
    162        if (!validate(clazz)) {
    163          return false;
    164        }
    165        if (!field.getDeclaringClass().isAssignableFrom(clazz)) {
    166          return false;
    167        }
    168        if (super.allow(clazz, field)) {
    169          return true;
    170        }
    171        return isClassAllowed(clazz);
    172      }
    173
    174      @Override
    175      public boolean allow(final Class<?> clazz, final Method method) {
    176        if (!validate(method)) {
    177          return false;
    178        }
    179        if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
    180          return false;
    181        }
    182        if (super.allow(clazz, method)) {
    183          return true;
    184        }
    185        return isClassAllowed(clazz);
    186      }
    187
    188      @Override
    189      public JexlPermissions compose(final String... src) {
    190        return new ClassPermissions(base.compose(src), allowedClasses);
    191      }
    192
    193      private boolean isClassAllowed(final Class<?> aClass) {
    194        Class<?> clazz = aClass;
    195        // let's walk all interfaces
    196        for (final Class<?> inter : clazz.getInterfaces()) {
    197          if (allowedClasses.contains(inter.getCanonicalName())) {
    198            return true;
    199          }
    200        }
    201        // let's walk all super classes
    202        while (clazz != null) {
    203          if (allowedClasses.contains(clazz.getCanonicalName())) {
    204            return true;
    205          }
    206          clazz = clazz.getSuperclass();
    207        }
    208        return false;
    209      }
    210    }
    211
    212    /**
    213     * A base for permission delegation allowing functional refinement.
    214     * Overloads should call the appropriate validate() method early in their body.
    215     */
    216    class Delegate implements JexlPermissions {
    217        /**
    218         * The permissions we delegate to.
    219         */
    220        protected final JexlPermissions base;
    221
    222        /**
    223         * Constructs a new instance.
    224         *
    225         * @param delegate the delegate.
    226         */
    227        protected Delegate(final JexlPermissions delegate) {
    228            base = delegate;
    229        }
    230
    231        @Override
    232        public boolean allow(final Class<?> clazz) {
    233            return base.allow(clazz);
    234        }
    235
    236        @Override
    237        public boolean allow(final Constructor<?> ctor) {
    238            return base.allow(ctor);
    239        }
    240
    241        @Override
    242        public boolean allow(final Field field) {
    243            return validate(field) && allow(field.getDeclaringClass(), field);
    244        }
    245
    246        @Override
    247        public boolean allow(final Class<?> clazz, final Field field) {
    248            return base.allow(clazz, field);
    249        }
    250
    251        @Override
    252        public boolean allow(final Method method) {
    253            return validate(method) && allow(method.getDeclaringClass(), method);
    254        }
    255
    256        @Override
    257        public boolean allow(final Class<?> clazz, final Method method) {
    258            return base.allow(clazz, method);
    259        }
    260
    261        @Override
    262        public boolean allow(final Package pack) {
    263            return base.allow(pack);
    264        }
    265
    266        @Override
    267        public JexlPermissions compose(final String... src) {
    268            return new Delegate(base.compose(src));
    269        }
    270    }
    271
    272    /**
    273     * A permission delegate that logs every allow/deny decision.
    274     * <p>This is a debugging aid to determine which reflective elements (classes, constructors, methods, fields)
    275     * a permission set allows or denies; wrap any permissions with {@link JexlPermissions#logging()} (or
    276     * {@link JexlPermissions#logging(String)} to pick the logger name) and inspect the log to diagnose why a
    277     * given object is or is not reachable from scripts.</p>
    278     *
    279     * @since 3.7.0
    280     */
    281    class LoggingPermissions extends Delegate {
    282        /** The logger that decisions are written to (at info level). */
    283        private final Log logger;
    284        /** The set of already-emitted log lines, so each decision is logged only once. */
    285        private final Set<String> logged = ConcurrentHashMap.newKeySet();
    286
    287        /**
    288         * Constructs an instance logging to a logger named after this class.
    289         *
    290         * @param delegate the permissions to delegate to
    291         */
    292        public LoggingPermissions(final JexlPermissions delegate) {
    293            this(LogFactory.getLog(LoggingPermissions.class), delegate);
    294        }
    295
    296        /**
    297         * Constructs an instance logging to a named logger.
    298         *
    299         * @param loggerName the name of the logger to use
    300         * @param delegate the permissions to delegate to
    301         */
    302        public LoggingPermissions(final String loggerName, final JexlPermissions delegate) {
    303            this(LogFactory.getLog(loggerName), delegate);
    304        }
    305
    306        /**
    307         * Constructs an instance with an explicit logger.
    308         *
    309         * @param log the logger
    310         * @param delegate the permissions to delegate to
    311         */
    312        protected LoggingPermissions(final Log log, final JexlPermissions delegate) {
    313            super(delegate);
    314            this.logger = log;
    315        }
    316
    317        /**
    318         * Logs a decision once: the first time a given message is seen, it is written to the logger;
    319         * subsequent identical messages are suppressed.
    320         *
    321         * @param allowed the decision to return
    322         * @param message the message to log
    323         * @return the decision
    324         */
    325        private boolean log(final boolean allowed, final String message) {
    326            if (logged.add(message)) {
    327                logger.info(message);
    328            }
    329            return allowed;
    330        }
    331
    332        @Override
    333        public boolean allow(final Class<?> clazz) {
    334            final boolean allowed = super.allow(clazz);
    335            return log(allowed, String.format("Class %s is %s",
    336                clazz.getCanonicalName(), allowed ? "allowed" : "denied"));
    337        }
    338
    339        @Override
    340        public boolean allow(final Constructor<?> ctor) {
    341            final boolean allowed = super.allow(ctor);
    342            return log(allowed, String.format("Constructor %s.%s() is %s",
    343                ctor.getDeclaringClass().getCanonicalName(), ctor.getName(),
    344                allowed ? "allowed" : "denied"));
    345        }
    346
    347        @Override
    348        public boolean allow(final Field field) {
    349            final boolean allowed = super.allow(field);
    350            return log(allowed, String.format("Field %s.%s is %s",
    351                field.getDeclaringClass().getCanonicalName(), field.getName(),
    352                allowed ? "allowed" : "denied"));
    353        }
    354
    355        @Override
    356        public boolean allow(final Class<?> clazz, final Field field) {
    357            final boolean allowed = super.allow(clazz, field);
    358            return log(allowed, String.format("Field %s.%s is %s for class %s",
    359                field.getDeclaringClass().getCanonicalName(), field.getName(),
    360                allowed ? "allowed" : "denied", clazz.getCanonicalName()));
    361        }
    362
    363        @Override
    364        public boolean allow(final Method method) {
    365            final boolean allowed = super.allow(method);
    366            return log(allowed, String.format("Method %s.%s() is %s",
    367                method.getDeclaringClass().getCanonicalName(), method.getName(),
    368                allowed ? "allowed" : "denied"));
    369        }
    370
    371        @Override
    372        public boolean allow(final Class<?> clazz, final Method method) {
    373            final boolean allowed = super.allow(clazz, method);
    374            return log(allowed, String.format("Method %s.%s() is %s for class %s",
    375                method.getDeclaringClass().getCanonicalName(), method.getName(),
    376                allowed ? "allowed" : "denied", clazz.getCanonicalName()));
    377        }
    378
    379        @Override
    380        public JexlPermissions compose(final String... src) {
    381            return new LoggingPermissions(logger, base.compose(src));
    382        }
    383    }
    384
    385    /**
    386     * The unrestricted permissions.
    387     * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p>
    388     * <p>It is <em>highly</em> discouraged to use this permissions outside of testing.</p>
    389     * @since 3.3
    390     */
    391    JexlPermissions UNRESTRICTED = JexlPermissions.parse();
    392
    393    /**
    394     * A permission set that denies everything: the empty base to build permissions from scratch.
    395     * <p>Unlike {@link #UNRESTRICTED} (the empty {@link #parse(String...)}, which allows everything), NONE allows
    396     * nothing. Compose positive declarations on top to grant access, for example:</p>
    397     * <pre>JexlPermissions.NONE.compose("java.lang { +String{} }")</pre>
    398     * <p>or use the {@link #create(String...)} factory. This is the recommended starting point when you want
    399     * a closed-world, deny-by-default policy listing only what your scripts actually need.</p>
    400     * @since 3.7.0
    401     */
    402    JexlPermissions NONE = new JexlPermissions() {
    403        @Override public boolean allow(final Package pack)        { return false; }
    404        @Override public boolean allow(final Class<?> clazz)      { return false; }
    405        @Override public boolean allow(final Constructor<?> ctor) { return false; }
    406        @Override public boolean allow(final Field field)         { return false; }
    407        @Override public boolean allow(final Method method)       { return false; }
    408        @Override public JexlPermissions compose(final String... src) {
    409            // NONE has no state to merge; composing rules builds a closed-world set from scratch
    410            return src == null || src.length == 0 ? this : JexlPermissions.parse(src);
    411        }
    412    };
    413
    414    /**
    415     * A restricted singleton.
    416     * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p>
    417     * <p>
    418     * RESTRICTED attempts to strike a balance between reasonable out-of-the-box isolation and allowing most
    419     * legitimate features; it is convenient when scripts need a broad slice of the JDK. In a mission-critical
    420     * scenario, prefer {@link #SECURE} as a base instead and {@link #compose(String...) compose} only what your
    421     * scripts actually need on top of it. Be aware that the isolation RESTRICTED provides may be incomplete and
    422     * could expose more than intended; should such a case be identified, we will endeavour to resolve it in a
    423     * subsequent release. Use {@link #logging()} to audit exactly which elements your workload reaches.
    424     * </p>
    425     * <p>RESTRICTED is not exhaustive and must not be considered sufficient on its own for executing untrusted user
    426     * input. For untrusted scripts, define a tailored, strict whitelist of exactly what your scripts need - ideally
    427     * composed on top of {@link #NONE} - rather than relying on RESTRICTED as-is.</p>
    428     * <p>Of particular importance are the restrictions on the {@link System},
    429     * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net},
    430     * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts
    431     * and its host.
    432     * </p>
    433     * <p>
    434     * Every allowed package is declared explicitly using the positive {@code +{}} syntax rather than a
    435     * {@code .*} wildcard. A wildcard matches a package <em>and all of its sub-packages</em>, which is not
    436     * future-proof: a sub-package added by a later JDK (or a dangerous existing one such as
    437     * {@code java.util.zip}/{@code java.util.jar} - which can read files - or {@code java.nio.file}) would be
    438     * silently exposed. Listing each package explicitly keeps the perimeter closed: only the packages below are
    439     * visible, nothing else.
    440     * </p>
    441     * <p>Allowed packages (each member is visible unless explicitly denied):</p>
    442     * <ul>
    443     * <li>java.math</li>
    444     * <li>java.text</li>
    445     * <li>java.time, java.time.chrono, java.time.format, java.time.temporal, java.time.zone</li>
    446     * <li>java.util, java.util.concurrent, java.util.concurrent.atomic, java.util.function, java.util.stream, java.util.regex</li>
    447     * <li>java.nio, java.nio.charset</li>
    448     * <li>org.w3c.dom</li>
    449     * <li>java.lang (minus the denied classes below)</li>
    450     * <li>org.apache.commons.jexl3 (minus JexlBuilder)</li>
    451     * </ul>
    452     * <p>Denied classes / members (carved out of otherwise-allowed packages):</p>
    453     * <ul>
    454     * <li>java.lang { Runtime, System, ProcessBuilder, Process, RuntimePermission, SecurityManager, Thread, ThreadGroup, Class, ClassLoader }
    455     * and the system-property readers Integer.getInteger, Long.getLong, Boolean.getBoolean</li>
    456     * <li>java.io { everything except PrintWriter, Writer, StringWriter, Reader, InputStream, OutputStream }</li>
    457     * <li>java.util: the classes stay visible but their file/loader members are carved out -
    458     * Formatter and Scanner constructors (file I/O), Properties.load/store/loadFromXML/storeToXML/save (file I/O),
    459     * ResourceBundle.getBundle/clearCache and PropertyResourceBundle constructors (property-file/class loading),
    460     * ServiceLoader.load/loadInstalled (service/class loading). No file can be read or written and no class or
    461     * service loaded through java.util.</li>
    462     * <li>java.util.concurrent { Executors and the thread-pool / fork-join executor classes }</li>
    463     * <li>java.time.zone { ZoneRulesProvider } (prevents JVM-wide time-zone provider registration)</li>
    464     * <li>org.apache.commons.jexl3 { JexlBuilder }</li>
    465     * </ul>
    466     * <p>Notably absent (and therefore denied) are file/IO/persistence/loader-bearing packages such as
    467     * {@code java.util.zip}, {@code java.util.jar}, {@code java.util.prefs}, {@code java.util.logging},
    468     * {@code java.util.concurrent.locks}, {@code java.nio.file}, {@code java.lang.reflect},
    469     * {@code java.lang.invoke} and {@code org.w3c.dom.ls}.</p>
    470     * <p>A class is visible only when its <em>own</em> package or class declaration permits it; it is never made
    471     * visible merely because one of its super-types is allowed. Consequently a foreign implementation of an allowed
    472     * type (for instance a {@code java.util.Map} provided by another library) is not visible unless its own package
    473     * is explicitly allowed, e.g. {@code RESTRICTED.compose("com.example.foreign +{}")}. Use {@link #logging()} to
    474     * diagnose which elements are allowed or denied.</p>
    475     */
    476
    477    JexlPermissions RESTRICTED = JexlPermissions.parse(
    478        "# Default Uberspect Permissions",
    479        "java.math +{}",
    480        "java.text +{}",
    481        "java.time +{}",
    482        "java.time.chrono +{}",
    483        "java.time.format +{}",
    484        "java.time.temporal +{}",
    485        "java.time.zone +{ -ZoneRulesProvider{} }",
    486        "java.util +{" +
    487            " -Formatter { Formatter(); }" +
    488            " -Scanner { Scanner(); }" +
    489            " -Properties { load(); store(); loadFromXML(); storeToXML(); save(); }" +
    490            " -ResourceBundle { getBundle(); clearCache(); }" +
    491            " -PropertyResourceBundle { PropertyResourceBundle(); }" +
    492            " -ServiceLoader { load(); loadInstalled(); }" +
    493            " }",
    494        "java.util.concurrent +{" +
    495            "-Executors{} -ExecutorService{} -AbstractExecutorService{}" +
    496            "-ThreadPoolExecutor{} -ScheduledThreadPoolExecutor{} -ScheduledExecutorService{}" +
    497            "-ForkJoinPool{} -ForkJoinTask{} -ForkJoinWorkerThread{}" +
    498            "}",
    499        "java.util.concurrent.atomic +{}",
    500        "java.util.function +{}",
    501        "java.util.stream +{}",
    502        "java.util.regex +{}",
    503        "org.w3c.dom +{}",
    504        "java.lang +{" +
    505            "-Runtime{} -System{} -ProcessBuilder{} -Process{}" +
    506            "-RuntimePermission{} -SecurityManager{}" +
    507            "-Thread{} -ThreadGroup{} -Class{} -ClassLoader{}" +
    508            "-Integer { getInteger(); } -Long { getLong(); } -Boolean { getBoolean(); }" +
    509            "}",
    510        "java.io -{ +PrintWriter{ -PrintWriter(); } +Writer{} +StringWriter{} +Reader{} +InputStream{} +OutputStream{} }",
    511        "java.nio +{}",
    512        "java.nio.charset +{}",
    513        "org.apache.commons.jexl3 +{ -JexlBuilder{} -JexlConfigLoader{} }"
    514    );
    515
    516    /**
    517     * An absolute-minimum, allow-list-first permission set.
    518     * <p>This is the tightest sensible baseline: nothing is reachable unless explicitly whitelisted here.
    519     * It exposes only the safe {@code java.lang} value types, {@code java.math} big numbers and the
    520     * {@code java.util} collection types - enough for arithmetic, string and collection scripting.</p>
    521     * <p>Allowed:</p>
    522     * <ul>
    523     * <li>{@code java.lang}: {@code Object} (minus {@code getClass}/{@code wait}/{@code notify}/{@code notifyAll}),
    524     * {@code Number} and the boxed primitives, {@code String}, {@code CharSequence}, {@code StringBuilder},
    525     * {@code Math}, {@code Comparable}, {@code Iterable}; everything else in {@code java.lang}
    526     * (e.g. {@code System}, {@code Runtime}, {@code Thread}, {@code Class}, {@code ClassLoader}) is denied.</li>
    527     * <li>{@code java.math} (for {@code BigInteger}/{@code BigDecimal}, i.e. the {@code 1B}/{@code 1H} literals).</li>
    528     * <li>{@code java.util} - the collection types produced by list/map/set literals (and their iterators, views
    529     * and entries), <em>minus</em> the file/loader/thread-bearing classes which are denied: {@code Formatter} and
    530     * {@code Scanner} (file I/O), {@code ServiceLoader} and the {@code ResourceBundle} family (class/resource
    531     * loading), {@code Properties} (file {@code load}/{@code store}) and {@code Timer}/{@code TimerTask} (threads).
    532     * Because a positive package does not cover sub-packages, {@code java.util.zip}/{@code concurrent}/{@code jar}/…
    533     * stay denied as well.</li>
    534     * </ul>
    535     * <p><strong>Guarantee:</strong> no class that SECURE allows <em>by default</em> can read or write files, read
    536     * environment variables or system properties, load classes, or start threads. In particular {@code Object.getClass()}
    537     * is denied (so no {@link Class} can be obtained), and the boxed-type system-property readers
    538     * {@code Integer.getInteger}, {@code Long.getLong} and {@code Boolean.getBoolean} are denied.</p>
    539     * <p>SECURE is nonetheless a hardened baseline, <em>not</em> a turnkey sandbox: it is not exhaustive and must not be
    540     * considered sufficient on its own for executing untrusted user input. For that, define a tailored, strict whitelist
    541     * of exactly what your scripts need - ideally composed on top of {@link #NONE} - rather than relying on SECURE as-is.</p>
    542     * <p>Arithmetic, comparisons and string concatenation require no permission at all (they are handled by
    543     * {@link org.apache.commons.jexl3.JexlArithmetic}); ranges ({@code 1..n}) iterate as a language primitive.
    544     * Compose more in with {@link #compose(String...)} (e.g. {@code SECURE.compose("java.time +{}")}), and use
    545     * {@link #logging()} to discover what a script is denied.</p>
    546     * @since 3.7.0
    547     */
    548    JexlPermissions SECURE = JexlPermissions.parse(
    549        "# Absolute-minimum permissions: safe java.lang value types + java.math + java.util collections",
    550        "java.lang -{"
    551            + " +Object{ -getClass(); -wait(); -notify(); -notifyAll(); }"
    552            + " +Number{} +Boolean{ -getBoolean(); } "
    553            + " +Character{} +Byte{} +Short{} +Integer{ -getInteger(); } +Long{ -getLong(); } +Float{} +Double{}"
    554            + " +String{} +CharSequence{} +StringBuilder{} +Math{} +Comparable{} +Iterable{}"
    555            + " }",
    556        "java.math +{}",
    557        "java.util +{"
    558            + " -Formatter{} -Scanner{} -ServiceLoader{}"
    559            + " -ResourceBundle{} -PropertyResourceBundle{} -ListResourceBundle{}"
    560            + " -Properties{} -Timer{} -TimerTask{}"
    561            + " }"
    562    );
    563
    564    /**
    565     * Parses a set of permissions.
    566     * <p>
    567     * In JEXL 3.3, the syntax recognizes 2 types of permissions:
    568     * </p>
    569     * <ul>
    570     * <li>Allowing access to a wildcard restricted set of packages. </li>
    571     * <li>Denying access to packages, classes (and inner classes), methods and fields</li>
    572     * </ul>
    573     * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be
    574     * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards.
    575     * These allow composing the volume of what is allowed by addition.</p>
    576     * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation;
    577     * they can restrict access to package, class, inner-class, methods and fields.
    578     *  These allow refining the volume of what is allowed by extrusion.</p>
    579     *  An example of a tight environment that would not allow scripts to wander could be:
    580     *  <pre>
    581     *  # allow a very restricted set of base classes
    582     *  java.math.*
    583     *  java.text.*
    584     *  java.util.*
    585     *  # deny classes that could pose a security risk
    586     *  java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
    587     *  org.apache.commons.jexl3 { JexlBuilder {} }
    588     *  </pre>
    589     *  <p><b>Syntax Overview:</b></p>
    590     *  <ul>
    591     *  <li>Syntax for wildcards is the name of the package suffixed by {@code .*}.</li>
    592     *  <li>Syntax for restrictions is a list of package restrictions.</li>
    593     *  <li>A package restriction is a package name followed by a block (as in curly-bracket block {})
    594     *  that contains a list of class restrictions.</li>
    595     *  <li>A class restriction is a class name prefixed by an optional {@code -} or {@code +} sign
    596     *  followed by a block of member restrictions.</li>
    597     *  <li>A member restriction can be a class restriction - to restrict
    598     *  nested classes -, a field which is the Java field name suffixed with {@code ;}, a method composed of
    599     *  its Java name suffixed with {@code ();}. Constructor restrictions are specified like methods using the
    600     *  class name as method name.</li>
    601     *  </ul>
    602     *  <p><b>Negative ({@code -}) vs Positive ({@code +}) Restrictions:</b></p>
    603     *  <ul>
    604     *  <li><b>Negative restriction (default or {@code -} prefix)</b>: Explicitly <b>denies</b> access to the members
    605     *  declared in its block. If the block is empty, the entire class is denied.
    606     *  <br>Example: {@code java.lang { -System { exit(); } }} denies System.exit() but allows other System methods.
    607     *  <br>Example: {@code java.lang { Runtime {} }} denies the entire Runtime class (empty block means deny all).</li>
    608     *  <li><b>Positive restriction ({@code +} prefix)</b>: Explicitly <b>allows only</b> the members declared
    609     *  in its block, denying all others not listed. If the block is empty, the entire class is allowed.
    610     *  <br>Example: {@code java.lang { +System { currentTimeMillis(); } }} allows only System.currentTimeMillis(),
    611     *  denying all other System methods.
    612     *  <br>Example: {@code java.io -{ +PrintWriter{} +Writer{} }} in the context of a denied java.io package,
    613     *  allows only PrintWriter and Writer classes entirely (empty blocks mean allow all members).</li>
    614     *  </ul>
    615     *  <p>
    616     *  All overrides and overloads of constructors or methods are allowed or restricted at the same time,
    617     *  the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation.
    618     *  </p>
    619     *  <p><b>Complete Example:</b></p>
    620     *  <pre>
    621     *  # some wildcards
    622     *  java.util.* # java.util is pretty much a must-have
    623     *  my.allowed.package0.*
    624     *  another.allowed.package1.*
    625     *  # nojexl like restrictions
    626     *  my.package.internal {} # the whole package is hidden
    627     *  my.package {
    628     *   +class4 { theMethod(); } # POSITIVE: only theMethod can be called in class4, all others denied
    629     *   class0 {
    630     *     class1 {} # NEGATIVE (default): the whole class1 is hidden
    631     *     class2 {
    632     *         class2(); # class2 constructors cannot be invoked
    633     *         class3 {
    634     *             aMethod(); # aMethod cannot be called
    635     *             aField; # aField cannot be accessed
    636     *         }
    637     *     } # end of class2
    638     *     class0(); # class0 constructors cannot be invoked
    639     *     method(); # method cannot be called
    640     *     field; # field cannot be accessed
    641     *   } # end class0
    642     * } # end package my.package
    643     * </pre>
    644     *
    645     * @param src the permissions source, the default (NoJexl aware) permissions if null
    646     * @return the permissions instance
    647     * @since 3.3
    648     */
    649    static JexlPermissions parse(final String... src) {
    650        return new PermissionsParser().parse(src);
    651    }
    652
    653    /**
    654     * Creates a permission set from scratch: everything is denied unless a rule explicitly allows it.
    655     * <p>Equivalent to composing the rules onto {@link #NONE}. Use positive declarations - for instance
    656     * {@code "java.lang { +String{} }"} or {@code "java.util.*"} - to grant access; {@code create()} with no
    657     * rules denies everything. This differs from {@link #parse(String...)}, whose empty form
    658     * ({@link #UNRESTRICTED}) allows everything.</p>
    659     *
    660     * @param rules the permission DSL declarations
    661     * @return the closed-world permission set
    662     * @since 3.7.0
    663     */
    664    static JexlPermissions create(final String... rules) {
    665        return NONE.compose(rules);
    666    }
    667
    668    /**
    669     * Wraps these permissions in a {@link LoggingPermissions} that logs every allow/deny decision.
    670     * <p>Useful to discover which reflective elements a permission set allows or denies.</p>
    671     *
    672     * @return a logging view of these permissions
    673     * @since 3.7.0
    674     */
    675    default JexlPermissions logging() {
    676        return new LoggingPermissions(this);
    677    }
    678
    679    /**
    680     * Wraps these permissions in a {@link LoggingPermissions} that logs every allow/deny decision
    681     * to a named logger.
    682     *
    683     * @param loggerName the name of the logger to log decisions to
    684     * @return a logging view of these permissions
    685     * @since 3.7.0
    686     */
    687    default JexlPermissions logging(final String loggerName) {
    688        return new LoggingPermissions(loggerName, this);
    689    }
    690
    691    /**
    692     * Wraps these permissions in a {@link LoggingPermissions} that logs every allow/deny decision
    693     * to the given logger.
    694     *
    695     * @param log the logger to log decisions to
    696     * @return a logging view of these permissions
    697     * @since 3.7.0
    698     */
    699    default JexlPermissions logging(final Log log) {
    700        return new LoggingPermissions(log, this);
    701    }
    702
    703    /**
    704     * Checks whether a class allows JEXL introspection.
    705     * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
    706     * as well as derived classes are visible to JEXL and cannot be used in scripts or expressions.
    707     * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
    708     * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
    709     *
    710     * @param clazz the class to check
    711     * @return true if JEXL is allowed to introspect, false otherwise
    712     * @since 3.3
    713     */
    714    boolean allow(Class<?> clazz);
    715
    716    /**
    717     * Checks whether a constructor allows JEXL introspection.
    718     * <p>If a constructor is not allowed, the new operator cannot be used to instantiate its declared class
    719     * in scripts or expressions.</p>
    720     *
    721     * @param ctor the constructor to check
    722     * @return true if JEXL is allowed to introspect, false otherwise
    723     * @since 3.3
    724     */
    725    boolean allow(Constructor<?> ctor);
    726
    727    /**
    728     * Checks whether a field explicitly allows JEXL introspection.
    729     * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
    730     *
    731     * @param field the field to check
    732     * @return true if JEXL is allowed to introspect, false otherwise
    733     * @since 3.3
    734     */
    735    boolean allow(Field field);
    736
    737    /**
    738     * Checks whether a field explicitly allows JEXL introspection.
    739     * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
    740     * @param clazz the class from which the field is accessed, used to check that the field is allowed for this class
    741     * @param field the field to check
    742     * @return true if JEXL is allowed to introspect, false otherwise
    743     * @since 3.6.3
    744   */
    745    default boolean allow(Class<?> clazz, Field field) {
    746      return allow(field);
    747    }
    748
    749    /**
    750     * Checks whether a method allows JEXL introspection.
    751     * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
    752     * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface
    753     * explicitly disallows this method.</p>
    754     *
    755     * @param method the method to check
    756     * @return true if JEXL is allowed to introspect, false otherwise
    757     * @since 3.3
    758     */
    759    boolean allow(Method method);
    760
    761    /**
    762     * Checks whether a method allows JEXL introspection.
    763     * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
    764     * <p>Since methods can be overridden and overloaded, this checks that this class explicitly allows
    765     * this method - superseding any superclass or interface specified permissions.</p>
    766     *
    767     * @param clazz the class from which the method is accessed, used to check that the method is allowed for this class
    768     * @param method the method to check
    769     * @return true if JEXL is allowed to introspect, false otherwise
    770     * @since 3.6.3
    771     */
    772    default boolean allow(Class<?> clazz, Method method) {
    773      return allow(method);
    774    }
    775
    776    /**
    777     * Checks whether a package allows JEXL introspection.
    778     * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
    779     * to JEXL and cannot be used in scripts or expression.</p>
    780     *
    781     * @param pack the package
    782     * @return true if JEXL is allowed to introspect, false otherwise
    783     * @since 3.3
    784     */
    785    boolean allow(Package pack);
    786
    787    /**
    788     * Compose these permissions with a new set.
    789     * <p>This is a convenience method meant to easily give access to the packages JEXL is
    790     * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code>
    791     * would extend the restricted set of permissions by allowing the com.my.app package.</p>
    792     *
    793     * @param src the new constraints
    794     * @return the new permissions
    795     */
    796    JexlPermissions compose(String... src);
    797
    798    /**
    799     * Checks that a class is valid for permission check.
    800     *
    801     * @param clazz the class
    802     * @return true if the class is not null, false otherwise
    803     */
    804    default boolean validate(final Class<?> clazz) {
    805        return clazz != null;
    806    }
    807
    808    /**
    809     * Checks that a constructor is valid for permission check.
    810     *
    811     * @param constructor the constructor
    812     * @return true if constructor is not null and public, false otherwise
    813     */
    814    default boolean validate(final Constructor<?> constructor) {
    815        return constructor != null && Modifier.isPublic(constructor.getModifiers());
    816    }
    817
    818    /**
    819     * Checks that a field is valid for permission check.
    820     *
    821     * @param field the constructor
    822     * @return true if field is not null and public, false otherwise
    823     */
    824    default boolean validate(final Field field) {
    825        return field != null && Modifier.isPublic(field.getModifiers());
    826    }
    827
    828    /**
    829     * Checks that a method is valid for permission check.
    830     *
    831     * @param method the method
    832     * @return true if method is not null and public, false otherwise
    833     */
    834    default boolean validate(final Method method) {
    835        return method != null && Modifier.isPublic(method.getModifiers());
    836    }
    837
    838    /**
    839     * Checks that a package is valid for permission check.
    840     *
    841     * @param pack the package
    842     * @return true if the class is not null, false otherwise
    843     */
    844    default boolean validate(final Package pack) {
    845        return pack != null;
    846    }
    847}