    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...)}.</p>
    048 *
    049 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
    050 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
    051 * be to instantiate a {@link JexlUberspect} with those permissions and call
    052 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
    053 *
    054 * <p>
    055 *     To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior
    056 *     by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with
    057 *     {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance.
    058 * </p>
    059 * <p>
    060 *     For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to
    061 *     JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)}
    062 *     with {@link #UNRESTRICTED} as parameter.
    063 * </p>
    064 * @since 3.3
    065 */
    066public interface JexlPermissions {
    067    /**
    068     * A permission delegation that augments the RESTRICTED permission with an explicit
    069     * set of classes.
    070     * <p>Typical use case is to deny access to a package - and thus all its classes - but allow
    071     * a few specific classes.</p>
    072     * <p>Note that the newer positive restriction syntax is preferable as in:
    073     * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
    074     */
    075     final class ClassPermissions extends JexlPermissions.Delegate {
    076        /** The set of explicitly allowed classes, overriding the delegate permissions. */
    077        private final Set<String> allowedClasses;
    078
    079        /**
    080         * Creates permissions based on the RESTRICTED set but allowing an explicit set.
    081         * @param allow the set of allowed classes
    082         */
    083        public ClassPermissions(final Class<?>... allow) {
    084            this(JexlPermissions.RESTRICTED,
    085                    Arrays.stream(Objects.requireNonNull(allow))
    086                        .map(Class::getCanonicalName)
    087                        .collect(Collectors.toList()));
    088        }
    089
    090        /**
    091         * Required for compose().
    092         * @param delegate the base to delegate to
    093         * @param allow the list of class canonical names
    094         */
    095        public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) {
    096            super(Objects.requireNonNull(delegate));
    097            allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
    098        }
    099
    100        @Override
    101        public boolean allow(final Class<?> clazz) {
    102            return validate(clazz) && isClassAllowed(clazz) || super.allow(clazz);
    103        }
    104
    105        @Override
    106        public boolean allow(final Constructor<?> constructor) {
    107            return validate(constructor) && isClassAllowed(constructor.getDeclaringClass()) || super.allow(constructor);
    108        }
    109
    110        @Override
    111        public boolean allow(final Method method) {
    112            return validate(method) && isClassAllowed(method.getDeclaringClass()) || super.allow(method);
    113        }
    114
    115        @Override
    116        public JexlPermissions compose(final String... src) {
    117            return new ClassPermissions(base.compose(src), allowedClasses);
    118        }
    119
    120        private boolean isClassAllowed(final Class<?> clazz) {
    121            return allowedClasses.contains(clazz.getCanonicalName());
    122        }
    123    }
    124
    125    /**
    126     * A base for permission delegation allowing functional refinement.
    127     * Overloads should call the appropriate validate() method early in their body.
    128     */
    129     class Delegate implements JexlPermissions {
    130         /** The permissions we delegate to. */
    131        protected final JexlPermissions base;
    132
    133        /**
    134         * Constructs a new instance.
    135         *
    136         * @param delegate the delegate.
    137         */
    138        protected Delegate(final JexlPermissions delegate) {
    139            base = delegate;
    140        }
    141
    142        @Override
    143        public boolean allow(final Class<?> clazz) {
    144            return base.allow(clazz);
    145        }
    146
    147        @Override
    148        public boolean allow(final Constructor<?> ctor) {
    149            return base.allow(ctor);
    150        }
    151
    152        @Override
    153        public boolean allow(final Field field) {
    154            return base.allow(field);
    155        }
    156
    157        @Override
    158        public boolean allow(final Method method) {
    159            return base.allow(method);
    160        }
    161
    162        @Override
    163        public boolean allow(final Package pack) {
    164            return base.allow(pack);
    165        }
    166
    167        @Override
    168        public JexlPermissions compose(final String... src) {
    169            return new Delegate(base.compose(src));
    170        }
    171    }
    172
    173    /**
    174     * The unrestricted permissions.
    175     * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p>
    176     * @since 3.3
    177     */
    178    JexlPermissions UNRESTRICTED = JexlPermissions.parse();
    179
    180    /**
    181     * A restricted singleton.
    182     * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p>
    183     * <p>Of particular importance are the restrictions on the {@link System},
    184     * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net}, {@link java.net},
    185     * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts
    186     * and its host.
    187     * </p>
    188     * <p>
    189     * As a simple guide, any line that ends with &quot;.*&quot; is allowing a package, any other is
    190     * denying a package, class or method.
    191     * </p>
    192     * <ul>
    193     * <li>java.nio.*</li>
    194     * <li>java.io.*</li>
    195     * <li>java.lang.*</li>
    196     * <li>java.math.*</li>
    197     * <li>java.text.*</li>
    198     * <li>java.util.*</li>
    199     * <li>org.w3c.dom.*</li>
    200     * <li>org.apache.commons.jexl3.*</li>
    201     *
    202     * <li>org.apache.commons.jexl3 { JexlBuilder {} }</li>
    203     * <li>org.apache.commons.jexl3.internal { Engine {} }</li>
    204     * <li>java.lang { Runtime {} System {} ProcessBuilder {} Class {} }</li>
    205     * <li>java.lang.annotation {}</li>
    206     * <li>java.lang.instrument {}</li>
    207     * <li>java.lang.invoke {}</li>
    208     * <li>java.lang.management {}</li>
    209     * <li>java.lang.ref {}</li>
    210     * <li>java.lang.reflect {}</li>
    211     * <li>java.net {}</li>
    212     * <li>java.io { File { } }</li>
    213     * <li>java.nio { Path { } Paths { } Files { } }</li>
    214     * <li>java.rmi {}</li>
    215     * </ul>
    216     */
    217    JexlPermissions RESTRICTED = JexlPermissions.parse(
    218            "# Restricted Uberspect Permissions",
    219            "java.nio.*",
    220            "java.io.*",
    221            "java.lang.*",
    222            "java.math.*",
    223            "java.text.*",
    224            "java.util.*",
    225            "org.w3c.dom.*",
    226            "org.apache.commons.jexl3.*",
    227            "org.apache.commons.jexl3 { JexlBuilder {} }",
    228            "org.apache.commons.jexl3.introspection { JexlPermissions {} JexlPermissions$ClassPermissions {} }",
    229            "org.apache.commons.jexl3.internal { Engine {} Engine32 {} TemplateEngine {} }",
    230            "org.apache.commons.jexl3.internal.introspection { Uberspect {} Introspector {} }",
    231            "java.lang { Runtime{} System{} ProcessBuilder{} Process{}" +
    232                    " RuntimePermission{} SecurityManager{}" +
    233                    " Thread{} ThreadGroup{} Class{} }",
    234            "java.lang.annotation {}",
    235            "java.lang.instrument {}",
    236            "java.lang.invoke {}",
    237            "java.lang.management {}",
    238            "java.lang.ref {}",
    239            "java.lang.reflect {}",
    240            "java.net {}",
    241            "java.io { File{} FileDescriptor{} }",
    242            "java.nio { Path { } Paths { } Files { } }",
    243            "java.rmi"
    244    );
    245
    246    /**
    247     * Parses a set of permissions.
    248     * <p>
    249     * In JEXL 3.3, the syntax recognizes 2 types of permissions:
    250     * </p>
    251     * <ul>
    252     * <li>Allowing access to a wildcard restricted set of packages. </li>
    253     * <li>Denying access to packages, classes (and inner classes), methods and fields</li>
    254     * </ul>
    255     * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be
    256     * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards.
    257     * These allow composing the volume of what is allowed by addition.</p>
    258     * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation;
    259     * they can restrict access to package, class, inner-class, methods and fields.
    260     *  These allow refining the volume of what is allowed by extrusion.</p>
    261     *  An example of a tight environment that would not allow scripts to wander could be:
    262     *  <pre>
    263     *  # allow a very restricted set of base classes
    264     *  java.math.*
    265     *  java.text.*
    266     *  java.util.*
    267     *  # deny classes that could pose a security risk
    268     *  java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
    269     *  org.apache.commons.jexl3 { JexlBuilder {} }
    270     *  </pre>
    271     *  <ul>
    272     *  <li>Syntax for wildcards is the name of the package suffixed by {@code .*}.</li>
    273     *  <li>Syntax for restrictions is a list of package restrictions.</li>
    274     *  <li>A package restriction is a package name followed by a block (as in curly-bracket block {})
    275     *  that contains a list of class restrictions.</li>
    276     *  <li>A class restriction is a class name prefixed by an optional {@code -} or {@code +} sign
    277     *  followed by a block of member restrictions.</li>
    278     *  <li>A member restriction can be a class restriction - to restrict
    279     *  nested classes -, a field which is the Java field name suffixed with {@code ;}, a method composed of
    280     *  its Java name suffixed with {@code ();}. Constructor restrictions are specified like methods using the
    281     *  class name as method name.</li>
    282     *  <li>By default or when prefixed with a {@code -}, a class restriction is explicitly denying the members
    283     *  declared in its block (or the whole class)</li>
    284     *  <li>When prefixed with a {@code +}, a class restriction is explicitly allowing the members
    285     *  declared in its block (or the whole class)</li>
    286     *  </ul>
    287     *  <p>
    288     *  All overrides and overloads of a constructors or method are allowed or restricted at the same time,
    289     *  the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation.
    290     *  </p>
    291     *  <pre>
    292     *  # some wildcards
    293     *  java.lang.*; # java.lang is pretty much a must have
    294     *  my.allowed.package0.*
    295     *  another.allowed.package1.*
    296     *  # nojexl like restrictions
    297     *  my.package.internal {} # the whole package is hidden
    298     *  my.package {
    299     *   +class4 { theMethod(); } # only theMethod can be called in class4
    300     *   class0 {
    301     *     class1 {} # the whole class1 is hidden
    302     *     class2 {
    303     *         class2(); # class2 constructors cannot be invoked
    304     *         class3 {
    305     *             aMethod(); # aMethod cannot be called
    306     *             aField; # aField cannot be accessed
    307     *         }
    308     *     } # end of class2
    309     *     class0(); # class0 constructors cannot be invoked
    310     *     method(); # method cannot be called
    311     *     field; # field cannot be accessed
    312     *   } # end class0
    313     * } # end package my.package
    314     * </pre>
    315     *
    316     * @param src the permissions source, the default (NoJexl aware) permissions if null
    317     * @return the permissions instance
    318     * @since 3.3
    319     */
    320    static JexlPermissions parse(final String... src) {
    321        return new PermissionsParser().parse(src);
    322    }
    323
    324    /**
    325     * Checks whether a class allows JEXL introspection.
    326     * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
    327     * as well as derived classes are visible to JEXL and cannot be used in scripts or expressions.
    328     * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
    329     * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
    330     * @param clazz the class to check
    331     * @return true if JEXL is allowed to introspect, false otherwise
    332     * @since 3.3
    333     */
    334    boolean allow(final Class<?> clazz);
    335
    336    /**
    337     * Checks whether a constructor allows JEXL introspection.
    338     * <p>If a constructor is not allowed, the new operator cannot be used to instantiate its declared class
    339     * in scripts or expressions.</p>
    340     * @param ctor the constructor to check
    341     * @return true if JEXL is allowed to introspect, false otherwise
    342     * @since 3.3
    343     */
    344    boolean allow(final Constructor<?> ctor);
    345
    346    /**
    347     * Checks whether a field explicitly disallows JEXL introspection.
    348     * <p>If a field is not allowed, it cannot resolved and accessed in scripts or expressions.</p>
    349     * @param field the field to check
    350     * @return true if JEXL is allowed to introspect, false otherwise
    351     * @since 3.3
    352     */
    353    boolean allow(final Field field);
    354    /**
    355     * Checks whether a method allows JEXL introspection.
    356     * <p>If a method is not allowed, it cannot resolved and called in scripts or expressions.</p>
    357     * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface
    358     * explicitly disallows this methods.</p>
    359     * @param method the method to check
    360     * @return true if JEXL is allowed to introspect, false otherwise
    361     * @since 3.3
    362     */
    363    boolean allow(final Method method);
    364
    365    /**
    366     * Checks whether a package allows JEXL introspection.
    367     * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
    368     * to JEXL and cannot be used in scripts or expression.</p>
    369     * @param pack the package
    370     * @return true if JEXL is allowed to introspect, false otherwise
    371     * @since 3.3
    372     */
    373    boolean allow(final Package pack);
    374
    375    /**
    376     * Compose these permissions with a new set.
    377     * <p>This is a convenience method meant to easily give access to the packages JEXL is
    378     * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code>
    379     * would extend the restricted set of permissions by allowing the com.my.app package.</p>
    380     * @param src the new constraints
    381     * @return the new permissions
    382     */
    383    JexlPermissions compose(final String... src);
    384
    385    /**
    386     * Checks that a class is valid for permission check.
    387     * @param clazz the class
    388     * @return true if the class is not null, false otherwise
    389     */
    390    default boolean validate(final Class<?> clazz) {
    391        return clazz != null;
    392    }
    393
    394    /**
    395     * Checks that a constructor is valid for permission check.
    396     * @param constructor the constructor
    397     * @return true if constructor is not null and public, false otherwise
    398     */
    399    default boolean validate(final Constructor<?> constructor) {
    400        return constructor != null && Modifier.isPublic(constructor.getModifiers());
    401    }
    402
    403    /**
    404     * Checks that a field is valid for permission check.
    405     * @param field the constructor
    406     * @return true if field is not null and public, false otherwise
    407     */
    408    default boolean validate(final Field field) {
    409        return field != null && Modifier.isPublic(field.getModifiers());
    410    }
    411
    412    /**
    413     * Checks that a method is valid for permission check.
    414     * @param method the method
    415     * @return true if method is not null and public, false otherwise
    416     */
    417    default boolean validate(final Method method) {
    418        return method != null && Modifier.isPublic(method.getModifiers());
    419    }
    420
    421    /**
    422     * Checks that a package is valid for permission check.
    423     * @param pack the package
    424     * @return true if the class is not null, false otherwise
    425     */
    426    default boolean validate(final Package pack) {
    427        return pack != null;
    428    }
    429}