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...)}. The permission syntax supports both positive (+) and negative (-)
048 * declarations:</p>
049 * <ul>
050 * <li><b>Negative restrictions ({@code -})</b>: By default or when prefixed with {@code -}, class restrictions
051 * explicitly <b>deny</b> access to the specified members (or the entire class if the block is empty).
052 * This is the default mode and works like {@link org.apache.commons.jexl3.annotations.NoJexl}.</li>
053 * <li><b>Positive restrictions ({@code +})</b>: When prefixed with {@code +}, class restrictions
054 * explicitly <b>allow only</b> the specified members (or the entire class if the block is empty), denying
055 * all others. This provides a whitelist approach where you must explicitly list what is permitted.</li>
056 * </ul>
057 * <p>For example:</p>
058 * <pre>
059 * // Deny specific methods in a class (negative restriction - default)
060 * java.lang { System { exit(); } }  // or -System { exit(); }
061 *
062 * // Allow only specific methods in a class (positive restriction)
063 * java.lang { +System { currentTimeMillis(); nanoTime(); } }
064 *
065 * // Allow entire class (positive restriction with empty block)
066 * java.io -{ +PrintWriter{} +Writer{} }
067 * </pre>
068 *
069 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
070 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
071 * be to instantiate a {@link JexlUberspect} with those permissions and call
072 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
073 *
074 * <p>
075 *     To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior
076 *     by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with
077 *     {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance.
078 * </p>
079 * <p>
080 *     For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to
081 *     JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)}
082 *     with {@link #UNRESTRICTED} as parameter.
083 * </p>
084 *
085 * @since 3.3
086 */
087public interface JexlPermissions {
088
089    /**
090     * A permission delegation that augments the RESTRICTED permission with an explicit
091     * set of classes.
092     * <p>A typical use case is to deny access to a package - and thus all its classes - but allow
093     * a few specific classes.</p>
094     * <p>Note that the newer positive restriction syntax is preferable as in:
095     * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
096     */
097    final class ClassPermissions extends JexlPermissions.Delegate {
098      /**
099       * The set of explicitly allowed classes, overriding the delegate permissions.
100       */
101      private final Set<String> allowedClasses;
102
103      /**
104       * Creates permissions based on the RESTRICTED set but allowing an explicit set.
105       *
106       * @param allow the set of allowed classes
107       */
108      public ClassPermissions(final Class<?>... allow) {
109        this(JexlPermissions.RESTRICTED, allow);
110      }
111
112      /**
113       * Creates permissions by augmenting an existing set with an explicit set of allowed classes.
114       * @param permissions the base permissions to augment
115       * @param allow the set of allowed classes
116       */
117      public ClassPermissions(final JexlPermissions permissions, final Class<?>... allow) {
118        this(permissions, Arrays.stream(Objects.requireNonNull(allow)).map(Class::getCanonicalName).collect(Collectors.toList()));
119      }
120
121      /**
122       * Creates permissions by augmenting an existing set with an explicit set of allowed canonical class names.
123       *
124       * @param delegate the base to delegate to
125       * @param allow    the list of class canonical names
126       */
127      public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) {
128        super(Objects.requireNonNull(delegate));
129        allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
130      }
131
132      @Override
133      public boolean allow(final Constructor<?> constructor) {
134        return validate(constructor) &&
135            (allowedClasses.contains(constructor.getDeclaringClass().getCanonicalName()) || super.allow(constructor));
136      }
137
138      @Override
139      public boolean allow(final Class<?> clazz) {
140        return validate(clazz) &&
141            (allowedClasses.contains(clazz.getCanonicalName()) || super.allow(clazz));
142      }
143
144      @Override
145      public boolean allow(final Class<?> clazz, final Field field) {
146        if (!validate(field)) {
147          return false;
148        }
149        if (!validate(clazz)) {
150          return false;
151        }
152        if (!field.getDeclaringClass().isAssignableFrom(clazz)) {
153          return false;
154        }
155        if (super.allow(clazz, field)) {
156          return true;
157        }
158        return isClassAllowed(clazz);
159      }
160
161      @Override
162      public boolean allow(final Class<?> clazz, final Method method) {
163        if (!validate(method)) {
164          return false;
165        }
166        if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
167          return false;
168        }
169        if (super.allow(clazz, method)) {
170          return true;
171        }
172        return isClassAllowed(clazz);
173      }
174
175      @Override
176      public JexlPermissions compose(final String... src) {
177        return new ClassPermissions(base.compose(src), allowedClasses);
178      }
179
180      private boolean isClassAllowed(final Class<?> aClass) {
181        Class<?> clazz = aClass;
182        // let's walk all interfaces
183        for (final Class<?> inter : clazz.getInterfaces()) {
184          if (allowedClasses.contains(inter.getCanonicalName())) {
185            return true;
186          }
187        }
188        // let's walk all super classes
189        while (clazz != null) {
190          if (allowedClasses.contains(clazz.getCanonicalName())) {
191            return true;
192          }
193          clazz = clazz.getSuperclass();
194        }
195        return false;
196      }
197    }
198
199    /**
200     * A base for permission delegation allowing functional refinement.
201     * Overloads should call the appropriate validate() method early in their body.
202     */
203    class Delegate implements JexlPermissions {
204        /**
205         * The permissions we delegate to.
206         */
207        protected final JexlPermissions base;
208
209        /**
210         * Constructs a new instance.
211         *
212         * @param delegate the delegate.
213         */
214        protected Delegate(final JexlPermissions delegate) {
215            base = delegate;
216        }
217
218        @Override
219        public boolean allow(final Class<?> clazz) {
220            return base.allow(clazz);
221        }
222
223        @Override
224        public boolean allow(final Constructor<?> ctor) {
225            return base.allow(ctor);
226        }
227
228        @Override
229        public boolean allow(final Field field) {
230            return validate(field) && allow(field.getDeclaringClass(), field);
231        }
232
233        @Override
234        public boolean allow(final Class<?> clazz, final Field field) {
235            return base.allow(clazz, field);
236        }
237
238        @Override
239        public boolean allow(final Method method) {
240            return validate(method) && allow(method.getDeclaringClass(), method);
241        }
242
243        @Override
244        public boolean allow(final Class<?> clazz, final Method method) {
245            return base.allow(clazz, method);
246        }
247
248        @Override
249        public boolean allow(final Package pack) {
250            return base.allow(pack);
251        }
252
253        @Override
254        public JexlPermissions compose(final String... src) {
255            return new Delegate(base.compose(src));
256        }
257    }
258
259    /**
260     * The unrestricted permissions.
261     * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p>
262     *
263     * @since 3.3
264     */
265    JexlPermissions UNRESTRICTED = JexlPermissions.parse();
266
267    /**
268     * A restricted singleton.
269     * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p>
270     * <p>Of particular importance are the restrictions on the {@link System},
271     * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net},
272     * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts
273     * and its host.
274     * </p>
275     * <p>
276     * Every allowed package is declared explicitly using the positive {@code +{}} syntax rather than a
277     * {@code .*} wildcard. A wildcard matches a package <em>and all of its sub-packages</em>, which is not
278     * future-proof: a sub-package added by a later JDK (or a dangerous existing one such as
279     * {@code java.util.zip}/{@code java.util.jar} - which can read files - or {@code java.nio.file}) would be
280     * silently exposed. Listing each package explicitly keeps the perimeter closed: only the packages below are
281     * visible, nothing else.
282     * </p>
283     * <p>Allowed packages (each member is visible unless explicitly denied):</p>
284     * <ul>
285     * <li>java.math</li>
286     * <li>java.text</li>
287     * <li>java.time, java.time.chrono, java.time.format, java.time.temporal, java.time.zone</li>
288     * <li>java.util, java.util.concurrent, java.util.concurrent.atomic, java.util.function, java.util.stream, java.util.regex</li>
289     * <li>java.nio, java.nio.charset</li>
290     * <li>org.w3c.dom</li>
291     * <li>java.lang (minus the denied classes below)</li>
292     * <li>org.apache.commons.jexl3 (minus JexlBuilder)</li>
293     * </ul>
294     * <p>Denied classes / members (carved out of otherwise-allowed packages):</p>
295     * <ul>
296     * <li>java.lang { Runtime, System, ProcessBuilder, Process, RuntimePermission, SecurityManager, Thread, ThreadGroup, Class, ClassLoader }</li>
297     * <li>java.io { everything except PrintWriter, Writer, StringWriter, Reader, InputStream, OutputStream }</li>
298     * <li>java.util.concurrent { Executors and the thread-pool / fork-join executor classes }</li>
299     * <li>java.time.zone { ZoneRulesProvider } (prevents JVM-wide time-zone provider registration)</li>
300     * <li>org.apache.commons.jexl3 { JexlBuilder }</li>
301     * </ul>
302     * <p>Notably absent (and therefore denied) are file/IO/persistence/loader-bearing packages such as
303     * {@code java.util.zip}, {@code java.util.jar}, {@code java.util.prefs}, {@code java.util.logging},
304     * {@code java.util.concurrent.locks}, {@code java.nio.file}, {@code java.lang.reflect},
305     * {@code java.lang.invoke} and {@code org.w3c.dom.ls}.</p>
306     */
307
308    JexlPermissions RESTRICTED = JexlPermissions.parse(
309        "# Default Uberspect Permissions",
310        "java.math +{}",
311        "java.text +{}",
312        "java.time +{}",
313        "java.time.chrono +{}",
314        "java.time.format +{}",
315        "java.time.temporal +{}",
316        "java.time.zone +{ -ZoneRulesProvider{} }",
317        "java.util +{}",
318        "java.util.concurrent +{" +
319            "-Executors{} -ExecutorService{} -AbstractExecutorService{}" +
320            "-ThreadPoolExecutor{} -ScheduledThreadPoolExecutor{} -ScheduledExecutorService{}" +
321            "-ForkJoinPool{} -ForkJoinTask{} -ForkJoinWorkerThread{}" +
322            "}",
323        "java.util.concurrent.atomic +{}",
324        "java.util.function +{}",
325        "java.util.stream +{}",
326        "java.util.regex +{}",
327        "org.w3c.dom +{}",
328        "java.lang +{" +
329            "-Runtime{} -System{} -ProcessBuilder{} -Process{}" +
330            "-RuntimePermission{} -SecurityManager{}" +
331            "-Thread{} -ThreadGroup{} -Class{} -ClassLoader{}" +
332            "}",
333        "java.io -{ +PrintWriter{} +Writer{} +StringWriter{} +Reader{} +InputStream{} +OutputStream{} }",
334        "java.nio +{}",
335        "java.nio.charset +{}",
336        "org.apache.commons.jexl3 +{ -JexlBuilder{} }"
337    );
338
339    /**
340     * Parses a set of permissions.
341     * <p>
342     * In JEXL 3.3, the syntax recognizes 2 types of permissions:
343     * </p>
344     * <ul>
345     * <li>Allowing access to a wildcard restricted set of packages. </li>
346     * <li>Denying access to packages, classes (and inner classes), methods and fields</li>
347     * </ul>
348     * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be
349     * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards.
350     * These allow composing the volume of what is allowed by addition.</p>
351     * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation;
352     * they can restrict access to package, class, inner-class, methods and fields.
353     *  These allow refining the volume of what is allowed by extrusion.</p>
354     *  An example of a tight environment that would not allow scripts to wander could be:
355     *  <pre>
356     *  # allow a very restricted set of base classes
357     *  java.math.*
358     *  java.text.*
359     *  java.util.*
360     *  # deny classes that could pose a security risk
361     *  java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
362     *  org.apache.commons.jexl3 { JexlBuilder {} }
363     *  </pre>
364     *  <p><b>Syntax Overview:</b></p>
365     *  <ul>
366     *  <li>Syntax for wildcards is the name of the package suffixed by {@code .*}.</li>
367     *  <li>Syntax for restrictions is a list of package restrictions.</li>
368     *  <li>A package restriction is a package name followed by a block (as in curly-bracket block {})
369     *  that contains a list of class restrictions.</li>
370     *  <li>A class restriction is a class name prefixed by an optional {@code -} or {@code +} sign
371     *  followed by a block of member restrictions.</li>
372     *  <li>A member restriction can be a class restriction - to restrict
373     *  nested classes -, a field which is the Java field name suffixed with {@code ;}, a method composed of
374     *  its Java name suffixed with {@code ();}. Constructor restrictions are specified like methods using the
375     *  class name as method name.</li>
376     *  </ul>
377     *  <p><b>Negative ({@code -}) vs Positive ({@code +}) Restrictions:</b></p>
378     *  <ul>
379     *  <li><b>Negative restriction (default or {@code -} prefix)</b>: Explicitly <b>denies</b> access to the members
380     *  declared in its block. If the block is empty, the entire class is denied.
381     *  <br>Example: {@code java.lang { -System { exit(); } }} denies System.exit() but allows other System methods.
382     *  <br>Example: {@code java.lang { Runtime {} }} denies the entire Runtime class (empty block means deny all).</li>
383     *  <li><b>Positive restriction ({@code +} prefix)</b>: Explicitly <b>allows only</b> the members declared
384     *  in its block, denying all others not listed. If the block is empty, the entire class is allowed.
385     *  <br>Example: {@code java.lang { +System { currentTimeMillis(); } }} allows only System.currentTimeMillis(),
386     *  denying all other System methods.
387     *  <br>Example: {@code java.io -{ +PrintWriter{} +Writer{} }} in the context of a denied java.io package,
388     *  allows only PrintWriter and Writer classes entirely (empty blocks mean allow all members).</li>
389     *  </ul>
390     *  <p>
391     *  All overrides and overloads of constructors or methods are allowed or restricted at the same time,
392     *  the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation.
393     *  </p>
394     *  <p><b>Complete Example:</b></p>
395     *  <pre>
396     *  # some wildcards
397     *  java.util.* # java.util is pretty much a must-have
398     *  my.allowed.package0.*
399     *  another.allowed.package1.*
400     *  # nojexl like restrictions
401     *  my.package.internal {} # the whole package is hidden
402     *  my.package {
403     *   +class4 { theMethod(); } # POSITIVE: only theMethod can be called in class4, all others denied
404     *   class0 {
405     *     class1 {} # NEGATIVE (default): the whole class1 is hidden
406     *     class2 {
407     *         class2(); # class2 constructors cannot be invoked
408     *         class3 {
409     *             aMethod(); # aMethod cannot be called
410     *             aField; # aField cannot be accessed
411     *         }
412     *     } # end of class2
413     *     class0(); # class0 constructors cannot be invoked
414     *     method(); # method cannot be called
415     *     field; # field cannot be accessed
416     *   } # end class0
417     * } # end package my.package
418     * </pre>
419     *
420     * @param src the permissions source, the default (NoJexl aware) permissions if null
421     * @return the permissions instance
422     * @since 3.3
423     */
424    static JexlPermissions parse(final String... src) {
425        return new PermissionsParser().parse(src);
426    }
427
428    /**
429     * Checks whether a class allows JEXL introspection.
430     * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
431     * as well as derived classes are visible to JEXL and cannot be used in scripts or expressions.
432     * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
433     * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
434     *
435     * @param clazz the class to check
436     * @return true if JEXL is allowed to introspect, false otherwise
437     * @since 3.3
438     */
439    boolean allow(Class<?> clazz);
440
441    /**
442     * Checks whether a constructor allows JEXL introspection.
443     * <p>If a constructor is not allowed, the new operator cannot be used to instantiate its declared class
444     * in scripts or expressions.</p>
445     *
446     * @param ctor the constructor to check
447     * @return true if JEXL is allowed to introspect, false otherwise
448     * @since 3.3
449     */
450    boolean allow(Constructor<?> ctor);
451
452    /**
453     * Checks whether a field explicitly allows JEXL introspection.
454     * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
455     *
456     * @param field the field to check
457     * @return true if JEXL is allowed to introspect, false otherwise
458     * @since 3.3
459     */
460    boolean allow(Field field);
461
462    /**
463     * Checks whether a field explicitly allows JEXL introspection.
464     * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
465     * @param clazz the class from which the field is accessed, used to check that the field is allowed for this class
466     * @param field the field to check
467     * @return true if JEXL is allowed to introspect, false otherwise
468     * @since 3.6.3
469   */
470    default boolean allow(Class<?> clazz, Field field) {
471      return allow(field);
472    }
473
474    /**
475     * Checks whether a method allows JEXL introspection.
476     * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
477     * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface
478     * explicitly disallows this method.</p>
479     *
480     * @param method the method to check
481     * @return true if JEXL is allowed to introspect, false otherwise
482     * @since 3.3
483     */
484    boolean allow(Method method);
485
486    /**
487     * Checks whether a method allows JEXL introspection.
488     * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
489     * <p>Since methods can be overridden and overloaded, this checks that this class explicitly allows
490     * this method - superseding any superclass or interface specified permissions.</p>
491     *
492     * @param clazz the class from which the method is accessed, used to check that the method is allowed for this class
493     * @param method the method to check
494     * @return true if JEXL is allowed to introspect, false otherwise
495     * @since 3.6.3
496     */
497    default boolean allow(Class<?> clazz, Method method) {
498      return allow(method);
499    }
500
501    /**
502     * Checks whether a package allows JEXL introspection.
503     * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
504     * to JEXL and cannot be used in scripts or expression.</p>
505     *
506     * @param pack the package
507     * @return true if JEXL is allowed to introspect, false otherwise
508     * @since 3.3
509     */
510    boolean allow(Package pack);
511
512    /**
513     * Compose these permissions with a new set.
514     * <p>This is a convenience method meant to easily give access to the packages JEXL is
515     * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code>
516     * would extend the restricted set of permissions by allowing the com.my.app package.</p>
517     *
518     * @param src the new constraints
519     * @return the new permissions
520     */
521    JexlPermissions compose(String... src);
522
523    /**
524     * Checks that a class is valid for permission check.
525     *
526     * @param clazz the class
527     * @return true if the class is not null, false otherwise
528     */
529    default boolean validate(final Class<?> clazz) {
530        return clazz != null;
531    }
532
533    /**
534     * Checks that a constructor is valid for permission check.
535     *
536     * @param constructor the constructor
537     * @return true if constructor is not null and public, false otherwise
538     */
539    default boolean validate(final Constructor<?> constructor) {
540        return constructor != null && Modifier.isPublic(constructor.getModifiers());
541    }
542
543    /**
544     * Checks that a field is valid for permission check.
545     *
546     * @param field the constructor
547     * @return true if field is not null and public, false otherwise
548     */
549    default boolean validate(final Field field) {
550        return field != null && Modifier.isPublic(field.getModifiers());
551    }
552
553    /**
554     * Checks that a method is valid for permission check.
555     *
556     * @param method the method
557     * @return true if method is not null and public, false otherwise
558     */
559    default boolean validate(final Method method) {
560        return method != null && Modifier.isPublic(method.getModifiers());
561    }
562
563    /**
564     * Checks that a package is valid for permission check.
565     *
566     * @param pack the package
567     * @return true if the class is not null, false otherwise
568     */
569    default boolean validate(final Package pack) {
570        return pack != null;
571    }
572}