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    package org.apache.commons.jexl3.introspection;
018    
019    import java.util.HashMap;
020    import java.util.HashSet;
021    import java.util.Map;
022    import 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     * <ul>
030     * <li>
031     * If a whitelist is empty and thus does not contain any names, all properties/methods are allowed for its class.
032     * </li>
033     * <li>
034     * If it is not empty, the only allowed properties/methods are the ones contained.
035     * </li>
036     * </ul>
037     * </p>
038     * <p>
039     * A <b>blacklist</b> explicitly forbids methods/properties for a class;
040     * <ul>
041     * <li>
042     * If a blacklist is empty and thus does not contain any names, all properties/methods are forbidden for its class.
043     * </li>
044     * <li>
045     * If it is not empty, the only forbidden properties/methods are the ones contained.
046     * </li>
047     * </ul>
048     * <p>
049     * Permissions are composed of three lists, read, write, execute, each being "white" or "black":
050     * <ul>
051     * <li><b>read</b> controls readable properties </li>
052     * <li><b>write</b> controls writeable properties</li>
053     * <li><b>execute</b> controls executable methods and constructor</li>
054     * </ul>
055     * </p>
056     * 
057     * @since 3.0
058     */
059    public 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            protected 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            protected 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    }