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