Skip to content

Commit 0ef0a9c

Browse files
committed
Fixed space encoding in base string URI used in the signature base string.
1 parent b4d0d1e commit 0ef0a9c

File tree

3 files changed

+55
-15
lines changed

3 files changed

+55
-15
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changelog
22
=========
33

4+
TBD
5+
---
6+
7+
* Fixed space encoding in base string URI used in the signature base string.
8+
49
3.0.0 (2019-01-01)
510
------------------
611
OAuth2.0 Provider - outstanding Features

oauthlib/oauth1/rfc5849/signature.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ def construct_base_string(http_method, base_string_uri,
105105
return base_string
106106

107107

108-
def normalize_base_string_uri(uri, host=None):
108+
def base_string_uri(uri, host=None):
109109
"""**Base String URI**
110-
Per `section 3.4.1.2`_ of the spec.
110+
Per `section 3.4.1.2`_ of RFC 5849.
111111
112112
For example, the HTTP request::
113113
@@ -177,7 +177,31 @@ def normalize_base_string_uri(uri, host=None):
177177
if (scheme, port) in default_ports:
178178
netloc = host
179179

180-
return urlparse.urlunparse((scheme, netloc, path, params, '', ''))
180+
v = urlparse.urlunparse((scheme, netloc, path, params, '', ''))
181+
182+
# RFC 5849 does not specify which characters are encoded in the
183+
# "base string URI", nor how they are encoded - which is very bad, since
184+
# the signatures won't match if there are any differences. Fortunately,
185+
# most URIs only use characters that are clearly not encoded (e.g. digits
186+
# and A-Z, a-z), so have avoided any differences between implementations.
187+
#
188+
# The example from its section 3.4.1.2 illustrates that spaces in
189+
# the path are percent encoded. But it provides no guidance as to what other
190+
# characters (if any) must be encoded (nor how); nor if characters in the
191+
# other components are to be encoded or not.
192+
#
193+
# This implementation **assumes** that **only** the space is percent-encoded
194+
# and it is done to the entire value (not just to spaces in the path).
195+
#
196+
# This code may need to be changed if it is discovered that other characters
197+
# are expected to be encoded.
198+
#
199+
# Note: the "base string URI" returned by this function will be encoded
200+
# again before being concatenated into the "signature base string". So any
201+
# spaces in the URI will actually appear in the "signature base string"
202+
# as "%2520" (the "%20" further encoded according to section 3.6).
203+
204+
return v.replace(' ', '%20')
181205

182206

183207
# ** Request Parameters **
@@ -624,8 +648,8 @@ def verify_hmac_sha1(request, client_secret=None,
624648
625649
"""
626650
norm_params = normalize_parameters(request.params)
627-
uri = normalize_base_string_uri(request.uri)
628-
base_string = construct_base_string(request.http_method, uri, norm_params)
651+
bs_uri = base_string_uri(request.uri)
652+
base_string = construct_base_string(request.http_method, bs_uri, norm_params)
629653
signature = sign_hmac_sha1(base_string, client_secret,
630654
resource_owner_secret)
631655
match = safe_string_equals(signature, request.signature)
@@ -657,8 +681,8 @@ def verify_rsa_sha1(request, rsa_public_key):
657681
.. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
658682
"""
659683
norm_params = normalize_parameters(request.params)
660-
uri = normalize_base_string_uri(request.uri)
661-
message = construct_base_string(request.http_method, uri, norm_params).encode('utf-8')
684+
bs_uri = base_string_uri(request.uri)
685+
message = construct_base_string(request.http_method, bs_uri, norm_params).encode('utf-8')
662686
sig = binascii.a2b_base64(request.signature.encode('utf-8'))
663687

664688
alg = _jwt_rs1_signing_algorithm()

tests/oauth1/rfc5849/test_signatures.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from oauthlib.common import unicode_type
55
from oauthlib.oauth1.rfc5849.signature import (collect_parameters,
66
construct_base_string,
7-
normalize_base_string_uri,
7+
base_string_uri,
88
normalize_parameters,
99
sign_hmac_sha1,
1010
sign_hmac_sha1_with_client,
@@ -125,7 +125,7 @@ def test_construct_base_string(self):
125125

126126
self.assertEqual(self.control_base_string, base_string)
127127

128-
def test_normalize_base_string_uri(self):
128+
def test_base_string_uri(self):
129129
"""
130130
Example text to be turned into a normalized base string uri::
131131
@@ -137,33 +137,44 @@ def test_normalize_base_string_uri(self):
137137
https://www.example.net:8080/
138138
"""
139139

140+
# test first example from RFC 5849 section 3.4.1.2.
141+
# Note: there is a space between "r" and "v"
142+
uri = 'http://EXAMPLE.COM:80/r v/X?id=123'
143+
self.assertEqual(base_string_uri(uri),
144+
'http://example.com/r%20v/X')
145+
146+
# test second example from RFC 5849 section 3.4.1.2.
147+
uri = 'https://www.example.net:8080/?q=1'
148+
self.assertEqual(base_string_uri(uri),
149+
'https://www.example.net:8080/')
150+
140151
# test for unicode failure
141152
uri = b"www.example.com:8080"
142-
self.assertRaises(ValueError, normalize_base_string_uri, uri)
153+
self.assertRaises(ValueError, base_string_uri, uri)
143154

144155
# test for missing scheme
145156
uri = "www.example.com:8080"
146-
self.assertRaises(ValueError, normalize_base_string_uri, uri)
157+
self.assertRaises(ValueError, base_string_uri, uri)
147158

148159
# test a URI with the default port
149160
uri = "http://www.example.com:80/"
150-
self.assertEqual(normalize_base_string_uri(uri),
161+
self.assertEqual(base_string_uri(uri),
151162
"http://www.example.com/")
152163

153164
# test a URI missing a path
154165
uri = "http://www.example.com"
155-
self.assertEqual(normalize_base_string_uri(uri),
166+
self.assertEqual(base_string_uri(uri),
156167
"http://www.example.com/")
157168

158169
# test a relative URI
159170
uri = "/a-host-relative-uri"
160171
host = "www.example.com"
161-
self.assertRaises(ValueError, normalize_base_string_uri, (uri, host))
172+
self.assertRaises(ValueError, base_string_uri, (uri, host))
162173

163174
# test overriding the URI's netloc with a host argument
164175
uri = "http://www.example.com/a-path"
165176
host = "alternatehost.example.com"
166-
self.assertEqual(normalize_base_string_uri(uri, host),
177+
self.assertEqual(base_string_uri(uri, host),
167178
"http://alternatehost.example.com/a-path")
168179

169180
def test_collect_parameters(self):

0 commit comments

Comments
 (0)