Skip to content

Commit 5efb0d5

Browse files
sangonzalrayluo
authored andcommitted
Add correlation id, public api id, and current request header (#103)
* Add correlation id, public api id, and current request header * Make get_new_correlation_id private * Update calls to _get_new_correlation_id() * PR feedback. * Fix authority test * PR feedback * More PR feedback * Remove indent * Refactor and fix a bug in the initiate_device_flow() * Refactor acquire_token_silent() to reuse same correlation_id * fixup! Refactor and fix a bug in the initiate_device_flow() * Define consts for all telemetry headers * Replace one last magic string with const
1 parent 5380645 commit 5efb0d5

File tree

2 files changed

+89
-18
lines changed

2 files changed

+89
-18
lines changed

msal/application.py

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import sys
88
import warnings
9+
import uuid
910

1011
import 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

5364
def 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

6980
class 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

565601
class 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

msal/authority.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,16 @@ def __init__(self, authority_url, validate_authority=True,
8282
_, _, self.tenant = canonicalize(self.token_endpoint) # Usually a GUID
8383
self.is_adfs = self.tenant.lower() == 'adfs'
8484

85-
def user_realm_discovery(self, username, response=None):
85+
def user_realm_discovery(self, username, correlation_id=None, response=None):
8686
# It will typically return a dict containing "ver", "account_type",
8787
# "federation_protocol", "cloud_audience_urn",
8888
# "federation_metadata_url", "federation_active_auth_url", etc.
8989
if self.instance not in self.__class__._domains_without_user_realm_discovery:
9090
resp = response or requests.get(
9191
"https://{netloc}/common/userrealm/{username}?api-version=1.0".format(
9292
netloc=self.instance, username=username),
93-
headers={'Accept':'application/json'},
93+
headers={'Accept':'application/json',
94+
'client-request-id': correlation_id},
9495
verify=self.verify, proxies=self.proxies, timeout=self.timeout)
9596
if resp.status_code != 404:
9697
resp.raise_for_status()

0 commit comments

Comments
 (0)