66import logging
77import sys
88import warnings
9+ import uuid
910
1011import requests
1112
@@ -49,6 +50,16 @@ def decorate_scope(
4950 decorated = scope_set | reserved_scope
5051 return list (decorated )
5152
53+ CLIENT_REQUEST_ID = 'client-request-id'
54+ CLIENT_CURRENT_TELEMETRY = 'x-client-current-telemetry'
55+
56+ def _get_new_correlation_id ():
57+ return str (uuid .uuid4 ())
58+
59+
60+ def _build_current_telemetry_request_header (public_api_id , force_refresh = False ):
61+ return "1|{},{}|" .format (public_api_id , "1" if force_refresh else "0" )
62+
5263
5364def extract_certs (public_cert_content ):
5465 # Parses raw public certificate file contents and returns a list of strings
@@ -68,6 +79,15 @@ def extract_certs(public_cert_content):
6879
6980class ClientApplication (object ):
7081
82+ ACQUIRE_TOKEN_SILENT_ID = "84"
83+ ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID = "301"
84+ ACQUIRE_TOKEN_ON_BEHALF_OF_ID = "523"
85+ ACQUIRE_TOKEN_BY_DEVICE_FLOW_ID = "622"
86+ ACQUIRE_TOKEN_FOR_CLIENT_ID = "730"
87+ ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE_ID = "832"
88+ GET_ACCOUNTS_ID = "902"
89+ REMOVE_ACCOUNT_ID = "903"
90+
7191 def __init__ (
7292 self , client_id ,
7393 client_credential = None , authority = None , validate_authority = True ,
@@ -303,6 +323,11 @@ def acquire_token_by_authorization_code(
303323 data = dict (
304324 kwargs .pop ("data" , {}),
305325 scope = decorate_scope (scopes , self .client_id )),
326+ headers = {
327+ CLIENT_REQUEST_ID : _get_new_correlation_id (),
328+ CLIENT_CURRENT_TELEMETRY : _build_current_telemetry_request_header (
329+ self .ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE_ID ),
330+ },
306331 ** kwargs )
307332
308333 def get_accounts (self , username = None ):
@@ -426,14 +451,17 @@ def acquire_token_silent(
426451 """
427452 assert isinstance (scopes , list ), "Invalid parameter type"
428453 self ._validate_ssh_cert_input_data (kwargs .get ("data" , {}))
454+ correlation_id = _get_new_correlation_id ()
429455 if authority :
430456 warnings .warn ("We haven't decided how/if this method will accept authority parameter" )
431457 # the_authority = Authority(
432458 # authority,
433459 # verify=self.verify, proxies=self.proxies, timeout=self.timeout,
434460 # ) if authority else self.authority
435461 result = self ._acquire_token_silent_from_cache_and_possibly_refresh_it (
436- scopes , account , self .authority , force_refresh = force_refresh , ** kwargs )
462+ scopes , account , self .authority , force_refresh = force_refresh ,
463+ correlation_id = correlation_id ,
464+ ** kwargs )
437465 if result :
438466 return result
439467 for alias in self ._get_authority_aliases (self .authority .instance ):
@@ -442,7 +470,9 @@ def acquire_token_silent(
442470 validate_authority = False ,
443471 verify = self .verify , proxies = self .proxies , timeout = self .timeout )
444472 result = self ._acquire_token_silent_from_cache_and_possibly_refresh_it (
445- scopes , account , the_authority , force_refresh = force_refresh , ** kwargs )
473+ scopes , account , the_authority , force_refresh = force_refresh ,
474+ correlation_id = correlation_id ,
475+ ** kwargs )
446476 if result :
447477 return result
448478
@@ -480,7 +510,7 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
480510 }
481511 return self ._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family (
482512 authority , decorate_scope (scopes , self .client_id ), account ,
483- ** kwargs )
513+ force_refresh = force_refresh , ** kwargs )
484514
485515 def _acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family (
486516 self , authority , scopes , account , ** kwargs ):
@@ -526,7 +556,8 @@ def _get_app_metadata(self, environment):
526556
527557 def _acquire_token_silent_by_finding_specific_refresh_token (
528558 self , authority , scopes , query ,
529- rt_remover = None , break_condition = lambda response : False , ** kwargs ):
559+ rt_remover = None , break_condition = lambda response : False ,
560+ force_refresh = False , correlation_id = None , ** kwargs ):
530561 matches = self .token_cache .find (
531562 self .token_cache .CredentialType .REFRESH_TOKEN ,
532563 # target=scopes, # AAD RTs are scope-independent
@@ -539,6 +570,11 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
539570 entry , rt_getter = lambda token_item : token_item ["secret" ],
540571 on_removing_rt = rt_remover or self .token_cache .remove_rt ,
541572 scope = scopes ,
573+ headers = {
574+ CLIENT_REQUEST_ID : correlation_id or _get_new_correlation_id (),
575+ CLIENT_CURRENT_TELEMETRY : _build_current_telemetry_request_header (
576+ self .ACQUIRE_TOKEN_SILENT_ID , force_refresh = force_refresh ),
577+ },
542578 ** kwargs )
543579 if "error" not in response :
544580 return response
@@ -564,6 +600,8 @@ def _validate_ssh_cert_input_data(self, data):
564600
565601class PublicClientApplication (ClientApplication ): # browser app or mobile app
566602
603+ DEVICE_FLOW_CORRELATION_ID = "_correlation_id"
604+
567605 def __init__ (self , client_id , client_credential = None , ** kwargs ):
568606 if client_credential is not None :
569607 raise ValueError ("Public Client should not possess credentials" )
@@ -581,9 +619,16 @@ def initiate_device_flow(self, scopes=None, **kwargs):
581619 - A successful response would contain "user_code" key, among others
582620 - an error response would contain some other readable key/value pairs.
583621 """
584- return self .client .initiate_device_flow (
622+ correlation_id = _get_new_correlation_id ()
623+ flow = self .client .initiate_device_flow (
585624 scope = decorate_scope (scopes or [], self .client_id ),
625+ headers = {
626+ CLIENT_REQUEST_ID : correlation_id ,
627+ # CLIENT_CURRENT_TELEMETRY is not currently required
628+ },
586629 ** kwargs )
630+ flow [self .DEVICE_FLOW_CORRELATION_ID ] = correlation_id
631+ return flow
587632
588633 def acquire_token_by_device_flow (self , flow , ** kwargs ):
589634 """Obtain token by a device flow object, with customizable polling effect.
@@ -600,12 +645,18 @@ def acquire_token_by_device_flow(self, flow, **kwargs):
600645 - an error response would contain "error" and usually "error_description".
601646 """
602647 return self .client .obtain_token_by_device_flow (
603- flow ,
604- data = dict (kwargs .pop ("data" , {}), code = flow ["device_code" ]),
605- # 2018-10-4 Hack:
606- # during transition period,
607- # service seemingly need both device_code and code parameter.
608- ** kwargs )
648+ flow ,
649+ data = dict (kwargs .pop ("data" , {}), code = flow ["device_code" ]),
650+ # 2018-10-4 Hack:
651+ # during transition period,
652+ # service seemingly need both device_code and code parameter.
653+ headers = {
654+ CLIENT_REQUEST_ID :
655+ flow .get (self .DEVICE_FLOW_CORRELATION_ID ) or _get_new_correlation_id (),
656+ CLIENT_CURRENT_TELEMETRY : _build_current_telemetry_request_header (
657+ self .ACQUIRE_TOKEN_BY_DEVICE_FLOW_ID ),
658+ },
659+ ** kwargs )
609660
610661 def acquire_token_by_username_password (
611662 self , username , password , scopes , ** kwargs ):
@@ -625,13 +676,22 @@ def acquire_token_by_username_password(
625676 - an error response would contain "error" and usually "error_description".
626677 """
627678 scopes = decorate_scope (scopes , self .client_id )
679+ headers = {
680+ CLIENT_REQUEST_ID : _get_new_correlation_id (),
681+ CLIENT_CURRENT_TELEMETRY : _build_current_telemetry_request_header (
682+ self .ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID ),
683+ }
628684 if not self .authority .is_adfs :
629- user_realm_result = self .authority .user_realm_discovery (username )
685+ user_realm_result = self .authority .user_realm_discovery (
686+ username , correlation_id = headers [CLIENT_REQUEST_ID ])
630687 if user_realm_result .get ("account_type" ) == "Federated" :
631688 return self ._acquire_token_by_username_password_federated (
632- user_realm_result , username , password , scopes = scopes , ** kwargs )
689+ user_realm_result , username , password , scopes = scopes ,
690+ headers = headers , ** kwargs )
633691 return self .client .obtain_token_by_username_password (
634- username , password , scope = scopes , ** kwargs )
692+ username , password , scope = scopes ,
693+ headers = headers ,
694+ ** kwargs )
635695
636696 def _acquire_token_by_username_password_federated (
637697 self , user_realm_result , username , password , scopes = None , ** kwargs ):
@@ -687,8 +747,13 @@ def acquire_token_for_client(self, scopes, **kwargs):
687747 """
688748 # TBD: force_refresh behavior
689749 return self .client .obtain_token_for_client (
690- scope = scopes , # This grant flow requires no scope decoration
691- ** kwargs )
750+ scope = scopes , # This grant flow requires no scope decoration
751+ headers = {
752+ CLIENT_REQUEST_ID : _get_new_correlation_id (),
753+ CLIENT_CURRENT_TELEMETRY : _build_current_telemetry_request_header (
754+ self .ACQUIRE_TOKEN_FOR_CLIENT_ID ),
755+ },
756+ ** kwargs )
692757
693758 def acquire_token_on_behalf_of (self , user_assertion , scopes , ** kwargs ):
694759 """Acquires token using on-behalf-of (OBO) flow.
@@ -723,5 +788,10 @@ def acquire_token_on_behalf_of(self, user_assertion, scopes, **kwargs):
723788 # so that the calling app could use id_token_claims to implement
724789 # their own cache mapping, which is likely needed in web apps.
725790 data = dict (kwargs .pop ("data" , {}), requested_token_use = "on_behalf_of" ),
791+ headers = {
792+ CLIENT_REQUEST_ID : _get_new_correlation_id (),
793+ CLIENT_CURRENT_TELEMETRY : _build_current_telemetry_request_header (
794+ self .ACQUIRE_TOKEN_ON_BEHALF_OF_ID ),
795+ },
726796 ** kwargs )
727797
0 commit comments