Skip to content

Commit e4405e4

Browse files
committed
Merge branch 'oauth2' into refactor-internal-scope
2 parents e295896 + cb4231f commit e4405e4

File tree

1 file changed

+31
-22
lines changed

1 file changed

+31
-22
lines changed

msal/oauth2cli/oauth2.py

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
import warnings
1111
import time
1212
import base64
13+
import sys
1314

1415
import requests
1516

1617

18+
string_types = (str,) if sys.version_info[0] >= 3 else (basestring, )
19+
1720

1821
class BaseClient(object):
1922
# This low-level interface works. Yet you'll find its sub-class
@@ -163,13 +166,15 @@ def _obtain_token( # The verb "obtain" is influenced by OAUTH2 RFC 6749
163166
raise
164167

165168
def obtain_token_by_refresh_token(self, refresh_token, scope=None, **kwargs):
169+
# type: (str, Union[str, list, set, tuple]) -> dict
166170
"""Obtain an access token via a refresh token.
167171
168172
:param refresh_token: The refresh token issued to the client
169173
:param scope: If omitted, is treated as equal to the scope originally
170174
granted by the resource ownser,
171175
according to https://tools.ietf.org/html/rfc6749#section-6
172176
"""
177+
assert isinstance(refresh_token, string_types)
173178
data = kwargs.pop('data', {})
174179
data.update(refresh_token=refresh_token, scope=scope)
175180
return self._obtain_token("refresh_token", data=data, **kwargs)
@@ -328,7 +333,7 @@ def parse_auth_response(params, state=None):
328333
return params
329334

330335
def obtain_token_by_authorization_code(
331-
self, code, redirect_uri=None, **kwargs):
336+
self, code, redirect_uri=None, scope=None, **kwargs):
332337
"""Get a token via auhtorization code. a.k.a. Authorization Code Grant.
333338
334339
This is typically used by a server-side app (Confidential Client),
@@ -339,9 +344,15 @@ def obtain_token_by_authorization_code(
339344
:param redirect_uri:
340345
Required, if the "redirect_uri" parameter was included in the
341346
authorization request, and their values MUST be identical.
347+
:param scope:
348+
It is both unnecessary and harmless to use scope here, per RFC 6749.
349+
We suggest to use the same scope already used in auth request uri,
350+
so that this library can link the obtained tokens with their scope.
342351
"""
343352
data = kwargs.pop("data", {})
344353
data.update(code=code, redirect_uri=redirect_uri)
354+
if scope:
355+
data["scope"] = scope
345356
if not self.client_secret:
346357
# client_id is required, if the client is not authenticating itself.
347358
# See https://tools.ietf.org/html/rfc6749#section-4.1.3
@@ -380,14 +391,10 @@ def __init__(self,
380391
self.on_removing_rt = on_removing_rt
381392
self.on_updating_rt = on_updating_rt
382393

383-
def _obtain_token(self, grant_type, params=None, data=None,
384-
rt_getter=lambda token_item: token_item["refresh_token"],
385-
*args, **kwargs):
394+
def _obtain_token(self, grant_type, params=None, data=None, *args, **kwargs):
386395
RT = "refresh_token"
387396
_data = data.copy() # to prevent side effect
388397
refresh_token = _data.get(RT)
389-
if grant_type == RT and isinstance(refresh_token, dict):
390-
_data[RT] = rt_getter(refresh_token) # Put raw RT in _data
391398
resp = super(Client, self)._obtain_token(
392399
grant_type, params, _data, *args, **kwargs)
393400
if "error" not in resp:
@@ -399,7 +406,9 @@ def _obtain_token(self, grant_type, params=None, data=None,
399406
scope = _resp["scope"].split() # It is conceptually a set,
400407
# but we represent it as a list which can be persisted to JSON
401408
else:
402-
# TODO: Deal with absent scope in authorization grant
409+
# Note: The scope will generally be absent in authorization grant,
410+
# but our obtain_token_by_authorization_code(...) encourages
411+
# app developer to still explicitly provide a scope here.
403412
scope = _data.get("scope")
404413
self.on_obtaining_tokens({
405414
"client_id": self.client_id,
@@ -416,31 +425,31 @@ def obtain_token_by_refresh_token(self, token_item, scope=None,
416425
on_removing_rt=None,
417426
**kwargs):
418427
# type: (Union[str, dict], Union[str, list, set, tuple], Callable) -> dict
419-
"""This is an "overload" which accepts a refresh token item as a dict,
420-
therefore this method can relay refresh_token item to event listeners.
428+
"""This is an overload which will trigger token storage callbacks.
421429
422430
:param token_item:
423-
A refresh token item as a dict, came from the cache managed by this lib.
431+
A refresh token (RT) item, in flexible format. It can be a string,
432+
or a whatever data structure containing RT string and its metadata,
433+
in such case the `rt_getter` callable must be able to
434+
extract the RT string out from the token item data structure.
435+
436+
Either way, this token_item will be passed into other callbacks as-is.
424437
425-
Alternatively, you can still use a refresh token (RT) as a string,
426-
supposedly came from a token cache managed by a different library,
427-
then this library will store the new RT (if Authority Server issued one)
428-
into this lib's cache. This is a way to migrate from other lib to us.
429438
:param scope: If omitted, is treated as equal to the scope originally
430439
granted by the resource ownser,
431440
according to https://tools.ietf.org/html/rfc6749#section-6
432-
:param rt_getter: A callable used to extract the RT from token_item
441+
:param rt_getter: A callable to translate the token_item to a raw RT string
433442
:param on_removing_rt: If absent, fall back to the one defined in initialization
434443
"""
435444
resp = super(Client, self).obtain_token_by_refresh_token(
436-
token_item, scope=scope,
437-
rt_getter=rt_getter, # Wire up this for _obtain_token()
445+
rt_getter(token_item)
446+
if not isinstance(token_item, string_types) else token_item,
447+
scope=scope,
438448
**kwargs)
439-
if isinstance(token_item, dict):
440-
if resp.get('error') == 'invalid_grant':
441-
(on_removing_rt or self.on_removing_rt)(token_item) # Discard old RT
442-
if 'refresh_token' in resp:
443-
self.on_updating_rt(token_item, resp['refresh_token'])
449+
if resp.get('error') == 'invalid_grant':
450+
(on_removing_rt or self.on_removing_rt)(token_item) # Discard old RT
451+
if 'refresh_token' in resp:
452+
self.on_updating_rt(token_item, resp['refresh_token'])
444453
return resp
445454

446455
def obtain_token_by_assertion(

0 commit comments

Comments
 (0)