Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support duplicate query param values #615

Merged
merged 3 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ wheel>=0.22.0
cryptography
twine
recommonmark
django
multidict
27 changes: 26 additions & 1 deletion tests/unit/test_request_validator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# -*- coding: utf-8 -*-
import unittest

from django.conf import settings
from django.http import QueryDict
from multidict import MultiDict

from twilio.request_validator import RequestValidator


class ValidationTest(unittest.TestCase):

def setUp(self):
if not settings.configured:
settings.configure()

token = "12345"
self.validator = RequestValidator(token)

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

def test_compute_signature(self):
expected = (self.expected)
expected = self.expected
signature = self.validator.compute_signature(self.uri, self.params)
assert signature == expected

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

assert expected == body_hash

def test_compute_signature_duplicate_multi_dict(self):
expected = self.duplicate_expected
params = MultiDict([
("Sid", "CA123"),
("SidAccount", "AC123"),
("Digits", "1234"),
("Digits", "5678"),
])
signature = self.validator.compute_signature(self.uri, params)
assert signature == expected

def test_compute_signature_duplicate_query_dict(self):
expected = self.duplicate_expected
params = QueryDict('Sid=CA123&SidAccount=AC123&Digits=1234&Digits=5678', encoding='utf-8')
signature = self.validator.compute_signature(self.uri, params)
assert signature == expected

def test_validation(self):
assert self.validator.validate(self.uri, self.params, self.expected)

Expand Down
21 changes: 17 additions & 4 deletions twilio/request_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ def compute_signature(self, uri, params):
"""
s = uri
if params:
for k, v in sorted(params.items()):
s += k + v
for param_name in sorted(set(params)):
values = self.get_values(params, param_name)

for value in sorted(values):
s += param_name + value

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

return computed.strip()

def get_values(self, param_dict, param_name):
try:
# Support MultiDict used by Flask.
return param_dict.getall(param_name)
except AttributeError:
try:
# Support QueryDict used by Django.
return param_dict.getlist(param_name)
except AttributeError:
# Fallback to a standard dict.
return [param_dict[param_name]]

def compute_hash(self, body):
computed = sha256(body.encode("utf-8")).hexdigest()

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

valid_signature = False # Default fail
valid_signature_with_port = False
valid_body_hash = True # May not receive body hash, so default succeed

query = parse_qs(parsed_uri.query)
Expand Down