The SandboxShutter interface:
public interface SandboxShutter
{
public boolean allowClassAccess(Class<?> type);
public boolean allowFieldAccess(Class<?> type, Object instance, String fieldName);
public boolean allowMethodAccess(Class<?> type, Object instance, String methodName);
public boolean allowStaticFieldAccess(Class<?> type, String fieldName);
public boolean allowStaticMethodAccess(Class<?> type, String methodName);
}
When the following javascript code is executed:
importPackage(Packages.my.game); // assuming the Java class my.game.Player exists var player = new Player("Jake"); player.gender = "female"; // this is a Java method! player.setGender("male"); // this the same Java method. player.age = 18; // this is a Java field player.age += 3; var player = new Player("Jane"); player.gender = "male"; player.gender = "female"; player.age = 19; player.age += 2; var count = Player.PLAYER_COUNT; Player.PLAYER_COUNT += 2;
The following SandboxShutter calls will be made:
allowClassAccess: my.game.Player allowMethodAccess: my.game.Player.setGender() instance=Player@2346 allowFieldAccess: my.game.Player.age instance=Player@2346 allowMethodAccess: my.game.Player.setGender() instance=Player@54326 allowFieldAccess: my.game.Player.age instance=Player@54326 allowStaticFieldAccess: my.game.Player.PLAYER_COUNT
Create the SandboxContextFactory:
public static class SandboxContextFactory extends ContextFactory
{
final SandboxShutter shutter;
public SandboxContextFactory(SandboxShutter shutter)
{
this.shutter = shutter;
}
@Override
protected Context makeContext()
{
Context cx = super.makeContext();
cx.setWrapFactory(new SandboxWrapFactory());
cx.setClassShutter(new ClassShutter()
{
private final Map<String, Boolean> nameToAccepted = new HashMap<String, Boolean>();
@Override
public boolean visibleToScripts(String name)
{
Boolean granted = this.nameToAccepted.get(name);
if (granted != null)
{
return granted.booleanValue();
}
Class< ? > staticType;
try
{
staticType = Class.forName(name);
}
catch (Exception exc)
{
this.nameToAccepted.put(name, Boolean.FALSE);
return false;
}
boolean grant = shutter.allowClassAccess(staticType);
this.nameToAccepted.put(name, Boolean.valueOf(grant));
return grant;
}
});
return cx;
}
class SandboxWrapFactory extends WrapFactory
{
@Override
public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj)
{
this.ensureReplacedClass(scope, obj, null);
return super.wrapNewObject(cx, scope, obj);
}
@Override
public Object wrap(Context cx, Scriptable scope, Object obj, Class< ? > staticType)
{
this.ensureReplacedClass(scope, obj, staticType);
return super.wrap(cx, scope, obj, staticType);
}
@Override
public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class< ? > staticType)
{
final Class< ? > type = this.ensureReplacedClass(scope, javaObject, staticType);
return new NativeJavaObject(scope, javaObject, staticType)
{
private final Map<String, Boolean> instanceMethodToAllowed = new HashMap<String, Boolean>();
@Override
public Object get(String name, Scriptable scope)
{
Object wrapped = super.get(name, scope);
if (wrapped instanceof BaseFunction)
{
String id = type.getName() + "." + name;
Boolean allowed = this.instanceMethodToAllowed.get(id);
if (allowed == null)
{
boolean allow = shutter.allowMethodAccess(type, javaObject, name);
this.instanceMethodToAllowed.put(id, allowed = Boolean.valueOf(allow));
}
if (!allowed.booleanValue())
{
return NOT_FOUND;
}
}
else
{
// NativeJavaObject + only boxed primitive types?
if (!shutter.allowFieldAccess(type, javaObject, name))
{
return NOT_FOUND;
}
}
return wrapped;
}
};
}
//
private final Set<Class< ? >> replacedClasses = new HashSet<Class< ? >>();
private Class< ? > ensureReplacedClass(Scriptable scope, Object obj, Class< ? > staticType)
{
final Class< ? > type = (staticType == null && obj != null) ? obj.getClass() : staticType;
if (!type.isPrimitive() && !type.getName().startsWith("java.") && this.replacedClasses.add(type))
{
this.replaceJavaNativeClass(type, scope);
}
return type;
}
private void replaceJavaNativeClass(final Class< ? > type, Scriptable scope)
{
Object clazz = Context.jsToJava(ScriptableObject.getProperty(scope, "Packages"), Object.class);
Object holder = null;
for (String part : Text.split(type.getName(), '.'))
{
holder = clazz;
clazz = ScriptableObject.getProperty((Scriptable) clazz, part);
}
NativeJavaClass nativeClass = (NativeJavaClass) clazz;
nativeClass = new NativeJavaClass(scope, type)
{
@Override
public Object get(String name, Scriptable start)
{
Object wrapped = super.get(name, start);
if (wrapped instanceof BaseFunction)
{
if (!shutter.allowStaticMethodAccess(type, name))
{
return NOT_FOUND;
}
}
else
{
// NativeJavaObject + only boxed primitive types?
if (!shutter.allowStaticFieldAccess(type, name))
{
return NOT_FOUND;
}
}
return wrapped;
}
};
ScriptableObject.putProperty((Scriptable) holder, type.getSimpleName(), nativeClass);
ScriptableObject.putProperty(scope, type.getSimpleName(), nativeClass);
}
}
}
Install the (global) SandboxContextFactory:
ContextFactory.initGlobal(new SandboxContextFactory(new SandboxShutter() { ... })); // create and initialize Rhino Context Context cx = Context.enter(); Scriptable prototype = cx.initStandardObjects(); Scriptable topLevel = new ImporterTopLevel(cx); prototype.setParentScope(topLevel); Scriptable scope = cx.newObject(prototype); scope.setPrototype(prototype); // your scripts