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 */ 017 018package org.apache.commons.jexl3.introspection; 019 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026 027/** 028 * A sandbox describes permissions on a class by explicitly allowing or forbidding 029 * access to methods and properties through "allowlists" and "blocklists". 030 * 031 * <p>A <strong>allowlist</strong> explicitly allows methods/properties for a class;</p> 032 * 033 * <ul> 034 * <li>If a allowlist is empty and thus does not contain any names, 035 * all properties/methods are allowed for its class.</li> 036 * <li>If it is not empty, the only allowed properties/methods are the ones contained.</li> 037 * </ul> 038 * 039 * <p>A <strong>blocklist</strong> explicitly forbids methods/properties for a class;</p> 040 * 041 * <ul> 042 * <li>If a blocklist is empty and thus does not contain any names, 043 * all properties/methods are forbidden for its class.</li> 044 * <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li> 045 * </ul> 046 * 047 * <p>Permissions are composed of three lists, read, write, execute, each being 048 * "allow" or "block":</p> 049 * 050 * <ul> 051 * <li><strong>read</strong> controls readable properties </li> 052 * <li><strong>write</strong> controls writable properties</li> 053 * <li><strong>execute</strong> controls executable methods and constructor</li> 054 * </ul> 055 * 056 * <p>When specified, permissions - allow or block lists - can be created inheritable 057 * on interfaces or classes and thus applicable to their implementations or derived 058 * classes; the sandbox must be created with the 'inheritable' flag for this behavior 059 * to be triggered. Note that even in this configuration, it is still possible to 060 * add non-inheritable permissions. 061 * Adding inheritable lists to a non inheritable sandbox has no added effect; 062 * permissions only apply to their specified class.</p> 063 * 064 * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox 065 * used to built it preventing permission changes after its instantiation.</p> 066 * 067 * @since 3.0 068 */ 069public final class JexlSandbox { 070 071 /** 072 * The marker string for explicitly disallowed null properties. 073 */ 074 public static final String NULL = "?"; 075 076 /** 077 * The pass-thru name set. 078 */ 079 static final Names ALLOW_NAMES = new Names() { 080 @Override 081 public boolean add(final String name) { 082 return false; 083 } 084 085 @Override 086 public String toString() { 087 return "allowAll"; 088 } 089 }; 090 091 /** 092 * The block-all name set. 093 */ 094 private static final Names BLOCK_NAMES = new Names() { 095 @Override 096 public boolean add(final String name) { 097 return false; 098 } 099 100 @Override 101 public String get(final String name) { 102 return name == null ? NULL : null; 103 } 104 105 @Override 106 public String toString() { 107 return "blockAll"; 108 } 109 }; 110 111 /** 112 * The block-all permissions. 113 */ 114 private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES); 115 116 /** 117 * The pass-thru permissions. 118 */ 119 private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES); 120 121 /** 122 * The map from class names to permissions. 123 */ 124 private final Map<String, Permissions> sandbox; 125 126 /** 127 * Whether permissions can be inherited (through implementation or extension). 128 */ 129 private final boolean inherit; 130 131 /** 132 * Default behavior, block or allow. 133 */ 134 private final boolean allow; 135 136 /** 137 * Creates a new default sandbox. 138 * <p>In the absence of explicit permissions on a class, the 139 * sandbox is an allow-box, allow-listing that class for all permissions (read, write and execute). 140 */ 141 public JexlSandbox() { 142 this(true, false, null); 143 } 144 145 /** 146 * Creates a new default sandbox. 147 * <p>A allow-box considers no permissions as "everything is allowed" when 148 * a block-box considers no permissions as "nothing is allowed". 149 * 150 * @param ab whether this sandbox is allow (true) or block (false) 151 * if no permission is explicitly defined for a class. 152 * @since 3.1 153 */ 154 public JexlSandbox(final boolean ab) { 155 this(ab, false, null); 156 } 157 158 /** 159 * Creates a sandbox. 160 * 161 * @param ab whether this sandbox is allow (true) or block (false) 162 * @param inh whether permissions on interfaces and classes are inherited (true) or not (false) 163 * @since 3.2 164 */ 165 public JexlSandbox(final boolean ab, final boolean inh) { 166 this(ab, inh, null); 167 } 168 169 /** 170 * Creates a sandbox based on an existing permissions map. 171 * 172 * @param ab whether this sandbox is allow (true) or block (false) 173 * @param inh whether permissions are inherited, default false 174 * @param map the permissions map 175 * @since 3.2 176 */ 177 private JexlSandbox(final boolean ab, final boolean inh, final Map<String, Permissions> map) { 178 allow = ab; 179 inherit = inh; 180 sandbox = map != null ? map : new HashMap<>(); 181 } 182 183 /** 184 * Gets a class by name, crude mechanism for backwards (<3.2 ) compatibility. 185 * 186 * @param cname the class name 187 * @return the class 188 */ 189 static Class<?> forName(final String cname) { 190 try { 191 return Class.forName(cname); 192 } catch (final Exception xany) { 193 return null; 194 } 195 } 196 197 /** 198 * Creates a new set of permissions based on allow lists for methods and properties for a given class. 199 * <p>The sandbox inheritance property will apply to the permissions created by this method 200 * 201 * @param clazz the allowed class name 202 * @return the permissions instance 203 */ 204 public Permissions allow(final String clazz) { 205 return permissions(clazz, true, true, true); 206 } 207 208 /** 209 * Creates a new set of permissions based on block lists for methods and properties for a given class. 210 * <p>The sandbox inheritance property will apply to the permissions created by this method 211 * 212 * @param clazz the blocked class name 213 * @return the permissions instance 214 */ 215 public Permissions block(final String clazz) { 216 return permissions(clazz, false, false, false); 217 } 218 219 /** 220 * Gets a copy of this sandbox 221 * 222 * @return a copy of this sandbox 223 */ 224 public JexlSandbox copy() { 225 // modified concurrently at runtime so... 226 final Map<String, Permissions> map = new ConcurrentHashMap<>(); 227 for (final Map.Entry<String, Permissions> entry : sandbox.entrySet()) { 228 map.put(entry.getKey(), entry.getValue().copy()); 229 } 230 return new JexlSandbox(allow, inherit, map); 231 } 232 233 /** 234 * Gets the execute permission value for a given method of a class. 235 * 236 * @param clazz the class 237 * @param name the method name 238 * @return null if not allowed, the name of the method to use otherwise 239 */ 240 public String execute(final Class<?> clazz, final String name) { 241 final String m = get(clazz).execute().get(name); 242 return "".equals(name) && m != null ? clazz.getName() : m; 243 } 244 245 /** 246 * Gets the execute permission value for a given method of a class. 247 * 248 * @param clazz the class name 249 * @param name the method name 250 * @return null if not allowed, the name of the method to use otherwise 251 * @deprecated 3.3 252 */ 253 @Deprecated 254 public String execute(final String clazz, final String name) { 255 final String m = get(clazz).execute().get(name); 256 return "".equals(name) && m != null ? clazz : m; 257 } 258 259 /** 260 * Gets the set of permissions associated to a class. 261 * 262 * @param clazz the class name 263 * @return the defined permissions or an all-allow permission instance if none were defined 264 */ 265 public Permissions get(final String clazz) { 266 return get(forName(clazz)); 267 } 268 269 /** 270 * Gets the permissions associated to a class. 271 * 272 * @param clazz the class 273 * @return the permissions 274 */ 275 @SuppressWarnings("null") 276 public Permissions get(final Class<?> clazz) { 277 // argument clazz cannot be null since permissions would be not null and block: 278 // we only store the result for classes we actively seek permissions for. 279 return compute(clazz, true); 280 } 281 282 private static Permissions inheritable(final Permissions p) { 283 return p != null && p.isInheritable() ? p : null; 284 } 285 286 /** 287 * Computes and optionally stores the permissions associated to a class. 288 * 289 * @param clazz the class 290 * @param store whether the computed permissions should be stored in the sandbox 291 * @return the permissions 292 */ 293 private Permissions compute(final Class<?> clazz, final boolean store) { 294 // belt and suspender; recursion should not lead here 295 if (clazz == null) { 296 return BLOCK_ALL; 297 } 298 final String className = clazz.getName(); 299 Permissions permissions = sandbox.get(className); 300 if (permissions == null) { 301 if (inherit) { 302 // find first inherited interface that defines permissions 303 final Class<?>[] interfaces = clazz.getInterfaces(); 304 for (int i = 0; permissions == null && i < interfaces.length; ++i) { 305 permissions = inheritable(compute(interfaces[i], false)); 306 } 307 // nothing defined yet, find first superclass that defines permissions 308 if (permissions == null) { 309 // let's recurse on super classes 310 final Class<?> superClazz = clazz.getSuperclass(); 311 if (null != superClazz) { 312 permissions = inheritable(compute(superClazz, false)); 313 } 314 } 315 } 316 // nothing was inheritable 317 if (permissions == null) { 318 permissions = allow ? ALLOW_ALL : BLOCK_ALL; 319 } 320 // store the info to avoid doing this costly look-up 321 if (store) { 322 sandbox.put(className, permissions); 323 } 324 } 325 return permissions; 326 } 327 328 /** 329 * Creates the set of permissions for a given class. 330 * <p>The sandbox inheritance property will apply to the permissions created by this method 331 * 332 * @param clazz the class for which these permissions apply 333 * @param readFlag whether the readable property list is allow - true - or block - false - 334 * @param writeFlag whether the writable property list is allow - true - or block - false - 335 * @param executeFlag whether the executable method list is allow - true - or block - false - 336 * @return the set of permissions 337 */ 338 public Permissions permissions(final String clazz, 339 final boolean readFlag, 340 final boolean writeFlag, 341 final boolean executeFlag) { 342 return permissions(clazz, inherit, readFlag, writeFlag, executeFlag); 343 } 344 345 /** 346 * Creates the set of permissions for a given class. 347 * 348 * @param clazz the class for which these permissions apply 349 * @param inhf whether these permissions are inheritable 350 * @param readf whether the readable property list is allow - true - or block - false - 351 * @param writef whether the writable property list is allow - true - or block - false - 352 * @param execf whether the executable method list is allow - true - or block - false - 353 * @return the set of permissions 354 */ 355 public Permissions permissions(final String clazz, 356 final boolean inhf, 357 final boolean readf, 358 final boolean writef, 359 final boolean execf) { 360 final Permissions box = new Permissions(inhf, readf, writef, execf); 361 sandbox.put(clazz, box); 362 return box; 363 } 364 365 /** 366 * Gets the read permission value for a given property of a class. 367 * 368 * @param clazz the class 369 * @param name the property name 370 * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise 371 */ 372 public String read(final Class<?> clazz, final String name) { 373 return get(clazz).read().get(name); 374 } 375 376 /** 377 * Gets the write permission value for a given property of a class. 378 * 379 * @param clazz the class 380 * @param name the property name 381 * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise 382 */ 383 public String write(final Class<?> clazz, final String name) { 384 return get(clazz).write().get(name); 385 } 386 387 /** 388 * Gets the write permission value for a given property of a class. 389 * 390 * @param clazz the class name 391 * @param name the property name 392 * @return null if not allowed, the name of the property to use otherwise 393 * @deprecated 3.3 394 */ 395 @Deprecated 396 public String write(final String clazz, final String name) { 397 return get(clazz).write().get(name); 398 } 399 400 /** 401 * An allow set of names. 402 */ 403 static class AllowSet extends Names { 404 405 /** 406 * The map of controlled names and aliases. 407 */ 408 private Map<String, String> names; 409 410 @Override 411 public boolean add(final String name) { 412 if (names == null) { 413 names = new HashMap<>(); 414 } 415 return names.put(name, name) == null; 416 } 417 418 @Override 419 public boolean alias(final String name, final String alias) { 420 if (names == null) { 421 names = new HashMap<>(); 422 } 423 return names.put(alias, name) == null; 424 } 425 426 @Override 427 protected Names copy() { 428 final AllowSet copy = new AllowSet(); 429 copy.names = names == null ? null : new HashMap<>(names); 430 return copy; 431 } 432 433 @Override 434 public String get(final String name) { 435 if (names == null) { 436 return name; 437 } 438 final String actual = names.get(name); 439 // if null is not explicitly allowed, explicit null aka NULL 440 if (name == null && actual == null && !names.containsKey(null)) { 441 return JexlSandbox.NULL; 442 } 443 return actual; 444 } 445 446 @Override 447 public String toString() { 448 return "allow{" + (names == null ? "all" : Objects.toString(names.entrySet())) + "}"; 449 } 450 } 451 452 /** 453 * A block set of names. 454 */ 455 static class BlockSet extends Names { 456 457 /** 458 * The set of controlled names. 459 */ 460 private Set<String> names; 461 462 @Override 463 public boolean add(final String name) { 464 if (names == null) { 465 names = new HashSet<>(); 466 } 467 return names.add(name); 468 } 469 470 @Override 471 protected Names copy() { 472 final BlockSet copy = new BlockSet(); 473 copy.names = names == null ? null : new HashSet<>(names); 474 return copy; 475 } 476 477 @Override 478 public String get(final String name) { 479 // if name is null and contained in set, explicit null aka NULL 480 if (names != null && !names.contains(name)) { 481 return name; 482 } 483 if (name != null) { 484 return null; 485 } 486 return NULL; 487 } 488 489 @Override 490 public String toString() { 491 return "block{" + (names == null ? "all" : Objects.toString(names)) + "}"; 492 } 493 } 494 495 /** 496 * A base set of names. 497 */ 498 public abstract static class Names { 499 500 /** Default constructor */ 501 public Names() {} // Keep Javadoc happy 502 503 /** 504 * Adds a name to this set. 505 * 506 * @param name the name to add 507 * @return true if the name was really added, false if not 508 */ 509 public abstract boolean add(String name); 510 511 /** 512 * Adds an alias to a name to this set. 513 * <p>This only has an effect on allow lists.</p> 514 * 515 * @param name the name to alias 516 * @param alias the alias 517 * @return true if the alias was added, false if it was already present 518 */ 519 public boolean alias(final String name, final String alias) { 520 return false; 521 } 522 523 /** 524 * Gets a copy of these Names 525 * 526 * @return a copy of these Names 527 */ 528 protected Names copy() { 529 return this; 530 } 531 532 /** 533 * Gets whether a given name is allowed or not. 534 * 535 * @param name the method/property name to check 536 * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise 537 */ 538 public String get(final String name) { 539 return name; 540 } 541 } 542 543 /** 544 * Contains the allow or block lists for properties and methods for a given class. 545 */ 546 public static final class Permissions { 547 548 /** 549 * Whether these permissions are inheritable, ie can be used by derived classes. 550 */ 551 private final boolean inheritable; 552 553 /** 554 * The controlled readable properties. 555 */ 556 private final Names read; 557 558 /** 559 * The controlled writable properties. 560 */ 561 private final Names write; 562 563 /** 564 * The controlled methods. 565 */ 566 private final Names execute; 567 568 /** 569 * Creates a new permissions instance. 570 * 571 * @param inherit whether these permissions are inheritable 572 * @param readFlag whether the read property list is allow or block 573 * @param writeFlag whether the write property list is allow or block 574 * @param executeFlag whether the method list is allow of block 575 */ 576 Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) { 577 this(inherit, 578 readFlag ? new AllowSet() : new BlockSet(), 579 writeFlag ? new AllowSet() : new BlockSet(), 580 executeFlag ? new AllowSet() : new BlockSet()); 581 } 582 583 /** 584 * Creates a new permissions instance. 585 * 586 * @param inherit whether these permissions are inheritable 587 * @param nread the read set 588 * @param nwrite the write set 589 * @param nexecute the method set 590 */ 591 Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) { 592 this.read = nread != null ? nread : ALLOW_NAMES; 593 this.write = nwrite != null ? nwrite : ALLOW_NAMES; 594 this.execute = nexecute != null ? nexecute : ALLOW_NAMES; 595 this.inheritable = inherit; 596 } 597 598 /** 599 * @return a copy of these permissions 600 */ 601 Permissions copy() { 602 return new Permissions(inheritable, read.copy(), write.copy(), execute.copy()); 603 } 604 605 /** 606 * Gets the set of method names in these permissions. 607 * 608 * @return the set of method names 609 */ 610 public Names execute() { 611 return execute; 612 } 613 614 /** 615 * Adds a list of executable methods names to these permissions. 616 * <p>The constructor is denoted as the empty-string, all other methods by their names.</p> 617 * 618 * @param methodNames the method names 619 * @return {@code this} instance of permissions 620 */ 621 public Permissions execute(final String... methodNames) { 622 for (final String methodName : methodNames) { 623 execute.add(methodName); 624 } 625 return this; 626 } 627 628 /** 629 * Do these permissions apply to derived classes? 630 * 631 * @return whether these permissions apply to derived classes. 632 */ 633 public boolean isInheritable() { 634 return inheritable; 635 } 636 637 /** 638 * Gets the set of readable property names in these permissions. 639 * 640 * @return the set of property names 641 */ 642 public Names read() { 643 return read; 644 } 645 646 /** 647 * Adds a list of readable property names to these permissions. 648 * 649 * @param propertyNames the property names 650 * @return {@code this} instance of permissions 651 */ 652 public Permissions read(final String... propertyNames) { 653 for (final String propertyName : propertyNames) { 654 read.add(propertyName); 655 } 656 return this; 657 } 658 659 /** 660 * Gets the set of writable property names in these permissions. 661 * 662 * @return the set of property names 663 */ 664 public Names write() { 665 return write; 666 } 667 668 /** 669 * Adds a list of writable property names to these permissions. 670 * 671 * @param propertyNames the property names 672 * @return {@code this} instance of permissions 673 */ 674 public Permissions write(final String... propertyNames) { 675 for (final String propertyName : propertyNames) { 676 write.add(propertyName); 677 } 678 return this; 679 } 680 } 681 682 /** 683 * @deprecated since 3.2, use {@link BlockSet} 684 */ 685 @Deprecated 686 public static final class BlackSet extends BlockSet { 687 688 /** Default constructor */ 689 public BlackSet() { } // Keep Javadoc happy 690 } 691 692 /** 693 * @deprecated since 3.2, use {@link AllowSet} 694 */ 695 @Deprecated 696 public static final class WhiteSet extends AllowSet { 697 698 /** Default constructor */ 699 public WhiteSet() { } // Keep Javadoc happy 700 } 701 702 /** 703 * Use block() instead. 704 * 705 * @param clazz the blocked class name 706 * @return the permissions instance 707 * @deprecated 3.3 708 */ 709 @Deprecated 710 public Permissions black(final String clazz) { 711 return block(clazz); 712 } 713 714 /** 715 * Gets the read permission value for a given property of a class. 716 * 717 * @param clazz the class name 718 * @param name the property name 719 * @return null if not allowed, the name of the property to use otherwise 720 * @deprecated 3.3 721 */ 722 @Deprecated 723 public String read(final String clazz, final String name) { 724 return get(clazz).read().get(name); 725 } 726 727 /** 728 * Use allow() instead. 729 * 730 * @param clazz the allowed class name 731 * @return the permissions instance 732 * @deprecated 3.3 733 */ 734 @Deprecated 735 public Permissions white(final String clazz) { 736 return allow(clazz); 737 } 738}