From 7cafa8b45f13e969784cb2445ce7e708082bdd58 Mon Sep 17 00:00:00 2001 From: Jim Cortez Date: Sun, 20 Dec 2015 10:13:27 -0800 Subject: [PATCH] Add refresh_token_request compliance hook This hook is to be able to modify refresh token requests. It is useful when doing automatic refreshes. Also added example for the fitbit developer api, which requires a Authorization header at refresh time. --- .../compliance_fixes/__init__.py | 1 + requests_oauthlib/compliance_fixes/fitbit.py | 15 +++++++++ requests_oauthlib/oauth2_session.py | 6 ++++ tests/test_compliance_fixes.py | 32 +++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 requests_oauthlib/compliance_fixes/fitbit.py diff --git a/requests_oauthlib/compliance_fixes/__init__.py b/requests_oauthlib/compliance_fixes/__init__.py index 46eacb8b..4872bea6 100644 --- a/requests_oauthlib/compliance_fixes/__init__.py +++ b/requests_oauthlib/compliance_fixes/__init__.py @@ -5,3 +5,4 @@ from .slack import slack_compliance_fix from .mailchimp import mailchimp_compliance_fix from .weibo import weibo_compliance_fix +from .fitbit import fitbit_compliance_fix diff --git a/requests_oauthlib/compliance_fixes/fitbit.py b/requests_oauthlib/compliance_fixes/fitbit.py new file mode 100644 index 00000000..0af463fe --- /dev/null +++ b/requests_oauthlib/compliance_fixes/fitbit.py @@ -0,0 +1,15 @@ +import base64 + +from oauthlib.common import add_params_to_uri + + +def fitbit_compliance_fix(session, client_secret): + def _non_compliant_auth_header(url, headers, body): + basic_auth_value = "{}:{}".format(session._client.client_id, client_secret) + headers["Authorization"] = 'Basic {}'.format(base64.b64encode(basic_auth_value)) + token = [('token', session._client.access_token)] + url = add_params_to_uri(url, token) + return url, headers, body + + session.register_compliance_hook('refresh_token_request', _non_compliant_auth_header) + return session diff --git a/requests_oauthlib/oauth2_session.py b/requests_oauthlib/oauth2_session.py index 9aa2e02b..8dcb0c5f 100644 --- a/requests_oauthlib/oauth2_session.py +++ b/requests_oauthlib/oauth2_session.py @@ -80,6 +80,7 @@ def __init__(self, client_id=None, client=None, auto_refresh_url=None, 'access_token_response': set([]), 'refresh_token_response': set([]), 'protected_request': set([]), + 'refresh_token_request': set([]), } def new_state(self): @@ -288,6 +289,10 @@ def refresh_token(self, token_url, refresh_token=None, body='', auth=None, ), } + for hook in self.compliance_hook['refresh_token_request']: + log.debug('Invoking hook %s.', hook) + token_url, headers, body = hook(token_url, headers, body) + r = self.post(token_url, data=dict(urldecode(body)), auth=auth, timeout=timeout, headers=headers, verify=verify) log.debug('Request to refresh token completed with status %s.', @@ -351,6 +356,7 @@ def register_compliance_hook(self, hook_type, hook): access_token_response invoked before token parsing. refresh_token_response invoked before refresh token parsing. protected_request invoked before making a request. + refresh_token_request invoked before making a refresh request If you find a new hook is needed please send a GitHub PR request or open an issue. diff --git a/tests/test_compliance_fixes.py b/tests/test_compliance_fixes.py index ada09ab1..20b5f700 100644 --- a/tests/test_compliance_fixes.py +++ b/tests/test_compliance_fixes.py @@ -6,7 +6,9 @@ import requests import requests_mock +import mock import time +import base64 try: from urlparse import urlparse, parse_qs except ImportError: @@ -18,6 +20,7 @@ from requests_oauthlib.compliance_fixes import mailchimp_compliance_fix from requests_oauthlib.compliance_fixes import weibo_compliance_fix from requests_oauthlib.compliance_fixes import slack_compliance_fix +from requests_oauthlib.compliance_fixes import fitbit_compliance_fix class FacebookComplianceFixTest(TestCase): @@ -216,3 +219,32 @@ def test_protected_request_override_token_url(self): query = parse_qs(urlparse(url).query) self.assertEqual(query["token"], ["different-token"]) self.assertIsNone(response.request.body) + + +class FitbitComplianceFixTest(TestCase): + def test_refresh_token(self): + fitbit = OAuth2Session('foo', redirect_uri='https://i.b') + fitbit = fitbit_compliance_fix(fitbit, "mocksecret") + + fitbit.post = mock.MagicMock() + response = requests.Response() + response.status_code = 200 + response.request = mock.MagicMock() + response._content = '{"access_token":"fitbit"}'.encode('UTF-8') + fitbit.post.return_value = response + + token = fitbit.refresh_token('https://mocked.out', + refresh_token='foobar') + + self.assertEqual(token, {'access_token': 'fitbit', 'refresh_token': 'foobar'}) + + auth_value = base64.b64encode("foo:mocksecret") + fitbit.post.assert_called_with(u'https://mocked.out?token=None', auth=None, + data={u'grant_type': u'refresh_token', u'refresh_token': u'foobar'}, + headers={ + 'Accept': 'application/json', + 'Content-Type': ( + 'application/x-www-form-urlencoded;charset=UTF-8' + ), + 'Authorization': 'Basic {}'.format(auth_value) + }, timeout=None, verify=True)