jsonrpc: Don't allow objects at the root
This commit is contained in:
parent
b33df8200a
commit
8f604204da
@ -15,43 +15,24 @@ class JsonRpcWrapper(object):
|
||||
processing of JSON-RPC 2.0 messages. The transport of the messages over
|
||||
HTTP, WebSocket, TCP, or whatever is of no concern to this class.
|
||||
|
||||
The wrapper supports exporting the methods of multiple objects. If so, they
|
||||
must be exported with different prefixes, called "mounts".
|
||||
The wrapper supports exporting the methods of one or more objects. Either
|
||||
way, the objects must be exported with method name prefixes, called
|
||||
"mounts".
|
||||
|
||||
- To expose a single object, add it to the objects mapping using the empty
|
||||
string as the mount::
|
||||
To expose objects, add them all to the objects mapping. The key in the
|
||||
mapping is used as the object's mounting point in the exposed API::
|
||||
|
||||
jrw = JsonRpcWrapper(objects={'': my_object})
|
||||
jrw = JsonRpcWrapper(objects={
|
||||
'foo': foo,
|
||||
'hello': lambda: 'Hello, world!',
|
||||
})
|
||||
|
||||
If ``my_object`` has a method named ``my_method()`` will be exported as
|
||||
the JSON-RPC 2.0 method name ``my_method``.
|
||||
This will export the Python callables on the left as the JSON-RPC 2.0
|
||||
method names on the right::
|
||||
|
||||
- To expose multiple objects, add them all to the objects mapping. The key
|
||||
in the mapping is used as the object's mounting point in the exposed
|
||||
API::
|
||||
|
||||
jrw = JsonRpcWrapper(objects={
|
||||
'': foo,
|
||||
'hello': lambda: 'Hello, world!',
|
||||
'abc': abc,
|
||||
})
|
||||
|
||||
This will export the Python callables on the left as the JSON-RPC 2.0
|
||||
method names on the right::
|
||||
|
||||
foo.bar() -> bar
|
||||
foo.baz() -> baz
|
||||
lambda -> hello
|
||||
abc.def() -> abc.def
|
||||
|
||||
If the ``foo`` object mounted at the root also got a method named
|
||||
``hello``, there will be a name collision between ``foo.hello()`` and the
|
||||
lambda function mounted at ``hello``. In that case, the JSON-RPC 2.0
|
||||
method name ``hello`` will refer to the lambda function, because it was
|
||||
mounted explicitly as ``hello``.
|
||||
|
||||
It is recommended to avoid name collisions entirely by using non-empty
|
||||
mounts for all objects.
|
||||
foo.bar() -> foo.bar
|
||||
foo.baz() -> foo.baz
|
||||
lambda -> hello
|
||||
|
||||
Only the public methods of the mounted objects, or functions/methods
|
||||
included directly in the mapping, will be exposed.
|
||||
@ -73,6 +54,9 @@ class JsonRpcWrapper(object):
|
||||
"""
|
||||
|
||||
def __init__(self, objects, decoders=None, encoders=None):
|
||||
if '' in objects.keys():
|
||||
raise AttributeError(
|
||||
'The empty string is not allowed as an object mount')
|
||||
self.objects = objects
|
||||
self.decoder = get_combined_json_decoder(decoders or [])
|
||||
self.encoder = get_combined_json_encoder(encoders or [])
|
||||
@ -305,18 +289,13 @@ class JsonRpcInspector(object):
|
||||
Inspects a group of classes and functions to create a description of what
|
||||
methods they can expose over JSON-RPC 2.0.
|
||||
|
||||
To inspect a single class, add it to the objects mapping using the empty
|
||||
string as the key::
|
||||
|
||||
jri = JsonRpcInspector(objects={'': MyClass})
|
||||
|
||||
To inspect multiple classes, add them all to the objects mapping. The key
|
||||
in the mapping is used as the classes' mounting point in the exposed API::
|
||||
To inspect one or more classes, add them all to the objects mapping. The
|
||||
key in the mapping is used as the classes' mounting point in the exposed
|
||||
API::
|
||||
|
||||
jri = JsonRpcInspector(objects={
|
||||
'': Foo,
|
||||
'foo': Foo,
|
||||
'hello': lambda: 'Hello, world!',
|
||||
'abc': Abc,
|
||||
})
|
||||
|
||||
Since the inspector is based on inspecting classes and not instances, it
|
||||
@ -328,6 +307,9 @@ class JsonRpcInspector(object):
|
||||
"""
|
||||
|
||||
def __init__(self, objects):
|
||||
if '' in objects.keys():
|
||||
raise AttributeError(
|
||||
'The empty string is not allowed as an object mount')
|
||||
self.objects = objects
|
||||
|
||||
def describe(self):
|
||||
|
||||
@ -44,10 +44,10 @@ class JsonRpcTestBase(unittest.TestCase):
|
||||
self.jrw = jsonrpc.JsonRpcWrapper(
|
||||
objects={
|
||||
'hello': lambda: 'Hello, world!',
|
||||
'calc': Calculator(),
|
||||
'core': self.core,
|
||||
'core.playback': self.core.playback,
|
||||
'core.tracklist': self.core.tracklist,
|
||||
'': Calculator(),
|
||||
},
|
||||
encoders=[models.ModelJSONEncoder],
|
||||
decoders=[models.model_json_decoder])
|
||||
@ -56,6 +56,12 @@ class JsonRpcTestBase(unittest.TestCase):
|
||||
pykka.ActorRegistry.stop_all()
|
||||
|
||||
|
||||
class JsonRpcSetupTest(JsonRpcTestBase):
|
||||
def test_empty_object_mounts_is_not_allowed(self):
|
||||
test = lambda: jsonrpc.JsonRpcWrapper(objects={'': Calculator()})
|
||||
self.assertRaises(AttributeError, test)
|
||||
|
||||
|
||||
class JsonRpcSerializationTest(JsonRpcTestBase):
|
||||
def test_handle_json_converts_from_and_to_json(self):
|
||||
self.jrw.handle_data = mock.Mock()
|
||||
@ -132,10 +138,10 @@ class JsonRpcSingleCommandTest(JsonRpcTestBase):
|
||||
self.assertNotIn('error', response)
|
||||
self.assertEqual(response['result'], 'Hello, world!')
|
||||
|
||||
def test_call_method_on_plain_object_as_root(self):
|
||||
def test_call_method_on_plain_object(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'model',
|
||||
'method': 'calc.model',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
@ -148,7 +154,7 @@ class JsonRpcSingleCommandTest(JsonRpcTestBase):
|
||||
def test_call_method_which_returns_dict_from_plain_object(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'describe',
|
||||
'method': 'calc.describe',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
@ -510,6 +516,10 @@ class JsonRpcBatchErrorTest(JsonRpcTestBase):
|
||||
|
||||
|
||||
class JsonRpcInspectorTest(JsonRpcTestBase):
|
||||
def test_empty_object_mounts_is_not_allowed(self):
|
||||
test = lambda: jsonrpc.JsonRpcInspector(objects={'': Calculator})
|
||||
self.assertRaises(AttributeError, test)
|
||||
|
||||
def test_can_describe_method_on_root(self):
|
||||
inspector = jsonrpc.JsonRpcInspector({
|
||||
'hello': lambda: 'Hello, world!',
|
||||
@ -520,24 +530,24 @@ class JsonRpcInspectorTest(JsonRpcTestBase):
|
||||
self.assertIn('hello', methods)
|
||||
self.assertEqual(len(methods['hello']['params']), 0)
|
||||
|
||||
def test_inspector_can_describe_methods_on_the_root_class(self):
|
||||
def test_inspector_can_describe_an_object_with_methods(self):
|
||||
inspector = jsonrpc.JsonRpcInspector({
|
||||
'': Calculator,
|
||||
'calc': Calculator,
|
||||
})
|
||||
|
||||
methods = inspector.describe()
|
||||
|
||||
self.assertIn('add', methods)
|
||||
self.assertIn('calc.add', methods)
|
||||
self.assertEqual(
|
||||
methods['add']['description'],
|
||||
methods['calc.add']['description'],
|
||||
'Returns the sum of the given numbers')
|
||||
|
||||
self.assertIn('sub', methods)
|
||||
self.assertIn('take_it_all', methods)
|
||||
self.assertNotIn('_secret', methods)
|
||||
self.assertNotIn('__init__', methods)
|
||||
self.assertIn('calc.sub', methods)
|
||||
self.assertIn('calc.take_it_all', methods)
|
||||
self.assertNotIn('calc._secret', methods)
|
||||
self.assertNotIn('calc.__init__', methods)
|
||||
|
||||
method = methods['take_it_all']
|
||||
method = methods['calc.take_it_all']
|
||||
self.assertIn('params', method)
|
||||
|
||||
params = method['params']
|
||||
@ -559,16 +569,6 @@ class JsonRpcInspectorTest(JsonRpcTestBase):
|
||||
self.assertNotIn('default', params[4])
|
||||
self.assertEqual(params[4]['kwargs'], True)
|
||||
|
||||
def test_inspector_can_describe_methods_not_on_the_root(self):
|
||||
inspector = jsonrpc.JsonRpcInspector({
|
||||
'calc': Calculator,
|
||||
})
|
||||
|
||||
methods = inspector.describe()
|
||||
|
||||
self.assertIn('calc.add', methods)
|
||||
self.assertIn('calc.sub', methods)
|
||||
|
||||
def test_inspector_can_describe_a_bunch_of_large_classes(self):
|
||||
inspector = jsonrpc.JsonRpcInspector({
|
||||
'core.library': core.LibraryController,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user