View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.introspection;
18  
19  import org.apache.commons.jexl3.internal.introspection.PermissionsParser;
20  
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Modifier;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.HashSet;
28  import java.util.Objects;
29  import java.util.Set;
30  import java.util.stream.Collectors;
31  
32  /**
33   * This interface describes permissions used by JEXL introspection that constrain which
34   * packages/classes/constructors/fields/methods are made visible to JEXL scripts.
35   * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated
36   * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control
37   * over what can be executed. JEXL introspection mechanism will check whether it is permitted to
38   * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions
39   * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}.
40   * </p>
41   * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated -
42   * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl}
43   * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments;
44   * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be
45   * processed.</p>
46   * <p>A simple textual configuration can be used to create user defined permissions using
47   * {@link JexlPermissions#parse(String...)}.</p>
48   *
49   *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
50   * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
51   * be to instantiate a {@link JexlUberspect} with those permissions and call
52   * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
53   *
54   * <p>
55   *     To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior
56   *     by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with
57   *     {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance.
58   * </p>
59   * <p>
60   *     For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behaviour to
61   *     JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)}
62   *     with {@link #UNRESTRICTED} as parameter.
63   * </p>
64   * @since 3.3
65   */
66  public interface JexlPermissions {
67  
68      /**
69       * Checks whether a package allows JEXL introspection.
70       * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
71       * to JEXL and can not be used in scripts or expression.</p>
72       * @param pack the package
73       * @return true if JEXL is allowed to introspect, false otherwise
74       * @since 3.3
75       */
76      boolean allow(final Package pack);
77  
78      /**
79       * Checks whether a class allows JEXL introspection.
80       * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
81       * as well as derived classes are visible to JEXL and can not be used in scripts or expressions.
82       * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
83       * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
84       * @param clazz the class to check
85       * @return true if JEXL is allowed to introspect, false otherwise
86       * @since 3.3
87       */
88      boolean allow(final Class<?> clazz);
89  
90      /**
91       * Checks whether a constructor allows JEXL introspection.
92       * <p>If a constructor is not allowed, the new operator can not be used to instantiate its declared class
93       * in scripts or expressions.</p>
94       * @param ctor the constructor to check
95       * @return true if JEXL is allowed to introspect, false otherwise
96       * @since 3.3
97       */
98      boolean allow(final Constructor<?> ctor);
99  
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 }