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    *      https://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   *
65   * @since 3.3
66   */
67  public interface JexlPermissions {
68  
69      /**
70       * A permission delegation that augments the RESTRICTED permission with an explicit
71       * set of classes.
72       * <p>Typical use case is to deny access to a package - and thus all its classes - but allow
73       * a few specific classes.</p>
74       * <p>Note that the newer positive restriction syntax is preferable as in:
75       * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
76       */
77       final class ClassPermissions extends JexlPermissions.Delegate {
78  
79          /** The set of explicitly allowed classes, overriding the delegate permissions. */
80          private final Set<String> allowedClasses;
81  
82          /**
83           * Creates permissions based on the RESTRICTED set but allowing an explicit set.
84           *
85           * @param allow the set of allowed classes
86           */
87          public ClassPermissions(final Class<?>... allow) {
88              this(JexlPermissions.RESTRICTED,
89                      Arrays.stream(Objects.requireNonNull(allow))
90                          .map(Class::getCanonicalName)
91                          .collect(Collectors.toList()));
92          }
93  
94          /**
95           * Required for compose().
96           *
97           * @param delegate the base to delegate to
98           * @param allow the list of class canonical names
99           */
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 }