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