Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions requests_oauthlib/oauth2_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ def authorization_url(self, url, state=None, **kwargs):
**kwargs), state

def fetch_token(self, token_url, code=None, authorization_response=None,
body='', auth=None, username=None, password=None, method='POST',
timeout=None, headers=None, verify=True, proxies=None,
include_client_id=None, client_secret=None, **kwargs):
body='', auth=None, username=None, password=None, method='POST',
timeout=None, headers=None, verify=True, proxies=None,
include_client_id=None, client_secret=None, url_params=False, **kwargs):
"""Generic method for fetching an access token from the token endpoint.

If you are using the MobileApplicationClient you will want to use
Expand Down Expand Up @@ -197,6 +197,9 @@ def fetch_token(self, token_url, code=None, authorization_response=None,
`auth` tuple. If the value is `None`, it will be
omitted from the request, however if the value is
an empty string, an empty string will be sent.
:param url_params: In POST request,
decides whether to send request data as data or in URL.
some APIs require URL params.
:param kwargs: Extra parameters to include in the token request.
:return: A token dict
"""
Expand Down Expand Up @@ -261,7 +264,8 @@ def fetch_token(self, token_url, code=None, authorization_response=None,
log.debug('Encoding `client_id` "%s" with `client_secret` '
'as Basic auth credentials.', client_id)
client_secret = client_secret if client_secret is not None else ''
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
auth = requests.auth.HTTPBasicAuth(
client_id, client_secret)

if include_client_id:
# this was pulled out of the params
Expand All @@ -279,9 +283,12 @@ def fetch_token(self, token_url, code=None, authorization_response=None,
}
self.token = {}
if method.upper() == 'POST':
r = self.post(token_url, data=dict(urldecode(body)),
timeout=timeout, headers=headers, auth=auth,
verify=verify, proxies=proxies)
post_body = {'data': dict(urldecode(body))}
if url_params:
post_body = {'params': body}
r = self.post(token_url, timeout=timeout, headers=headers,
auth=auth, verify=verify,
proxies=proxies, **post_body)
log.debug('Prepared fetch token request body %s', body)
elif method.upper() == 'GET':
# if method is not 'POST', switch body to querystring and GET
Expand Down Expand Up @@ -321,7 +328,8 @@ def token_from_fragment(self, authorization_response):
return self.token

def refresh_token(self, token_url, refresh_token=None, body='', auth=None,
timeout=None, headers=None, verify=True, proxies=None, **kwargs):
timeout=None, headers=None, verify=True,
proxies=None, url_params=False, **kwargs):
"""Fetch a new access token using a refresh token.

:param token_url: The token endpoint, must be HTTPS.
Expand All @@ -333,6 +341,9 @@ def refresh_token(self, token_url, refresh_token=None, body='', auth=None,
:param headers: A dict of headers to be used by `requests`.
:param verify: Verify SSL certificate.
:param proxies: The `proxies` argument will be passed to `requests`.
:param url_params: In POST request,
decides whether to send request data as data or in URL.
some APIs require URL params.
:param kwargs: Extra parameters to include in the token request.
:return: A token dict
"""
Expand All @@ -359,8 +370,12 @@ def refresh_token(self, token_url, refresh_token=None, body='', auth=None,
),
}

