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 * https://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.Objects;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 /**
28 * A sandbox describes permissions on a class by explicitly allowing or forbidding
29 * access to methods and properties through "allowlists" and "blocklists".
30 *
31 * <p>A <strong>allowlist</strong> explicitly allows methods/properties for a class;</p>
32 *
33 * <ul>
34 * <li>If a allowlist is empty and thus does not contain any names,
35 * all properties/methods are allowed for its class.</li>
36 * <li>If it is not empty, the only allowed properties/methods are the ones contained.</li>
37 * </ul>
38 *
39 * <p>A <strong>blocklist</strong> explicitly forbids methods/properties for a class;</p>
40 *
41 * <ul>
42 * <li>If a blocklist is empty and thus does not contain any names,
43 * all properties/methods are forbidden for its class.</li>
44 * <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li>
45 * </ul>
46 *
47 * <p>Permissions are composed of three lists, read, write, execute, each being
48 * "allow" or "block":</p>
49 *
50 * <ul>
51 * <li><strong>read</strong> controls readable properties </li>
52 * <li><strong>write</strong> controls writable properties</li>
53 * <li><strong>execute</strong> controls executable methods and constructor</li>
54 * </ul>
55 *
56 * <p>When specified, permissions - allow or block lists - can be created inheritable
57 * on interfaces or classes and thus applicable to their implementations or derived
58 * classes; the sandbox must be created with the 'inheritable' flag for this behavior
59 * to be triggered. Note that even in this configuration, it is still possible to
60 * add non-inheritable permissions.
61 * Adding inheritable lists to a non inheritable sandbox has no added effect;
62 * permissions only apply to their specified class.</p>
63 *
64 * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox
65 * used to built it preventing permission changes after its instantiation.</p>
66 *
67 * @since 3.0
68 */
69 public final class JexlSandbox {
70
71 /**
72 * The marker string for explicitly disallowed null properties.
73 */
74 public static final String NULL = "?";
75
76 /**
77 * The pass-thru name set.
78 */
79 static final Names ALLOW_NAMES = new Names() {
80 @Override
81 public boolean add(final String name) {
82 return false;
83 }
84
85 @Override
86 public String toString() {
87 return "allowAll";
88 }
89 };
90
91 /**
92 * The block-all name set.
93 */
94 private static final Names BLOCK_NAMES = new Names() {
95 @Override
96 public boolean add(final String name) {
97 return false;
98 }
99
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 }