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     * A allow set of names.
071     */
072    static class AllowSet extends Names {
073        /** The map of controlled names and aliases. */
074        private Map<String, String> names;
075
076        @Override
077        public boolean add(final String name) {
078            if (names == null) {
079                names = new HashMap<>();
080            }
081            return names.put(name, name) == null;
082        }
083
084        @Override
085        public boolean alias(final String name, final String alias) {
086            if (names == null) {
087                names = new HashMap<>();
088            }
089            return names.put(alias, name) == null;
090        }
091
092        @Override
093        protected Names copy() {
094            final AllowSet copy = new AllowSet();
095            copy.names = names == null ? null : new HashMap<>(names);
096            return copy;
097        }
098
099        @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}