r = self.post(token_url, data=dict(urldecode(body)), auth=auth,
timeout=timeout, headers=headers, verify=verify, withhold_token=True, proxies=proxies)
post_body = {'data': dict(urldecode(body))}
if url_params:
post_body = {'params': body}
r = self.post(token_url, auth=auth, timeout=timeout,
headers=headers, verify=verify,
withhold_token=True, proxies=proxies, **post_body)
log.debug('Request to refresh token completed with status %s.',
r.status_code)
log.debug('Response headers were %s and content %s.',
Expand All @@ -371,7 +386,8 @@ def refresh_token(self, token_url, refresh_token=None, body='', auth=None,
log.debug('Invoking hook %s.', hook)
r = hook(r)

self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
self.token = self._client.parse_request_body_response(
r.text, scope=self.scope)
if not 'refresh_token' in self.token:
log.debug('No new refresh token given. Re-using old.')
self.token['refresh_token'] = refresh_token
Expand Down Expand Up @@ -402,8 +418,10 @@ def request(self, method, url, data=None, headers=None, withhold_token=False,
# We mustn't pass auth twice.
auth = kwargs.pop('auth', None)
if client_id and client_secret and (auth is None):
log.debug('Encoding client_id "%s" with client_secret as Basic auth credentials.', client_id)
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
log.debug(
'Encoding client_id "%s" with client_secret as Basic auth credentials.', client_id)
auth = requests.auth.HTTPBasicAuth(
client_id, client_secret)
token = self.refresh_token(
self.auto_refresh_url, auth=auth, **kwargs
)
Expand Down
25 changes: 16 additions & 9 deletions tests/test_oauth2_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from requests.auth import _basic_auth_str

CODE = 'asdf345xdf'

fake_time = time.time()

Expand Down Expand Up @@ -50,7 +51,7 @@ def setUp(self):
self.client_secret = 'someclientsecret'
self.user_username = 'user_username'
self.user_password = 'user_password'
self.client_WebApplication = WebApplicationClient(self.client_id, code='asdf345xdf')
self.client_WebApplication = WebApplicationClient(self.client_id, code=CODE)
self.client_LegacyApplication = LegacyApplicationClient(self.client_id)
self.client_BackendApplication = BackendApplicationClient(self.client_id)
self.client_MobileApplication = MobileApplicationClient(self.client_id)
Expand Down Expand Up @@ -244,22 +245,28 @@ def fake_send(r, **kwargs):
self.assertIn('username=%s' % self.user_username, _fetch_history[3][1])
self.assertIn('password=%s' % self.user_password, _fetch_history[3][1])

# scenario 5 - send data in `params` and not in `data` for networks
# that expect data in URL

self.assertEqual(sess.fetch_token(url, client_secret='somesecret', url_params=True), self.token)
self.assertIn("code=%s" % CODE, _fetch_history[4][0])

# some quick tests for valid ways of supporting `client_secret`

# scenario 2b - force the `client_id` into the body; but the `client_secret` is `None`
self.assertEqual(sess.fetch_token(url, client_secret=None, include_client_id=True), self.token)
self.assertEqual(len(_fetch_history), 5)
self.assertIn('client_id=%s' % self.client_id, _fetch_history[4][1])
self.assertNotIn('client_secret', _fetch_history[4][1]) # no `client_secret` in the body
self.assertEqual(_fetch_history[4][2], None) # ensure NO Basic Authorization header

# scenario 2c - force the `client_id` into the body; but the `client_secret` is an empty string
self.assertEqual(sess.fetch_token(url, client_secret='', include_client_id=True), self.token)
self.assertEqual(len(_fetch_history), 6)
self.assertIn('client_id=%s' % self.client_id, _fetch_history[5][1])
self.assertIn('client_secret=', _fetch_history[5][1])
self.assertNotIn('client_secret', _fetch_history[5][1]) # no `client_secret` in the body
self.assertEqual(_fetch_history[5][2], None) # ensure NO Basic Authorization header

# scenario 2c - force the `client_id` into the body; but the `client_secret` is an empty string
self.assertEqual(sess.fetch_token(url, client_secret='', include_client_id=True), self.token)
self.assertEqual(len(_fetch_history), 7)
self.assertIn('client_id=%s' % self.client_id, _fetch_history[6][1])
self.assertIn('client_secret=', _fetch_history[6][1])
self.assertEqual(_fetch_history[6][2], None) # ensure NO Basic Authorization header

def test_cleans_previous_token_before_fetching_new_one(self):
"""Makes sure the previous token is cleaned before fetching a new one.

Expand Down