1818
1919
2020# The __init__.py will import this. Not the other way around.
21- __version__ = "0.6.1 "
21+ __version__ = "0.7.0 "
2222
2323logger = logging .getLogger (__name__ )
2424
@@ -238,7 +238,7 @@ def acquire_token_by_authorization_code(
238238 # REQUIRED, if the "redirect_uri" parameter was included in the
239239 # authorization request as described in Section 4.1.1, and their
240240 # values MUST be identical.
241- ):
241+ ** kwargs ):
242242 """The second half of the Authorization Code Grant.
243243
244244 :param code: The authorization code returned from Authorization Server.
@@ -270,9 +270,11 @@ def acquire_token_by_authorization_code(
270270 # really empty.
271271 assert isinstance (scopes , list ), "Invalid parameter type"
272272 return self .client .obtain_token_by_authorization_code (
273- code , redirect_uri = redirect_uri ,
274- data = {"scope" : decorate_scope (scopes , self .client_id )},
275- )
273+ code , redirect_uri = redirect_uri ,
274+ data = dict (
275+ kwargs .pop ("data" , {}),
276+ scope = decorate_scope (scopes , self .client_id )),
277+ ** kwargs )
276278
277279 def get_accounts (self , username = None ):
278280 """Get a list of accounts which previously signed in, i.e. exists in cache.
@@ -439,7 +441,7 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
439441 logger .debug ("Cache hit an AT" )
440442 return { # Mimic a real response
441443 "access_token" : entry ["secret" ],
442- "token_type" : " Bearer" ,
444+ "token_type" : entry . get ( "token_type" , " Bearer") ,
443445 "expires_in" : int (expires_in ), # OAuth2 specs defines it as int
444446 }
445447 return self ._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family (
@@ -551,7 +553,8 @@ def acquire_token_by_device_flow(self, flow, **kwargs):
551553 """
552554 return self .client .obtain_token_by_device_flow (
553555 flow ,
554- data = {"code" : flow ["device_code" ]}, # 2018-10-4 Hack:
556+ data = dict (kwargs .pop ("data" , {}), code = flow ["device_code" ]),
557+ # 2018-10-4 Hack:
555558 # during transition period,
556559 # service seemingly need both device_code and code parameter.
557560 ** kwargs )
@@ -624,7 +627,7 @@ def _acquire_token_by_username_password_federated(
624627class ConfidentialClientApplication (ClientApplication ): # server-side web app
625628
626629 def acquire_token_for_client (self , scopes , ** kwargs ):
627- """Acquires token from the service for the confidential client .
630+ """Acquires token for the current confidential client, not for an end user .
628631
629632 :param list[str] scopes: (Required)
630633 Scopes requested to access a protected API (a resource).
@@ -639,6 +642,38 @@ def acquire_token_for_client(self, scopes, **kwargs):
639642 scope = scopes , # This grant flow requires no scope decoration
640643 ** kwargs )
641644
642- def acquire_token_on_behalf_of (self , user_assertion , scopes , authority = None ):
643- raise NotImplementedError ()
645+ def acquire_token_on_behalf_of (self , user_assertion , scopes , ** kwargs ):
646+ """Acquires token using on-behalf-of (OBO) flow.
647+
648+ The current app is a middle-tier service which was called with a token
649+ representing an end user.
650+ The current app can use such token (a.k.a. a user assertion) to request
651+ another token to access downstream web API, on behalf of that user.
652+ See `detail docs here <https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow>`_ .
653+
654+ The current middle-tier app has no user interaction to obtain consent.
655+ See how to gain consent upfront for your middle-tier app from this article.
656+ https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#gaining-consent-for-the-middle-tier-application
657+
658+ :param str user_assertion: The incoming token already received by this app
659+ :param list[str] scopes: Scopes required by downstream API (a resource).
660+
661+ :return: A dict representing the json response from AAD:
662+
663+ - A successful response would contain "access_token" key,
664+ - an error response would contain "error" and usually "error_description".
665+ """
666+ # The implementation is NOT based on Token Exchange
667+ # https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16
668+ return self .client .obtain_token_by_assertion ( # bases on assertion RFC 7521
669+ user_assertion ,
670+ self .client .GRANT_TYPE_JWT , # IDTs and AAD ATs are all JWTs
671+ scope = decorate_scope (scopes , self .client_id ), # Decoration is used for:
672+ # 1. Explicitly requesting an RT, without relying on AAD default
673+ # behavior, even though it currently still issues an RT.
674+ # 2. Requesting an IDT (which would otherwise be unavailable)
675+ # so that the calling app could use id_token_claims to implement
676+ # their own cache mapping, which is likely needed in web apps.
677+ data = dict (kwargs .pop ("data" , {}), requested_token_use = "on_behalf_of" ),
678+ ** kwargs )
644679
0 commit comments