Skip to content

Commit e0d3e1b

Browse files
authored
Merge pull request #33 from guiajlopes/feature/add-request-signing
Add request signing
2 parents 54f35ca + 20ca9af commit e0d3e1b

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

examples/signed_request.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
import argparse
3+
import messagebird
4+
5+
parser = argparse.ArgumentParser()
6+
parser.add_argument('--signingKey', help='access key for MessageBird API', type=str, required=True)
7+
parser.add_argument('--requestTimestamp', help='the request timestamp', type=str, required=True)
8+
parser.add_argument('--requestBody', help='the request body', type=str, required=True)
9+
parser.add_argument('--signature', help='the signature', type=str, required=True)
10+
args = vars(parser.parse_args())
11+
12+
signed_request = messagebird.SignedRequest(args['signature'], args['timestamp'], args['requestBody'], {})
13+
14+
if signed_request.verify(args['signingKey']):
15+
print("The signed request has been verified.")
16+
else:
17+
print("The signed request cannot be verified.")
18+
19+
if signed_request.is_recent():
20+
print("The signed request is recent.")
21+
else:
22+
print("The signed request is not recent.")

messagebird/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from messagebird.client import Client, ErrorException
2+
from messagebird.signed_request import SignedRequest

messagebird/signed_request.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import hashlib
2+
import hmac
3+
import base64
4+
import time
5+
from collections import OrderedDict
6+
7+
try:
8+
from urllib.parse import urlencode
9+
except ImportError:
10+
from urllib import urlencode
11+
12+
13+
class SignedRequest:
14+
15+
def __init__(self, requestSignature, requestTimestamp, requestBody, requestParameters):
16+
self._requestSignature = requestSignature
17+
self._requestTimestamp = str(requestTimestamp)
18+
self._requestBody = requestBody
19+
self._requestParameters = requestParameters
20+
21+
def verify(self, signing_key):
22+
payload = self._build_payload()
23+
expected_signature = base64.b64decode(self._requestSignature)
24+
calculated_signature = hmac.new(signing_key.encode('latin-1'), payload.encode('latin-1'),
25+
hashlib.sha256).digest()
26+
return expected_signature == calculated_signature
27+
28+
def is_recent(self, offset=10):
29+
return int(time.time()) - int(self._requestTimestamp) < offset
30+
31+
def _build_payload(self):
32+
checksum_body = hashlib.sha256(self._requestBody.encode('latin-1')).digest()
33+
str_checksum_body = checksum_body.decode('latin-1')
34+
parts = [self._requestTimestamp, urlencode(self._sort_dict(self._requestParameters), True), str_checksum_body]
35+
return "\n".join(parts)
36+
37+
def _sort_dict(self, dict):
38+
sorted_dict = OrderedDict()
39+
40+
for key in sorted(dict):
41+
sorted_dict[key] = dict[key]
42+
43+
return sorted_dict

tests/test_signed_request.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import unittest
2+
import time
3+
from messagebird.signed_request import SignedRequest
4+
5+
try:
6+
from unittest.mock import Mock
7+
except ImportError:
8+
# mock was added to unittest in Python 3.3, but was an external library
9+
# before.
10+
from mock import Mock
11+
12+
SIGNING_KEY = 'PlLrKaqvZNRR5zAjm42ZT6q1SQxgbbGd'
13+
14+
15+
class TestMessage(unittest.TestCase):
16+
17+
def test_signed_request_withou_body(self):
18+
query = {
19+
'recipient': '31612345678',
20+
'reference': 'FOO',
21+
'statusDatetime': '2019-01-11T09:17:11+00:00',
22+
'id': 'eef0ab57a9e049be946f3821568c2b2e',
23+
'status': 'delivered',
24+
'mccmnc': '20408',
25+
'ported': '1',
26+
}
27+
28+
signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w='
29+
timestamp = 1547198231
30+
body = ''
31+
32+
signed_request = SignedRequest(signature, timestamp, body, query)
33+
self.assertTrue(signed_request.verify(SIGNING_KEY))
34+
35+
def test_signed_request_with_body(self):
36+
query = {
37+
'recipient': '31612345678',
38+
'reference': 'FOO',
39+
'statusDatetime': '2019-01-11T09:17:11+00:00',
40+
'id': 'eef0ab57a9e049be946f3821568c2b2e',
41+
'status': 'delivered',
42+
'mccmnc': '20408',
43+
'ported': '1',
44+
}
45+
46+
signature = '2bl+38H4oHVg03pC3bk2LvCB0IHFgfC4cL5HPQ0LdmI='
47+
timestamp = 1547198231
48+
body = '{"foo":"bar"}'
49+
50+
signed_request = SignedRequest(signature, timestamp, body, query)
51+
self.assertTrue(signed_request.verify(SIGNING_KEY))
52+
53+
def test_incorrectly_signed_request(self):
54+
query = {
55+
'recipient': '31612345678',
56+
'reference': 'BAR',
57+
'statusDatetime': '2019-01-11T09:17:11+00:00',
58+
'id': 'eef0ab57a9e049be946f3821568c2b2e',
59+
'status': 'delivered',
60+
'mccmnc': '20408',
61+
'ported': '1',
62+
}
63+
64+
signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w='
65+
timestamp = 1547198231
66+
body = ''
67+
68+
signed_request = SignedRequest(signature, timestamp, body, query)
69+
self.assertFalse(signed_request.verify(SIGNING_KEY))
70+
71+
def test_recent_signed_request(self):
72+
query = {}
73+
signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w='
74+
timestamp = int(time.time()) - 1
75+
body = ''
76+
77+
signed_request = SignedRequest(signature, timestamp, body, query)
78+
self.assertTrue(signed_request.is_recent())
79+
80+
def test_not_recent_signed_request(self):
81+
query = {}
82+
signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w='
83+
timestamp = int(time.time()) - 100
84+
body = ''
85+
86+
signed_request = SignedRequest(signature, timestamp, body, query)
87+
self.assertFalse(signed_request.is_recent())

0 commit comments

Comments
 (0)