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