Skip to content

Commit 486ee81

Browse files
committed
Fix an issue with non-dict values in flow exchange.
The OAuth2 for Devices implementation picked up an incidental change that failed to treat dicts and dict-like objects the same way. This is particularly painful in environments like App Engine, where `self.request.params` is a common dict-like value to pass in. This restores the previous behavior, and adds a test.
1 parent dc8b40e commit 486ee81

File tree

3 files changed

+35
-5
lines changed

3 files changed

+35
-5
lines changed

oauth2client/client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,10 +1767,10 @@ def step2_exchange(self, code=None, http=None, device_flow_info=None):
17671767
17681768
Args:
17691769
1770-
code: string, dict or None. For a non-device flow, this is
1771-
either the response code as a string, or a dictionary of
1772-
query parameters to the redirect_uri. For a device flow,
1773-
this should be None.
1770+
code: string, a dict-like object, or None. For a non-device
1771+
flow, this is either the response code as a string, or a
1772+
dictionary of query parameters to the redirect_uri. For a
1773+
device flow, this should be None.
17741774
http: httplib2.Http, optional http instance to use when fetching
17751775
credentials.
17761776
device_flow_info: DeviceFlowInfo, return value from step1 in the
@@ -1793,7 +1793,7 @@ def step2_exchange(self, code=None, http=None, device_flow_info=None):
17931793

17941794
if code is None:
17951795
code = device_flow_info.device_code
1796-
elif isinstance(code, dict):
1796+
elif not isinstance(code, basestring):
17971797
if 'code' not in code:
17981798
raise FlowExchangeError(code.get(
17991799
'error', 'No code was supplied in the query parameters.'))

tests/http_mock.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def __init__(self, iterable):
9090
"""
9191
self._iterable = iterable
9292
self.follow_redirects = True
93+
self.requests = []
9394

9495
def request(self, uri,
9596
method='GET',
@@ -98,6 +99,7 @@ def request(self, uri,
9899
redirections=1,
99100
connection_type=None):
100101
resp, content = self._iterable.pop(0)
102+
self.requests.append({'uri': uri, 'body': body, 'headers': headers})
101103
if content == 'echo_request_headers':
102104
content = headers
103105
elif content == 'echo_request_headers_as_json':

tests/test_oauth2client.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,34 @@ def test_exchange_success(self):
875875
self.assertEqual('8xLOxBtZp8', credentials.refresh_token)
876876
self.assertEqual('dummy_revoke_uri', credentials.revoke_uri)
877877

878+
def test_exchange_dictlike(self):
879+
class FakeDict(object):
880+
def __init__(self, d):
881+
self.d = d
882+
883+
def __getitem__(self, name):
884+
return self.d[name]
885+
886+
def __contains__(self, name):
887+
return name in self.d
888+
889+
code = 'some random code'
890+
not_a_dict = FakeDict({'code': code})
891+
http = HttpMockSequence([
892+
({'status': '200'},
893+
"""{ "access_token":"SlAV32hkKG",
894+
"expires_in":3600,
895+
"refresh_token":"8xLOxBtZp8" }"""),
896+
])
897+
898+
credentials = self.flow.step2_exchange(not_a_dict, http=http)
899+
self.assertEqual('SlAV32hkKG', credentials.access_token)
900+
self.assertNotEqual(None, credentials.token_expiry)
901+
self.assertEqual('8xLOxBtZp8', credentials.refresh_token)
902+
self.assertEqual('dummy_revoke_uri', credentials.revoke_uri)
903+
request_code = urlparse.parse_qs(http.requests[0]['body'])['code'][0]
904+
self.assertEqual(code, request_code)
905+
878906
def test_urlencoded_exchange_success(self):
879907
http = HttpMockSequence([
880908
({'status': '200'}, 'access_token=SlAV32hkKG&expires_in=3600'),

0 commit comments

Comments
 (0)