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