Skip to content

Commit dd225d4

Browse files
author
childish-sambino
authored
fix: support duplicate query param values (#615)
1 parent 318118c commit dd225d4

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

tests/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ wheel>=0.22.0
1010
cryptography
1111
twine
1212
recommonmark
13+
django
14+
multidict

tests/unit/test_request_validator.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
# -*- coding: utf-8 -*-
22
import unittest
33

4+
from django.conf import settings
5+
from django.http import QueryDict
6+
from multidict import MultiDict
7+
48
from twilio.request_validator import RequestValidator
59

610

711
class ValidationTest(unittest.TestCase):
812

913
def setUp(self):
14+
if not settings.configured:
15+
settings.configure()
16+
1017
token = "12345"
1118
self.validator = RequestValidator(token)
1219

@@ -22,9 +29,10 @@ def setUp(self):
2229
self.body = "{\"property\": \"value\", \"boolean\": true}"
2330
self.bodyHash = "0a1ff7634d9ab3b95db5c9a2dfe9416e41502b283a80c7cf19632632f96e6620"
2431
self.uriWithBody = self.uri + "&bodySHA256=" + self.bodyHash
32+
self.duplicate_expected = 'IK+Dwps556ElfBT0I3Rgjkr1wJU='
2533

2634
def test_compute_signature(self):
27-
expected = (self.expected)
35+
expected = self.expected
2836
signature = self.validator.compute_signature(self.uri, self.params)
2937
assert signature == expected
3038

@@ -34,6 +42,23 @@ def test_compute_hash_unicode(self):
3442

3543
assert expected == body_hash
3644

45+
def test_compute_signature_duplicate_multi_dict(self):
46+
expected = self.duplicate_expected
47+
params = MultiDict([
48+
("Sid", "CA123"),
49+
("SidAccount", "AC123"),
50+
("Digits", "1234"),
51+
("Digits", "5678"),
52+
])
53+
signature = self.validator.compute_signature(self.uri, params)
54+
assert signature == expected
55+
56+
def test_compute_signature_duplicate_query_dict(self):
57+
expected = self.duplicate_expected
58+
params = QueryDict('Sid=CA123&SidAccount=AC123&Digits=1234&Digits=5678', encoding='utf-8')
59+
signature = self.validator.compute_signature(self.uri, params)
60+
assert signature == expected
61+
3762
def test_validation(self):
3863
assert self.validator.validate(self.uri, self.params, self.expected)
3964

twilio/request_validator.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@ def compute_signature(self, uri, params):
7373
"""
7474
s = uri
7575
if params:
76-
for k, v in sorted(params.items()):
77-
s += k + v
76+
for param_name in sorted(set(params)):
77+
values = self.get_values(params, param_name)
78+
79+
for value in sorted(values):
80+
s += param_name + value
7881

7982
# compute signature and compare signatures
8083
mac = hmac.new(self.token, s.encode("utf-8"), sha1)
@@ -83,6 +86,18 @@ def compute_signature(self, uri, params):
8386

8487
return computed.strip()
8588

89+
def get_values(self, param_dict, param_name):
90+
try:
91+
# Support MultiDict used by Flask.
92+
return param_dict.getall(param_name)
93+
except AttributeError:
94+
try:
95+
# Support QueryDict used by Django.
96+
return param_dict.getlist(param_name)
97+
except AttributeError:
98+
# Fallback to a standard dict.
99+
return [param_dict[param_name]]
100+
86101
def compute_hash(self, body):
87102
computed = sha256(body.encode("utf-8")).hexdigest()
88103

@@ -104,8 +119,6 @@ def validate(self, uri, params, signature):
104119
uri_with_port = add_port(parsed_uri)
105120
uri_without_port = remove_port(parsed_uri)
106121

107-
valid_signature = False # Default fail
108-
valid_signature_with_port = False
109122
valid_body_hash = True # May not receive body hash, so default succeed
110123

111124
query = parse_qs(parsed_uri.query)

0 commit comments

Comments
 (0)