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