1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jexl2.introspection;
18
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Set;
23
24 /**
25 * A sandbox describes permissions on a class by explicitly allowing or forbidding access to methods and properties
26 * through "whitelists" and "blacklists".
27 * <p>
28 * A <b>whitelist</b> explicitly allows methods/properties for a class;
29 * <ul>
30 * <li>
31 * If a whitelist is empty and thus does not contain any names, all properties/methods are allowed for its class.
32 * </li>
33 * <li>
34 * If it is not empty, the only allowed properties/methods are the ones contained.
35 * </li>
36 * </ul>
37 * </p>
38 * <p>
39 * A <b>blacklist</b> explicitly forbids methods/properties for a class;
40 * <ul>
41 * <li>
42 * If a blacklist is empty and thus does not contain any names, all properties/methods are forbidden for its class.
43 * </li>
44 * <li>
45 * If it is not empty, the only forbidden properties/methods are the ones contained.
46 * </li>
47 * </ul>
48 * <p>
49 * Permissions are composed of three lists, read, write, execute, each being "white" or "black":
50 * <ul>
51 * <li><b>read</b> controls readable properties </li>
52 * <li><b>write</b> controls writeable properties</li>
53 * <li><b>execute</b> controls executable methods and constructor</li>
54 * </ul>
55 * </p>
56 * @since 2.1
57 */
58 public final class Sandbox {
59 /**
60 * The map from class names to permissions.
61 */
62 private final Map<String, Permissions> sandbox;
63
64 /**
65 * Creates a new default sandbox.
66 */
67 public Sandbox() {
68 this(new HashMap<String, Permissions>());
69 }
70
71 /**
72 * Creates a sandbox based on an existing permissions map.
73 * @param map the permissions map
74 */
75 protected Sandbox(Map<String, Permissions> map) {
76 sandbox = map;
77 }
78
79 /**
80 * Gets the read permission value for a given property of a class.
81 * @param clazz the class
82 * @param name the property name
83 * @return null if not allowed, the name of the property to use otherwise
84 */
85 public String read(Class<?> clazz, String name) {
86 return read(clazz.getName(), name);
87 }
88
89 /**
90 * Gets the read permission value for a given property of a class.
91 * @param clazz the class name
92 * @param name the property name
93 * @return null if not allowed, the name of the property to use otherwise
94 */
95 public String read(String clazz, String name) {
96 Permissions permissions = sandbox.get(clazz);
97 if (permissions == null) {
98 return name;
99 } 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 }