| /* |
| * 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.jexl3.introspection; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import org.apache.commons.jexl3.JexlBuilder; |
| import org.apache.commons.jexl3.JexlContext; |
| import org.apache.commons.jexl3.JexlEngine; |
| import org.apache.commons.jexl3.JexlException; |
| import org.apache.commons.jexl3.JexlScript; |
| import org.apache.commons.jexl3.JexlTestCase; |
| import org.apache.commons.jexl3.MapContext; |
| import org.apache.commons.jexl3.annotations.NoJexl; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| /** |
| * Tests sandbox features. |
| */ |
| @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"}) |
| public class SandboxTest extends JexlTestCase { |
| static final Log LOGGER = LogFactory.getLog(SandboxTest.class.getName()); |
| |
| public SandboxTest() { |
| super("SandboxTest"); |
| } |
| |
| |
| public static class CantSeeMe { |
| public boolean doIt() { |
| return false; |
| } |
| } |
| |
| @NoJexl |
| public interface CantCallMe { |
| void tryMe(); |
| } |
| |
| public interface TryCallMe { |
| @NoJexl |
| void tryMeARiver(); |
| } |
| |
| public static abstract class CallMeNot { |
| public @NoJexl |
| String NONO = "should not be accessible!"; |
| |
| @NoJexl |
| public void callMeNot() { |
| throw new RuntimeException("should not be callable!"); |
| } |
| |
| public String allowInherit() { |
| return "this is allowed"; |
| } |
| } |
| |
| public static class Foo extends CallMeNot implements CantCallMe, TryCallMe { |
| String name; |
| public String alias; |
| |
| public @NoJexl |
| Foo(final String name, final String notcallable) { |
| throw new RuntimeException("should not be callable!"); |
| } |
| |
| public Foo(final String name) { |
| this.name = name; |
| this.alias = name + "-alias"; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public void setName(final String name) { |
| this.name = name; |
| } |
| |
| public String Quux() { |
| return name + "-quux"; |
| } |
| |
| public int doIt() { |
| return 42; |
| } |
| |
| @NoJexl |
| public String cantCallMe() { |
| throw new RuntimeException("should not be callable!"); |
| } |
| |
| @Override |
| public void tryMe() { |
| throw new RuntimeException("should not be callable!"); |
| } |
| |
| @Override |
| public void tryMeARiver() { |
| throw new RuntimeException("should not be callable!"); |
| } |
| } |
| |
| @Test |
| public void testCtorBlock() throws Exception { |
| final String expr = "new('" + Foo.class.getName() + "', '42')"; |
| JexlScript script = JEXL.createScript(expr); |
| Object result; |
| result = script.execute(null); |
| Assert.assertEquals("42", ((Foo) result).getName()); |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.block(Foo.class.getName()).execute(""); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| script = sjexl.createScript(expr); |
| try { |
| result = script.execute(null); |
| Assert.fail("ctor should not be accessible"); |
| } catch (final JexlException.Method xmethod) { |
| // ok, ctor should not have been accessible |
| LOGGER.info(xmethod.toString()); |
| } |
| } |
| |
| @Test |
| public void testMethodBlock() throws Exception { |
| final String expr = "foo.Quux()"; |
| JexlScript script = JEXL.createScript(expr, "foo"); |
| final Foo foo = new Foo("42"); |
| Object result; |
| result = script.execute(null, foo); |
| Assert.assertEquals(foo.Quux(), result); |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.block(Foo.class.getName()).execute("Quux"); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| script = sjexl.createScript(expr, "foo"); |
| try { |
| result = script.execute(null, foo); |
| Assert.fail("Quux should not be accessible"); |
| } catch (final JexlException.Method xmethod) { |
| // ok, Quux should not have been accessible |
| LOGGER.info(xmethod.toString()); |
| } |
| } |
| |
| @Test |
| public void testGetBlock() throws Exception { |
| final String expr = "foo.alias"; |
| JexlScript script = JEXL.createScript(expr, "foo"); |
| final Foo foo = new Foo("42"); |
| Object result; |
| result = script.execute(null, foo); |
| Assert.assertEquals(foo.alias, result); |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.block(Foo.class.getName()).read("alias"); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| script = sjexl.createScript(expr, "foo"); |
| try { |
| result = script.execute(null, foo); |
| Assert.fail("alias should not be accessible"); |
| } catch (final JexlException.Property xvar) { |
| // ok, alias should not have been accessible |
| LOGGER.info(xvar.toString()); |
| } |
| } |
| |
| @Test |
| public void testSetBlock() throws Exception { |
| final String expr = "foo.alias = $0"; |
| JexlScript script = JEXL.createScript(expr, "foo", "$0"); |
| final Foo foo = new Foo("42"); |
| Object result; |
| result = script.execute(null, foo, "43"); |
| Assert.assertEquals("43", result); |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.block(Foo.class.getName()).write("alias"); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| script = sjexl.createScript(expr, "foo", "$0"); |
| try { |
| result = script.execute(null, foo, "43"); |
| Assert.fail("alias should not be accessible"); |
| } catch (final JexlException.Property xvar) { |
| // ok, alias should not have been accessible |
| LOGGER.info(xvar.toString()); |
| } |
| } |
| |
| @Test |
| public void testCantSeeMe() throws Exception { |
| final JexlContext jc = new MapContext(); |
| final String expr = "foo.doIt()"; |
| JexlScript script; |
| Object result; |
| |
| final JexlSandbox sandbox = new JexlSandbox(false); |
| sandbox.allow(Foo.class.getName()); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| jc.set("foo", new CantSeeMe()); |
| script = sjexl.createScript(expr); |
| try { |
| result = script.execute(jc); |
| Assert.fail("should have failed, doIt()"); |
| } catch (final JexlException xany) { |
| // |
| } |
| jc.set("foo", new Foo("42")); |
| result = script.execute(jc); |
| Assert.assertEquals(42, ((Integer) result).intValue()); |
| } |
| |
| @Test |
| public void testCtorAllow() throws Exception { |
| final String expr = "new('" + Foo.class.getName() + "', '42')"; |
| JexlScript script; |
| Object result; |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.allow(Foo.class.getName()).execute(""); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| script = sjexl.createScript(expr); |
| result = script.execute(null); |
| Assert.assertEquals("42", ((Foo) result).getName()); |
| } |
| |
| @Test |
| public void testMethodAllow() throws Exception { |
| final Foo foo = new Foo("42"); |
| final String expr = "foo.Quux()"; |
| JexlScript script; |
| Object result; |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.allow(Foo.class.getName()).execute("Quux"); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create(); |
| |
| script = sjexl.createScript(expr, "foo"); |
| result = script.execute(null, foo); |
| Assert.assertEquals(foo.Quux(), result); |
| } |
| |
| @Test |
| public void testMethodNoJexl() throws Exception { |
| final Foo foo = new Foo("42"); |
| final String[] exprs = { |
| "foo.cantCallMe()", |
| "foo.tryMe()", |
| "foo.tryMeARiver()", |
| "foo.callMeNot()", |
| "foo.NONO", |
| "new('org.apache.commons.jexl3.SandboxTest$Foo', 'one', 'two')" |
| }; |
| JexlScript script; |
| Object result; |
| |
| final JexlEngine sjexl = new JexlBuilder().strict(true).safe(false).create(); |
| for (final String expr : exprs) { |
| script = sjexl.createScript(expr, "foo"); |
| try { |
| result = script.execute(null, foo); |
| Assert.fail("should have not been possible"); |
| } catch (JexlException.Method | JexlException.Property xjm) { |
| // ok |
| LOGGER.info(xjm.toString()); |
| } |
| } |
| } |
| |
| @Test |
| public void testGetAllow() throws Exception { |
| final Foo foo = new Foo("42"); |
| final String expr = "foo.alias"; |
| JexlScript script; |
| Object result; |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.allow(Foo.class.getName()).read("alias"); |
| sandbox.get(Foo.class.getName()).read().alias("alias", "ALIAS"); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create(); |
| |
| script = sjexl.createScript(expr, "foo"); |
| result = script.execute(null, foo); |
| Assert.assertEquals(foo.alias, result); |
| |
| script = sjexl.createScript("foo.ALIAS", "foo"); |
| result = script.execute(null, foo); |
| Assert.assertEquals(foo.alias, result); |
| } |
| |
| @Test |
| public void testSetAllow() throws Exception { |
| final Foo foo = new Foo("42"); |
| final String expr = "foo.alias = $0"; |
| JexlScript script; |
| Object result; |
| |
| final JexlSandbox sandbox = new JexlSandbox(); |
| sandbox.allow(Foo.class.getName()).write("alias"); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create(); |
| |
| script = sjexl.createScript(expr, "foo", "$0"); |
| result = script.execute(null, foo, "43"); |
| Assert.assertEquals("43", result); |
| Assert.assertEquals("43", foo.alias); |
| } |
| |
| @Test |
| public void testRestrict() throws Exception { |
| final JexlContext context = new MapContext(); |
| context.set("System", System.class); |
| final JexlSandbox sandbox = new JexlSandbox(); |
| // only allow call to currentTimeMillis (avoid exit, gc, loadLibrary, etc) |
| sandbox.allow(System.class.getName()).execute("currentTimeMillis"); |
| // can not create a new file |
| sandbox.block(java.io.File.class.getName()).execute(""); |
| |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create(); |
| |
| String expr; |
| JexlScript script; |
| Object result; |
| |
| script = sjexl.createScript("System.exit()"); |
| try { |
| result = script.execute(context); |
| Assert.fail("should not allow calling exit!"); |
| } catch (final JexlException xjexl) { |
| LOGGER.info(xjexl.toString()); |
| } |
| |
| script = sjexl.createScript("System.exit(1)"); |
| try { |
| result = script.execute(context); |
| Assert.fail("should not allow calling exit!"); |
| } catch (final JexlException xjexl) { |
| LOGGER.info(xjexl.toString()); |
| } |
| |
| script = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')"); |
| try { |
| result = script.execute(context); |
| Assert.fail("should not allow creating a file"); |
| } catch (final JexlException xjexl) { |
| LOGGER.info(xjexl.toString()); |
| } |
| |
| expr = "System.currentTimeMillis()"; |
| script = sjexl.createScript("System.currentTimeMillis()"); |
| result = script.execute(context); |
| Assert.assertNotNull(result); |
| } |
| |
| @Test |
| public void testSandboxInherit0() throws Exception { |
| Object result; |
| final JexlContext ctxt = null; |
| final List<String> foo = new ArrayList<String>(); |
| final JexlSandbox sandbox = new JexlSandbox(false, true); |
| sandbox.allow(java.util.List.class.getName()); |
| |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create(); |
| final JexlScript method = sjexl.createScript("foo.add(y)", "foo", "y"); |
| final JexlScript set = sjexl.createScript("foo[x] = y", "foo", "x", "y"); |
| final JexlScript get = sjexl.createScript("foo[x]", "foo", "x"); |
| |
| result = method.execute(ctxt, foo, "nothing"); |
| Assert.assertEquals(true, result); |
| result = null; |
| result = get.execute(null, foo, 0); |
| Assert.assertEquals("nothing", result); |
| result = null; |
| result = set.execute(null, foo, 0, "42"); |
| Assert.assertEquals("42", result); |
| |
| result = null; |
| result = get.execute(null, foo, 0); |
| Assert.assertEquals("42", result); |
| } |
| |
| public abstract static class Operation { |
| protected final int base; |
| public Operation(final int sz) { |
| base = sz; |
| } |
| |
| public abstract int someOp(int x); |
| public abstract int nonCallable(int y); |
| } |
| |
| public static class Operation2 extends Operation { |
| public Operation2(final int sz) { |
| super(sz); |
| } |
| |
| @Override |
| public int someOp(final int x) { |
| return base + x; |
| } |
| |
| @Override |
| public int nonCallable(final int y) { |
| throw new UnsupportedOperationException("do NOT call"); |
| } |
| } |
| |
| @Test |
| public void testSandboxInherit1() throws Exception { |
| Object result; |
| final JexlContext ctxt = null; |
| final Operation2 foo = new Operation2(12); |
| final JexlSandbox sandbox = new JexlSandbox(false, true); |
| sandbox.allow(Operation.class.getName()); |
| sandbox.block(Operation.class.getName()).execute("nonCallable"); |
| //sandbox.block(Foo.class.getName()).execute(); |
| final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create(); |
| final JexlScript someOp = sjexl.createScript("foo.someOp(y)", "foo", "y"); |
| result = someOp.execute(ctxt, foo, 30); |
| Assert.assertEquals(42, result); |
| final JexlScript nonCallable = sjexl.createScript("foo.nonCallable(y)", "foo", "y"); |
| try { |
| result = nonCallable.execute(null, foo, 0); |
| Assert.fail("should not be possible"); |
| } catch (final JexlException xjm) { |
| // ok |
| LOGGER.info(xjm.toString()); |
| } |
| } |
| |
| public static class Foo42 { |
| public int getFoo() { |
| return 42; |
| } |
| } |
| |
| public static class Foo43 extends Foo42 { |
| @Override |
| @NoJexl |
| public int getFoo() { |
| return 43; |
| } |
| } |
| |
| public static class Foo44 extends Foo43 { |
| @Override |
| public int getFoo() { |
| return 44; |
| } |
| } |
| |
| @Test |
| public void testNoJexl312() throws Exception { |
| final JexlContext ctxt = new MapContext(); |
| |
| final JexlEngine sjexl = new JexlBuilder().safe(false).strict(true).create(); |
| final JexlScript foo = sjexl.createScript("x.getFoo()", "x"); |
| try { |
| foo.execute(ctxt, new Foo44()); |
| Assert.fail("should have thrown"); |
| } catch (final JexlException xany) { |
| Assert.assertNotNull(xany); |
| } |
| } |
| } |