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