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.concurrent.ConcurrentHashMap; 029import java.util.stream.Collectors; 030 031import org.apache.commons.jexl3.internal.introspection.PermissionsParser; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034 035/** 036 * This interface describes permissions used by JEXL introspection that constrain which 037 * packages/classes/constructors/fields/methods are made visible to JEXL scripts. 038 * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated 039 * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control 040 * over what can be executed. JEXL introspection mechanism will check whether it is permitted to 041 * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions 042 * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}. 043 * </p> 044 * <p><strong>Security disclaimer.</strong> Neither {@link #RESTRICTED} nor {@link #SECURE} is exhaustive, and neither 045 * must be considered completely safe or sufficient on its own for executing untrusted user input. They are hardened 046 * baselines, not guarantees. Any application that evaluates untrusted scripts <em>must</em> define its own tailored, 047 * strict whitelist of exactly the classes, methods and fields its scripts legitimately need - ideally by composing on 048 * top of {@link #NONE} (which denies everything) via {@link #create(String...)} / {@link #compose(String...)} - and 049 * audit the result with {@link #logging()}.</p> 050 * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated - 051 * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl} 052 * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments; 053 * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be 054 * processed.</p> 055 * <p>A simple textual configuration can be used to create user-defined permissions using 056 * {@link JexlPermissions#parse(String...)}. The permission syntax supports both positive (+) and negative (-) 057 * declarations:</p> 058 * <ul> 059 * <li><b>Negative restrictions ({@code -})</b>: By default or when prefixed with {@code -}, class restrictions 060 * explicitly <b>deny</b> access to the specified members (or the entire class if the block is empty). 061 * This is the default mode and works like {@link org.apache.commons.jexl3.annotations.NoJexl}.</li> 062 * <li><b>Positive restrictions ({@code +})</b>: When prefixed with {@code +}, class restrictions 063 * explicitly <b>allow only</b> the specified members (or the entire class if the block is empty), denying 064 * all others. This provides a whitelist approach where you must explicitly list what is permitted.</li> 065 * </ul> 066 * <p>For example:</p> 067 * <pre> 068 * // Deny specific methods in a class (negative restriction - default) 069 * java.lang { System { exit(); } } // or -System { exit(); } 070 * 071 * // Allow only specific methods in a class (positive restriction) 072 * java.lang { +System { currentTimeMillis(); nanoTime(); } } 073 * 074 * // Allow entire class (positive restriction with empty block) 075 * java.io -{ +PrintWriter{} +Writer{} } 076 * </pre> 077 * 078 * <p>To build a policy from scratch, start from {@link #NONE} (or {@link #create(String...)}), which denies 079 * everything, and compose only what scripts need on top - the closed-world, deny-by-default approach. This is the 080 * opposite of {@link #UNRESTRICTED} (the empty {@link #parse(String...)}), which allows everything.</p> 081 * 082 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder} 083 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would 084 * be to instantiate a {@link JexlUberspect} with those permissions and call 085 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p> 086 * 087 * <p> 088 * To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior 089 * by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with 090 * {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance. 091 * </p> 092 * <p> 093 * For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to 094 * JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)} 095 * with {@link #UNRESTRICTED} as parameter. 096 * </p> 097 * 098 * @since 3.3 099 */ 100public 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}