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.Objects;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026
027/**
028 * A sandbox describes permissions on a class by explicitly allowing or forbidding
029 * access to methods and properties through "allowlists" and "blocklists".
030 *
031 * <p>A <strong>allowlist</strong> explicitly allows methods/properties for a class;</p>
032 *
033 * <ul>
034 *   <li>If a allowlist is empty and thus does not contain any names,
035 *       all properties/methods are allowed for its class.</li>
036 *   <li>If it is not empty, the only allowed properties/methods are the ones contained.</li>
037 * </ul>
038 *
039 * <p>A <strong>blocklist</strong> explicitly forbids methods/properties for a class;</p>
040 *
041 * <ul>
042 *   <li>If a blocklist is empty and thus does not contain any names,
043 *       all properties/methods are forbidden for its class.</li>
044 *   <li>If it is not empty, the only forbidden properties/methods are the ones contained.</li>
045 * </ul>
046 *
047 * <p>Permissions are composed of three lists, read, write, execute, each being
048 * "allow" or "block":</p>
049 *
050 * <ul>
051 *   <li><strong>read</strong> controls readable properties </li>
052 *   <li><strong>write</strong> controls writable properties</li>
053 *   <li><strong>execute</strong> controls executable methods and constructor</li>
054 * </ul>
055 *
056 * <p>When specified, permissions - allow or block lists - can be created inheritable
057 * on interfaces or classes and thus applicable to their implementations or derived
058 * classes; the sandbox must be created with the 'inheritable' flag for this behavior
059 * to be triggered. Note that even in this configuration, it is still possible to
060 * add non-inheritable permissions.
061 * Adding inheritable lists to a non inheritable sandbox has no added effect;
062 * permissions only apply to their specified class.</p>
063 *
064 * <p>Note that a JexlUberspect always uses a <em>copy</em> of the JexlSandbox
065 * used to built it preventing permission changes after its instantiation.</p>
066 *
067 * @since 3.0
068 */
069public final class JexlSandbox {
070    /**
071     * The marker string for explicitly disallowed null properties.
072     */
073    public static final String NULL = "?";
074
075    /**
076     * The pass-thru name set.
077     */
078    static final Names ALLOW_NAMES = new Names() {
079        @Override
080        public boolean add(final String name) {
081            return false;
082        }
083
084        @Override
085        public String toString() {
086            return "allowAll";
087        }
088    };
089
090    /**
091     * The block-all name set.
092     */
093    private static final Names BLOCK_NAMES = new Names() {
094        @Override
095        public boolean add(final String name) {
096            return false;
097        }
098
099        @Override
100        public String get(final String name) {
101            return name == null ? NULL : null;
102        }
103
104        @Override
105        public String toString() {
106            return "blockAll";
107        }
108    };
109
110    /**
111     * The block-all permissions.
112     */
113    private static final Permissions BLOCK_ALL = new Permissions(false, BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES);
114    /**
115     * The pass-thru permissions.
116     */
117    private static final Permissions ALLOW_ALL = new Permissions(false, ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES);
118    /**
119     * The map from class names to permissions.
120     */
121    private final Map<String, Permissions> sandbox;
122    /**
123     * Whether permissions can be inherited (through implementation or extension).
124     */
125    private final boolean inherit;
126    /**
127     * Default behavior, block or allow.
128     */
129    private final boolean allow;
130
131    /**
132     * Creates a new default sandbox.
133     * <p>In the absence of explicit permissions on a class, the
134     * sandbox is an allow-box, allow-listing that class for all permissions (read, write and execute).
135     */
136    public JexlSandbox() {
137        this(true, false, null);
138    }
139
140    /**
141     * Creates a new default sandbox.
142     * <p>A allow-box considers no permissions as &quot;everything is allowed&quot; when
143     * a block-box considers no permissions as &quot;nothing is allowed&quot;.
144     *
145     * @param ab whether this sandbox is allow (true) or block (false)
146     *           if no permission is explicitly defined for a class.
147     * @since 3.1
148     */
149    public JexlSandbox(final boolean ab) {
150        this(ab, false, null);
151    }
152
153    /**
154     * Creates a sandbox.
155     *
156     * @param ab  whether this sandbox is allow (true) or block (false)
157     * @param inh whether permissions on interfaces and classes are inherited (true) or not (false)
158     * @since 3.2
159     */
160    public JexlSandbox(final boolean ab, final boolean inh) {
161        this(ab, inh, null);
162    }
163
164    /**
165     * Creates a sandbox based on an existing permissions map.
166     *
167     * @param ab  whether this sandbox is allow (true) or block (false)
168     * @param inh whether permissions are inherited, default false
169     * @param map the permissions map
170     * @since 3.2
171     */
172    private JexlSandbox(final boolean ab, final boolean inh, final Map<String, Permissions> map) {
173        allow = ab;
174        inherit = inh;
175        sandbox = map != null ? map : new HashMap<>();
176    }
177
178    /**
179     * Gets a class by name, crude mechanism for backwards (&lt;3.2 ) compatibility.
180     *
181     * @param cname the class name
182     * @return the class
183     */
184    static Class<?> forName(final String cname) {
185        try {
186            return Class.forName(cname);
187        } catch (final Exception xany) {
188            return null;
189        }
190    }
191
192    /**
193     * Creates a new set of permissions based on allow lists for methods and properties for a given class.
194     * <p>The sandbox inheritance property will apply to the permissions created by this method
195     *
196     * @param clazz the allowed class name
197     * @return the permissions instance
198     */
199    public Permissions allow(final String clazz) {
200        return permissions(clazz, true, true, true);
201    }
202
203    /**
204     * Creates a new set of permissions based on block lists for methods and properties for a given class.
205     * <p>The sandbox inheritance property will apply to the permissions created by this method
206     *
207     * @param clazz the blocked class name
208     * @return the permissions instance
209     */
210    public Permissions block(final String clazz) {
211        return permissions(clazz, false, false, false);
212    }
213
214    /**
215     * Gets a copy of this sandbox
216     * @return a copy of this sandbox
217     */
218    public JexlSandbox copy() {
219        // modified concurrently at runtime so...
220        final Map<String, Permissions> map = new ConcurrentHashMap<>();
221        for (final Map.Entry<String, Permissions> entry : sandbox.entrySet()) {
222            map.put(entry.getKey(), entry.getValue().copy());
223        }
224        return new JexlSandbox(allow, inherit, map);
225    }
226
227    /**
228     * Gets the execute permission value for a given method of a class.
229     *
230     * @param clazz the class
231     * @param name  the method name
232     * @return null if not allowed, the name of the method to use otherwise
233     */
234    public String execute(final Class<?> clazz, final String name) {
235        final String m = get(clazz).execute().get(name);
236        return "".equals(name) && m != null ? clazz.getName() : m;
237    }
238
239    /**
240     * Gets the execute permission value for a given method of a class.
241     *
242     * @param clazz the class name
243     * @param name  the method name
244     * @return null if not allowed, the name of the method to use otherwise
245     * @deprecated 3.3
246     */
247    @Deprecated
248    public String execute(final String clazz, final String name) {
249        final String m = get(clazz).execute().get(name);
250        return "".equals(name) && m != null ? clazz : m;
251    }
252
253    /**
254     * Gets the set of permissions associated to a class.
255     *
256     * @param clazz the class name
257     * @return the defined permissions or an all-allow permission instance if none were defined
258     */
259    public Permissions get(final String clazz) {
260        return get(forName(clazz));
261    }
262
263    /**
264     * Gets the permissions associated to a class.
265     *
266     * @param clazz the class
267     * @return the permissions
268     */
269    @SuppressWarnings("null")
270    public Permissions get(final Class<?> clazz) {
271        // argument clazz cannot be null since permissions would be not null and block:
272        // we only store the result for classes we actively seek permissions for.
273        return compute(clazz, true);
274    }
275
276    private static Permissions inheritable(final Permissions p) {
277        return p != null && p.isInheritable() ? p : null;
278    }
279
280    /**
281     * Computes and optionally stores the permissions associated to a class.
282     *
283     * @param clazz the class
284     * @param store whether the computed permissions should be stored in the sandbox
285     * @return the permissions
286     */
287    private Permissions compute(final Class<?> clazz, final boolean store) {
288        // belt and suspender; recursion should not lead here
289        if (clazz == null) {
290            return BLOCK_ALL;
291        }
292        final String className = clazz.getName();
293        Permissions permissions = sandbox.get(className);
294        if (permissions == null) {
295            if (inherit) {
296                // find first inherited interface that defines permissions
297                final Class<?>[] interfaces = clazz.getInterfaces();
298                for (int i = 0; permissions == null && i < interfaces.length; ++i) {
299                    permissions = inheritable(compute(interfaces[i], false));
300                }
301                // nothing defined yet, find first superclass that defines permissions
302                if (permissions == null) {
303                    // let's recurse on super classes
304                    final Class<?> superClazz = clazz.getSuperclass();
305                    if (Object.class != superClazz) {
306                        permissions = inheritable(compute(superClazz, false));
307                    }
308                }
309            }
310            // nothing was inheritable
311            if (permissions == null) {
312                permissions = allow ? ALLOW_ALL : BLOCK_ALL;
313            }
314            // store the info to avoid doing this costly look-up
315            if (store) {
316                sandbox.put(className, permissions);
317            }
318        }
319        return permissions;
320    }
321
322    /**
323     * Creates the set of permissions for a given class.
324     * <p>The sandbox inheritance property will apply to the permissions created by this method
325     *
326     * @param clazz       the class for which these permissions apply
327     * @param readFlag    whether the readable property list is allow - true - or block - false -
328     * @param writeFlag   whether the writable property list is allow - true - or block - false -
329     * @param executeFlag whether the executable method list is allow - true - or block - false -
330     * @return the set of permissions
331     */
332    public Permissions permissions(final String clazz,
333                                   final boolean readFlag,
334                                   final boolean writeFlag,
335                                   final boolean executeFlag) {
336        return permissions(clazz, inherit, readFlag, writeFlag, executeFlag);
337    }
338
339    /**
340     * Creates the set of permissions for a given class.
341     *
342     * @param clazz  the class for which these permissions apply
343     * @param inhf   whether these permissions are inheritable
344     * @param readf  whether the readable property list is allow - true - or block - false -
345     * @param writef whether the writable property list is allow - true - or block - false -
346     * @param execf  whether the executable method list is allow - true - or block - false -
347     * @return the set of permissions
348     */
349    public Permissions permissions(final String clazz,
350                                   final boolean inhf,
351                                   final boolean readf,
352                                   final boolean writef,
353                                   final boolean execf) {
354        final Permissions box = new Permissions(inhf, readf, writef, execf);
355        sandbox.put(clazz, box);
356        return box;
357    }
358
359    /**
360     * Gets the read permission value for a given property of a class.
361     *
362     * @param clazz the class
363     * @param name  the property name
364     * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise
365     */
366    public String read(final Class<?> clazz, final String name) {
367        return get(clazz).read().get(name);
368    }
369
370    /**
371     * Gets the write permission value for a given property of a class.
372     *
373     * @param clazz the class
374     * @param name  the property name
375     * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise
376     */
377    public String write(final Class<?> clazz, final String name) {
378        return get(clazz).write().get(name);
379    }
380
381    /**
382     * Gets the write permission value for a given property of a class.
383     *
384     * @param clazz the class name
385     * @param name  the property name
386     * @return null if not allowed, the name of the property to use otherwise
387     * @deprecated 3.3
388     */
389    @Deprecated
390    public String write(final String clazz, final String name) {
391        return get(clazz).write().get(name);
392    }
393
394    /**
395     * An allow set of names.
396     */
397    static class AllowSet extends Names {
398        /**
399         * The map of controlled names and aliases.
400         */
401        private Map<String, String> names;
402
403        @Override
404        public boolean add(final String name) {
405            if (names == null) {
406                names = new HashMap<>();
407            }
408            return names.put(name, name) == null;
409        }
410
411        @Override
412        public boolean alias(final String name, final String alias) {
413            if (names == null) {
414                names = new HashMap<>();
415            }
416            return names.put(alias, name) == null;
417        }
418
419        @Override
420        protected Names copy() {
421            final AllowSet copy = new AllowSet();
422            copy.names = names == null ? null : new HashMap<>(names);
423            return copy;
424        }
425
426        @Override
427        public String get(final String name) {
428            if (names == null) {
429                return name;
430            }
431            final String actual = names.get(name);
432            // if null is not explicitly allowed, explicit null aka NULL
433            if (name == null && actual == null && !names.containsKey(null)) {
434                return JexlSandbox.NULL;
435            }
436            return actual;
437        }
438
439        @Override
440        public String toString() {
441            return "allow{" + (names == null ? "all" : Objects.toString(names.entrySet())) + "}";
442        }
443    }
444
445    /**
446     * A block set of names.
447     */
448    static class BlockSet extends Names {
449        /**
450         * The set of controlled names.
451         */
452        private Set<String> names;
453
454        @Override
455        public boolean add(final String name) {
456            if (names == null) {
457                names = new HashSet<>();
458            }
459            return names.add(name);
460        }
461
462        @Override
463        protected Names copy() {
464            final BlockSet copy = new BlockSet();
465            copy.names = names == null ? null : new HashSet<>(names);
466            return copy;
467        }
468
469        @Override
470        public String get(final String name) {
471            // if name is null and contained in set, explicit null aka NULL
472            if (names != null && !names.contains(name)) {
473                return name;
474            }
475            if (name != null) {
476                return null;
477            }
478            return NULL;
479        }
480
481        @Override
482        public String toString() {
483            return "block{" + (names == null ? "all" : Objects.toString(names)) + "}";
484        }
485    }
486
487    /**
488     * A base set of names.
489     */
490    public abstract static class Names {
491
492        /** Default constructor */
493        public Names() {} // Keep Javadoc happy
494
495        /**
496         * Adds a name to this set.
497         *
498         * @param name the name to add
499         * @return true if the name was really added, false if not
500         */
501        public abstract boolean add(String name);
502
503        /**
504         * Adds an alias to a name to this set.
505         * <p>This only has an effect on allow lists.</p>
506         *
507         * @param name  the name to alias
508         * @param alias the alias
509         * @return true if the alias was added, false if it was already present
510         */
511        public boolean alias(final String name, final String alias) {
512            return false;
513        }
514
515        /**
516         * Gets a copy of these Names
517         * @return a copy of these Names
518         */
519        protected Names copy() {
520            return this;
521        }
522
523        /**
524         * Gets whether a given name is allowed or not.
525         *
526         * @param name the method/property name to check
527         * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise
528         */
529        public String get(final String name) {
530            return name;
531        }
532    }
533
534    /**
535     * Contains the allow or block lists for properties and methods for a given class.
536     */
537    public static final class Permissions {
538        /**
539         * Whether these permissions are inheritable, ie can be used by derived classes.
540         */
541        private final boolean inheritable;
542        /**
543         * The controlled readable properties.
544         */
545        private final Names read;
546        /**
547         * The controlled  writable properties.
548         */
549        private final Names write;
550        /**
551         * The controlled methods.
552         */
553        private final Names execute;
554
555        /**
556         * Creates a new permissions instance.
557         *
558         * @param inherit     whether these permissions are inheritable
559         * @param readFlag    whether the read property list is allow or block
560         * @param writeFlag   whether the write property list is allow or block
561         * @param executeFlag whether the method list is allow of block
562         */
563        Permissions(final boolean inherit, final boolean readFlag, final boolean writeFlag, final boolean executeFlag) {
564            this(inherit,
565                readFlag ? new AllowSet() : new BlockSet(),
566                writeFlag ? new AllowSet() : new BlockSet(),
567                executeFlag ? new AllowSet() : new BlockSet());
568        }
569
570        /**
571         * Creates a new permissions instance.
572         *
573         * @param inherit  whether these permissions are inheritable
574         * @param nread    the read set
575         * @param nwrite   the write set
576         * @param nexecute the method set
577         */
578        Permissions(final boolean inherit, final Names nread, final Names nwrite, final Names nexecute) {
579            this.read = nread != null ? nread : ALLOW_NAMES;
580            this.write = nwrite != null ? nwrite : ALLOW_NAMES;
581            this.execute = nexecute != null ? nexecute : ALLOW_NAMES;
582            this.inheritable = inherit;
583        }
584
585        /**
586         * @return a copy of these permissions
587         */
588        Permissions copy() {
589            return new Permissions(inheritable, read.copy(), write.copy(), execute.copy());
590        }
591
592        /**
593         * Gets the set of method names in these permissions.
594         *
595         * @return the set of method names
596         */
597        public Names execute() {
598            return execute;
599        }
600
601        /**
602         * Adds a list of executable methods names to these permissions.
603         * <p>The constructor is denoted as the empty-string, all other methods by their names.</p>
604         *
605         * @param methodNames the method names
606         * @return this instance of permissions
607         */
608        public Permissions execute(final String... methodNames) {
609            for (final String methodName : methodNames) {
610                execute.add(methodName);
611            }
612            return this;
613        }
614
615        /**
616         * Do these permissions apply to derived classes?
617         * @return whether these permissions apply to derived classes.
618         */
619        public boolean isInheritable() {
620            return inheritable;
621        }
622
623        /**
624         * Gets the set of readable property names in these permissions.
625         *
626         * @return the set of property names
627         */
628        public Names read() {
629            return read;
630        }
631
632        /**
633         * Adds a list of readable property names to these permissions.
634         *
635         * @param propertyNames the property names
636         * @return this instance of permissions
637         */
638        public Permissions read(final String... propertyNames) {
639            for (final String propertyName : propertyNames) {
640                read.add(propertyName);
641            }
642            return this;
643        }
644
645        /**
646         * Gets the set of writable property names in these permissions.
647         *
648         * @return the set of property names
649         */
650        public Names write() {
651            return write;
652        }
653
654        /**
655         * Adds a list of writable property names to these permissions.
656         *
657         * @param propertyNames the property names
658         * @return this instance of permissions
659         */
660        public Permissions write(final String... propertyNames) {
661            for (final String propertyName : propertyNames) {
662                write.add(propertyName);
663            }
664            return this;
665        }
666    }
667
668    /**
669     * @deprecated since 3.2, use {@link BlockSet}
670     */
671    @Deprecated
672    public static final class BlackSet extends BlockSet {
673        /** Default constructor */
674        public BlackSet() { } // Keep Javadoc happy
675    }
676
677    /**
678     * @deprecated since 3.2, use {@link AllowSet}
679     */
680    @Deprecated
681    public static final class WhiteSet extends AllowSet {
682        /** Default constructor */
683        public WhiteSet() { } // Keep Javadoc happy
684    }
685
686    /**
687     * Use block() instead.
688     *
689     * @param clazz the blocked class name
690     * @return the permissions instance
691     * @deprecated 3.3
692     */
693    @Deprecated
694    public Permissions black(final String clazz) {
695        return block(clazz);
696    }
697    /**
698     * Gets the read permission value for a given property of a class.
699     *
700     * @param clazz the class name
701     * @param name  the property name
702     * @return null if not allowed, the name of the property to use otherwise
703     * @deprecated 3.3
704     */
705    @Deprecated
706    public String read(final String clazz, final String name) {
707        return get(clazz).read().get(name);
708    }
709
710    /**
711     * Use allow() instead.
712     *
713     * @param clazz the allowed class name
714     * @return the permissions instance
715     * @deprecated 3.3
716     */
717    @Deprecated
718    public Permissions white(final String clazz) {
719        return allow(clazz);
720    }
721}