Pyro4: вызов метода на удаленном подобъекте без попытки возврата подобъекта
Допустим, у меня есть объект, который является свойством другого объекта.
import Pyro4
@Pyro4.expose
class ClassB:
def foo(self):
return 'Hello from ClassB::foo()'
@Pyro4.expose
class ClassA:
def __init__(self):
self._b = ClassB()
def foo(self):
return 'Hello from ClassA::foo()'
@property
def b(self):
return self._b
if __name__ == '__main__':
daemon = Pyro4.Daemon(host='localhost')
ns = Pyro4.locateNS(host='localhost', port=9090)
ns.register('ClassA', daemon.register(ClassA))
daemon.requestLoop()
И на локальной машине я запускаю эту
import Pyro4
if __name__ == '__main__':
ns = Pyro4.locateNS(host='localhost', port=9090)
uri = ns.lookup('ClassA')
a = Pyro4.Proxy(uri)
print(a.foo())
print(a.b.foo()) # Fails here
Когда я пытаюсь вызвать abfoo, он пытается сериализовать ab и локально вызвать foo для него, но я хочу вызвать foo для экземпляра ClassB, который уже существует на удаленном экземпляре ClassA.
Конечно, я мог бы добавить метод к ClassA, который делегирует b.foo(), например
def classA:
# ...
def foo_on_b(self):
return self._b.foo()
Но я бы предпочел не делать этого.
2 ответа
Решение
Это невозможно, по замыслу. Это уязвимость безопасности. https://pyro4.readthedocs.io/en/stable/security.html
Удалось взломать это так.
remote.py
import Pyro4
class C:
def __init__(self):
self.val = 123
class B:
def __init__(self):
self.c = C()
class A:
def __init__(self):
self.b = B()
def getattr_r(obj, *attrs):
return getattr_r(getattr(obj, attrs[0]), *attrs[1:]) if attrs else obj
class ClassC:
def foo(self, x, y):
return 'Hello from ClassC::foo()! {} + {} = {}'.format(x, y, x + y)
class ClassB:
def __init__(self):
self.c = ClassC()
def foo(self, x, y):
return 'Hello from ClassB::foo()! {} + {} = {}'.format(x, y, x + y)
@Pyro4.expose
class ClassA:
def __init__(self):
self.b = ClassB()
def foo(self, x, y):
return 'Hello from ClassA::foo()! {} + {} = {}'.format(x, y, x + y)
def invoke_on_subobj(self, obj_chain, *args, **kwargs):
return getattr_r(self, *obj_chain)(*args, **kwargs)
if __name__ == '__main__':
daemon = Pyro4.Daemon(host='localhost')
ns = Pyro4.locateNS(host='localhost', port=9090)
ns.register('ClassA', daemon.register(ClassA))
daemon.requestLoop()
local.py
import Pyro4
class ObjTraversableProxy:
def __init__(self, proxy, bound_attrs=[]):
self._proxy = proxy
self._bound_attrs = bound_attrs
def __getattr__(self, attr):
return ObjTraversableProxy(self._proxy, self._bound_attrs + [attr])
def __call__(self, *args, **kwargs):
if len(self._bound_attrs) > 1:
return self._proxy.invoke_on_subobj(self._bound_attrs, *args, **kwargs)
else:
return getattr(self._proxy, self._bound_attrs[0])(*args, **kwargs)
if __name__ == '__main__':
ns = Pyro4.locateNS(host='localhost', port=9090)
uri = ns.lookup('ClassA')
a = ObjTraversableProxy(Pyro4.Proxy(uri))
print(a.foo(3, 4))
print(a.b.foo(5, 6))
print(a.b.c.foo(6, 7))
Результат
Hello from ClassA::foo()! 3 + 4 = 7
Hello from ClassB::foo()! 5 + 6 = 11
Hello from ClassC::foo()! 6 + 7 = 13