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