18
18
19
19
20
20
# The __init__.py will import this. Not the other way around.
21
- __version__ = "0.6.1 "
21
+ __version__ = "0.7.0 "
22
22
23
23
logger = logging .getLogger (__name__ )
24
24
@@ -238,7 +238,7 @@ def acquire_token_by_authorization_code(
238
238
# REQUIRED, if the "redirect_uri" parameter was included in the
239
239
# authorization request as described in Section 4.1.1, and their
240
240
# values MUST be identical.
241
- ):
241
+ ** kwargs ):
242
242
"""The second half of the Authorization Code Grant.
243
243
244
244
:param code: The authorization code returned from Authorization Server.
@@ -270,9 +270,11 @@ def acquire_token_by_authorization_code(
270
270
# really empty.
271
271
assert isinstance (scopes , list ), "Invalid parameter type"
272
272
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 )
276
278
277
279
def get_accounts (self , username = None ):
278
280
"""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(
439
441
logger .debug ("Cache hit an AT" )
440
442
return { # Mimic a real response
441
443
"access_token" : entry ["secret" ],
442
- "token_type" : " Bearer" ,
444
+ "token_type" : entry . get ( "token_type" , " Bearer") ,
443
445
"expires_in" : int (expires_in ), # OAuth2 specs defines it as int
444
446
}
445
447
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):
551
553
"""
552
554
return self .client .obtain_token_by_device_flow (
553
555
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:
555
558
# during transition period,
556
559
# service seemingly need both device_code and code parameter.
557
560
** kwargs )
@@ -624,7 +627,7 @@ def _acquire_token_by_username_password_federated(
624
627
class ConfidentialClientApplication (ClientApplication ): # server-side web app
625
628
626
629
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 .
628
631
629
632
:param list[str] scopes: (Required)
630
633
Scopes requested to access a protected API (a resource).
@@ -639,6 +642,38 @@ def acquire_token_for_client(self, scopes, **kwargs):
639
642
scope = scopes , # This grant flow requires no scope decoration
640
643
** kwargs )
641
644
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 )
644
679
0 commit comments