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