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}