10
10
import warnings
11
11
import time
12
12
import base64
13
+ import sys
13
14
14
15
import requests
15
16
16
17
18
+ string_types = (str ,) if sys .version_info [0 ] >= 3 else (basestring , )
19
+
17
20
18
21
class BaseClient (object ):
19
22
# 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
163
166
raise
164
167
165
168
def obtain_token_by_refresh_token (self , refresh_token , scope = None , ** kwargs ):
169
+ # type: (str, Union[str, list, set, tuple]) -> dict
166
170
"""Obtain an access token via a refresh token.
167
171
168
172
:param refresh_token: The refresh token issued to the client
169
173
:param scope: If omitted, is treated as equal to the scope originally
170
174
granted by the resource ownser,
171
175
according to https://tools.ietf.org/html/rfc6749#section-6
172
176
"""
177
+ assert isinstance (refresh_token , string_types )
173
178
data = kwargs .pop ('data' , {})
174
179
data .update (refresh_token = refresh_token , scope = scope )
175
180
return self ._obtain_token ("refresh_token" , data = data , ** kwargs )
@@ -328,7 +333,7 @@ def parse_auth_response(params, state=None):
328
333
return params
329
334
330
335
def obtain_token_by_authorization_code (
331
- self , code , redirect_uri = None , ** kwargs ):
336
+ self , code , redirect_uri = None , scope = None , ** kwargs ):
332
337
"""Get a token via auhtorization code. a.k.a. Authorization Code Grant.
333
338
334
339
This is typically used by a server-side app (Confidential Client),
@@ -339,9 +344,15 @@ def obtain_token_by_authorization_code(
339
344
:param redirect_uri:
340
345
Required, if the "redirect_uri" parameter was included in the
341
346
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.
342
351
"""
343
352
data = kwargs .pop ("data" , {})
344
353
data .update (code = code , redirect_uri = redirect_uri )
354
+ if scope :
355
+ data ["scope" ] = scope
345
356
if not self .client_secret :
346
357
# client_id is required, if the client is not authenticating itself.
347
358
# See https://tools.ietf.org/html/rfc6749#section-4.1.3
@@ -380,14 +391,10 @@ def __init__(self,
380
391
self .on_removing_rt = on_removing_rt
381
392
self .on_updating_rt = on_updating_rt
382
393
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 ):
386
395
RT = "refresh_token"
387
396
_data = data .copy () # to prevent side effect
388
397
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
391
398
resp = super (Client , self )._obtain_token (
392
399
grant_type , params , _data , * args , ** kwargs )
393
400
if "error" not in resp :
@@ -399,7 +406,9 @@ def _obtain_token(self, grant_type, params=None, data=None,
399
406
scope = _resp ["scope" ].split () # It is conceptually a set,
400
407
# but we represent it as a list which can be persisted to JSON
401
408
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.
403
412
scope = _data .get ("scope" )
404
413
self .on_obtaining_tokens ({
405
414
"client_id" : self .client_id ,
@@ -416,31 +425,31 @@ def obtain_token_by_refresh_token(self, token_item, scope=None,
416
425
on_removing_rt = None ,
417
426
** kwargs ):
418
427
# 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.
421
429
422
430
: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.
424
437
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.
429
438
:param scope: If omitted, is treated as equal to the scope originally
430
439
granted by the resource ownser,
431
440
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
433
442
:param on_removing_rt: If absent, fall back to the one defined in initialization
434
443
"""
435
444
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 ,
438
448
** 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' ])
444
453
return resp
445
454
446
455
def obtain_token_by_assertion (
0 commit comments