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 *      http://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 org.apache.commons.jexl3.internal.introspection.PermissionsParser;
020
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.HashSet;
028import java.util.Objects;
029import java.util.Set;
030import java.util.stream.Collectors;
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 behaviour 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 * @since 3.3
065 */
066public interface JexlPermissions {
067
068    /**
069     * Checks whether a package allows JEXL introspection.
070     * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
071     * to JEXL and can not be used in scripts or expression.</p>
072     * @param pack the package
073     * @return true if JEXL is allowed to introspect, false otherwise
074     * @since 3.3
075     */
076    boolean allow(final Package pack);
077
078    /**
079     * Checks whether a class allows JEXL introspection.
080     * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
081     * as well as derived classes are visible to JEXL and can not be used in scripts or expressions.
082     * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
083     * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
084     * @param clazz the class to check
085     * @return true if JEXL is allowed to introspect, false otherwise
086     * @since 3.3
087     */
088    boolean allow(final Class<?> clazz);
089
090    /**
091     * Checks whether a constructor allows JEXL introspection.
092     * <p>If a constructor is not allowed, the new operator can not be used to instantiate its declared class
093     * in scripts or expressions.</p>
094     * @param ctor the constructor to check
095     * @return true if JEXL is allowed to introspect, false otherwise
096     * @since 3.3
097     */
098    boolean allow(final Constructor<?> ctor);
099
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}