diff --git a/CHANGELOG.md b/CHANGELOG.md index ec72c7b..bec243a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased -No unreleased changes. +- Add Webhook ID argument to signals (allow to fix #13) ## 0.6.0 - 2022-05-08 ### Added diff --git a/shopify_webhook/decorators.py b/shopify_webhook/decorators.py index bbf6dab..1d99ead 100644 --- a/shopify_webhook/decorators.py +++ b/shopify_webhook/decorators.py @@ -30,6 +30,7 @@ def wrapper(request, *args, **kwargs): topic = request.META['HTTP_X_SHOPIFY_TOPIC'] domain = request.META['HTTP_X_SHOPIFY_SHOP_DOMAIN'] hmac = request.META['HTTP_X_SHOPIFY_HMAC_SHA256'] if 'HTTP_X_SHOPIFY_HMAC_SHA256' in request.META else None + webhook_id = request.META['HTTP_X_SHOPIFY_WEBHOOK_ID'] data = json.loads(request.body.decode('utf-8')) except (KeyError, ValueError) as e: return HttpResponseBadRequest() @@ -46,6 +47,7 @@ def wrapper(request, *args, **kwargs): request.webhook_topic = topic request.webhook_data = data request.webhook_domain = domain + request.webhook_id = webhook_id return f(request, *args, **kwargs) return wrapper diff --git a/shopify_webhook/signals.py b/shopify_webhook/signals.py index 84c825c..dcb73a8 100644 --- a/shopify_webhook/signals.py +++ b/shopify_webhook/signals.py @@ -8,6 +8,7 @@ class WebhookSignal(Signal): * domain * topic * data + * webhook_id """ pass diff --git a/shopify_webhook/tests/__init__.py b/shopify_webhook/tests/__init__.py index 8091a18..c818413 100644 --- a/shopify_webhook/tests/__init__.py +++ b/shopify_webhook/tests/__init__.py @@ -21,7 +21,7 @@ def setUp(self): super(WebhookTestCase, self).setUp() self.webhook_url = reverse('webhook') - def post_shopify_webhook(self, topic = None, domain = None, data = None, headers = None, send_hmac = True): + def post_shopify_webhook(self, topic = None, domain = None, data = None, webhook_id = None, headers = None, send_hmac = True): """ Simulate a webhook being sent to the application's webhook endpoint with the provided parameters. """ @@ -42,6 +42,8 @@ def post_shopify_webhook(self, topic = None, domain = None, data = None, headers headers['HTTP_X_SHOPIFY_TOPIC'] = topic if send_hmac: headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = str(get_hmac(data.encode("latin-1"), settings.SHOPIFY_APP_API_SECRET)) + if webhook_id: + headers['HTTP_X_SHOPIFY_WEBHOOK_ID'] = webhook_id return self.client.post(self.webhook_url, data = data, content_type = 'application/json', **headers) diff --git a/shopify_webhook/tests/test_webhook.py b/shopify_webhook/tests/test_webhook.py index 91f4853..1f8264a 100644 --- a/shopify_webhook/tests/test_webhook.py +++ b/shopify_webhook/tests/test_webhook.py @@ -15,23 +15,23 @@ def test_empty_post_message_is_bad_request(self): self.assertEqual(response.status_code, 400, 'Empty POST request returns 400 (Bad Request).') def test_no_hmac_is_forbidden(self): - response = self.post_shopify_webhook(topic = 'orders/create', data = {'id': 123}, send_hmac = False) + response = self.post_shopify_webhook(topic = 'orders/create', data = {'id': 123}, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043', send_hmac = False) self.assertEqual(response.status_code, 401, 'POST orders/create request with no HMAC returns 401 (Forbidden).') def test_invalid_hmac_is_forbidden(self): - response = self.post_shopify_webhook(topic = 'orders/create', data = {'id': 123}, headers = {'HTTP_X_SHOPIFY_HMAC_SHA256': 'invalid'}, send_hmac = False) + response = self.post_shopify_webhook(topic = 'orders/create', data = {'id': 123}, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043', headers = {'HTTP_X_SHOPIFY_HMAC_SHA256': 'invalid'}, send_hmac = False) self.assertEqual(response.status_code, 401, 'POST orders/create request with invalid HMAC returns 401 (Forbidden).') def test_unknown_topic_is_bad_request(self): - response = self.post_shopify_webhook(topic = 'tests/invalid', data = {'id': 123}) + response = self.post_shopify_webhook(topic = 'tests/invalid', data = {'id': 123}, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043') self.assertEqual(response.status_code, 400, 'POST tests/invalid request with valid HMAC returns 400 (Bad Request).') def test_missing_domain_is_bad_request(self): - response = self.post_shopify_webhook(topic = 'orders/create', domain = '', data = {'id': 123}) + response = self.post_shopify_webhook(topic = 'orders/create', domain = '', data = {'id': 123}, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043') self.assertEqual(response.status_code, 400, 'POST orders/create request with missing domain returns 400 (Bad Request).') def test_valid_hmac_is_ok(self): - response = self.post_shopify_webhook(topic = 'orders/create', data = {'id': 123}) + response = self.post_shopify_webhook(topic = 'orders/create', data = {'id': 123}, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043') self.assertEqual(response.status_code, 200, 'POST orders/create request with valid HMAC returns 200 (OK).') def test_webook_received_signal_triggered(self): @@ -43,7 +43,7 @@ def test_webhook_received_receiver(sender, data, **kwargs): test_webhook_received_receiver.data = data test_webhook_received_receiver.data = None - response = self.post_shopify_webhook(topic = 'fulfillments/update', data = data) + response = self.post_shopify_webhook(topic = 'fulfillments/update', data = data, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043') self.assertEqual(data, test_webhook_received_receiver.data, 'POST fulfillments/update correctly triggered webhook_received signal.') def test_order_created_signal_triggered(self): @@ -55,5 +55,5 @@ def test_order_create_receiver(sender, data, **kwargs): test_order_create_receiver.data = data test_order_create_receiver.data = None - response = self.post_shopify_webhook(topic = 'orders/create', data = data) + response = self.post_shopify_webhook(topic = 'orders/create', data = data, webhook_id = 'b54557e4-bdd9-4b37-8a5f-bf7d70bcd043') self.assertEqual(data, test_order_create_receiver.data, 'POST orders/create correctly triggered order_created signal.') diff --git a/shopify_webhook/views.py b/shopify_webhook/views.py index a59468d..edf40a7 100644 --- a/shopify_webhook/views.py +++ b/shopify_webhook/views.py @@ -32,8 +32,8 @@ def post(self, request, *args, **kwargs): # Convert the topic to a signal name and trigger it. signal_name = get_signal_name_for_topic(request.webhook_topic) try: - signals.webhook_received.send_robust(self, domain = request.webhook_domain, topic = request.webhook_topic, data = request.webhook_data) - getattr(signals, signal_name).send_robust(self, domain = request.webhook_domain, topic = request.webhook_topic, data = request.webhook_data) + signals.webhook_received.send_robust(self, domain = request.webhook_domain, topic = request.webhook_topic, webhook_id = request.webhook_id, data = request.webhook_data) + getattr(signals, signal_name).send_robust(self, domain = request.webhook_domain, topic = request.webhook_topic, webhook_id = request.webhook_id, data = request.webhook_data) except AttributeError: return HttpResponseBadRequest()