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 ".*" 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 }