@@ -287,3 +287,64 @@ class initialization.
287287 data .update (scope = scope )
288288 return self ._obtain_token ("client_credentials" , data = data , ** kwargs )
289289
290+ def __init__ (self ,
291+ client_id ,
292+ on_obtaining_tokens = lambda event : None , # event is defined in _obtain_token(...)
293+ on_removing_rt = lambda token_item : None ,
294+ on_updating_rt = lambda token_item , new_rt : None ,
295+ ** kwargs ):
296+ super (Client , self ).__init__ (client_id , ** kwargs )
297+ self .on_obtaining_tokens = on_obtaining_tokens
298+ self .on_removing_rt = on_removing_rt
299+ self .on_updating_rt = on_updating_rt
300+
301+ def _obtain_token (self , grant_type , params = None , data = None , * args , ** kwargs ):
302+ resp = super (Client , self )._obtain_token (
303+ grant_type , params , data , * args , ** kwargs )
304+ if "error" not in resp :
305+ _resp = resp .copy ()
306+ if grant_type == "refresh_token" and "refresh_token" in _resp :
307+ _resp .pop ("refresh_token" ) # We'll handle this in its own method
308+ if "scope" in _resp :
309+ scope = _resp ["scope" ].split () # It is conceptually a set,
310+ # but we represent it as a list which can be persisted to JSON
311+ else :
312+ # TODO: Deal with absent scope in authorization grant
313+ scope = data .get ("scope" )
314+ self .on_obtaining_tokens ({
315+ "client_id" : self .client_id ,
316+ "scope" : scope ,
317+ "token_endpoint" : self .configuration ["token_endpoint" ],
318+ "grant_type" : grant_type , # can be used to know an IdToken-less
319+ # response is for an app or for a user
320+ "response" : _resp , "params" : params , "data" : data ,
321+ })
322+ return resp
323+
324+ def obtain_token_with_refresh_token (self , token_item , scope = None ,
325+ rt_getter = lambda token_item : token_item ["refresh_token" ],
326+ ** kwargs ):
327+ # type: (Union[str, dict], Union[str, list, set, tuple], Callable) -> dict
328+ """This is an "overload" which accepts a refresh token item as a dict,
329+ therefore this method can relay refresh_token item to event listeners.
330+
331+ :param refresh_token_item: A refresh token item came from storage
332+ :param scope: If omitted, is treated as equal to the scope originally
333+ granted by the resource ownser,
334+ according to https://tools.ietf.org/html/rfc6749#section-6
335+ :param rt_getter: A callable used to extract the RT from token_item
336+ """
337+ if isinstance (token_item , str ):
338+ # Satisfy the L of SOLID, although we expect caller uses a dict
339+ return super (Client , self ).obtain_token_with_refresh_token (
340+ token_item , scope = scope , ** kwargs )
341+ if isinstance (token_item , dict ):
342+ resp = super (Client , self ).obtain_token_with_refresh_token (
343+ rt_getter (token_item ), scope = scope , ** kwargs )
344+ if resp .get ('error' ) == 'invalid_grant' :
345+ self .on_removing_rt (token_item ) # Discard old RT
346+ if 'refresh_token' in resp :
347+ self .on_updating_rt (token_item , resp ['refresh_token' ])
348+ return resp
349+ raise ValueError ("token_item should not be a type %s" % type (token_item ))
350+
0 commit comments