changeset 24:dace90a7f5e3

Added a 'jsexposed' decorator and, for security purposes, required that __jsexposed__ be True on all callables passed into JS-land as well.
author Atul Varma <varmaa@toolness.com>
date Mon, 07 Sep 2009 16:53:41 -0700
parents 35ab0ad3c294
children 64fb84017d87
files pydershell/pydershell.py pydershell/test.py
diffstat 2 files changed, 36 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/pydershell/pydershell.py	Mon Sep 07 16:25:58 2009 -0700
+++ b/pydershell/pydershell.py	Mon Sep 07 16:53:41 2009 -0700
@@ -194,6 +194,31 @@
     lines.insert(0, "Traceback (most recent call last):")
     return '\n'.join(lines)
 
+def jsexposed(name=None, on=None):
+    """
+    Decorator used to expose the decorated function or method to
+    untrusted JS.
+
+    'name' is an optional alternative name for the function.
+
+    'on' is an optional SafeJsObjectWrapper that the function can be
+    automatically attached as a property to.
+    """
+
+    if callable(name):
+        func = name
+        func.__jsexposed__ = True
+        return func
+
+    def make_exposed(func):
+        if name:
+            func.__name__ = name
+        func.__jsexposed__ = True
+        if on:
+            on[func.__name__] = func
+        return func
+    return make_exposed
+
 class JsExposedObject(object):
     """
     Trivial base/mixin class for any Python classes that choose to
@@ -337,6 +362,10 @@
             # It's already wrapped, just unwrap it.
             return value.wrapped_jsobject
         elif callable(value):
+            if not (hasattr(value, '__jsexposed__') and
+                    value.__jsexposed__):
+                raise ValueError("Callable isn't configured for exposure "
+                                 "to untrusted JS code")
             return self.__wrap_pycallable(value)
         elif isinstance(value, JsExposedObject):
             return self.__wrap_pyinstance(value)
--- a/pydershell/test.py	Mon Sep 07 16:25:58 2009 -0700
+++ b/pydershell/test.py	Mon Sep 07 16:53:41 2009 -0700
@@ -5,38 +5,38 @@
 import sys
 
 import pydermonkey
-from pydershell import JsSandbox, JsExposedObject
+from pydershell import JsSandbox, JsExposedObject, jsexposed
 
 if __name__ == '__main__':
     sandbox = JsSandbox()
 
     class Baz(JsExposedObject):
+        @jsexposed
         def woozle(self, blap):
             return blap + 5
-        woozle.__jsexposed__ = True
 
+    @jsexposed(on=sandbox.root)
     def baz():
         return Baz()
-    sandbox.root.baz = baz
 
+    @jsexposed(on=sandbox.root)
     def bar(obj):
         print obj.bar()
-    sandbox.root.bar = bar
 
+    @jsexposed(on=sandbox.root)
     def foo(callback):
         return callback()
-    sandbox.root.foo = foo
 
+    @jsexposed(on=sandbox.root)
     def ensureBaz(baz):
         if not isinstance(baz, Baz):
             print "Uhoh"
         else:
             print "ok"
-    sandbox.root.ensureBaz = ensureBaz
 
+    @jsexposed(name="print", on=sandbox.root)
     def jsprint(string):
         print string
-    sandbox.root['print'] = jsprint
 
     retval = sandbox.run_script('test.js')