You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
7.0 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 Vauxoo - https://www.vauxoo.com/
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. import logging
  5. import traceback
  6. from odoo import api, exceptions, fields, models, tools
  7. from odoo.tools.safe_eval import safe_eval
  8. from odoo.tools.translate import _
  9. _logger = logging.getLogger(__name__)
  10. try:
  11. import ipaddress
  12. except ImportError as err:
  13. _logger.debug(err)
  14. class WebhookAddress(models.Model):
  15. _name = 'webhook.address'
  16. name = fields.Char(
  17. 'IP or Network Address',
  18. required=True,
  19. help='IP or network address of your consumer webhook:\n'
  20. 'ip address e.g.: 10.10.0.8\n'
  21. 'network address e.g. of: 10.10.0.8/24',
  22. )
  23. webhook_id = fields.Many2one(
  24. 'webhook', 'Webhook', required=True, ondelete='cascade')
  25. class Webhook(models.Model):
  26. _name = 'webhook'
  27. name = fields.Char(
  28. 'Consumer name',
  29. required=True,
  30. help='Name of your consumer webhook. '
  31. 'This name will be used in named of event methods')
  32. address_ids = fields.One2many(
  33. 'webhook.address', 'webhook_id', 'IP or Network Address',
  34. required=True,
  35. help='This address will be filter to know who is '
  36. 'consumer webhook')
  37. python_code_get_event = fields.Text(
  38. 'Get event',
  39. required=True,
  40. help='Python code to get event from request data.\n'
  41. 'You have object.env.request variable with full '
  42. 'webhook request.',
  43. default='# You can use object.env.request variable '
  44. 'to get full data of webhook request.\n'
  45. '# Example:\n#request.httprequest.'
  46. 'headers.get("X-Github-Event")',
  47. )
  48. python_code_get_ip = fields.Text(
  49. 'Get IP',
  50. required=True,
  51. help='Python code to get remote IP address '
  52. 'from request data.\n'
  53. 'You have object.env.request variable with full '
  54. 'webhook request.',
  55. default='# You can use object.env.request variable '
  56. 'to get full data of webhook request.\n'
  57. '# Example:\n'
  58. '#object.env.request.httprequest.remote_addr'
  59. '\nrequest.httprequest.remote_addr',
  60. )
  61. active = fields.Boolean(default=True)
  62. @api.multi
  63. def process_python_code(self, python_code, request=None):
  64. """
  65. Execute a python code with eval.
  66. :param string python_code: Python code to process
  67. :param object request: Request object with data of json
  68. and http request
  69. :return: Result of process python code.
  70. """
  71. self.ensure_one()
  72. res = None
  73. eval_dict = {
  74. 'user': self.env.user,
  75. 'object': self,
  76. 'request': request,
  77. # copy context to prevent side-effects of eval
  78. 'context': dict(self.env.context),
  79. }
  80. try:
  81. res = safe_eval(python_code, eval_dict)
  82. except BaseException:
  83. error = tools.ustr(traceback.format_exc())
  84. _logger.debug(
  85. 'python_code "%s" with dict [%s] error [%s]',
  86. python_code, eval_dict, error)
  87. if isinstance(res, basestring):
  88. res = tools.ustr(res)
  89. return res
  90. @api.model
  91. def search_with_request(self, request):
  92. """
  93. Method to search of all webhook
  94. and return only one that match with remote address
  95. and range of address.
  96. :param object request: Request object with data of json
  97. and http request
  98. :return: object of webhook found or
  99. if not found then return False
  100. """
  101. for webhook in self.search([]):
  102. remote_address = webhook.process_python_code(
  103. webhook.python_code_get_ip, request)
  104. if not remote_address:
  105. continue
  106. if webhook.is_address_range(remote_address):
  107. return webhook
  108. return False
  109. @api.multi
  110. def is_address_range(self, remote_address):
  111. """
  112. Check if a remote IP address is in range of one
  113. IP or network address of current object data.
  114. :param string remote_address: Remote IP address
  115. :return: if remote address match then return True
  116. else then return False
  117. """
  118. self.ensure_one()
  119. for address in self.address_ids:
  120. ipn = ipaddress.ip_network(u'' + address.name)
  121. hosts = [host.exploded for host in ipn.hosts()]
  122. hosts.append(address.name)
  123. if remote_address in hosts:
  124. return True
  125. return False
  126. @api.model
  127. def get_event_methods(self, event_method_base):
  128. """
  129. List all methods of current object that start with base name.
  130. e.g. if exists methods called 'x1', 'x2'
  131. and other ones called 'y1', 'y2'
  132. if you have event_method_base='x'
  133. Then will return ['x1', 'x2']
  134. :param string event_method_base: Name of method event base
  135. returns: List of methods with that start wtih method base
  136. """
  137. # TODO: Filter just callable attributes
  138. return sorted(
  139. attr for attr in dir(self) if attr.startswith(
  140. event_method_base)
  141. )
  142. @api.model
  143. def get_ping_events(self):
  144. """
  145. List all name of event type ping.
  146. This event is a dummy event just to
  147. know if a provider is working.
  148. :return: List with names of ping events
  149. """
  150. return ['ping']
  151. @api.multi
  152. def run_webhook(self, request):
  153. """
  154. Run methods to process a webhook event.
  155. Get all methods with base name
  156. 'run_CONSUMER_EVENT*'
  157. Invoke all methods found.
  158. :param object request: Request object with data of json
  159. and http request
  160. :return: True
  161. """
  162. self.ensure_one()
  163. event = self.process_python_code(
  164. self.python_code_get_event, request)
  165. if not event:
  166. raise exceptions.ValidationError(_(
  167. 'event is not defined'))
  168. method_event_name_base = \
  169. 'run_' + self.name + \
  170. '_' + event
  171. methods_event_name = self.get_event_methods(method_event_name_base)
  172. if not methods_event_name:
  173. # if is a 'ping' event then return True
  174. # because the request is received fine.
  175. if event in self.get_ping_events():
  176. return True
  177. raise exceptions.ValidationError(_(
  178. 'Not defined methods "%s" yet' % (
  179. method_event_name_base)))
  180. self.env.request = request
  181. for method_event_name in methods_event_name:
  182. method = getattr(self, method_event_name)
  183. res_method = method()
  184. if isinstance(res_method, list) and len(res_method) == 1:
  185. if res_method[0] is NotImplemented:
  186. _logger.debug(
  187. 'Not implemented method "%s" yet', method_event_name)
  188. return True