Skip to content

Commit d91226c

Browse files
committed
Enforced encoding before stipping hmac and gettoken from params
1 parent c52f063 commit d91226c

3 files changed

Lines changed: 53 additions & 47 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525

2626
* Removed the deprecated `cp` argument from `laterpay.ItemDefinition`
2727

28+
* Reliably ignore `hmac` and `gettoken` parameters when creating the signature
29+
message. In the past `signing.sign()` and `signing.verify()` stripped those
30+
keys when a `dict()` was passed from the passed function arguments but not
31+
for lists or tuples. Note that as a result the provided parameters are not
32+
touched anymore and calling either function will not have side-effects on
33+
the provided arguments.
34+
2835

2936
## 4.6.0
3037

laterpay/signing.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from . import compat
1717

1818
ALLOWED_METHODS = ('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD')
19+
MESSAGE_FORMAT = '{method}&{url}&{params}'
1920

2021

2122
def time_independent_HMAC_compare(a, b):
@@ -145,29 +146,34 @@ def create_base_message(params, url, method='POST'):
145146
146147
http://docs.laterpay.net/platform/intro/signing_urls/
147148
"""
148-
msg = '{method}&{url}&{params}'
149-
150-
method = compat.encode_if_unicode(method).upper()
151-
152-
url = quote(compat.encode_if_unicode(url), safe='')
153-
149+
# Process method
150+
method = compat.stringify(method).upper()
154151
if method not in ALLOWED_METHODS:
155152
raise ValueError('method should be one of: {}'.format(ALLOWED_METHODS))
156153

154+
# Process params
157155
params = normalise_param_structure(params)
158-
156+
if 'hmac' in params:
157+
params.pop('hmac')
158+
if 'gettoken' in params:
159+
params.pop('gettoken')
159160
params = {
161+
# urlquote all keys and values
160162
quote(key, safe=''): [quote(value, safe='') for value in values]
161163
for key, values
162164
in six.iteritems(params)
163165
}
164-
165166
params = _sort_params(params)
166-
167167
param_str = '&'.join('{}={}'.format(k, v) for k, v in params)
168168
param_str = quote(param_str, safe='')
169169

170-
return msg.format(method=method, url=url, params=param_str)
170+
# Process url
171+
url = compat.stringify(url)
172+
url_parsed = urlparse(url)
173+
url = url_parsed.scheme + "://" + url_parsed.netloc + url_parsed.path
174+
url = quote(url, safe='')
175+
176+
return MESSAGE_FORMAT.format(method=method, url=url, params=param_str)
171177

172178

173179
def sign(secret, params, url, method='POST'):
@@ -182,21 +188,8 @@ def sign(secret, params, url, method='POST'):
182188
:param method: HTTP method used to transport the signed data
183189
('POST' is default)
184190
"""
185-
if 'hmac' in params:
186-
params.pop('hmac')
187-
188-
if 'gettoken' in params:
189-
params.pop('gettoken')
190-
191-
secret = compat.encode_if_unicode(secret)
192-
193-
url_parsed = urlparse(url)
194-
base_url = url_parsed.scheme + "://" + url_parsed.netloc + url_parsed.path
195-
196-
msg = create_base_message(params, base_url, method=method)
197-
198-
mac = create_HMAC(secret, msg)
199-
191+
message = create_base_message(params, url, method=method)
192+
mac = create_HMAC(secret, message)
200193
return mac
201194

202195

tests/test_signing.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,44 +86,50 @@ def test_create_message_wrong_method(self):
8686
signing.create_base_message(params, url, method='WRONG')
8787

8888
def test_sign(self):
89+
signature = '5b341f4321476715ab1ae252794783c6dffa32dbdcc94512193ea3cf'
8990
params = {
9091
u'parĄm1': u'valuĘ',
91-
'param2': ['value2', 'value3'],
92-
'hmac': 'to-be-removed',
93-
'gettoken': 'to-be-removed-too',
92+
b'par\xc4\x84m1': ['value2', 'value3'],
93+
b'hmac': 'will-be-removed',
94+
u'gettoken': 'will-be-removed-too',
9495
}
95-
url = u'https://endpoint.com/api'
96+
url = b'https://endpoint.com/api'
97+
secret = b'secret'
9698

97-
secret = u'secret' # unicode is what we usually get from api/db..
99+
# sha224 hmac
100+
self.assertEqual(signing.sign(secret, params, url), signature)
98101

99-
mac = signing.sign(secret, params, url)
100-
self.assertNotIn('hmac', params)
101-
self.assertNotIn('gettoken', params)
102+
# Test that `hmac` and `gettoken` are not being removed from the
103+
# original arguments passed to the function
104+
self.assertIn(b'hmac', params)
105+
self.assertIn(u'gettoken', params)
102106

103-
# sha224 hmac
104-
self.assertEqual(
105-
mac,
106-
'346f3d53ad762f3ed3fb7f2427dec2bbfaf0338bb7f91f0460aff15c',
107-
)
107+
del params[b'hmac']
108+
del params[u'gettoken']
109+
self.assertEqual(signing.sign(secret, params, url), signature)
108110

109111
def test_sign_unicode_secret(self):
112+
signature = '635cef6498fc5f1a829275cc1b24a191d5267d6023034e3e0953e4c6'
110113
params = {
111114
u'parĄm1': u'valuĘ',
112115
'param2': ['value2', 'value3'],
113-
'hmac': 'will-be-removed',
114-
'gettoken': 'will-be-removed-too',
116+
u'hmac': 'to-be-removed',
117+
b'gettoken': 'to-be-removed-too',
115118
}
116119
url = u'https://endpoint.com/api'
117-
118120
secret = u'☃🐍' # unicode is what we usually get from api/db..
119121

120-
mac = signing.sign(secret, params, url)
121-
122122
# sha224 hmac
123-
self.assertEqual(
124-
mac,
125-
'635cef6498fc5f1a829275cc1b24a191d5267d6023034e3e0953e4c6',
126-
)
123+
self.assertEqual(signing.sign(secret, params, url), signature)
124+
125+
# Test that `hmac` and `gettoken` are not being removed from the
126+
# original arguments passed to the function
127+
self.assertIn(u'hmac', params)
128+
self.assertIn(b'gettoken', params)
129+
130+
del params[u'hmac']
131+
del params[b'gettoken']
132+
self.assertEqual(signing.sign(secret, params, url), signature)
127133

128134
def test_verify_byte_signature(self):
129135
params = {

0 commit comments

Comments
 (0)