1414import requests
1515
1616from .oauth2cli import Client , JwtAssertionCreator
17+ from .oauth2cli .oidc import decode_part
1718from .authority import Authority
1819from .mex import send_request as mex_send_request
1920from .wstrust_request import send_request as wst_send_request
@@ -111,6 +112,34 @@ def _preferred_browser():
111112 return None
112113
113114
115+ class _ClientWithCcsRoutingInfo (Client ):
116+
117+ def initiate_auth_code_flow (self , ** kwargs ):
118+ return super (_ClientWithCcsRoutingInfo , self ).initiate_auth_code_flow (
119+ client_info = 1 , # To be used as CSS Routing info
120+ ** kwargs )
121+
122+ def obtain_token_by_auth_code_flow (
123+ self , auth_code_flow , auth_response , ** kwargs ):
124+ # Note: the obtain_token_by_browser() is also covered by this
125+ assert isinstance (auth_code_flow , dict ) and isinstance (auth_response , dict )
126+ headers = kwargs .pop ("headers" , {})
127+ client_info = json .loads (
128+ decode_part (auth_response ["client_info" ])
129+ ) if auth_response .get ("client_info" ) else {}
130+ if "uid" in client_info and "utid" in client_info :
131+ # Note: The value of X-AnchorMailbox is also case-insensitive
132+ headers ["X-AnchorMailbox" ] = "Oid:{uid}@{utid}" .format (** client_info )
133+ return super (_ClientWithCcsRoutingInfo , self ).obtain_token_by_auth_code_flow (
134+ auth_code_flow , auth_response , headers = headers , ** kwargs )
135+
136+ def obtain_token_by_username_password (self , username , password , ** kwargs ):
137+ headers = kwargs .pop ("headers" , {})
138+ headers ["X-AnchorMailbox" ] = "upn:{}" .format (username )
139+ return super (_ClientWithCcsRoutingInfo , self ).obtain_token_by_username_password (
140+ username , password , headers = headers , ** kwargs )
141+
142+
114143class ClientApplication (object ):
115144
116145 ACQUIRE_TOKEN_SILENT_ID = "84"
@@ -481,7 +510,7 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
481510 authority .device_authorization_endpoint or
482511 urljoin (authority .token_endpoint , "devicecode" ),
483512 }
484- central_client = Client (
513+ central_client = _ClientWithCcsRoutingInfo (
485514 central_configuration ,
486515 self .client_id ,
487516 http_client = self .http_client ,
@@ -506,7 +535,7 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
506535 regional_authority .device_authorization_endpoint or
507536 urljoin (regional_authority .token_endpoint , "devicecode" ),
508537 }
509- regional_client = Client (
538+ regional_client = _ClientWithCcsRoutingInfo (
510539 regional_configuration ,
511540 self .client_id ,
512541 http_client = self .http_client ,
@@ -577,7 +606,7 @@ def initiate_auth_code_flow(
577606 3. and then relay this dict and subsequent auth response to
578607 :func:`~acquire_token_by_auth_code_flow()`.
579608 """
580- client = Client (
609+ client = _ClientWithCcsRoutingInfo (
581610 {"authorization_endpoint" : self .authority .authorization_endpoint },
582611 self .client_id ,
583612 http_client = self .http_client )
@@ -654,7 +683,7 @@ def get_authorization_request_url(
654683 self .http_client
655684 ) if authority else self .authority
656685
657- client = Client (
686+ client = _ClientWithCcsRoutingInfo (
658687 {"authorization_endpoint" : the_authority .authorization_endpoint },
659688 self .client_id ,
660689 http_client = self .http_client )
@@ -1178,6 +1207,10 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
11781207 key = lambda e : int (e .get ("last_modification_time" , "0" )),
11791208 reverse = True ):
11801209 logger .debug ("Cache attempts an RT" )
1210+ headers = telemetry_context .generate_headers ()
1211+ if "home_account_id" in query : # Then use it as CCS Routing info
1212+ headers ["X-AnchorMailbox" ] = "Oid:{}" .format ( # case-insensitive value
1213+ query ["home_account_id" ].replace ("." , "@" ))
11811214 response = client .obtain_token_by_refresh_token (
11821215 entry , rt_getter = lambda token_item : token_item ["secret" ],
11831216 on_removing_rt = lambda rt_item : None , # Disable RT removal,
@@ -1189,7 +1222,7 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
11891222 skip_account_creation = True , # To honor a concurrent remove_account()
11901223 )),
11911224 scope = scopes ,
1192- headers = telemetry_context . generate_headers () ,
1225+ headers = headers ,
11931226 data = dict (
11941227 kwargs .pop ("data" , {}),
11951228 claims = _merge_claims_challenge_and_capabilities (
0 commit comments