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.concurrent.ConcurrentHashMap;
29 import java.util.stream.Collectors;
30
31 import org.apache.commons.jexl3.internal.introspection.PermissionsParser;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 /**
36 * This interface describes permissions used by JEXL introspection that constrain which
37 * packages/classes/constructors/fields/methods are made visible to JEXL scripts.
38 * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated
39 * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control
40 * over what can be executed. JEXL introspection mechanism will check whether it is permitted to
41 * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions
42 * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}.
43 * </p>
44 * <p><strong>Security disclaimer.</strong> Neither {@link #RESTRICTED} nor {@link #SECURE} is exhaustive, and neither
45 * must be considered completely safe or sufficient on its own for executing untrusted user input. They are hardened
46 * baselines, not guarantees. Any application that evaluates untrusted scripts <em>must</em> define its own tailored,
47 * strict whitelist of exactly the classes, methods and fields its scripts legitimately need - ideally by composing on
48 * top of {@link #NONE} (which denies everything) via {@link #create(String...)} / {@link #compose(String...)} - and
49 * audit the result with {@link #logging()}.</p>
50 * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated -
51 * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl}
52 * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments;
53 * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be
54 * processed.</p>
55 * <p>A simple textual configuration can be used to create user-defined permissions using
56 * {@link JexlPermissions#parse(String...)}. The permission syntax supports both positive (+) and negative (-)
57 * declarations:</p>
58 * <ul>
59 * <li><b>Negative restrictions ({@code -})</b>: By default or when prefixed with {@code -}, class restrictions
60 * explicitly <b>deny</b> access to the specified members (or the entire class if the block is empty).
61 * This is the default mode and works like {@link org.apache.commons.jexl3.annotations.NoJexl}.</li>
62 * <li><b>Positive restrictions ({@code +})</b>: When prefixed with {@code +}, class restrictions
63 * explicitly <b>allow only</b> the specified members (or the entire class if the block is empty), denying
64 * all others. This provides a whitelist approach where you must explicitly list what is permitted.</li>
65 * </ul>
66 * <p>For example:</p>
67 * <pre>
68 * // Deny specific methods in a class (negative restriction - default)
69 * java.lang { System { exit(); } } // or -System { exit(); }
70 *
71 * // Allow only specific methods in a class (positive restriction)
72 * java.lang { +System { currentTimeMillis(); nanoTime(); } }
73 *
74 * // Allow entire class (positive restriction with empty block)
75 * java.io -{ +PrintWriter{} +Writer{} }
76 * </pre>
77 *
78 * <p>To build a policy from scratch, start from {@link #NONE} (or {@link #create(String...)}), which denies
79 * everything, and compose only what scripts need on top - the closed-world, deny-by-default approach. This is the
80 * opposite of {@link #UNRESTRICTED} (the empty {@link #parse(String...)}), which allows everything.</p>
81 *
82 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder}
83 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would
84 * be to instantiate a {@link JexlUberspect} with those permissions and call
85 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p>
86 *
87 * <p>
88 * To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior
89 * by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with
90 * {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance.
91 * </p>
92 * <p>
93 * For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to
94 * JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)}
95 * with {@link #UNRESTRICTED} as parameter.
96 * </p>
97 *
98 * @since 3.3
99 */
100 public interface JexlPermissions {
101
102 /**
103 * A permission delegation that augments the RESTRICTED permission with an explicit
104 * set of classes.
105 * <p>A typical use case is to deny access to a package - and thus all its classes - but allow
106 * a few specific classes.</p>
107 * <p>Note that the newer positive restriction syntax is preferable as in:
108 * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
109 */
110 final class ClassPermissions extends JexlPermissions.Delegate {
111 /**
112 * The set of explicitly allowed classes, overriding the delegate permissions.
113 */
114 private final Set<String> allowedClasses;
115
116 /**
117 * Creates permissions based on the RESTRICTED set but allowing an explicit set.
118 *
119 * @param allow the set of allowed classes
120 */
121 public ClassPermissions(final Class<?>... allow) {
122 this(JexlPermissions.RESTRICTED, allow);
123 }
124
125 /**
126 * Creates permissions by augmenting an existing set with an explicit set of allowed classes.
127 * @param permissions the base permissions to augment
128 * @param allow the set of allowed classes
129 */
130 public ClassPermissions(final JexlPermissions permissions, final Class<?>... allow) {
131 this(permissions, Arrays.stream(Objects.requireNonNull(allow)).map(Class::getCanonicalName).collect(Collectors.toList()));
132 }
133
134 /**
135 * Creates permissions by augmenting an existing set with an explicit set of allowed canonical class names.
136 *
137 * @param delegate the base to delegate to
138 * @param allow the list of class canonical names
139 */
140 public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) {
141 super(Objects.requireNonNull(delegate));
142 allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
143 }
144
145 @Override
146 public boolean allow(final Constructor<?> constructor) {
147 return validate(constructor) &&
148 (allowedClasses.contains(constructor.getDeclaringClass().getCanonicalName()) || super.allow(constructor));
149 }
150
151 @Override
152 public boolean allow(final Class<?> clazz) {
153 return validate(clazz) &&
154 (allowedClasses.contains(clazz.getCanonicalName()) || super.allow(clazz));
155 }
156
157 @Override
158 public boolean allow(final Class<?> clazz, final Field field) {
159 if (!validate(field)) {
160 return false;
161 }
162 if (!validate(clazz)) {
163 return false;
164 }
165 if (!field.getDeclaringClass().isAssignableFrom(clazz)) {
166 return false;
167 }
168 if (super.allow(clazz, field)) {
169 return true;
170 }
171 return isClassAllowed(clazz);
172 }
173
174 @Override
175 public boolean allow(final Class<?> clazz, final Method method) {
176 if (!validate(method)) {
177 return false;
178 }
179 if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
180 return false;
181 }
182 if (super.allow(clazz, method)) {
183 return true;
184 }
185 return isClassAllowed(clazz);
186 }
187
188 @Override
189 public JexlPermissions compose(final String... src) {
190 return new ClassPermissions(base.compose(src), allowedClasses);
191 }
192
193 private boolean isClassAllowed(final Class<?> aClass) {
194 Class<?> clazz = aClass;
195 // let's walk all interfaces
196 for (final Class<?> inter : clazz.getInterfaces()) {
197 if (allowedClasses.contains(inter.getCanonicalName())) {
198 return true;
199 }
200 }
201 // let's walk all super classes
202 while (clazz != null) {
203 if (allowedClasses.contains(clazz.getCanonicalName())) {
204 return true;
205 }
206 clazz = clazz.getSuperclass();
207 }
208 return false;
209 }
210 }
211
212 /**
213 * A base for permission delegation allowing functional refinement.
214 * Overloads should call the appropriate validate() method early in their body.
215 */
216 class Delegate implements JexlPermissions {
217 /**
218 * The permissions we delegate to.
219 */
220 protected final JexlPermissions base;
221
222 /**
223 * Constructs a new instance.
224 *
225 * @param delegate the delegate.
226 */
227 protected Delegate(final JexlPermissions delegate) {
228 base = delegate;
229 }
230
231 @Override
232 public boolean allow(final Class<?> clazz) {
233 return base.allow(clazz);
234 }
235
236 @Override
237 public boolean allow(final Constructor<?> ctor) {
238 return base.allow(ctor);
239 }
240
241 @Override
242 public boolean allow(final Field field) {
243 return validate(field) && allow(field.getDeclaringClass(), field);
244 }
245
246 @Override
247 public boolean allow(final Class<?> clazz, final Field field) {
248 return base.allow(clazz, field);
249 }
250
251 @Override
252 public boolean allow(final Method method) {
253 return validate(method) && allow(method.getDeclaringClass(), method);
254 }
255
256 @Override
257 public boolean allow(final Class<?> clazz, final Method method) {
258 return base.allow(clazz, method);
259 }
260
261 @Override
262 public boolean allow(final Package pack) {
263 return base.allow(pack);
264 }
265
266 @Override
267 public JexlPermissions compose(final String... src) {
268 return new Delegate(base.compose(src));
269 }
270 }
271
272 /**
273 * A permission delegate that logs every allow/deny decision.
274 * <p>This is a debugging aid to determine which reflective elements (classes, constructors, methods, fields)
275 * a permission set allows or denies; wrap any permissions with {@link JexlPermissions#logging()} (or
276 * {@link JexlPermissions#logging(String)} to pick the logger name) and inspect the log to diagnose why a
277 * given object is or is not reachable from scripts.</p>
278 *
279 * @since 3.7.0
280 */
281 class LoggingPermissions extends Delegate {
282 /** The logger that decisions are written to (at info level). */
283 private final Log logger;
284 /** The set of already-emitted log lines, so each decision is logged only once. */
285 private final Set<String> logged = ConcurrentHashMap.newKeySet();
286
287 /**
288 * Constructs an instance logging to a logger named after this class.
289 *
290 * @param delegate the permissions to delegate to
291 */
292 public LoggingPermissions(final JexlPermissions delegate) {
293 this(LogFactory.getLog(LoggingPermissions.class), delegate);
294 }
295
296 /**
297 * Constructs an instance logging to a named logger.
298 *
299 * @param loggerName the name of the logger to use
300 * @param delegate the permissions to delegate to
301 */
302 public LoggingPermissions(final String loggerName, final JexlPermissions delegate) {
303 this(LogFactory.getLog(loggerName), delegate);
304 }
305
306 /**
307 * Constructs an instance with an explicit logger.
308 *
309 * @param log the logger
310 * @param delegate the permissions to delegate to
311 */
312 protected LoggingPermissions(final Log log, final JexlPermissions delegate) {
313 super(delegate);
314 this.logger = log;
315 }
316
317 /**
318 * Logs a decision once: the first time a given message is seen, it is written to the logger;
319 * subsequent identical messages are suppressed.
320 *
321 * @param allowed the decision to return
322 * @param message the message to log
323 * @return the decision
324 */
325 private boolean log(final boolean allowed, final String message) {
326 if (logged.add(message)) {
327 logger.info(message);
328 }
329 return allowed;
330 }
331
332 @Override
333 public boolean allow(final Class<?> clazz) {
334 final boolean allowed = super.allow(clazz);
335 return log(allowed, String.format("Class %s is %s",
336 clazz.getCanonicalName(), allowed ? "allowed" : "denied"));
337 }
338
339 @Override
340 public boolean allow(final Constructor<?> ctor) {
341 final boolean allowed = super.allow(ctor);
342 return log(allowed, String.format("Constructor %s.%s() is %s",
343 ctor.getDeclaringClass().getCanonicalName(), ctor.getName(),
344 allowed ? "allowed" : "denied"));
345 }
346
347 @Override
348 public boolean allow(final Field field) {
349 final boolean allowed = super.allow(field);
350 return log(allowed, String.format("Field %s.%s is %s",
351 field.getDeclaringClass().getCanonicalName(), field.getName(),
352 allowed ? "allowed" : "denied"));
353 }
354
355 @Override
356 public boolean allow(final Class<?> clazz, final Field field) {
357 final boolean allowed = super.allow(clazz, field);
358 return log(allowed, String.format("Field %s.%s is %s for class %s",
359 field.getDeclaringClass().getCanonicalName(), field.getName(),
360 allowed ? "allowed" : "denied", clazz.getCanonicalName()));
361 }
362
363 @Override
364 public boolean allow(final Method method) {
365 final boolean allowed = super.allow(method);
366 return log(allowed, String.format("Method %s.%s() is %s",
367 method.getDeclaringClass().getCanonicalName(), method.getName(),
368 allowed ? "allowed" : "denied"));
369 }
370
371 @Override
372 public boolean allow(final Class<?> clazz, final Method method) {
373 final boolean allowed = super.allow(clazz, method);
374 return log(allowed, String.format("Method %s.%s() is %s for class %s",
375 method.getDeclaringClass().getCanonicalName(), method.getName(),
376 allowed ? "allowed" : "denied", clazz.getCanonicalName()));
377 }
378
379 @Override
380 public JexlPermissions compose(final String... src) {
381 return new LoggingPermissions(logger, base.compose(src));
382 }
383 }
384
385 /**
386 * The unrestricted permissions.
387 * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p>
388 * <p>It is <em>highly</em> discouraged to use this permissions outside of testing.</p>
389 * @since 3.3
390 */
391 JexlPermissions UNRESTRICTED = JexlPermissions.parse();
392
393 /**
394 * A permission set that denies everything: the empty base to build permissions from scratch.
395 * <p>Unlike {@link #UNRESTRICTED} (the empty {@link #parse(String...)}, which allows everything), NONE allows
396 * nothing. Compose positive declarations on top to grant access, for example:</p>
397 * <pre>JexlPermissions.NONE.compose("java.lang { +String{} }")</pre>
398 * <p>or use the {@link #create(String...)} factory. This is the recommended starting point when you want
399 * a closed-world, deny-by-default policy listing only what your scripts actually need.</p>
400 * @since 3.7.0
401 */
402 JexlPermissions NONE = new JexlPermissions() {
403 @Override public boolean allow(final Package pack) { return false; }
404 @Override public boolean allow(final Class<?> clazz) { return false; }
405 @Override public boolean allow(final Constructor<?> ctor) { return false; }
406 @Override public boolean allow(final Field field) { return false; }
407 @Override public boolean allow(final Method method) { return false; }
408 @Override public JexlPermissions compose(final String... src) {
409 // NONE has no state to merge; composing rules builds a closed-world set from scratch
410 return src == null || src.length == 0 ? this : JexlPermissions.parse(src);
411 }
412 };
413
414 /**
415 * A restricted singleton.
416 * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p>
417 * <p>
418 * RESTRICTED attempts to strike a balance between reasonable out-of-the-box isolation and allowing most
419 * legitimate features; it is convenient when scripts need a broad slice of the JDK. In a mission-critical
420 * scenario, prefer {@link #SECURE} as a base instead and {@link #compose(String...) compose} only what your
421 * scripts actually need on top of it. Be aware that the isolation RESTRICTED provides may be incomplete and
422 * could expose more than intended; should such a case be identified, we will endeavour to resolve it in a
423 * subsequent release. Use {@link #logging()} to audit exactly which elements your workload reaches.
424 * </p>
425 * <p>RESTRICTED is not exhaustive and must not be considered sufficient on its own for executing untrusted user
426 * input. For untrusted scripts, define a tailored, strict whitelist of exactly what your scripts need - ideally
427 * composed on top of {@link #NONE} - rather than relying on RESTRICTED as-is.</p>
428 * <p>Of particular importance are the restrictions on the {@link System},
429 * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net},
430 * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts
431 * and its host.
432 * </p>
433 * <p>
434 * Every allowed package is declared explicitly using the positive {@code +{}} syntax rather than a
435 * {@code .*} wildcard. A wildcard matches a package <em>and all of its sub-packages</em>, which is not
436 * future-proof: a sub-package added by a later JDK (or a dangerous existing one such as
437 * {@code java.util.zip}/{@code java.util.jar} - which can read files - or {@code java.nio.file}) would be
438 * silently exposed. Listing each package explicitly keeps the perimeter closed: only the packages below are
439 * visible, nothing else.
440 * </p>
441 * <p>Allowed packages (each member is visible unless explicitly denied):</p>
442 * <ul>
443 * <li>java.math</li>
444 * <li>java.text</li>
445 * <li>java.time, java.time.chrono, java.time.format, java.time.temporal, java.time.zone</li>
446 * <li>java.util, java.util.concurrent, java.util.concurrent.atomic, java.util.function, java.util.stream, java.util.regex</li>
447 * <li>java.nio, java.nio.charset</li>
448 * <li>org.w3c.dom</li>
449 * <li>java.lang (minus the denied classes below)</li>
450 * <li>org.apache.commons.jexl3 (minus JexlBuilder)</li>
451 * </ul>
452 * <p>Denied classes / members (carved out of otherwise-allowed packages):</p>
453 * <ul>
454 * <li>java.lang { Runtime, System, ProcessBuilder, Process, RuntimePermission, SecurityManager, Thread, ThreadGroup, Class, ClassLoader }
455 * and the system-property readers Integer.getInteger, Long.getLong, Boolean.getBoolean</li>
456 * <li>java.io { everything except PrintWriter, Writer, StringWriter, Reader, InputStream, OutputStream }</li>
457 * <li>java.util: the classes stay visible but their file/loader members are carved out -
458 * Formatter and Scanner constructors (file I/O), Properties.load/store/loadFromXML/storeToXML/save (file I/O),
459 * ResourceBundle.getBundle/clearCache and PropertyResourceBundle constructors (property-file/class loading),
460 * ServiceLoader.load/loadInstalled (service/class loading). No file can be read or written and no class or
461 * service loaded through java.util.</li>
462 * <li>java.util.concurrent { Executors and the thread-pool / fork-join executor classes }</li>
463 * <li>java.time.zone { ZoneRulesProvider } (prevents JVM-wide time-zone provider registration)</li>
464 * <li>org.apache.commons.jexl3 { JexlBuilder }</li>
465 * </ul>
466 * <p>Notably absent (and therefore denied) are file/IO/persistence/loader-bearing packages such as
467 * {@code java.util.zip}, {@code java.util.jar}, {@code java.util.prefs}, {@code java.util.logging},
468 * {@code java.util.concurrent.locks}, {@code java.nio.file}, {@code java.lang.reflect},
469 * {@code java.lang.invoke} and {@code org.w3c.dom.ls}.</p>
470 * <p>A class is visible only when its <em>own</em> package or class declaration permits it; it is never made
471 * visible merely because one of its super-types is allowed. Consequently a foreign implementation of an allowed
472 * type (for instance a {@code java.util.Map} provided by another library) is not visible unless its own package
473 * is explicitly allowed, e.g. {@code RESTRICTED.compose("com.example.foreign +{}")}. Use {@link #logging()} to
474 * diagnose which elements are allowed or denied.</p>
475 */
476
477 JexlPermissions RESTRICTED = JexlPermissions.parse(
478 "# Default Uberspect Permissions",
479 "java.math +{}",
480 "java.text +{}",
481 "java.time +{}",
482 "java.time.chrono +{}",
483 "java.time.format +{}",
484 "java.time.temporal +{}",
485 "java.time.zone +{ -ZoneRulesProvider{} }",
486 "java.util +{" +
487 " -Formatter { Formatter(); }" +
488 " -Scanner { Scanner(); }" +
489 " -Properties { load(); store(); loadFromXML(); storeToXML(); save(); }" +
490 " -ResourceBundle { getBundle(); clearCache(); }" +
491 " -PropertyResourceBundle { PropertyResourceBundle(); }" +
492 " -ServiceLoader { load(); loadInstalled(); }" +
493 " }",
494 "java.util.concurrent +{" +
495 "-Executors{} -ExecutorService{} -AbstractExecutorService{}" +
496 "-ThreadPoolExecutor{} -ScheduledThreadPoolExecutor{} -ScheduledExecutorService{}" +
497 "-ForkJoinPool{} -ForkJoinTask{} -ForkJoinWorkerThread{}" +
498 "}",
499 "java.util.concurrent.atomic +{}",
500 "java.util.function +{}",
501 "java.util.stream +{}",
502 "java.util.regex +{}",
503 "org.w3c.dom +{}",
504 "java.lang +{" +
505 "-Runtime{} -System{} -ProcessBuilder{} -Process{}" +
506 "-RuntimePermission{} -SecurityManager{}" +
507 "-Thread{} -ThreadGroup{} -Class{} -ClassLoader{}" +
508 "-Integer { getInteger(); } -Long { getLong(); } -Boolean { getBoolean(); }" +
509 "}",
510 "java.io -{ +PrintWriter{ -PrintWriter(); } +Writer{} +StringWriter{} +Reader{} +InputStream{} +OutputStream{} }",
511 "java.nio +{}",
512 "java.nio.charset +{}",
513 "org.apache.commons.jexl3 +{ -JexlBuilder{} -JexlConfigLoader{} }"
514 );
515
516 /**
517 * An absolute-minimum, allow-list-first permission set.
518 * <p>This is the tightest sensible baseline: nothing is reachable unless explicitly whitelisted here.
519 * It exposes only the safe {@code java.lang} value types, {@code java.math} big numbers and the
520 * {@code java.util} collection types - enough for arithmetic, string and collection scripting.</p>
521 * <p>Allowed:</p>
522 * <ul>
523 * <li>{@code java.lang}: {@code Object} (minus {@code getClass}/{@code wait}/{@code notify}/{@code notifyAll}),
524 * {@code Number} and the boxed primitives, {@code String}, {@code CharSequence}, {@code StringBuilder},
525 * {@code Math}, {@code Comparable}, {@code Iterable}; everything else in {@code java.lang}
526 * (e.g. {@code System}, {@code Runtime}, {@code Thread}, {@code Class}, {@code ClassLoader}) is denied.</li>
527 * <li>{@code java.math} (for {@code BigInteger}/{@code BigDecimal}, i.e. the {@code 1B}/{@code 1H} literals).</li>
528 * <li>{@code java.util} - the collection types produced by list/map/set literals (and their iterators, views
529 * and entries), <em>minus</em> the file/loader/thread-bearing classes which are denied: {@code Formatter} and
530 * {@code Scanner} (file I/O), {@code ServiceLoader} and the {@code ResourceBundle} family (class/resource
531 * loading), {@code Properties} (file {@code load}/{@code store}) and {@code Timer}/{@code TimerTask} (threads).
532 * Because a positive package does not cover sub-packages, {@code java.util.zip}/{@code concurrent}/{@code jar}/…
533 * stay denied as well.</li>
534 * </ul>
535 * <p><strong>Guarantee:</strong> no class that SECURE allows <em>by default</em> can read or write files, read
536 * environment variables or system properties, load classes, or start threads. In particular {@code Object.getClass()}
537 * is denied (so no {@link Class} can be obtained), and the boxed-type system-property readers
538 * {@code Integer.getInteger}, {@code Long.getLong} and {@code Boolean.getBoolean} are denied.</p>
539 * <p>SECURE is nonetheless a hardened baseline, <em>not</em> a turnkey sandbox: it is not exhaustive and must not be
540 * considered sufficient on its own for executing untrusted user input. For that, define a tailored, strict whitelist
541 * of exactly what your scripts need - ideally composed on top of {@link #NONE} - rather than relying on SECURE as-is.</p>
542 * <p>Arithmetic, comparisons and string concatenation require no permission at all (they are handled by
543 * {@link org.apache.commons.jexl3.JexlArithmetic}); ranges ({@code 1..n}) iterate as a language primitive.
544 * Compose more in with {@link #compose(String...)} (e.g. {@code SECURE.compose("java.time +{}")}), and use
545 * {@link #logging()} to discover what a script is denied.</p>
546 * @since 3.7.0
547 */
548 JexlPermissions SECURE = JexlPermissions.parse(
549 "# Absolute-minimum permissions: safe java.lang value types + java.math + java.util collections",
550 "java.lang -{"
551 + " +Object{ -getClass(); -wait(); -notify(); -notifyAll(); }"
552 + " +Number{} +Boolean{ -getBoolean(); } "
553 + " +Character{} +Byte{} +Short{} +Integer{ -getInteger(); } +Long{ -getLong(); } +Float{} +Double{}"
554 + " +String{} +CharSequence{} +StringBuilder{} +Math{} +Comparable{} +Iterable{}"
555 + " }",
556 "java.math +{}",
557 "java.util +{"
558 + " -Formatter{} -Scanner{} -ServiceLoader{}"
559 + " -ResourceBundle{} -PropertyResourceBundle{} -ListResourceBundle{}"
560 + " -Properties{} -Timer{} -TimerTask{}"
561 + " }"
562 );
563
564 /**
565 * Parses a set of permissions.
566 * <p>
567 * In JEXL 3.3, the syntax recognizes 2 types of permissions:
568 * </p>
569 * <ul>
570 * <li>Allowing access to a wildcard restricted set of packages. </li>
571 * <li>Denying access to packages, classes (and inner classes), methods and fields</li>
572 * </ul>
573 * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be
574 * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards.
575 * These allow composing the volume of what is allowed by addition.</p>
576 * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation;
577 * they can restrict access to package, class, inner-class, methods and fields.
578 * These allow refining the volume of what is allowed by extrusion.</p>
579 * An example of a tight environment that would not allow scripts to wander could be:
580 * <pre>
581 * # allow a very restricted set of base classes
582 * java.math.*
583 * java.text.*
584 * java.util.*
585 * # deny classes that could pose a security risk
586 * java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
587 * org.apache.commons.jexl3 { JexlBuilder {} }
588 * </pre>
589 * <p><b>Syntax Overview:</b></p>
590 * <ul>
591 * <li>Syntax for wildcards is the name of the package suffixed by {@code .*}.</li>
592 * <li>Syntax for restrictions is a list of package restrictions.</li>
593 * <li>A package restriction is a package name followed by a block (as in curly-bracket block {})
594 * that contains a list of class restrictions.</li>
595 * <li>A class restriction is a class name prefixed by an optional {@code -} or {@code +} sign
596 * followed by a block of member restrictions.</li>
597 * <li>A member restriction can be a class restriction - to restrict
598 * nested classes -, a field which is the Java field name suffixed with {@code ;}, a method composed of
599 * its Java name suffixed with {@code ();}. Constructor restrictions are specified like methods using the
600 * class name as method name.</li>
601 * </ul>
602 * <p><b>Negative ({@code -}) vs Positive ({@code +}) Restrictions:</b></p>
603 * <ul>
604 * <li><b>Negative restriction (default or {@code -} prefix)</b>: Explicitly <b>denies</b> access to the members
605 * declared in its block. If the block is empty, the entire class is denied.
606 * <br>Example: {@code java.lang { -System { exit(); } }} denies System.exit() but allows other System methods.
607 * <br>Example: {@code java.lang { Runtime {} }} denies the entire Runtime class (empty block means deny all).</li>
608 * <li><b>Positive restriction ({@code +} prefix)</b>: Explicitly <b>allows only</b> the members declared
609 * in its block, denying all others not listed. If the block is empty, the entire class is allowed.
610 * <br>Example: {@code java.lang { +System { currentTimeMillis(); } }} allows only System.currentTimeMillis(),
611 * denying all other System methods.
612 * <br>Example: {@code java.io -{ +PrintWriter{} +Writer{} }} in the context of a denied java.io package,
613 * allows only PrintWriter and Writer classes entirely (empty blocks mean allow all members).</li>
614 * </ul>
615 * <p>
616 * All overrides and overloads of constructors or methods are allowed or restricted at the same time,
617 * the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation.
618 * </p>
619 * <p><b>Complete Example:</b></p>
620 * <pre>
621 * # some wildcards
622 * java.util.* # java.util is pretty much a must-have
623 * my.allowed.package0.*
624 * another.allowed.package1.*
625 * # nojexl like restrictions
626 * my.package.internal {} # the whole package is hidden
627 * my.package {
628 * +class4 { theMethod(); } # POSITIVE: only theMethod can be called in class4, all others denied
629 * class0 {
630 * class1 {} # NEGATIVE (default): the whole class1 is hidden
631 * class2 {
632 * class2(); # class2 constructors cannot be invoked
633 * class3 {
634 * aMethod(); # aMethod cannot be called
635 * aField; # aField cannot be accessed
636 * }
637 * } # end of class2
638 * class0(); # class0 constructors cannot be invoked
639 * method(); # method cannot be called
640 * field; # field cannot be accessed
641 * } # end class0
642 * } # end package my.package
643 * </pre>
644 *
645 * @param src the permissions source, the default (NoJexl aware) permissions if null
646 * @return the permissions instance
647 * @since 3.3
648 */
649 static JexlPermissions parse(final String... src) {
650 return new PermissionsParser().parse(src);
651 }
652
653 /**
654 * Creates a permission set from scratch: everything is denied unless a rule explicitly allows it.
655 * <p>Equivalent to composing the rules onto {@link #NONE}. Use positive declarations - for instance
656 * {@code "java.lang { +String{} }"} or {@code "java.util.*"} - to grant access; {@code create()} with no
657 * rules denies everything. This differs from {@link #parse(String...)}, whose empty form
658 * ({@link #UNRESTRICTED}) allows everything.</p>
659 *
660 * @param rules the permission DSL declarations
661 * @return the closed-world permission set
662 * @since 3.7.0
663 */
664 static JexlPermissions create(final String... rules) {
665 return NONE.compose(rules);
666 }
667
668 /**
669 * Wraps these permissions in a {@link LoggingPermissions} that logs every allow/deny decision.
670 * <p>Useful to discover which reflective elements a permission set allows or denies.</p>
671 *
672 * @return a logging view of these permissions
673 * @since 3.7.0
674 */
675 default JexlPermissions logging() {
676 return new LoggingPermissions(this);
677 }
678
679 /**
680 * Wraps these permissions in a {@link LoggingPermissions} that logs every allow/deny decision
681 * to a named logger.
682 *
683 * @param loggerName the name of the logger to log decisions to
684 * @return a logging view of these permissions
685 * @since 3.7.0
686 */
687 default JexlPermissions logging(final String loggerName) {
688 return new LoggingPermissions(loggerName, this);
689 }
690
691 /**
692 * Wraps these permissions in a {@link LoggingPermissions} that logs every allow/deny decision
693 * to the given logger.
694 *
695 * @param log the logger to log decisions to
696 * @return a logging view of these permissions
697 * @since 3.7.0
698 */
699 default JexlPermissions logging(final Log log) {
700 return new LoggingPermissions(log, this);
701 }
702
703 /**
704 * Checks whether a class allows JEXL introspection.
705 * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields
706 * as well as derived classes are visible to JEXL and cannot be used in scripts or expressions.
707 * If one of its super-classes is not allowed, tbe class is not allowed either.</p>
708 * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p>
709 *
710 * @param clazz the class to check
711 * @return true if JEXL is allowed to introspect, false otherwise
712 * @since 3.3
713 */
714 boolean allow(Class<?> clazz);
715
716 /**
717 * Checks whether a constructor allows JEXL introspection.
718 * <p>If a constructor is not allowed, the new operator cannot be used to instantiate its declared class
719 * in scripts or expressions.</p>
720 *
721 * @param ctor the constructor to check
722 * @return true if JEXL is allowed to introspect, false otherwise
723 * @since 3.3
724 */
725 boolean allow(Constructor<?> ctor);
726
727 /**
728 * Checks whether a field explicitly allows JEXL introspection.
729 * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
730 *
731 * @param field the field to check
732 * @return true if JEXL is allowed to introspect, false otherwise
733 * @since 3.3
734 */
735 boolean allow(Field field);
736
737 /**
738 * Checks whether a field explicitly allows JEXL introspection.
739 * <p>If a field is not allowed, it cannot be resolved and accessed in scripts or expressions.</p>
740 * @param clazz the class from which the field is accessed, used to check that the field is allowed for this class
741 * @param field the field to check
742 * @return true if JEXL is allowed to introspect, false otherwise
743 * @since 3.6.3
744 */
745 default boolean allow(Class<?> clazz, Field field) {
746 return allow(field);
747 }
748
749 /**
750 * Checks whether a method allows JEXL introspection.
751 * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
752 * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface
753 * explicitly disallows this method.</p>
754 *
755 * @param method the method to check
756 * @return true if JEXL is allowed to introspect, false otherwise
757 * @since 3.3
758 */
759 boolean allow(Method method);
760
761 /**
762 * Checks whether a method allows JEXL introspection.
763 * <p>If a method is not allowed, it cannot be resolved and called in scripts or expressions.</p>
764 * <p>Since methods can be overridden and overloaded, this checks that this class explicitly allows
765 * this method - superseding any superclass or interface specified permissions.</p>
766 *
767 * @param clazz the class from which the method is accessed, used to check that the method is allowed for this class
768 * @param method the method to check
769 * @return true if JEXL is allowed to introspect, false otherwise
770 * @since 3.6.3
771 */
772 default boolean allow(Class<?> clazz, Method method) {
773 return allow(method);
774 }
775
776 /**
777 * Checks whether a package allows JEXL introspection.
778 * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible
779 * to JEXL and cannot be used in scripts or expression.</p>
780 *
781 * @param pack the package
782 * @return true if JEXL is allowed to introspect, false otherwise
783 * @since 3.3
784 */
785 boolean allow(Package pack);
786
787 /**
788 * Compose these permissions with a new set.
789 * <p>This is a convenience method meant to easily give access to the packages JEXL is
790 * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code>
791 * would extend the restricted set of permissions by allowing the com.my.app package.</p>
792 *
793 * @param src the new constraints
794 * @return the new permissions
795 */
796 JexlPermissions compose(String... src);
797
798 /**
799 * Checks that a class is valid for permission check.
800 *
801 * @param clazz the class
802 * @return true if the class is not null, false otherwise
803 */
804 default boolean validate(final Class<?> clazz) {
805 return clazz != null;
806 }
807
808 /**
809 * Checks that a constructor is valid for permission check.
810 *
811 * @param constructor the constructor
812 * @return true if constructor is not null and public, false otherwise
813 */
814 default boolean validate(final Constructor<?> constructor) {
815 return constructor != null && Modifier.isPublic(constructor.getModifiers());
816 }
817
818 /**
819 * Checks that a field is valid for permission check.
820 *
821 * @param field the constructor
822 * @return true if field is not null and public, false otherwise
823 */
824 default boolean validate(final Field field) {
825 return field != null && Modifier.isPublic(field.getModifiers());
826 }
827
828 /**
829 * Checks that a method is valid for permission check.
830 *
831 * @param method the method
832 * @return true if method is not null and public, false otherwise
833 */
834 default boolean validate(final Method method) {
835 return method != null && Modifier.isPublic(method.getModifiers());
836 }
837
838 /**
839 * Checks that a package is valid for permission check.
840 *
841 * @param pack the package
842 * @return true if the class is not null, false otherwise
843 */
844 default boolean validate(final Package pack) {
845 return pack != null;
846 }
847 }