service.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. class Service(object):
  2. """The service base-class. Subclass this class to implement custom RPyC
  3. services:
  4. * The name of the class implementing the 'Foo' service should match the
  5. pattern 'FooService' (suffixed by the word 'Service'):
  6. class FooService(Service):
  7. pass
  8. FooService.get_service_name() # 'FOO'
  9. FooService.get_service_aliases() # ['FOO']
  10. * To supply a different name or aliases, use the ALIASES class attribute:
  11. class Foobar(Service):
  12. ALIASES = ["foo", "bar", "lalaland"]
  13. Foobar.get_service_name() # 'FOO'
  14. Foobar.get_service_aliases() # ['FOO', 'BAR', 'LALALAND']
  15. * Override on_connect to perform custom initialization
  16. * Override on_disconnect to perform custom finilization
  17. * To add exposed methods or attributes, simply define them as normally,
  18. but make sure their name is prefixed by 'exposed_', e.g.:
  19. class FooService(Service):
  20. def exposed_foo(self, x, y):
  21. return x + y
  22. * All other names (not prefixed by 'exposed_') are local (not accessible
  23. by the other party)
  24. """
  25. __slots__ = ["_conn"]
  26. ALIASES = ()
  27. def __init__(self, conn):
  28. self._conn = conn
  29. def on_connect(self):
  30. """called when the connection is established"""
  31. pass
  32. def on_disconnect(self):
  33. """called when the connection had already terminated for cleanup
  34. (must not perform any IO on the connection)"""
  35. pass
  36. def _rpyc_getattr(self, name):
  37. if name.startswith("exposed_"):
  38. name = name
  39. else:
  40. name = "exposed_" + name
  41. return getattr(self, name)
  42. def _rpyc_delattr(self, name):
  43. raise AttributeError("access denied")
  44. def _rpyc_setattr(self, name, value):
  45. raise AttributeError("access denied")
  46. @classmethod
  47. def get_service_aliases(cls):
  48. if cls.ALIASES:
  49. return tuple(str(n).upper() for n in cls.ALIASES)
  50. name = cls.__name__.upper()
  51. if name.endswith("SERVICE"):
  52. name = name[:-7]
  53. return (name,)
  54. @classmethod
  55. def get_service_name(cls):
  56. return cls.get_service_aliases()[0]
  57. exposed_get_service_aliases = get_service_aliases
  58. exposed_get_service_name = get_service_name
  59. class VoidService(Service):
  60. """void service - an empty service"""
  61. __slots__ = ()
  62. class ModuleNamespace(object):
  63. """used by the SlaveService to implement the magic 'module namespace'"""
  64. __slots__ = ["__getmodule", "__cache", "__weakref__"]
  65. def __init__(self, getmodule):
  66. self.__getmodule = getmodule
  67. self.__cache = {}
  68. def __getitem__(self, name):
  69. if type(name) is tuple:
  70. name = ".".join(name)
  71. if name not in self.__cache:
  72. self.__cache[name] = self.__getmodule(name)
  73. return self.__cache[name]
  74. def __getattr__(self, name):
  75. return self[name]
  76. class SlaveService(Service):
  77. """The SlaveService allows the other side to perform arbitrary imports and
  78. code execution on the server. This is provided for compatibility with
  79. the classic RPyC (2.6) modus operandi.
  80. This service is very useful in local, secured networks, but it exposes
  81. a major security risk otherwise."""
  82. __slots__ = ["exposed_namespace"]
  83. def on_connect(self):
  84. self.exposed_namespace = {}
  85. self._conn._config.update(dict(
  86. allow_all_attrs = True,
  87. allow_pickle = True,
  88. allow_getattr = True,
  89. allow_setattr = True,
  90. allow_delattr = True,
  91. import_custom_exceptions = True,
  92. instantiate_custom_exceptions = True,
  93. instantiate_oldstyle_exceptions = True,
  94. ))
  95. # shortcuts
  96. self._conn.modules = ModuleNamespace(self._conn.root.getmodule)
  97. self._conn.eval = self._conn.root.eval
  98. self._conn.execute = self._conn.root.execute
  99. self._conn.namespace = self._conn.root.namespace
  100. self._conn.builtin = self._conn.modules.__builtin__
  101. def exposed_execute(self, text):
  102. exec text in self.exposed_namespace
  103. def exposed_eval(self, text):
  104. return eval(text, self.exposed_namespace)
  105. def exposed_getmodule(self, name):
  106. return __import__(name, None, None, "*")
  107. def exposed_getconn(self):
  108. return self._conn