2
0

webhooks_worker.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import logging
  2. import requests
  3. from django.conf import settings
  4. from django_rq import job
  5. from jinja2.exceptions import TemplateError
  6. from .conditions import ConditionSet
  7. from .constants import WEBHOOK_EVENT_TYPES
  8. from .webhooks import generate_signature
  9. logger = logging.getLogger('netbox.webhooks_worker')
  10. def eval_conditions(webhook, data):
  11. """
  12. Test whether the given data meets the conditions of the webhook (if any). Return True
  13. if met or no conditions are specified.
  14. """
  15. if not webhook.conditions:
  16. return True
  17. logger.debug(f'Evaluating webhook conditions: {webhook.conditions}')
  18. if ConditionSet(webhook.conditions).eval(data):
  19. return True
  20. return False
  21. @job('default')
  22. def process_webhook(webhook, model_name, event, data, timestamp, username, request_id=None, snapshots=None):
  23. """
  24. Make a POST request to the defined Webhook
  25. """
  26. # Evaluate webhook conditions (if any)
  27. if not eval_conditions(webhook, data):
  28. return
  29. # Prepare context data for headers & body templates
  30. context = {
  31. 'event': WEBHOOK_EVENT_TYPES[event],
  32. 'timestamp': timestamp,
  33. 'model': model_name,
  34. 'username': username,
  35. 'request_id': request_id,
  36. 'data': data,
  37. }
  38. if snapshots:
  39. context.update({
  40. 'snapshots': snapshots
  41. })
  42. # Build the headers for the HTTP request
  43. headers = {
  44. 'Content-Type': webhook.http_content_type,
  45. }
  46. try:
  47. headers.update(webhook.render_headers(context))
  48. except (TemplateError, ValueError) as e:
  49. logger.error(f"Error parsing HTTP headers for webhook {webhook}: {e}")
  50. raise e
  51. # Render the request body
  52. try:
  53. body = webhook.render_body(context)
  54. except TemplateError as e:
  55. logger.error(f"Error rendering request body for webhook {webhook}: {e}")
  56. raise e
  57. # Prepare the HTTP request
  58. params = {
  59. 'method': webhook.http_method,
  60. 'url': webhook.render_payload_url(context),
  61. 'headers': headers,
  62. 'data': body.encode('utf8'),
  63. }
  64. logger.info(
  65. f"Sending {params['method']} request to {params['url']} ({context['model']} {context['event']})"
  66. )
  67. logger.debug(params)
  68. try:
  69. prepared_request = requests.Request(**params).prepare()
  70. except requests.exceptions.RequestException as e:
  71. logger.error(f"Error forming HTTP request: {e}")
  72. raise e
  73. # If a secret key is defined, sign the request with a hash of the key and its content
  74. if webhook.secret != '':
  75. prepared_request.headers['X-Hook-Signature'] = generate_signature(prepared_request.body, webhook.secret)
  76. # Send the request
  77. with requests.Session() as session:
  78. session.verify = webhook.ssl_verification
  79. if webhook.ca_file_path:
  80. session.verify = webhook.ca_file_path
  81. response = session.send(prepared_request, proxies=settings.HTTP_PROXIES)
  82. if 200 <= response.status_code <= 299:
  83. logger.info(f"Request succeeded; response status {response.status_code}")
  84. return f"Status {response.status_code} returned, webhook successfully processed."
  85. else:
  86. logger.warning(f"Request failed; response status {response.status_code}: {response.content}")
  87. raise requests.exceptions.RequestException(
  88. f"Status {response.status_code} returned with content '{response.content}', webhook FAILED to process."
  89. )