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