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 * http://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 * The marker string for explicitly disallowed null properties.
72 */
73 public static final String NULL = "?";
74
75 /**
76 * The pass-thru name set.
77 */
78 static final Names ALLOW_NAMES = new Names() {
79 @Override
80 public boolean add(final String name) {
81 return false;
82 }
83
84 @Override
85 public String toString() {
86 return "allowAll";
87 }
88 };
89
90 /**
91 * The block-all name set.
92 */
93 private static final Names BLOCK_NAMES = new Names() {
94 @Override
95 public boolean add(final String name) {
96 return false;
97 }
98
99 @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 }