001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.jexl3.introspection;
019
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024import java.util.concurrent.ConcurrentHashMap;
025
026/**
027 * A sandbox describes permissions on a class by explicitly allowing or forbidding
028 * access to methods and properties through "allowlists" and "blocklists".
029 *
030 * <p>A <b>allowlist</b> explicitly allows methods/properties for a class;</p>
031 *
032 * <ul>
033 *   <li>If a allowlist is empty and thus does not contain any names,
034 *       all properties/methods are allowed for its class.</li>
035 *   <li>If it is not empty, the only allowed properties/methods are the ones contained.</li>
036 * </ul>
037 *
038 * <p>A <b>blocklist</b> explicitly forbids methods/properties for a class;</p>
039 *
040 * <ul>
041 *   <li>If a blocklist is empty and thus does not contain any names,
042 *       all properties/methods are forbidden for its class.</li>
043 *   <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li>
044 * </ul>
045 *
046 * <p>Permissions are composed of three lists, read, write, execute, each being
047 * "allow" or "block":</p>
048 *
049 * <ul>
050 *   <li><b>read</b> controls readable properties </li>
051 *   <li><b>write</b> controls writable properties</li>
052 *   <li><b>execute</b> controls executable methods and constructor</li>
053 * </ul>
054 *
055 * <p>When specified, permissions - allow or block lists - can be created inheritable
056 * on interfaces or classes and thus applicable to their implementations or derived
057 * classes; the sandbox must be created with the 'inheritable' flag for this behavior
058 * to be triggered. Note that even in this configuration, it is still possible to
059 * add non-inheritable permissions.
060 * Adding inheritable lists to a non inheritable sandbox has no added effect;
061 * permissions only apply to their specified class.</p>
062 *
063 * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox
064 * used to built it preventing permission changes after its instantiation.</p>
065 *
066 * @since 3.0
067 */
068public final class JexlSandbox {
069    /**
070     * The marker string for explicitly disallowed null properties.
071     */
072    public static final String NULL = "?";
073    /**
074     * The map from class names to permissions.
075     */
076    private final Map<String, Permissions> sandbox;
077    /**
078     * Whether permissions can be inherited (through implementation or extension).
079     */
080    private final boolean inherit;
081    /**
082     * Default behavior, block or allow.
083     */
084    private final boolean allow;
085
086    /**
087     * Creates a new default sandbox.
088     * <p>In the absence of explicit permissions on a class, the
089     * sandbox is a allow-box, allow-listing that class for all permissions (read, write and execute).
090     */
091    public JexlSandbox() {
092        this(true, false, null);
093    }
094
095    /**
096     * Creates a new default sandbox.
097     * <p>A allow-box considers no permissions as &quot;everything is allowed&quot; when
098     * a block-box considers no permissions as &quot;nothing is allowed&quot;.
099     * @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}