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.

130 lines
4.5 KiB

  1. # -*- coding: utf-8 -*-
  2. # License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
  3. # See README.rst file on addon root folder for more details
  4. import json
  5. from hashlib import sha1
  6. import hmac
  7. import logging
  8. from psycopg2 import OperationalError
  9. import openerp
  10. from openerp import api, http, SUPERUSER_ID, tools
  11. from openerp.http import request
  12. _logger = logging.getLogger(__name__)
  13. class MailController(http.Controller):
  14. def _mandrill_validation(self, **kw):
  15. """
  16. Validate Mandrill POST reques using
  17. https://mandrill.zendesk.com/hc/en-us/articles/
  18. 205583257-Authenticating-webhook-requests
  19. """
  20. headers = request.httprequest.headers
  21. signature = headers.get('X-Mandrill-Signature', False)
  22. key = tools.config.options.get('mandrill_webhook_key', False)
  23. if not key:
  24. _logger.info("No Mandrill validation key configured. "
  25. "Please add 'mandrill_webhook_key' to [options] "
  26. "section in odoo configuration file to enable "
  27. "Mandrill authentication webhoook requests. "
  28. "More info at: "
  29. "https://mandrill.zendesk.com/hc/en-us/articles/"
  30. "205583257-Authenticating-webhook-requests")
  31. return True
  32. if not signature:
  33. return False
  34. url = tools.config.options.get('mandrill_webhook_url', False)
  35. if not url:
  36. url = request.httprequest.url_root.rstrip('/') + '/mandrill/event'
  37. data = url
  38. kw_keys = kw.keys()
  39. if kw_keys:
  40. kw_keys.sort()
  41. for kw_key in kw_keys:
  42. data += kw_key + kw.get(kw_key)
  43. hashed = hmac.new(key, data, sha1)
  44. hash_text = hashed.digest().encode("base64").rstrip('\n')
  45. if hash_text == signature:
  46. return True
  47. _logger.info("HASH[%s] != SIGNATURE[%s]" % (hash_text, signature))
  48. return False
  49. def _event_process(self, event):
  50. message_id = event.get('_id')
  51. event_type = event.get('event')
  52. message = event.get('msg')
  53. if not (message_id and event_type and message):
  54. return False
  55. info = "%s event for Message ID '%s'" % (event_type, message_id)
  56. metadata = message.get('metadata')
  57. db = None
  58. if metadata:
  59. db = metadata.get('odoo_db', None)
  60. # Check database selected by mandrill event
  61. if not db:
  62. _logger.info('%s: No DB selected', info)
  63. return False
  64. try:
  65. registry = openerp.registry(db)
  66. except OperationalError:
  67. _logger.info("%s: Selected BD '%s' not found", info, db)
  68. return False
  69. except:
  70. _logger.info("%s: Selected BD '%s' connection error", info, db)
  71. return False
  72. # Database has been selected, process event
  73. with registry.cursor() as cr:
  74. env = api.Environment(cr, SUPERUSER_ID, {})
  75. res = env['mail.mandrill.message'].process(
  76. message_id, event_type, event)
  77. if res:
  78. _logger.info('%s: OK', info)
  79. else:
  80. _logger.info('%s: FAILED', info)
  81. return res
  82. @http.route('/mandrill/event', type='http', auth='none')
  83. def event(self, **kw):
  84. """
  85. End-point to receive Mandrill event
  86. Configuration in Mandrill app > Settings > Webhooks
  87. (https://mandrillapp.com/settings/webhooks)
  88. Add a webhook, selecting this type of events:
  89. - Message Is Sent
  90. - Message Is Bounced
  91. - Message Is Opened
  92. - Message Is Marked As Spam
  93. - Message Is Rejected
  94. - Message Is Delayed
  95. - Message Is Soft-Bounced
  96. - Message Is Clicked
  97. - Message Recipient Unsubscribes
  98. and setting this Post to URL:
  99. https://your_odoodomain.com/mandrill/event
  100. """
  101. if not self._mandrill_validation(**kw):
  102. _logger.info('Validation error, ignoring this request')
  103. return 'NO_AUTH'
  104. events = []
  105. try:
  106. events = json.loads(kw.get('mandrill_events', '[]'))
  107. except:
  108. pass
  109. if not events:
  110. return 'NO_EVENTS'
  111. res = []
  112. for event in events:
  113. res.append(self._event_process(event))
  114. msg = 'ALL_EVENTS_FAILED'
  115. if all(res):
  116. msg = 'OK'
  117. elif any(res):
  118. msg = 'SOME_EVENTS_FAILED'
  119. return msg