Skip to content

Commit 8d62027

Browse files
authored
Application initializer does not make tenant discovery calls (#205)
1 parent f2f25bc commit 8d62027

File tree

5 files changed

+43
-16
lines changed

5 files changed

+43
-16
lines changed

msal/application.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ def __init__(
198198
authority or "https://login.microsoftonline.com/common/",
199199
self.http_client, validate_authority=validate_authority)
200200
# Here the self.authority is not the same type as authority in input
201+
self.client = None
201202
self.token_cache = token_cache or TokenCache()
202-
self.client = self._build_client(client_credential, self.authority)
203+
self._client_credential = client_credential
203204
self.authority_groups = None
204205

205206
def _build_client(self, client_credential, authority):
@@ -248,6 +249,12 @@ def _build_client(self, client_credential, authority):
248249
on_removing_rt=self.token_cache.remove_rt,
249250
on_updating_rt=self.token_cache.update_rt)
250251

252+
def _get_client(self):
253+
if not self.client:
254+
self.authority.initialize()
255+
self.client = self._build_client(self._client_credential, self.authority)
256+
return self.client
257+
251258
def get_authorization_request_url(
252259
self,
253260
scopes, # type: list[str]
@@ -307,6 +314,7 @@ def get_authorization_request_url(
307314
authority,
308315
self.http_client
309316
) if authority else self.authority
317+
the_authority.initialize()
310318

311319
client = Client(
312320
{"authorization_endpoint": the_authority.authorization_endpoint},
@@ -367,7 +375,7 @@ def acquire_token_by_authorization_code(
367375
# really empty.
368376
assert isinstance(scopes, list), "Invalid parameter type"
369377
self._validate_ssh_cert_input_data(kwargs.get("data", {}))
370-
return self.client.obtain_token_by_authorization_code(
378+
return self._get_client().obtain_token_by_authorization_code(
371379
code, redirect_uri=redirect_uri,
372380
scope=decorate_scope(scopes, self.client_id),
373381
headers={
@@ -391,6 +399,7 @@ def get_accounts(self, username=None):
391399
Your app can choose to display those information to end user,
392400
and allow user to choose one of his/her accounts to proceed.
393401
"""
402+
self.authority.initialize()
394403
accounts = self._find_msal_accounts(environment=self.authority.instance)
395404
if not accounts: # Now try other aliases of this authority instance
396405
for alias in self._get_authority_aliases(self.authority.instance):
@@ -543,6 +552,7 @@ def acquire_token_silent_with_error(
543552
# authority,
544553
# self.http_client,
545554
# ) if authority else self.authority
555+
self.authority.initialize()
546556
result = self._acquire_token_silent_from_cache_and_possibly_refresh_it(
547557
scopes, account, self.authority, force_refresh=force_refresh,
548558
correlation_id=correlation_id,
@@ -555,6 +565,7 @@ def acquire_token_silent_with_error(
555565
"https://" + alias + "/" + self.authority.tenant,
556566
self.http_client,
557567
validate_authority=False)
568+
the_authority.initialize()
558569
result = self._acquire_token_silent_from_cache_and_possibly_refresh_it(
559570
scopes, account, the_authority, force_refresh=force_refresh,
560571
correlation_id=correlation_id,
@@ -724,7 +735,7 @@ def acquire_token_by_refresh_token(self, refresh_token, scopes):
724735
* A dict contains "error" and some other keys, when error happened.
725736
* A dict contains no "error" key means migration was successful.
726737
"""
727-
return self.client.obtain_token_by_refresh_token(
738+
return self._get_client().obtain_token_by_refresh_token(
728739
refresh_token,
729740
decorate_scope(scopes, self.client_id),
730741
rt_getter=lambda rt: rt,
@@ -754,7 +765,7 @@ def initiate_device_flow(self, scopes=None, **kwargs):
754765
- an error response would contain some other readable key/value pairs.
755766
"""
756767
correlation_id = _get_new_correlation_id()
757-
flow = self.client.initiate_device_flow(
768+
flow = self._get_client().initiate_device_flow(
758769
scope=decorate_scope(scopes or [], self.client_id),
759770
headers={
760771
CLIENT_REQUEST_ID: correlation_id,
@@ -778,7 +789,7 @@ def acquire_token_by_device_flow(self, flow, **kwargs):
778789
- A successful response would contain "access_token" key,
779790
- an error response would contain "error" and usually "error_description".
780791
"""
781-
return self.client.obtain_token_by_device_flow(
792+
return self._get_client().obtain_token_by_device_flow(
782793
flow,
783794
data=dict(kwargs.pop("data", {}), code=flow["device_code"]),
784795
# 2018-10-4 Hack:
@@ -815,14 +826,15 @@ def acquire_token_by_username_password(
815826
CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header(
816827
self.ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID),
817828
}
829+
self.authority.initialize()
818830
if not self.authority.is_adfs:
819831
user_realm_result = self.authority.user_realm_discovery(
820832
username, correlation_id=headers[CLIENT_REQUEST_ID])
821833
if user_realm_result.get("account_type") == "Federated":
822834
return self._acquire_token_by_username_password_federated(
823835
user_realm_result, username, password, scopes=scopes,
824836
headers=headers, **kwargs)
825-
return self.client.obtain_token_by_username_password(
837+
return self._get_client().obtain_token_by_username_password(
826838
username, password, scope=scopes,
827839
headers=headers,
828840
**kwargs)
@@ -851,16 +863,16 @@ def _acquire_token_by_username_password_federated(
851863
GRANT_TYPE_SAML1_1 = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer'
852864
grant_type = {
853865
SAML_TOKEN_TYPE_V1: GRANT_TYPE_SAML1_1,
854-
SAML_TOKEN_TYPE_V2: self.client.GRANT_TYPE_SAML2,
866+
SAML_TOKEN_TYPE_V2: Client.GRANT_TYPE_SAML2,
855867
WSS_SAML_TOKEN_PROFILE_V1_1: GRANT_TYPE_SAML1_1,
856-
WSS_SAML_TOKEN_PROFILE_V2: self.client.GRANT_TYPE_SAML2
868+
WSS_SAML_TOKEN_PROFILE_V2: Client.GRANT_TYPE_SAML2
857869
}.get(wstrust_result.get("type"))
858870
if not grant_type:
859871
raise RuntimeError(
860872
"RSTR returned unknown token type: %s", wstrust_result.get("type"))
861-
self.client.grant_assertion_encoders.setdefault( # Register a non-standard type
862-
grant_type, self.client.encode_saml_assertion)
863-
return self.client.obtain_token_by_assertion(
873+
Client.grant_assertion_encoders.setdefault( # Register a non-standard type
874+
grant_type, Client.encode_saml_assertion)
875+
return self._get_client().obtain_token_by_assertion(
864876
wstrust_result["token"], grant_type, scope=scopes, **kwargs)
865877

866878

@@ -878,7 +890,7 @@ def acquire_token_for_client(self, scopes, **kwargs):
878890
- an error response would contain "error" and usually "error_description".
879891
"""
880892
# TBD: force_refresh behavior
881-
return self.client.obtain_token_for_client(
893+
return self._get_client().obtain_token_for_client(
882894
scope=scopes, # This grant flow requires no scope decoration
883895
headers={
884896
CLIENT_REQUEST_ID: _get_new_correlation_id(),
@@ -910,9 +922,9 @@ def acquire_token_on_behalf_of(self, user_assertion, scopes, **kwargs):
910922
"""
911923
# The implementation is NOT based on Token Exchange
912924
# https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16
913-
return self.client.obtain_token_by_assertion( # bases on assertion RFC 7521
925+
return self._get_client().obtain_token_by_assertion( # bases on assertion RFC 7521
914926
user_assertion,
915-
self.client.GRANT_TYPE_JWT, # IDTs and AAD ATs are all JWTs
927+
Client.GRANT_TYPE_JWT, # IDTs and AAD ATs are all JWTs
916928
scope=decorate_scope(scopes, self.client_id), # Decoration is used for:
917929
# 1. Explicitly requesting an RT, without relying on AAD default
918930
# behavior, even though it currently still issues an RT.

msal/authority.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ def __init__(self, authority_url, http_client, validate_authority=True):
5252
This parameter only controls whether an instance discovery will be
5353
performed.
5454
"""
55+
self._http_client = http_client
56+
self._authority_url = authority_url
57+
self._validate_authority = validate_authority
58+
self._is_initialized = False
59+
60+
def initialize(self):
61+
if not self._is_initialized:
62+
self.__initialize(self._authority_url, self._http_client, self._validate_authority)
63+
self._is_initialized = True
64+
65+
def __initialize(self, authority_url, http_client, validate_authority):
5566
self._http_client = http_client
5667
authority, self.instance, tenant = canonicalize(authority_url)
5768
parts = authority.path.split('/')

tests/test_application.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def setUp(self):
104104
self.authority_url = "https://login.microsoftonline.com/common"
105105
self.authority = msal.authority.Authority(
106106
self.authority_url, MinimalHttpClient())
107+
self.authority.initialize()
107108
self.scopes = ["s1", "s2"]
108109
self.uid = "my_uid"
109110
self.utid = "my_utid"

tests/test_authority.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def test_wellknown_host_and_tenant(self):
1313
for host in WELL_KNOWN_AUTHORITY_HOSTS:
1414
a = Authority(
1515
'https://{}/common'.format(host), MinimalHttpClient())
16+
a.initialize()
1617
self.assertEqual(
1718
a.authorization_endpoint,
1819
'https://%s/common/oauth2/v2.0/authorize' % host)
@@ -34,7 +35,7 @@ def test_unknown_host_wont_pass_instance_discovery(self):
3435
_assert = getattr(self, "assertRaisesRegex", self.assertRaisesRegexp) # Hack
3536
with _assert(ValueError, "invalid_instance"):
3637
Authority('https://example.com/tenant_doesnt_matter_in_this_case',
37-
MinimalHttpClient())
38+
MinimalHttpClient()).initialize()
3839

3940
def test_invalid_host_skipping_validation_can_be_turned_off(self):
4041
try:
@@ -85,7 +86,7 @@ def test_memorize(self):
8586
authority = "https://login.microsoftonline.com/common"
8687
self.assertNotIn(authority, Authority._domains_without_user_realm_discovery)
8788
a = Authority(authority, MinimalHttpClient(), validate_authority=False)
88-
89+
a.initialize()
8990
# We now pretend this authority supports no User Realm Discovery
9091
class MockResponse(object):
9192
status_code = 404

tests/test_authority_patch.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def test_authority_honors_a_patched_requests(self):
1515
# First, we test that the original, unmodified authority is working
1616
a = msal.authority.Authority(
1717
"https://login.microsoftonline.com/common", MinimalHttpClient())
18+
a.initialize()
1819
self.assertEqual(
1920
a.authorization_endpoint,
2021
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize')
@@ -27,6 +28,7 @@ def test_authority_honors_a_patched_requests(self):
2728
with self.assertRaises(RuntimeError):
2829
a = msal.authority.Authority(
2930
"https://login.microsoftonline.com/common", MinimalHttpClient())
31+
a.initialize()
3032
finally: # Tricky:
3133
# Unpatch is necessary otherwise other test cases would be affected
3234
msal.authority.requests = original

0 commit comments

Comments
 (0)