Skip to content

Commit

Permalink
Add Webhook ID argument to signals (allow to fix #13) (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
ilovelinux committed Sep 5, 2023
1 parent a0ba361 commit 3f6f886
Show file tree
Hide file tree
Showing 6 changed files with 16 additions and 11 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions shopify_webhook/decorators.py
Expand Up @@ -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()
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions shopify_webhook/signals.py
Expand Up @@ -8,6 +8,7 @@ class WebhookSignal(Signal):
* domain
* topic
* data
* webhook_id
"""
pass

Expand Down
4 changes: 3 additions & 1 deletion shopify_webhook/tests/__init__.py
Expand Up @@ -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.
"""
Expand All @@ -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)

Expand Down
14 changes: 7 additions & 7 deletions shopify_webhook/tests/test_webhook.py
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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.')
4 changes: 2 additions & 2 deletions shopify_webhook/views.py
Expand Up @@ -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()

Expand Down

0 comments on commit 3f6f886

Please sign in to comment.