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.stream.Collectors;
    029
    030import org.apache.commons.jexl3.internal.introspection.PermissionsParser;
    031
    032/**
    033 * This interface describes permissions used by JEXL introspection that constrain which
    034 * packages/classes/constructors/fields/methods are made visible to JEXL scripts.
    035 * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated
    036 * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control
    037 * over what can be executed. JEXL introspection mechanism will check whether it is permitted to
    038 * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions
    039 * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}.
    040 * </p>
    041 * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated -
    042 * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl}
    043 * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments;
    044 * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be
    045 * processed.</p>
    046 * <p>A simple textual configuration can be used to create user-defined permissions using
    047 * {@link JexlPermissions#parse(String...)}. The permission syntax supports both positive (+) and negative (-)
    048 * declarations:</p>
    049 * <ul>
    050 * <li><b>Negative restrictions ({@code -})</b>: By default or when prefixed with {@code -}, class restrictions
    051 * explicitly <b>deny</b> access to the specified members (or the entire class if the block is empty).
    052 * This is the default mode and works like {@link org.apache.commons.jexl3.annotations.NoJexl}.</li>
    053 * <li><b>Positive restrictions ({@code +})</b>: When prefixed with {@code +}, class restrictions
    054 * explicitly <b>allow only</b> the specified members (or the entire class if the block is empty), denying
    055 * all others. This provides a whitelist approach where you must explicitly list what is permitted.</li>
    056 * </ul>
    057 * <p>For example:</p>
    058 * <pre>
    059 * // Deny specific methods in a class (negative restriction - default)
    060 * java.lang { System { exit(); } }  // or -System { exit(); }
    061 *
    062 * // Allow only specific methods in a class (positive restriction)
    063 * java.lang { +System { currentTimeMillis(); nanoTime(); } }
    064 *
    065 * // Allow entire class (positive restriction with empty block)
    066 * java.io -{ +PrintWriter{} +Writer{} }
    067 * </pre>
    068 *
    069 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
    070 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
    071 * be to instantiate a {@link JexlUberspect} with those permissions and call
    072 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
    073 *
    074 * <p>
    075 *     To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior
    076 *     by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with
    077 *     {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance.
    078 * </p>
    079 * <p>
    080 *     For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to
    081 *     JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)}
    082 *     with {@link #UNRESTRICTED} as parameter.
    083 * </p>
    084 *
    085 * @since 3.3
    086 */
    087public interface JexlPermissions {
    088
    089    /**
    090     * A permission delegation that augments the RESTRICTED permission with an explicit
    091     * set of classes.
    092     * <p>A typical use case is to deny access to a package - and thus all its classes - but allow
    093     * a few specific classes.</p>
    094     * <p>Note that the newer positive restriction syntax is preferable as in:
    095     * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
    096     */
    097    final class ClassPermissions extends JexlPermissions.Delegate {
    098      /**
    099       * The set of explicitly allowed classes, overriding the delegate permissions.
    100       */
    101      private final Set<String> allowedClasses;
    102
    103      /**
    104       * Creates permissions based on the RESTRICTED set but allowing an explicit set.
    105       *
    106       * @param allow the set of allowed classes
    107       */
    108      public ClassPermissions(final Class<?>... allow) {
    109        this(JexlPermissions.RESTRICTED, allow);
    110      }
    111
    112      /**
    113       * Creates permissions by augmenting an existing set with an explicit set of allowed classes.
    114       * @param permissions the base permissions to augment
    115       * @param allow the set of allowed classes
    116       */
    117      public ClassPermissions(final JexlPermissions permissions, final Class<?>... allow) {
    118        this(permissions, Arrays.stream(Objects.requireNonNull(allow)).map(Class::getCanonicalName).collect(Collectors.toList()));
    119      }
    120
    121      /**
    122       * Creates permissions by augmenting an existing set with an explicit set of allowed canonical class names.
    123       *
    124       * @param delegate the base to delegate to
    125       * @param allow    the list of class canonical names
    126       */
    127      public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) {
    128        super(Objects.requireNonNull(delegate));
    129        allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
    130      }
    131
    132      @Override
    133      public boolean allow(final Constructor<?> constructor) {
    134        return validate(constructor) &&
    135            (allowedClasses.contains(constructor.getDeclaringClass().getCanonicalName()) || super.allow(constructor));
    136      }
    137
    138      @Override
    139      public boolean allow(final Class<?> clazz) {
    140        return validate(clazz) &&
    141            (allowedClasses.contains(clazz.getCanonicalName()) || super.allow(clazz));
    142      }
    143
    144      @Override
    145      public boolean allow(final Class<?> clazz, final Field field) {
    146        if (!validate(field)) {
    147          return false;
    148        }
    149        if (!validate(clazz)) {
    150          return false;
    151        }
    152        if (!field.getDeclaringClass().isAssignableFrom(clazz)) {
    153          return false;
    154        }
    155        if (super.allow(clazz, field)) {
    156          return true;
    157        }
    158        return isClassAllowed(clazz);
    159      }
    160
    161      @Override
    162      public boolean allow(final Class<?> clazz, final Method method) {
    163        if (!validate(method)) {
    164          return false;
    165        }
    166        if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
    167          return false;
    168        }
    169        if (super.allow(clazz, method)) {
    170          return true;
    171        }
    172        return isClassAllowed(clazz);
    173      }
    174
    175      @Override
    176      public JexlPermissions compose(final String... src) {
    177        return new ClassPermissions(base.compose(src), allowedClasses);
    178      }
    179
    180      private boolean isClassAllowed(final Class<?> aClass) {
    181        Class<?> clazz = aClass;
    182        // let's walk all interfaces
    183        for (final Class<?> inter : clazz.getInterfaces()) {
    184          if (allowedClasses.contains(inter.getCanonicalName())) {
    185            return true;
    186          }
    187        }
    188        // let's walk all super classes
    189        while (clazz != null) {
    190          if (allowedClasses.contains(clazz.getCanonicalName())) {
    191            return true;
    192          }
    193          clazz = clazz.getSuperclass();
    194        }
    195        return false;
    196      }
    197    }
    198
    199    /**
    200     * A base for permission delegation allowing functional refinement.
    201     * Overloads should call the appropriate validate() method early in their body.
    202     */
    203    class Delegate implements JexlPermissions {
    204        /**
    205         * The permissions we delegate to.
    206         */
    207        protected final JexlPermissions base;
    208
    209        /**
    210         * Constructs a new instance.
    211         *
    212         * @param delegate the delegate.
    213         */
    214        protected Delegate(final JexlPermissions delegate) {
    215            base = delegate;
    216        }
    217
    218        @Override
    219        public boolean allow(final Class<?> clazz) {
    220            return base.allow(clazz);
    221        }
    222
    223        @Override
    224        public boolean allow(final Constructor<?> ctor) {
    225            return base.allow(ctor);
    226        }
    227
    228        @Override
    229        public boolean allow(final Field field) {
    230            return validate(field) && allow(field.getDeclaringClass(), field);
    231        }
    232
    233        @Override
    234        public boolean allow(final Class<?> clazz, final Field field) {
    235            return base.allow(clazz, field);
    236        }
    237
    238        @Override
    239        public boolean allow(final Method method) {
    240            return validate(method) && allow(method.getDeclaringClass(), method);
    241        }
    242
    243        @Override
    244        public boolean allow(final Class<?> clazz, final Method method) {
    245            return base.allow(clazz, method);
    246        }
    247
    248        @Override
    249        public boolean allow(final Package pack) {
    250            return base.allow(pack);
    251        }
    252
    253        @Override
    254        public JexlPermissions compose(final String... src) {
    255            return new Delegate(base.compose(src));
    256        }
    257    }
    258
    259    /**
    260     * The unrestricted permissions.
    261     * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p>
    262     *
    263     * @since 3.3
    264     */
    265    JexlPermissions UNRESTRICTED = JexlPermissions.parse();
    266
    267    /**
    268     * A restricted singleton.
    269     * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p>
    270     * <p>Of particular importance are the restrictions on the {@link System},
    271     * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net},
    272     * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts
    273     * and its host.
    274     * </p>
    275     * <p>
    276     * As a simple guide, any line that ends with &quot;.*&quot; is allowing a package, any other is
    277     * denying a package, class or method.
    278     * </p>
    279     * <ul>
    280     * <li>java.nio.*</li>
    281     * <li>java.lang.*</li>
    282     * <li>java.math.*</li>
    283     * <li>java.text.*</li>
    284     * <li>java.util.*</li>
    285     * <li>org.w3c.dom.*</li>
    286     * <li>org.apache.commons.jexl3.*</li>
    287     *
    288     * <li>org.apache.commons.jexl3 { JexlBuilder {} }</li>
    289     * <li>org.apache.commons.jexl3.introspection { JexlPermissions {} JexlPermissions$ClassPermissions {} }</li>
    290     * <li>org.apache.commons.jexl3.internal { Engine {} Engine32 {} TemplateEngine {} }</li>
    291     * <li>org.apache.commons.jexl3.internal.introspection { Uberspect {} Introspector {} }</li>
    292     * <li>java.lang { Runtime {} System {} ProcessBuilder {} Process {} RuntimePermission {} SecurityManager {} Thread {} ThreadGroup {} Class {} }</li>
    293     * <li>java.io { +PrintWriter {} +Writer {} +StringWriter {} +Reader {} +InputStream {} +OutputStream {} }</li>
    294     * <li>java.nio +{}</li>
    295     * <li>java.rmi {}</li>
    296     * </ul>
    297     */
    298
    299    JexlPermissions RESTRICTED = JexlPermissions.parse(
    300        "# Default Uberspect Permissions",
    301        "java.math.*",
    302        "java.text.*",
    303        "java.time.*",
    304        "java.util.*",
    305        "org.w3c.dom.*",
    306        "java.lang +{" +
    307            "-Runtime{} -System{} -ProcessBuilder{} -Process{}" +
    308            "-RuntimePermission{} -SecurityManager{}" +
    309            "-Thread{} -ThreadGroup{} -Class{} -ClassLoader{}" +
    310            "}",
    311        "java.io -{ +PrintWriter{} +Writer{} +StringWriter{} +Reader{} +InputStream{} +OutputStream{} }",
    312        "java.nio +{}",
    313        "java.nio.charset +{}",
    314        "org.apache.commons.jexl3 +{ -JexlBuilder{} }"
    315    );
    316
    317    /**
    318     * Parses a set of permissions.
    319     * <p>
    320     * In JEXL 3.3, the syntax recognizes 2 types of permissions:
    321     * </p>
    322     * <ul>
    323     * <li>Allowing access to a wildcard restricted set of packages. </li>
    324     * <li>Denying access to packages, classes (and inner classes), methods and fields</li>
    325     * </ul>
    326     * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be
    327     * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards.
    328     * These allow composing the volume of what is allowed by addition.</p>
    329     * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation;
    330     * they can restrict access to package, class, inner-class, methods and fields.
    331     *  These allow refining the volume of what is allowed by extrusion.</p>
    332     *  An example of a tight environment that would not allow scripts to wander could be:
    333     *  <pre>
    334     *  # allow a very restricted set of base classes
    335     *  java.math.*
    336     *  java.text.*
    337     *  java.util.*
    338     *  # deny classes that could pose a security risk
    339     *  java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
    340     *  org.apache.commons.jexl3 { JexlBuilder {} }
    341     *  </pre>
    342     *  <p><b>Syntax Overview:</b></p>
    343     *  <ul>
    344     *  <li>Syntax for wildcards is the name of the package suffixed by {@code .*}.</li>
    345     *  <li>Syntax for restrictions is a list of package restrictions.</li>
    346     *  <li>A package restriction is a package name followed by a block (as in curly-bracket block {})
    347     *  that contains a list of class restrictions.</li>
    348     *  <li>A class restriction is a class name prefixed by an optional {@code -} or {@code +} sign
    349     *  followed by a block of member restrictions.</li>
    350     *  <li>A member restriction can be a class restriction - to restrict
    351     *  nested classes -, a field which is the Java field name suffixed with {@code ;}, a method composed of
    352     *  its Java name suffixed with {@code ();}. Constructor restrictions are specified like methods using the
    353     *  class name as method name.</li>
    354     *  </ul>
    355     *  <p><b>Negative ({@code -}) vs Positive ({@code +}) Restrictions:</b></p>
    356     *  <ul>
    357     *  <li><b>Negative restriction (default or {@code -} prefix)</b>: Explicitly <b>denies</b> access to the members
    358     *  declared in its block. If the block is empty, the entire class is denied.
    359     *  <br>Example: {@code java.lang { -System { exit(); } }} denies System.exit() but allows other System methods.
    360     *  <br>Example: {@code java.lang { Runtime {} }} denies the entire Runtime class (empty block means deny all).</li>
    361     *  <li><b>Positive restriction ({@code +} prefix)</b>: Explicitly <b>allows only</b> the members declared
    362     *  in its block, denying all others not listed. If the block is empty, the entire class is allowed.
    363     *  <br>Example: {@code java.lang { +System { currentTimeMillis(); } }} allows only System.currentTimeMillis(),
    364     *  denying all other System methods.
    365     *  <br>Example: {@code java.io -{ +PrintWriter{} +Writer{} }} in the context of a denied java.io package,
    366     *  allows only PrintWriter and Writer classes entirely (empty blocks mean allow all members).</li>
    367     *  </ul>
    368     *  <p>
    369     *  All overrides and overloads of constructors or methods are allowed or restricted at the same time,
    370     *  the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation.
    371     *  </p>
    372     *  <p><b>Complete Example:</b></p>
    373     *  <pre>
    374     *  # some wildcards
    375     *  java.util.* # java.util is pretty much a must-have
    376     *  my.allowed.package0.*
    377     *  another.allowed.package1.*
    378     *  # nojexl like restrictions
    379     *  my.package.internal {} # the whole package is hidden
    380     *  my.package {
    381     *   +class4 { theMethod(); } # POSITIVE: only theMethod can be called in class4, all others denied
    382     *   class0 {
    383     *     class1 {} # NEGATIVE (default): the whole class1 is hidden
    384     *     class2 {
    385     *         class2(); # class2 constructors cannot be invoked
    386     *         class3 {
    387     *             aMethod(); # aMethod cannot be called
    388     *             aField; # aField cannot be accessed
    389     *         }
    390     *     } # end of class2
    391     *     class0(); # class0 constructors cannot be invoked
    392     *     method(); # method cannot be called
    393     *     field; # field cannot be accessed
    394     *   } # end class0
    395     * } # end package my.package
    396     * </pre>
    397     *
    398     * @param src the permissions source, the default (NoJexl aware) permissions if null
    399     * @return the permissions instance
    400     * @since 3.3
    401     */
    402    static JexlPermissions parse(final String... src) {
    403        return new PermissionsParser().parse(src);
    404    }
    405
    406    /**
    407     * Checks whether a class allows JEXL introspection.
    408     * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
    409     * as well as derived classes are visible to JEXL and cannot be used in scripts or expressions.
    410     * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
    411     * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
    412     *
    413     * @param clazz the class to check
    414     * @return true if JEXL is allowed to introspect, false otherwise
    415     * @since 3.3
    416     */
    417    boolean allow(Class<?> clazz);
    418
    419    /**
    420     * Checks whether a constructor allows JEXL introspection.
    421     * <p>If a constructor is not allowed, the new operator cannot be used to instantiate its declared class
    422     * in scripts or expressions.</p>
    423     *
    424     * @param ctor the constructor to check
    425     * @return true if JEXL is allowed to introspect, false otherwise
    426     * @since 3.3
    427     */
    428    boolean allow(Constructor<?> ctor);
    429
    430    /**
    431     * Checks whether a field explicitly allows JEXL introspection.
    432     * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
    433     *
    434     * @param field the field to check
    435     * @return true if JEXL is allowed to introspect, false otherwise
    436     * @since 3.3
    437     */
    438    boolean allow(Field field);
    439
    440    /**
    441     * Checks whether a field explicitly allows JEXL introspection.
    442     * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
    443     * @param clazz the class from which the field is accessed, used to check that the field is allowed for this class
    444     * @param field the field to check
    445     * @return true if JEXL is allowed to introspect, false otherwise
    446     * @since 3.6.3
    447   */
    448    default boolean allow(Class<?> clazz, Field field) {
    449      return allow(field);
    450    }
    451
    452    /**
    453     * Checks whether a method allows JEXL introspection.
    454     * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
    455     * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface
    456     * explicitly disallows this method.</p>
    457     *
    458     * @param method the method to check
    459     * @return true if JEXL is allowed to introspect, false otherwise
    460     * @since 3.3
    461     */
    462    boolean allow(Method method);
    463
    464    /**
    465     * Checks whether a method allows JEXL introspection.
    466     * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
    467     * <p>Since methods can be overridden and overloaded, this checks that this class explicitly allows
    468     * this method - superseding any superclass or interface specified permissions.</p>
    469     *
    470     * @param clazz the class from which the method is accessed, used to check that the method is allowed for this class
    471     * @param method the method to check
    472     * @return true if JEXL is allowed to introspect, false otherwise
    473     * @since 3.6.3
    474     */
    475    default boolean allow(Class<?> clazz, Method method) {
    476      return allow(method);
    477    }
    478
    479    /**
    480     * Checks whether a package allows JEXL introspection.
    481     * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
    482     * to JEXL and cannot be used in scripts or expression.</p>
    483     *
    484     * @param pack the package
    485     * @return true if JEXL is allowed to introspect, false otherwise
    486     * @since 3.3
    487     */
    488    boolean allow(Package pack);
    489
    490    /**
    491     * Compose these permissions with a new set.
    492     * <p>This is a convenience method meant to easily give access to the packages JEXL is
    493     * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code>
    494     * would extend the restricted set of permissions by allowing the com.my.app package.</p>
    495     *
    496     * @param src the new constraints
    497     * @return the new permissions
    498     */
    499    JexlPermissions compose(String... src);
    500
    501    /**
    502     * Checks that a class is valid for permission check.
    503     *
    504     * @param clazz the class
    505     * @return true if the class is not null, false otherwise
    506     */
    507    default boolean validate(final Class<?> clazz) {
    508        return clazz != null;
    509    }
    510
    511    /**
    512     * Checks that a constructor is valid for permission check.
    513     *
    514     * @param constructor the constructor
    515     * @return true if constructor is not null and public, false otherwise
    516     */
    517    default boolean validate(final Constructor<?> constructor) {
    518        return constructor != null && Modifier.isPublic(constructor.getModifiers());
    519    }
    520
    521    /**
    522     * Checks that a field is valid for permission check.
    523     *
    524     * @param field the constructor
    525     * @return true if field is not null and public, false otherwise
    526     */
    527    default boolean validate(final Field field) {
    528        return field != null && Modifier.isPublic(field.getModifiers());
    529    }
    530
    531    /**
    532     * Checks that a method is valid for permission check.
    533     *
    534     * @param method the method
    535     * @return true if method is not null and public, false otherwise
    536     */
    537    default boolean validate(final Method method) {
    538        return method != null && Modifier.isPublic(method.getModifiers());
    539    }
    540
    541    /**
    542     * Checks that a package is valid for permission check.
    543     *
    544     * @param pack the package
    545     * @return true if the class is not null, false otherwise
    546     */
    547    default boolean validate(final Package pack) {
    548        return pack != null;
    549    }
    550}