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