13
13
14
14
from .oauth2cli import Client , JwtAssertionCreator
15
15
from .oauth2cli .oidc import decode_part
16
- from .authority import Authority
16
+ from .authority import Authority , WORLD_WIDE
17
17
from .mex import send_request as mex_send_request
18
18
from .wstrust_request import send_request as wst_send_request
19
19
from .wstrust_response import *
@@ -150,7 +150,6 @@ def obtain_token_by_username_password(self, username, password, **kwargs):
150
150
151
151
152
152
class ClientApplication (object ):
153
-
154
153
ACQUIRE_TOKEN_SILENT_ID = "84"
155
154
ACQUIRE_TOKEN_BY_REFRESH_TOKEN = "85"
156
155
ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID = "301"
@@ -178,6 +177,7 @@ def __init__(
178
177
# when we would eventually want to add this feature to PCA in future.
179
178
exclude_scopes = None ,
180
179
http_cache = None ,
180
+ instance_discovery = None ,
181
181
allow_broker = None ,
182
182
):
183
183
"""Create an instance of application.
@@ -415,6 +415,34 @@ def __init__(
415
415
416
416
New in version 1.16.0.
417
417
418
+ :param boolean instance_discovery:
419
+ Historically, MSAL would connect to a central endpoint located at
420
+ ``https://login.microsoftonline.com`` to acquire some metadata,
421
+ especially when using an unfamiliar authority.
422
+ This behavior is known as Instance Discovery.
423
+
424
+ This parameter defaults to None, which enables the Instance Discovery.
425
+
426
+ If you know some authorities which you allow MSAL to operate with as-is,
427
+ without involving any Instance Discovery, the recommended pattern is::
428
+
429
+ known_authorities = frozenset([ # Treat your known authorities as const
430
+ "https://contoso.com/adfs", "https://login.azs/foo"])
431
+ ...
432
+ authority = "https://contoso.com/adfs" # Assuming your app will use this
433
+ app1 = PublicClientApplication(
434
+ "client_id",
435
+ authority=authority,
436
+ # Conditionally disable Instance Discovery for known authorities
437
+ instance_discovery=authority not in known_authorities,
438
+ )
439
+
440
+ If you do not know some authorities beforehand,
441
+ yet still want MSAL to accept any authority that you will provide,
442
+ you can use a ``False`` to unconditionally disable Instance Discovery.
443
+
444
+ New in version 1.19.0.
445
+
418
446
:param boolean allow_broker:
419
447
Brokers provide Single-Sign-On, device identification,
420
448
and application identification verification.
@@ -448,6 +476,7 @@ def __init__(
448
476
self .client_credential = client_credential
449
477
self .client_claims = client_claims
450
478
self ._client_capabilities = client_capabilities
479
+ self ._instance_discovery = instance_discovery
451
480
452
481
if exclude_scopes and not isinstance (exclude_scopes , list ):
453
482
raise ValueError (
@@ -487,18 +516,24 @@ def __init__(
487
516
488
517
# Here the self.authority will not be the same type as authority in input
489
518
try :
519
+ authority_to_use = authority or "https://{}/common/" .format (WORLD_WIDE )
490
520
self .authority = Authority (
491
- authority or "https://login.microsoftonline.com/common/" ,
492
- self .http_client , validate_authority = validate_authority )
521
+ authority_to_use ,
522
+ self .http_client ,
523
+ validate_authority = validate_authority ,
524
+ instance_discovery = self ._instance_discovery ,
525
+ )
493
526
except ValueError : # Those are explicit authority validation errors
494
527
raise
495
528
except Exception : # The rest are typically connection errors
496
529
if validate_authority and azure_region :
497
530
# Since caller opts in to use region, here we tolerate connection
498
531
# errors happened during authority validation at non-region endpoint
499
532
self .authority = Authority (
500
- authority or "https://login.microsoftonline.com/common/" ,
501
- self .http_client , validate_authority = False )
533
+ authority_to_use ,
534
+ self .http_client ,
535
+ instance_discovery = False ,
536
+ )
502
537
else :
503
538
raise
504
539
is_confidential_app = bool (
@@ -584,10 +619,11 @@ def _get_regional_authority(self, central_authority):
584
619
"sts.windows.net" ,
585
620
)
586
621
else "{}.{}" .format (region_to_use , central_authority .instance ))
587
- return Authority (
622
+ return Authority ( # The central_authority has already been validated
588
623
"https://{}/{}" .format (regional_host , central_authority .tenant ),
589
624
self .http_client ,
590
- validate_authority = False ) # The central_authority has already been validated
625
+ instance_discovery = False ,
626
+ )
591
627
return None
592
628
593
629
def _build_client (self , client_credential , authority , skip_regional_client = False ):
@@ -839,7 +875,8 @@ def get_authorization_request_url(
839
875
# Multi-tenant app can use new authority on demand
840
876
the_authority = Authority (
841
877
authority ,
842
- self .http_client
878
+ self .http_client ,
879
+ instance_discovery = self ._instance_discovery ,
843
880
) if authority else self .authority
844
881
845
882
client = _ClientWithCcsRoutingInfo (
@@ -1062,14 +1099,23 @@ def _find_msal_accounts(self, environment):
1062
1099
}
1063
1100
return list (grouped_accounts .values ())
1064
1101
1102
+ def _get_instance_metadata (self ): # This exists so it can be mocked in unit test
1103
+ resp = self .http_client .get (
1104
+ "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize" , # TBD: We may extend this to use self._instance_discovery endpoint
1105
+ headers = {'Accept' : 'application/json' })
1106
+ resp .raise_for_status ()
1107
+ return json .loads (resp .text )['metadata' ]
1108
+
1065
1109
def _get_authority_aliases (self , instance ):
1110
+ if self ._instance_discovery is False :
1111
+ return []
1112
+ if self .authority ._is_known_to_developer :
1113
+ # Then it is an ADFS/B2C/known_authority_hosts situation
1114
+ # which may not reach the central endpoint, so we skip it.
1115
+ return []
1066
1116
if not self .authority_groups :
1067
- resp = self .http_client .get (
1068
- "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize" ,
1069
- headers = {'Accept' : 'application/json' })
1070
- resp .raise_for_status ()
1071
1117
self .authority_groups = [
1072
- set (group ['aliases' ]) for group in json . loads ( resp . text )[ 'metadata' ] ]
1118
+ set (group ['aliases' ]) for group in self . _get_instance_metadata () ]
1073
1119
for group in self .authority_groups :
1074
1120
if instance in group :
1075
1121
return [alias for alias in group if alias != instance ]
@@ -1223,6 +1269,7 @@ def acquire_token_silent_with_error(
1223
1269
# the_authority = Authority(
1224
1270
# authority,
1225
1271
# self.http_client,
1272
+ # instance_discovery=self._instance_discovery,
1226
1273
# ) if authority else self.authority
1227
1274
result = self ._acquire_token_silent_from_cache_and_possibly_refresh_it (
1228
1275
scopes , account , self .authority , force_refresh = force_refresh ,
@@ -1244,7 +1291,8 @@ def acquire_token_silent_with_error(
1244
1291
the_authority = Authority (
1245
1292
"https://" + alias + "/" + self .authority .tenant ,
1246
1293
self .http_client ,
1247
- validate_authority = False )
1294
+ instance_discovery = False ,
1295
+ )
1248
1296
result = self ._acquire_token_silent_from_cache_and_possibly_refresh_it (
1249
1297
scopes , account , the_authority , force_refresh = force_refresh ,
1250
1298
claims_challenge = claims_challenge ,
@@ -1539,10 +1587,9 @@ def acquire_token_by_username_password(
1539
1587
scopes , # Decorated scopes won't work due to offline_access
1540
1588
MSALRuntime_Username = username ,
1541
1589
MSALRuntime_Password = password ,
1542
- validateAuthority = "no"
1543
- if self .authority ._validate_authority is False
1544
- or self .authority .is_adfs or self .authority ._is_b2c
1545
- else None ,
1590
+ validateAuthority = "no" if (
1591
+ self .authority ._is_known_to_developer
1592
+ or self ._instance_discovery is False ) else None ,
1546
1593
claims = claims ,
1547
1594
)
1548
1595
return self ._process_broker_response (response , scopes , kwargs .get ("data" , {}))
@@ -1807,10 +1854,9 @@ def _acquire_token_interactive_via_broker(
1807
1854
logger .debug (kwargs ["welcome_template" ]) # Experimental
1808
1855
authority = "https://{}/{}" .format (
1809
1856
self .authority .instance , self .authority .tenant )
1810
- validate_authority = (
1811
- "no" if self .authority ._validate_authority is False
1812
- or self .authority .is_adfs or self .authority ._is_b2c
1813
- else None )
1857
+ validate_authority = "no" if (
1858
+ self .authority ._is_known_to_developer
1859
+ or self ._instance_discovery is False ) else None
1814
1860
# Calls different broker methods to mimic the OIDC behaviors
1815
1861
if login_hint and prompt != "select_account" : # OIDC prompts when the user did not sign in
1816
1862
accounts = self .get_accounts (username = login_hint )
0 commit comments