Author: henrib Date: Mon Jul 18 11:10:42 2011 New Revision: 1147809 URL: http://svn.apache.org/viewvc?rev=1147809&view=rev Log: JEXL-116: * Added sandbox and sandbox-uberspect + tests
Added: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Sandbox.java (with props) commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java (with props) commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java (with props) commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/SandboxTest.java (with props) Added: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Sandbox.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Sandbox.java?rev=1147809&view=auto ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Sandbox.java (added) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Sandbox.java Mon Jul 18 11:10:42 2011 @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl2.introspection; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A sandbox describes permissions on a class by explicitly allowing or forbidding access to methods and properties + * through "whitelists" and "blacklists". + * <p> + * A <b>whitelist</b> explicitly allows methods/properties for a class; + * <ul> + * <li> + * If a whitelist is empty and thus does not contain any names, all properties/methods are allowed for its class. + * </li> + * <li> + * If it is not empty, the only allowed properties/methods are the ones contained. + * </li> + * </ul> + * </p> + * <p> + * A <b>blacklist</b> explicitly forbids methods/properties for a class; + * <ul> + * <li> + * If a blacklist is empty and thus does not contain any names, all properties/methods are forbidden for its class. + * </li> + * <li> + * If it is not empty, the only forbidden properties/methods are the ones contained. + * </li> + * </ul> + * <p> + * Permissions are composed of three lists, read, write, execute, each being "white" or "black"; . + * <ul> + * <li><b>read</b> controls readable properties </li> + * <li><b>write</b> controls writeable properties</li> + * <li><b>execute</b> controls executable methods and constructor/<li> + * </ul> + * </p> + * + * @since 2.1 + */ +public class Sandbox { + /** + * The map from class names to permissions. + */ + private final Map<String, Permissions> sandbox; + + /** + * Creates a new default sandbox. + */ + public Sandbox() { + sandbox = new HashMap<String, Permissions>(); + } + + /** + * Creates a sandbox based on an existing permissions map. + * @param map the permissions map + */ + private Sandbox(Map<String, Permissions> map) { + sandbox = map; + } + + /** + * A base set of names. + */ + public abstract static class Names { + /** The set of controlled names. */ + protected Set<String> names = null; + + /** + * Adds a name to this set. + * @param name the name to add + * @return true if the name was really added, false if it was already present + */ + private boolean add(String name) { + if (names == null) { + names = new HashSet<String>(); + } + return names.add(name); + } + + /** + * Whether a given name is allowed or not. + * @param name the method/property name to check + * @return true if allowed, false if forbidden + */ + public abstract boolean allows(String name); + } + + /** + * A white set of names. + */ + public static class WhiteSet extends Names { + @Override + public boolean allows(String name) { + return names == null || names.contains(name); + } + } + + /** + * A black set of names. + */ + public static class BlackSet extends Names { + @Override + public boolean allows(String name) { + return names != null && !names.contains(name); + } + } + + /** + * Contains the white or black lists for properties and methods for a given class. + */ + public static class Permissions { + /** The controlled readable properties. */ + private final Names read; + /** The controlled writeable properties. */ + private final Names write; + /** The controlled methods. */ + private final Names execute; + + /** + * Creates a new permissions instance. + * @param readFlag whether the read property list is white or black + * @param writeFlag whether the write property list is white or black + * @param executeFlag whether the method list is white of black + */ + Permissions(boolean readFlag, boolean writeFlag, boolean executeFlag) { + this.read = readFlag ? new WhiteSet() : new BlackSet(); + this.write = writeFlag ? new WhiteSet() : new BlackSet(); + this.execute = executeFlag ? new WhiteSet() : new BlackSet(); + } + + /** + * Adds a list of readable property names to these permissions. + * @param pnames the property names + * @return this instance of permissions + */ + public Permissions read(String... pnames) { + for (String pname : pnames) { + read.add(pname); + } + return this; + } + /** + * Adds a list of writeable property names to these permissions. + * @param pnames the property names + * @return this instance of permissions + */ + public Permissions write(String... pnames) { + for (String pname : pnames) { + write.add(pname); + } + return this; + } + + /** + * Adds a list of executable methods names to these permissions. + * <p>The constructor is denoted as the empty-string, all other methods by their names.</p> + * @param mnames the method names + * @return this instance of permissions + */ + public Permissions execute(String... mnames) { + for (String mname : mnames) { + execute.add(mname); + } + return this; + } + + /** + * Gets the set of readable property names in these permissions. + * @return the set of property names + */ + public Names read() { + return read; + } + + /** + * Gets the set of writeable property names in these permissions. + * @return the set of property names + */ + public Names write() { + return write; + } + + /** + * Gets the set of method names in these permissions. + * @return the set of method names + */ + public Names execute() { + return execute; + } + + } + + /** + * Creates the set of permissions for a given class. + * @param clazz the class for which these permissions apply + * @param readFlag whether the readable property list is white or black + * @param writeFlag whether the writeable property list is white or black + * @param executeFlag whether the executable method list is white or black + * @return the + */ + public Permissions permissions(String clazz, boolean readFlag, boolean writeFlag, boolean executeFlag) { + Permissions box = new Permissions(readFlag, writeFlag, executeFlag); + sandbox.put(clazz, box); + return box; + } + + /** + * Creates the set of permissions based on white lists for methods and properties for a given class. + * @param clazz the whitened class name + * @return the permissions instance + */ + public Permissions white(String clazz) { + return permissions(clazz, true, true, true); + } + + /** + * Creates the set of permissions based on black lists for methods and properties for a given class. + * @param clazz the blackened class name + * @return the permissions instance + */ + public Permissions black(String clazz) { + return permissions(clazz, false, false, false); + } + + /** + * Gets the set of permissions associated to a class. + * @param clazz the class name + * @return the permissions or null if none were defined + */ + public Permissions get(String clazz) { + return sandbox.get(clazz); + } +} Propchange: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/Sandbox.java ------------------------------------------------------------------------------ svn:eol-style = native Added: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java?rev=1147809&view=auto ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java (added) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java Mon Jul 18 11:10:42 2011 @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl2.introspection; + +import java.lang.reflect.Constructor; +import org.apache.commons.jexl2.JexlInfo; +import org.apache.commons.logging.Log; + +/** + * An uberspect that controls usage of properties, methods and contructors through a sandbox. + * @since 2.1 + */ +public class SandboxUberspectImpl extends UberspectImpl { + /** The sandbox. */ + protected final Sandbox sandbox; + + /** + * A constructor for Sandbox uberspect. + * @param runtimeLogger the logger to use or null to use default + * @param theSandbox the sandbox instance to use + */ + public SandboxUberspectImpl(Log runtimeLogger, Sandbox theSandbox) { + super(runtimeLogger); + if (theSandbox == null) { + throw new NullPointerException("sandbox can not be null"); + } + this.sandbox = theSandbox; + } + + /** + * {@inheritDoc} + */ + @Override + public void setLoader(ClassLoader cloader) { + base().setLoader(cloader); + } + + /** + * {@inheritDoc} + */ + @Override + public Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info) { + String className = null; + Class<?> clazz = null; + if (ctorHandle instanceof Class<?>) { + clazz = (Class<?>) ctorHandle; + className = clazz.getName(); + } else if (ctorHandle != null) { + className = ctorHandle.toString(); + } else { + return null; + } + Sandbox.Permissions box = sandbox.get(className); + if (box == null || box.execute().allows("")) { + return getConstructor(className, args); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) { + if (obj != null) { + Sandbox.Permissions box = sandbox.get(obj.getClass().getName()); + if (box == null || box.execute().allows(method)) { + return getMethodExecutor(obj, method, args); + } + + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) { + if (obj != null) { + Sandbox.Permissions box = sandbox.get(obj.getClass().getName()); + if (box == null || box.read().allows(identifier.toString())) { + return super.getPropertyGet(obj, identifier, info); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public JexlPropertySet getPropertySet(final Object obj, final Object identifier, Object arg, JexlInfo info) { + if (obj != null) { + Sandbox.Permissions box = sandbox.get(obj.getClass().getName()); + if (box == null || box.write().allows(identifier.toString())) { + return super.getPropertySet(obj, identifier, arg, info); + } + } + return null; + + } +} Propchange: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java ------------------------------------------------------------------------------ svn:eol-style = native Added: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java?rev=1147809&view=auto ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java (added) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java Mon Jul 18 11:10:42 2011 @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl2.parser; + +/** + * Token Manager Error. + */ +public class TokenMgrError extends Error { + /** + * The version identifier for this Serializable class. + * Increment only if the <i>serialized</i> form of the + * class changes. + */ + private static final long serialVersionUID = 1L; + + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + /** + * Lexical error occurred. + */ + public static final int LEXICAL_ERROR = 0; + /** + * An attempt was made to create a second instance of a static token manager. + */ + public static final int STATIC_LEXER_ERROR = 1; + /** + * Tried to change to an invalid lexical state. + */ + public static final int INVALID_LEXICAL_STATE = 2; + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + public static final int LOOP_DETECTED = 3; + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + private int errorCode; + /** + * The lexer state. + */ + private int state; + /** + * The current character. + */ + private char current; + /** + * Last correct input before error occurs. + */ + private String after; + /** + * + */ + private boolean eof; + /** + * Error line. + */ + private int line; + /** + * Error column. + */ + private int column; + + /** + * Gets the reason why the exception is thrown. + * @return one of the 4 lexical error codes + */ + public int getErrorCode() { + return errorCode; + } + + /** + * Gets the line number. + * @return line number. + */ + public int getLine() { + return line; + } + + /** + * Gets the column number. + * @return the column. + */ + public int getColumn() { + return column; + } + + /** + * Gets the last correct input. + * @return the string after which the error occured + */ + public String getAfer() { + return after; + } + + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * @return the message + */ + @Override + public String getMessage() { + return ("Lexical error at line " + + line + ", column " + + column + ". Encountered: " + + (eof ? "<EOF> " + : ("\"" + StringParser.escapeString(String.valueOf(current)) + "\"") + " (" + (int) current + "), ") + + "after : \"" + StringParser.escapeString(after) + "\""); + } + + + /** Constructor with message and reason. */ + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + /** Full Constructor. */ + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + eof = EOFSeen; + state = lexState; + line = errorLine; + column = errorColumn; + after = errorAfter; + current = curChar; + errorCode = reason; + } +} Propchange: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java ------------------------------------------------------------------------------ svn:eol-style = native Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/SandboxTest.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/SandboxTest.java?rev=1147809&view=auto ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/SandboxTest.java (added) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/SandboxTest.java Mon Jul 18 11:10:42 2011 @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl2; + +import java.util.logging.Logger; +import org.apache.commons.jexl2.introspection.Sandbox; +import org.apache.commons.jexl2.introspection.SandboxUberspectImpl; +import org.apache.commons.jexl2.introspection.Uberspect; + +/** + * Tests sandbox features. + */ +public class SandboxTest extends JexlTestCase { + static final Logger LOGGER = Logger.getLogger(VarTest.class.getName()); + + public static class Foo { + String name; + public String alias; + + public Foo(String name) { + this.name = name; + this.alias = name + "-alias"; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String Quux() { + return name + "-quux"; + } + } + + public void testCtorBlack() throws Exception { + String expr = "new('" + Foo.class.getName() + "', '42')"; + Script script = JEXL.createScript(expr); + Object result; + result = script.execute(null); + assertEquals("42", ((Foo) result).getName()); + + Sandbox sandbox = new Sandbox(); + sandbox.black(Foo.class.getName()).execute(""); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr); + try { + result = script.execute(null); + fail("ctor should not be accessible"); + } catch (JexlException.Method xmethod) { + // ok, ctor should not have been accessible + LOGGER.info(xmethod.toString()); + } + } + + public void testMethodBlack() throws Exception { + String expr = "foo.Quux()"; + Script script = JEXL.createScript(expr, "foo"); + Foo foo = new Foo("42"); + Object result; + result = script.execute(null, foo); + assertEquals(foo.Quux(), result); + + Sandbox sandbox = new Sandbox(); + sandbox.black(Foo.class.getName()).execute("Quux"); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr, "foo"); + try { + result = script.execute(null, foo); + fail("Quux should not be accessible"); + } catch (JexlException.Method xmethod) { + // ok, Quux should not have been accessible + LOGGER.info(xmethod.toString()); + } + } + + public void testGetBlack() throws Exception { + String expr = "foo.alias"; + Script script = JEXL.createScript(expr, "foo"); + Foo foo = new Foo("42"); + Object result; + result = script.execute(null, foo); + assertEquals(foo.alias, result); + + Sandbox sandbox = new Sandbox(); + sandbox.black(Foo.class.getName()).read("alias"); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr, "foo"); + try { + result = script.execute(null, foo); + fail("alias should not be accessible"); + } catch (JexlException.Variable xvar) { + // ok, alias should not have been accessible + LOGGER.info(xvar.toString()); + } + } + + public void testSetBlack() throws Exception { + String expr = "foo.alias = $0"; + Script script = JEXL.createScript(expr, "foo", "$0"); + Foo foo = new Foo("42"); + Object result; + result = script.execute(null, foo, "43"); + assertEquals("43", result); + + Sandbox sandbox = new Sandbox(); + sandbox.black(Foo.class.getName()).write("alias"); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr, "foo", "$0"); + try { + result = script.execute(null, foo, "43"); + fail("alias should not be accessible"); + } catch (JexlException.Variable xvar) { + // ok, alias should not have been accessible + LOGGER.info(xvar.toString()); + } + } + + public void testCtorWhite() throws Exception { + String expr = "new('" + Foo.class.getName() + "', '42')"; + Script script; + Object result; + + Sandbox sandbox = new Sandbox(); + sandbox.white(Foo.class.getName()).execute(""); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr); + result = script.execute(null); + assertEquals("42", ((Foo) result).getName()); + } + + public void testMethodWhite() throws Exception { + Foo foo = new Foo("42"); + String expr = "foo.Quux()"; + Script script; + Object result; + + Sandbox sandbox = new Sandbox(); + sandbox.white(Foo.class.getName()).execute("Quux"); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr, "foo"); + result = script.execute(null, foo); + assertEquals(foo.Quux(), result); + } + + public void testGetWhite() throws Exception { + Foo foo = new Foo("42"); + String expr = "foo.alias"; + Script script; + Object result; + + Sandbox sandbox = new Sandbox(); + sandbox.white(Foo.class.getName()).read("alias"); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr, "foo"); + result = script.execute(null, foo); + assertEquals(foo.alias, result); + } + + public void testSetWhite() throws Exception { + Foo foo = new Foo("42"); + String expr = "foo.alias = $0"; + Script script; + Object result; + + Sandbox sandbox = new Sandbox(); + sandbox.white(Foo.class.getName()).write("alias"); + Uberspect uber = new SandboxUberspectImpl(null, sandbox); + JexlEngine sjexl = new JexlEngine(uber, null, null, null); + sjexl.setStrict(true); + + script = sjexl.createScript(expr, "foo", "$0"); + result = script.execute(null, foo, "43"); + assertEquals("43", result); + assertEquals("43", foo.alias); + } +} Propchange: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/SandboxTest.java ------------------------------------------------------------------------------ svn:eol-style = native