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 * http://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 18 package org.apache.commons.jexl3.introspection; 19 20 import java.util.HashMap; 21 import java.util.HashSet; 22 import java.util.Map; 23 import java.util.Set; 24 import java.util.concurrent.ConcurrentHashMap; 25 26 /** 27 * A sandbox describes permissions on a class by explicitly allowing or forbidding 28 * access to methods and properties through "allowlists" and "blocklists". 29 * 30 * <p>A <b>allowlist</b> explicitly allows methods/properties for a class;</p> 31 * 32 * <ul> 33 * <li>If a allowlist is empty and thus does not contain any names, 34 * all properties/methods are allowed for its class.</li> 35 * <li>If it is not empty, the only allowed properties/methods are the ones contained.</li> 36 * </ul> 37 * 38 * <p>A <b>blocklist</b> explicitly forbids methods/properties for a class;</p> 39 * 40 * <ul> 41 * <li>If a blocklist is empty and thus does not contain any names, 42 * all properties/methods are forbidden for its class.</li> 43 * <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li> 44 * </ul> 45 * 46 * <p>Permissions are composed of three lists, read, write, execute, each being 47 * "allow" or "block":</p> 48 * 49 * <ul> 50 * <li><b>read</b> controls readable properties </li> 51 * <li><b>write</b> controls writable properties</li> 52 * <li><b>execute</b> controls executable methods and constructor</li> 53 * </ul> 54 * 55 * <p>When specified, permissions - allow or block lists - can be created inheritable 56 * on interfaces or classes and thus applicable to their implementations or derived 57 * classes; the sandbox must be created with the 'inheritable' flag for this behavior 58 * to be triggered. Note that even in this configuration, it is still possible to 59 * add non-inheritable permissions. 60 * Adding inheritable lists to a non inheritable sandbox has no added effect; 61 * permissions only apply to their specified class.</p> 62 * 63 * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox 64 * used to built it preventing permission changes after its instantiation.</p> 65 * 66 * @since 3.0 67 */ 68 public final class JexlSandbox { 69 /** 70 * A allow set of names. 71 */ 72 static class AllowSet extends Names { 73 /** The map of controlled names and aliases. */ 74 private Map<String, String> names; 75 76 @Override 77 public boolean add(final String name) { 78 if (names == null) { 79 names = new HashMap<>(); 80 } 81 return names.put(name, name) == null; 82 } 83 84 @Override 85 public boolean alias(final String name, final String alias) { 86 if (names == null) { 87 names = new HashMap<>(); 88 } 89 return names.put(alias, name) == null; 90 } 91 92 @Override 93 protected Names copy() { 94 final AllowSet copy = new AllowSet(); 95 copy.names = names == null ? null : new HashMap<>(names); 96 return copy; 97 } 98 99 @Override 100 public String get(final String name) { 101 if (names == null) { 102 return name; 103 } 104 final String actual = names.get(name); 105 // if null is not explicitly allowed, explicit null aka NULL 106 if (name == null && actual == null && !names.containsKey(null)) { 107 return JexlSandbox.NULL; 108 } 109 return actual; 110 } 111 } 112 /** 113 * @deprecated since 3.2, use {@link BlockSet} 114 */ 115 @Deprecated 116 public static final class BlackSet extends BlockSet {} 117 /** 118 * A block set of names. 119 */ 120 static class BlockSet extends Names { 121 /** The set of controlled names. */ 122 private Set<String> names; 123 124 @Override 125 public boolean add(final String name) { 126 if (names == null) { 127 names = new HashSet<>(); 128 } 129 return names.add(name); 130 } 131 132 @Override 133 protected Names copy() { 134 final BlockSet copy = new BlockSet(); 135 copy.names = names == null ? null : new HashSet<>(names); 136 return copy; 137 } 138 139 @Override 140 public String get(final String name) { 141 // if name is null and contained in set, explicit null aka NULL 142 return names != null && !names.contains(name) ? name : name != null ? null : NULL; 143 } 144 } 145 /** 146 * A base set of names. 147 */ 148 public abstract static class Names { 149 150 /** 151 * Adds a name to this set. 152 * 153 * @param name the name to add 154 * @return true if the name was really added, false if not 155 */ 156 public abstract boolean add(String name); 157 158 /** 159 * Adds an alias to a name to this set. 160 * <p>This only has an effect on allow lists.</p> 161 * 162 * @param name the name to alias 163 * @param alias the alias 164 * @return true if the alias was added, false if it was already present 165 */ 166 public boolean alias(final String name, final String alias) { 167 return false; 168 } 169 170 /** 171 * @return a copy of these Names 172 */ 173 protected Names copy() { 174 return this; 175 } 176 177 /** 178 * Whether a given name is allowed or not. 179 * 180 * @param name the method/property name to check 181 * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise 182 */ 183 public String get(final String name) { 184 return name; 185 } 186 } 187 188 /** 189 * Contains the allow or block lists for properties and methods for a given class. 190 */ 191 public static final class Permissions { 192 /** Whether these permissions are inheritable, ie can be used by derived classes. */ 193 private final boolean inheritable; 194 /** The controlled readable properties. */ 195 private final Names read; 196 /** The controlled writable properties. */ 197 private final Names write; 198 /** The controlled methods. */ 199 private final Names execute; 200 201 /** 202 * Creates a new permissions instance. 203 * 204 * @param inherit whether these permissions are inheritable 205 * @param readFlag whether the read property list is allow or block 206 * @param writeFlag whether the write property list is allow or block 207 * @param executeFlag whether the method list is allow of block 208 */ 209 Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) { 210 this(inherit, 211 readFlag ? new AllowSet() : new BlockSet(), 212 writeFlag ? new AllowSet() : new BlockSet(), 213 executeFlag ? new AllowSet() : new BlockSet()); 214 } 215 216 /** 217 * Creates a new permissions instance. 218 * 219 * @param inherit whether these permissions are inheritable 220 * @param nread the read set 221 * @param nwrite the write set 222 * @param nexecute the method set 223 */ 224 Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) { 225 this.read = nread != null ? nread : ALLOW_NAMES; 226 this.write = nwrite != null ? nwrite : ALLOW_NAMES; 227 this.execute = nexecute != null ? nexecute : ALLOW_NAMES; 228 this.inheritable = inherit; 229 } 230 231 /** 232 * @return a copy of these permissions 233 */ 234 Permissions copy() { 235 return new Permissions(inheritable, read.copy(), write.copy(), execute.copy()); 236 } 237 238 /** 239 * Gets the set of method names in these permissions. 240 * 241 * @return the set of method names 242 */ 243 public Names execute() { 244 return execute; 245 } 246 247 /** 248 * Adds a list of executable methods names to these permissions. 249 * <p>The constructor is denoted as the empty-string, all other methods by their names.</p> 250 * 251 * @param methodNames the method names 252 * @return this instance of permissions 253 */ 254 public Permissions execute(final String... methodNames) { 255 for (final String methodName : methodNames) { 256 execute.add(methodName); 257 } 258 return this; 259 } 260 261 /** 262 * @return whether these permissions applies to derived classes. 263 */ 264 public boolean isInheritable() { 265 return inheritable; 266 } 267 268 /** 269 * Gets the set of readable property names in these permissions. 270 * 271 * @return the set of property names 272 */ 273 public Names read() { 274 return read; 275 } 276 277 /** 278 * Adds a list of readable property names to these permissions. 279 * 280 * @param propertyNames the property names 281 * @return this instance of permissions 282 */ 283 public Permissions read(final String... propertyNames) { 284 for (final String propertyName : propertyNames) { 285 read.add(propertyName); 286 } 287 return this; 288 } 289 290 /** 291 * Gets the set of writable property names in these permissions. 292 * 293 * @return the set of property names 294 */ 295 public Names write() { 296 return write; 297 } 298 299 /** 300 * Adds a list of writable property names to these permissions. 301 * 302 * @param propertyNames the property names 303 * @return this instance of permissions 304 */ 305 public Permissions write(final String... propertyNames) { 306 for (final String propertyName : propertyNames) { 307 write.add(propertyName); 308 } 309 return this; 310 } 311 } 312 313 /** 314 * @deprecated since 3.2, use {@link AllowSet} 315 */ 316 @Deprecated 317 public static final class WhiteSet extends AllowSet {} 318 319 /** 320 * The marker string for explicitly disallowed null properties. 321 */ 322 public static final String NULL = "?"; 323 324 /** 325 * The pass-thru name set. 326 */ 327 static final Names ALLOW_NAMES = new Names() { 328 @Override 329 public boolean add(final String name) { 330 return false; 331 } 332 333 @Override 334 protected Names copy() { 335 return this; 336 } 337 }; 338 339 /** 340 * The block-all name set. 341 */ 342 private static final Names BLOCK_NAMES = new Names() { 343 @Override 344 public boolean add(final String name) { 345 return false; 346 } 347 348 @Override 349 protected Names copy() { 350 return this; 351 } 352 353 @Override 354 public String get(final String name) { 355 return name == null ? NULL : null; 356 } 357 }; 358 359 /** 360 * The pass-thru permissions. 361 */ 362 private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES); 363 364 /** 365 * The block-all permissions. 366 */ 367 private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES); 368 369 /** 370 * Gets a class by name, crude mechanism for backwards (<3.2 ) compatibility. 371 * @param cname the class name 372 * @return the class 373 */ 374 static Class<?> forName(final String cname) { 375 try { 376 return Class.forName(cname); 377 } catch (final Exception xany) { 378 return null; 379 } 380 } 381 382 /** 383 * The map from class names to permissions. 384 */ 385 private final Map<String, Permissions> sandbox; 386 387 /** 388 * Whether permissions can be inherited (through implementation or extension). 389 */ 390 private final boolean inherit; 391 392 /** 393 * Default behavior, block or allow. 394 */ 395 private final boolean allow; 396 397 /** 398 * Creates a new default sandbox. 399 * <p>In the absence of explicit permissions on a class, the 400 * sandbox is a allow-box, allow-listing that class for all permissions (read, write and execute). 401 */ 402 public JexlSandbox() { 403 this(true, false, null); 404 } 405 406 /** 407 * Creates a new default sandbox. 408 * <p>A allow-box considers no permissions as "everything is allowed" when 409 * a block-box considers no permissions as "nothing is allowed". 410 * @param ab whether this sandbox is allow (true) or block (false) 411 * if no permission is explicitly defined for a class. 412 * @since 3.1 413 */ 414 public JexlSandbox(final boolean ab) { 415 this(ab, false, null); 416 } 417 418 /** 419 * Creates a sandbox. 420 * @param ab whether this sandbox is allow (true) or block (false) 421 * @param inh whether permissions on interfaces and classes are inherited (true) or not (false) 422 * @since 3.2 423 */ 424 public JexlSandbox(final boolean ab, final boolean inh) { 425 this(ab, inh, null); 426 } 427 428 /** 429 * Creates a sandbox based on an existing permissions map. 430 * @param ab whether this sandbox is allow (true) or block (false) 431 * @param inh whether permissions are inherited, default false 432 * @param map the permissions map 433 * @since 3.2 434 */ 435 protected JexlSandbox(final boolean ab, final boolean inh, final Map<String, Permissions> map) { 436 allow = ab; 437 inherit = inh; 438 sandbox = map != null ? map : new HashMap<>(); 439 } 440 441 /** 442 * Creates a sandbox based on an existing permissions map. 443 * @param ab whether this sandbox is allow (true) or block (false) 444 * @param map the permissions map 445 * @since 3.1 446 */ 447 @Deprecated 448 protected JexlSandbox(final boolean ab, final Map<String, Permissions> map) { 449 this(ab, false, map); 450 } 451 452 /** 453 * Creates a sandbox based on an existing permissions map. 454 * @param map the permissions map 455 */ 456 @Deprecated 457 protected JexlSandbox(final Map<String, Permissions> map) { 458 this(true, false, map); 459 } 460 461 /** 462 * Creates a new set of permissions based on allow lists for methods and properties for a given class. 463 * <p>The sandbox inheritance property will apply to the permissions created by this method 464 * 465 * @param clazz the allowed class name 466 * @return the permissions instance 467 */ 468 public Permissions allow(final String clazz) { 469 return permissions(clazz, true, true, true); 470 } 471 472 /** 473 * Use block() instead. 474 * @param clazz the allowed class name 475 * @return the permissions instance 476 */ 477 @Deprecated 478 public Permissions black(final String clazz) { 479 return block(clazz); 480 } 481 482 /** 483 * Creates a new set of permissions based on block lists for methods and properties for a given class. 484 * <p>The sandbox inheritance property will apply to the permissions created by this method 485 * 486 * @param clazz the blocked class name 487 * @return the permissions instance 488 */ 489 public Permissions block(final String clazz) { 490 return permissions(clazz, false, false, false); 491 } 492 493 /** 494 * @return a copy of this sandbox 495 */ 496 public JexlSandbox copy() { 497 // modified concurrently at runtime so... 498 final Map<String, Permissions> map = new ConcurrentHashMap<>(); 499 for (final Map.Entry<String, Permissions> entry : sandbox.entrySet()) { 500 map.put(entry.getKey(), entry.getValue().copy()); 501 } 502 return new JexlSandbox(allow, inherit, map); 503 } 504 505 /** 506 * Gets the execute permission value for a given method of a class. 507 * 508 * @param clazz the class 509 * @param name the method name 510 * @return null if not allowed, the name of the method to use otherwise 511 */ 512 public String execute(final Class<?> clazz, final String name) { 513 final String m = get(clazz).execute().get(name); 514 return "".equals(name) && m != null ? clazz.getName() : m; 515 } 516 517 /** 518 * Gets the execute permission value for a given method of a class. 519 * 520 * @param clazz the class name 521 * @param name the method name 522 * @return null if not allowed, the name of the method to use otherwise 523 */ 524 @Deprecated 525 public String execute(final String clazz, final String name) { 526 final String m = get(clazz).execute().get(name); 527 return "".equals(name) && m != null ? clazz : m; 528 } 529 /** 530 * Gets the permissions associated to a class. 531 * @param clazz the class 532 * @return the permissions 533 */ 534 @SuppressWarnings("null") // clazz can not be null since permissions would be not null and block; 535 public Permissions get(final Class<?> clazz) { 536 Permissions permissions = clazz == null ? BLOCK_ALL : sandbox.get(clazz.getName()); 537 if (permissions == null) { 538 if (inherit) { 539 // find first inherited interface that defines permissions 540 for (final Class<?> inter : clazz.getInterfaces()) { 541 permissions = sandbox.get(inter.getName()); 542 if (permissions != null) { 543 if (permissions.isInheritable()) { 544 break; 545 } 546 permissions = null; 547 } 548 } 549 // nothing defined yet, find first superclass that defines permissions 550 if (permissions == null) { 551 // lets walk all super classes 552 Class<?> zuper = clazz.getSuperclass(); 553 // walk all superclasses 554 while (zuper != null) { 555 permissions = sandbox.get(zuper.getName()); 556 if (permissions != null) { 557 if (permissions.isInheritable()) { 558 break; 559 } 560 permissions = null; 561 } 562 zuper = zuper.getSuperclass(); 563 } 564 } 565 // nothing was inheritable 566 if (permissions == null) { 567 permissions = allow ? ALLOW_ALL : BLOCK_ALL; 568 } 569 // store the info to avoid doing this costly look up 570 sandbox.put(clazz.getName(), permissions); 571 } else { 572 permissions = allow ? ALLOW_ALL : BLOCK_ALL; 573 } 574 } 575 return permissions; 576 } 577 578 /** 579 * Gets the set of permissions associated to a class. 580 * 581 * @param clazz the class name 582 * @return the defined permissions or an all-allow permission instance if none were defined 583 */ 584 public Permissions get(final String clazz) { 585 if (inherit) { 586 return get(forName(clazz)); 587 } 588 final Permissions permissions = sandbox.get(clazz); 589 if (permissions == null) { 590 return allow ? ALLOW_ALL : BLOCK_ALL; 591 } 592 return permissions; 593 } 594 595 /** 596 * Creates the set of permissions for a given class. 597 * <p>The sandbox inheritance property will apply to the permissions created by this method 598 * 599 * @param clazz the class for which these permissions apply 600 * @param readFlag whether the readable property list is allow - true - or block - false - 601 * @param writeFlag whether the writable property list is allow - true - or block - false - 602 * @param executeFlag whether the executable method list is allow - true - or block - false - 603 * @return the set of permissions 604 */ 605 public Permissions permissions(final String clazz, 606 final boolean readFlag, 607 final boolean writeFlag, 608 final boolean executeFlag) { 609 return permissions(clazz, inherit, readFlag, writeFlag, executeFlag); 610 } 611 612 /** 613 * Creates the set of permissions for a given class. 614 * 615 * @param clazz the class for which these permissions apply 616 * @param inhf whether these permissions are inheritable 617 * @param readf whether the readable property list is allow - true - or block - false - 618 * @param writef whether the writable property list is allow - true - or block - false - 619 * @param execf whether the executable method list is allow - true - or block - false - 620 * @return the set of permissions 621 */ 622 public Permissions permissions(final String clazz, 623 final boolean inhf, 624 final boolean readf, 625 final boolean writef, 626 final boolean execf) { 627 final Permissions box = new Permissions(inhf, readf, writef, execf); 628 sandbox.put(clazz, box); 629 return box; 630 } 631 /** 632 * Gets the read permission value for a given property of a class. 633 * 634 * @param clazz the class 635 * @param name the property name 636 * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise 637 */ 638 public String read(final Class<?> clazz, final String name) { 639 return get(clazz).read().get(name); 640 } 641 642 /** 643 * Gets the read permission value for a given property of a class. 644 * 645 * @param clazz the class name 646 * @param name the property name 647 * @return null if not allowed, the name of the property to use otherwise 648 */ 649 @Deprecated 650 public String read(final String clazz, final String name) { 651 return get(clazz).read().get(name); 652 } 653 654 /** 655 * Use allow() instead. 656 * @param clazz the allowed class name 657 * @return the permissions instance 658 */ 659 @Deprecated 660 public Permissions white(final String clazz) { 661 return allow(clazz); 662 } 663 664 /** 665 * Gets the write permission value for a given property of a class. 666 * 667 * @param clazz the class 668 * @param name the property name 669 * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise 670 */ 671 public String write(final Class<?> clazz, final String name) { 672 return get(clazz).write().get(name); 673 } 674 675 /** 676 * Gets the write permission value for a given property of a class. 677 * 678 * @param clazz the class name 679 * @param name the property name 680 * @return null if not allowed, the name of the property to use otherwise 681 */ 682 @Deprecated 683 public String write(final String clazz, final String name) { 684 return get(clazz).write().get(name); 685 } 686 687 }