diff --git a/core/src/main/java/io/apigee/trireme/core/NodeEnvironment.java b/core/src/main/java/io/apigee/trireme/core/NodeEnvironment.java index ed312de4..0ed6c725 100644 --- a/core/src/main/java/io/apigee/trireme/core/NodeEnvironment.java +++ b/core/src/main/java/io/apigee/trireme/core/NodeEnvironment.java @@ -268,6 +268,7 @@ private void initialize() contextFactory.setJsVersion(DEFAULT_JS_VERSION); contextFactory.setOptLevel(optLevel); contextFactory.setCountOperations(scriptTimeLimit > 0L); + contextFactory.setExtraClassShutter(getSandbox() == null ? null : getSandbox().getExtraClassShutter()); contextFactory.call(new ContextAction() { diff --git a/core/src/main/java/io/apigee/trireme/core/Sandbox.java b/core/src/main/java/io/apigee/trireme/core/Sandbox.java index 38337b57..331a16a5 100644 --- a/core/src/main/java/io/apigee/trireme/core/Sandbox.java +++ b/core/src/main/java/io/apigee/trireme/core/Sandbox.java @@ -21,6 +21,7 @@ */ package io.apigee.trireme.core; +import org.mozilla.javascript.ClassShutter; import org.mozilla.javascript.Scriptable; import java.io.InputStream; @@ -51,6 +52,7 @@ public class Sandbox private SubprocessPolicy processPolicy; private List> mounts; private boolean hideOsDetails; + private ClassShutter extraClassShutter; /** * Create a new sandbox that will not affect anything in any way. @@ -78,6 +80,7 @@ public Sandbox(Sandbox parent) this.networkPolicy = parent.networkPolicy; this.processPolicy = parent.processPolicy; this.hideOsDetails = parent.hideOsDetails; + this.extraClassShutter = parent.extraClassShutter; if (parent.mounts != null) { this.mounts = new ArrayList>(parent.mounts); } @@ -239,4 +242,20 @@ public Sandbox setHideOSDetails(boolean obscure) { public boolean isHideOSDetails() { return hideOsDetails; } + + /** + * Attach an extra {@link ClassShutter} that can allow Trireme embedders the ability to expose + * extra Java classes to JavaScript code. + * + * WARNING: This must be used with care as passing Java objects disallowed by the default + * Trireme ClassShutter can result in unknown consequences. + */ + public Sandbox setExtraClassShutter(ClassShutter extraClassShutter) { + this.extraClassShutter = extraClassShutter; + return this; + } + + public ClassShutter getExtraClassShutter() { + return extraClassShutter; + } } diff --git a/core/src/main/java/io/apigee/trireme/core/internal/RhinoContextFactory.java b/core/src/main/java/io/apigee/trireme/core/internal/RhinoContextFactory.java index 6bbf3086..9ca7c79e 100644 --- a/core/src/main/java/io/apigee/trireme/core/internal/RhinoContextFactory.java +++ b/core/src/main/java/io/apigee/trireme/core/internal/RhinoContextFactory.java @@ -26,7 +26,6 @@ import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.JavaScriptException; -import org.mozilla.javascript.RhinoException; import java.util.HashSet; @@ -35,9 +34,11 @@ public class RhinoContextFactory { private static final int DEFAULT_INSTRUCTION_THRESHOLD = 100000; + private final ClassShutter defaultClassShutter = new OpaqueClassShutter(); private int jsVersion = NodeEnvironment.DEFAULT_JS_VERSION; private int optLevel = NodeEnvironment.DEFAULT_OPT_LEVEL; private boolean countOperations; + private ClassShutter extraClassShutter; @Override protected Context makeContext() @@ -49,7 +50,7 @@ protected Context makeContext() if (countOperations) { c.setInstructionObserverThreshold(DEFAULT_INSTRUCTION_THRESHOLD); } - c.setClassShutter(OpaqueClassShutter.INSTANCE); + c.setClassShutter(defaultClassShutter); return c; } @@ -114,16 +115,22 @@ public void setCountOperations(boolean countOperations) this.countOperations = countOperations; } + public ClassShutter getExtraClassShutter() { + return this.extraClassShutter; + } + + public void setExtraClassShutter(ClassShutter extraClassShutter) { + this.extraClassShutter = extraClassShutter; + } + /** * Don't allow access to Java code at all from inside Node code. However, Rhino seems to depend on access * to certain internal classes, at least for error handing, so we will allow the code to have access * to them. */ - private static final class OpaqueClassShutter + private final class OpaqueClassShutter implements ClassShutter { - static final OpaqueClassShutter INSTANCE = new OpaqueClassShutter(); - private final HashSet whitelist = new HashSet(); private OpaqueClassShutter() @@ -152,7 +159,7 @@ private OpaqueClassShutter() @Override public boolean visibleToScripts(String s) { - return (whitelist.contains(s)); + return (whitelist.contains(s)) || (extraClassShutter != null && extraClassShutter.visibleToScripts(s)); } } } diff --git a/core/src/test/java/io/apigee/trireme/core/test/SandboxingTest.java b/core/src/test/java/io/apigee/trireme/core/test/SandboxingTest.java index b903d82b..d6d499fa 100644 --- a/core/src/test/java/io/apigee/trireme/core/test/SandboxingTest.java +++ b/core/src/test/java/io/apigee/trireme/core/test/SandboxingTest.java @@ -7,11 +7,13 @@ import io.apigee.trireme.core.ScriptFuture; import io.apigee.trireme.core.ScriptStatus; import org.junit.Test; +import org.mozilla.javascript.ClassShutter; import static org.junit.Assert.*; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -168,4 +170,31 @@ public void testStdoutSharing() env.close(); } } + + /** + * Verify the support for an extra {@link ClassShutter} works as expected. + */ + @Test + public void testExtraClassShutter() + throws NodeException, ExecutionException, InterruptedException, IOException + { + NodeEnvironment env = new NodeEnvironment(); + + env.setSandbox(new Sandbox().setExtraClassShutter(new ClassShutter() { + @Override + public boolean visibleToScripts(String fullClassName) { + return true; + } + })); + + File scriptFile = new File("./target/test-classes/tests/extraclassshuttertest.js"); + NodeScript script = env.createScript(scriptFile.getName(), + scriptFile, + new String[] { + scriptFile.getCanonicalPath() + }); + ScriptStatus status = script.execute().get(); + assertEquals(0, status.getExitCode()); + script.close(); + } } diff --git a/core/src/test/resources/tests/extraclassshuttertest.js b/core/src/test/resources/tests/extraclassshuttertest.js new file mode 100644 index 00000000..86dfd799 --- /dev/null +++ b/core/src/test/resources/tests/extraclassshuttertest.js @@ -0,0 +1,8 @@ +var assert = require('assert'); +var actualFilePath = new java.io.File(__filename).getCanonicalPath(); +var expectedFilePath = process.argv[2]; + +assert (actualFilePath.equals(expectedFilePath)); + +// We have to use "==" here because Rhino thinks actualFilePath is an object instead of a string +assert (actualFilePath == expectedFilePath);