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