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 */
017package org.apache.commons.jexl2.introspection;
018
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023
024/**
025 * A sandbox describes permissions on a class by explicitly allowing or forbidding access to methods and properties
026 * through "whitelists" and "blacklists".
027 * <p>
028 * A <b>whitelist</b> explicitly allows methods/properties for a class;
029 * </p>
030 * <ul>
031 * <li>
032 * If a whitelist is empty and thus does not contain any names, all properties/methods are allowed for its class.
033 * </li>
034 * <li>
035 * If it is not empty, the only allowed properties/methods are the ones contained.
036 * </li>
037 * </ul>
038 * <p>
039 * A <b>blacklist</b> explicitly forbids methods/properties for a class;
040 * </p>
041 * <ul>
042 * <li>
043 * If a blacklist is empty and thus does not contain any names, all properties/methods are forbidden for its class.
044 * </li>
045 * <li>
046 * If it is not empty, the only forbidden properties/methods are the ones contained.
047 * </li>
048 * </ul>
049 * <p>
050 * Permissions are composed of three lists, read, write, execute, each being "white" or "black":
051 * </p>
052 * <ul>
053 * <li><b>read</b> controls readable properties </li>
054 * <li><b>write</b> controls writeable properties</li>
055 * <li><b>execute</b> controls executable methods and constructor</li>
056 * </ul>
057 * @since 2.1
058 */
059public final class Sandbox {
060    /**
061     * The map from class names to permissions.
062     */
063    private final Map<String, Permissions> sandbox;
064
065    /**
066     * Creates a new default sandbox.
067     */
068    public Sandbox() {
069        this(new HashMap<String, Permissions>());
070    }
071
072    /**
073     * Creates a sandbox based on an existing permissions map.
074     * @param map the permissions map
075     */
076    protected Sandbox(Map<String, Permissions> map) {
077        sandbox = map;
078    }
079
080    /**
081     * Gets the read permission value for a given property of a class.
082     * @param clazz the class
083     * @param name the property name
084     * @return null if not allowed, the name of the property to use otherwise
085     */
086    public String read(Class<?> clazz, String name) {
087        return read(clazz.getName(), name);
088    }
089
090    /**
091     * Gets the read permission value for a given property of a class.
092     * @param clazz the class name
093     * @param name the property name
094     * @return null if not allowed, the name of the property to use otherwise
095     */
096    public String read(String clazz, String name) {
097        Permissions permissions = sandbox.get(clazz);
098        if (permissions == null) {
099            return name;
100        } else {
101            return permissions.read().get(name);
102        }
103    }
104
105    /**
106     * Gets the write permission value for a given property of a class.
107     * @param clazz the class
108     * @param name the property name
109     * @return null if not allowed, the name of the property to use otherwise
110     */
111    public String write(Class<?> clazz, String name) {
112        return write(clazz.getName(), name);
113    }
114
115    /**
116     * Gets the write permission value for a given property of a class.
117     * @param clazz the class name
118     * @param name the property name
119     * @return null if not allowed, the name of the property to use otherwise
120     */
121    public String write(String clazz, String name) {
122        Permissions permissions = sandbox.get(clazz);
123        if (permissions == null) {
124            return name;
125        } else {
126            return permissions.write().get(name);
127        }
128    }
129
130    /**
131     * Gets the execute permission value for a given method of a class.
132     * @param clazz the class
133     * @param name the method name
134     * @return null if not allowed, the name of the method to use otherwise
135     */
136    public String execute(Class<?> clazz, String name) {
137        return execute(clazz.getName(), name);
138    }
139
140    /**
141     * Gets the execute permission value for a given method of a class.
142     * @param clazz the class name
143     * @param name the method name
144     * @return null if not allowed, the name of the method to use otherwise
145     */
146    public String execute(String clazz, String name) {
147        Permissions permissions = sandbox.get(clazz);
148        if (permissions == null) {
149            return name;
150        } else {
151            return permissions.execute().get(name);
152        }
153    }
154
155    /**
156     * A base set of names.
157     */
158    public abstract static class Names {
159        /**
160         * Adds a name to this set.
161         * @param name the name to add
162         * @return  true if the name was really added, false if not
163         */
164        public abstract boolean add(String name);
165
166        /**
167         * Adds an alias to a name to this set.
168         * <p>This only has an effect on white lists.</p>
169         * @param name the name to alias
170         * @param alias the alias
171         * @return  true if the alias was added, false if it was already present
172         */
173        public boolean alias(String name, String alias) {
174            return false;
175        }
176
177        /**
178         * Whether a given name is allowed or not.
179         * @param name the method/property name to check
180         * @return null if not allowed, the actual name to use otherwise
181         */
182        public String get(String name) {
183            return name;
184        }
185    }
186    /**
187     * The pass-thru name set.
188     */
189    private static final Names WHITE_NAMES = new Names() {
190        @Override
191        public boolean add(String name) {
192            return false;
193        }
194    };
195
196    /**
197     * A white set of names.
198     */
199    public static final class WhiteSet extends Names {
200        /** The map of controlled names and aliases. */
201        private Map<String, String> names = null;
202
203        @Override
204        public boolean add(String name) {
205            if (names == null) {
206                names = new HashMap<String, String>();
207            }
208            return names.put(name, name) == null;
209        }
210
211        @Override
212        public boolean alias(String name, String alias) {
213            if (names == null) {
214                names = new HashMap<String, String>();
215            }
216            return names.put(alias, name) == null;
217        }
218
219        @Override
220        public String get(String name) {
221            if (names == null) {
222                return name;
223            } else {
224                return names.get(name);
225            }
226        }
227    }
228
229    /**
230     * A black set of names.
231     */
232    public static final class BlackSet extends Names {
233        /** The set of controlled names. */
234        private Set<String> names = null;
235
236        @Override
237        public boolean add(String name) {
238            if (names == null) {
239                names = new HashSet<String>();
240            }
241            return names.add(name);
242        }
243
244        @Override
245        public String get(String name) {
246            return names != null && !names.contains(name) ? name : null;
247        }
248    }
249
250    /**
251     * Contains the white or black lists for properties and methods for a given class.
252     */
253    public static final class Permissions {
254        /** The controlled readable properties. */
255        private final Names read;
256        /** The controlled  writeable properties. */
257        private final Names write;
258        /** The controlled methods. */
259        private final Names execute;
260
261        /**
262         * Creates a new permissions instance.
263         * @param readFlag whether the read property list is white or black
264         * @param writeFlag whether the write property list is white or black
265         * @param executeFlag whether the method list is white of black
266         */
267        Permissions(boolean readFlag, boolean writeFlag, boolean executeFlag) {
268            this(readFlag ? new WhiteSet() : new BlackSet(),
269                    writeFlag ? new WhiteSet() : new BlackSet(),
270                    executeFlag ? new WhiteSet() : new BlackSet());
271        }
272
273        /**
274         * Creates a new permissions instance.
275         * @param nread the read set
276         * @param nwrite the write set
277         * @param nexecute the method set 
278         */
279        Permissions(Names nread, Names nwrite, Names nexecute) {
280            this.read = nread != null ? nread : WHITE_NAMES;
281            this.write = nwrite != null ? nwrite : WHITE_NAMES;
282            this.execute = nexecute != null ? nexecute : WHITE_NAMES;
283        }
284
285        /**
286         * Adds a list of readable property names to these permissions.
287         * @param pnames the property names
288         * @return this instance of permissions
289         */
290        public Permissions read(String... pnames) {
291            for (String pname : pnames) {
292                read.add(pname);
293            }
294            return this;
295        }
296
297        /**
298         * Adds a list of writeable property names to these permissions.
299         * @param pnames the property names
300         * @return this instance of permissions
301         */
302        public Permissions write(String... pnames) {
303            for (String pname : pnames) {
304                write.add(pname);
305            }
306            return this;
307        }
308
309        /**
310         * Adds a list of executable methods names to these permissions.
311         * <p>The constructor is denoted as the empty-string, all other methods by their names.</p>
312         * @param mnames the method names
313         * @return this instance of permissions
314         */
315        public Permissions execute(String... mnames) {
316            for (String mname : mnames) {
317                execute.add(mname);
318            }
319            return this;
320        }
321
322        /**
323         * Gets the set of readable property names in these permissions.
324         * @return the set of property names
325         */
326        public Names read() {
327            return read;
328        }
329
330        /**
331         * Gets the set of writeable property names in these permissions.
332         * @return the set of property names
333         */
334        public Names write() {
335            return write;
336        }
337
338        /**
339         * Gets the set of method names in these permissions.
340         * @return the set of method names
341         */
342        public Names execute() {
343            return execute;
344        }
345    }
346    
347    /**
348     * The pass-thru permissions.
349     */
350    private static final Permissions ALL_WHITE = new Permissions(WHITE_NAMES, WHITE_NAMES, WHITE_NAMES);
351
352    /**
353     * Creates the set of permissions for a given class.
354     * @param clazz the class for which these permissions apply
355     * @param readFlag whether the readable property list is white - true - or black - false -
356     * @param writeFlag whether the writeable property list is white - true - or black - false -
357     * @param executeFlag whether the executable method list is white white - true - or black - false -
358     * @return the set of permissions
359     */
360    public Permissions permissions(String clazz, boolean readFlag, boolean writeFlag, boolean executeFlag) {
361        Permissions box = new Permissions(readFlag, writeFlag, executeFlag);
362        sandbox.put(clazz, box);
363        return box;
364    }
365
366    /**
367     * Creates a new set of permissions based on white lists for methods and properties for a given class.
368     * @param clazz the whitened class name
369     * @return the permissions instance
370     */
371    public Permissions white(String clazz) {
372        return permissions(clazz, true, true, true);
373    }
374
375    /**
376     * Creates a new set of permissions based on black lists for methods and properties for a given class.
377     * @param clazz the blackened class name
378     * @return the permissions instance
379     */
380    public Permissions black(String clazz) {
381        return permissions(clazz, false, false, false);
382    }
383
384    /**
385     * Gets the set of permissions associated to a class.
386     * @param clazz the class name
387     * @return the defined permissions or an all-white permission instance if none were defined
388     */
389    public Permissions get(String clazz) {
390        Permissions permissions = sandbox.get(clazz);
391        if (permissions == null) {
392            return ALL_WHITE;
393        } else {
394            return permissions;
395        }
396    }
397}