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