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 java.lang.reflect.Constructor;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.HashSet;
26  import java.util.Objects;
27  import java.util.Set;
28  import java.util.stream.Collectors;
29  
30  import org.apache.commons.jexl3.internal.introspection.PermissionsParser;
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 behavior 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       * A permission delegation that augments the RESTRICTED permission with an explicit
70       * set of classes.
71       * <p>Typical use case is to deny access to a package - and thus all its classes - but allow
72       * a few specific classes.</p>
73       * <p>Note that the newer positive restriction syntax is preferable as in:
74       * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
75       */
76       final class ClassPermissions extends JexlPermissions.Delegate {
77          /** The set of explicitly allowed classes, overriding the delegate permissions. */
78          private final Set<String> allowedClasses;
79  
80          /**
81           * Creates permissions based on the RESTRICTED set but allowing an explicit set.
82           * @param allow the set of allowed classes
83           */
84          public ClassPermissions(final Class<?>... allow) {
85              this(JexlPermissions.RESTRICTED,
86                      Arrays.stream(Objects.requireNonNull(allow))
87                          .map(Class::getCanonicalName)
88                          .collect(Collectors.toList()));
89          }
90  
91          /**
92           * Required for compose().
93           * @param delegate the base to delegate to
94           * @param allow the list of class canonical names
95           */
96          public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) {
97              super(Objects.requireNonNull(delegate));
98              allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
99          }
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 }