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 *
25
25
26
26
27
27
# The __init__.py will import this. Not the other way around.
28
- __version__ = "1.18 .0" # When releasing, also check and bump our dependencies's versions if needed
28
+ __version__ = "1.19 .0" # When releasing, also check and bump our dependencies's versions if needed
29
29
30
30
logger = logging .getLogger (__name__ )
31
31
_AUTHORITY_TYPE_CLOUDSHELL = "CLOUDSHELL"
@@ -146,7 +146,6 @@ def obtain_token_by_username_password(self, username, password, **kwargs):
146
146
147
147
148
148
class ClientApplication (object ):
149
-
150
149
ACQUIRE_TOKEN_SILENT_ID = "84"
151
150
ACQUIRE_TOKEN_BY_REFRESH_TOKEN = "85"
152
151
ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID = "301"
@@ -174,6 +173,7 @@ def __init__(
174
173
# when we would eventually want to add this feature to PCA in future.
175
174
exclude_scopes = None ,
176
175
http_cache = None ,
176
+ instance_discovery = None ,
177
177
):
178
178
"""Create an instance of application.
179
179
@@ -300,7 +300,7 @@ def __init__(
300
300
Client capability is implemented using "claims" parameter on the wire,
301
301
for now.
302
302
MSAL will combine them into
303
- `claims parameter <https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter`_
303
+ `claims parameter <https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter> `_
304
304
which you will later provide via one of the acquire-token request.
305
305
306
306
:param str azure_region:
@@ -409,11 +409,40 @@ def __init__(
409
409
Personally Identifiable Information (PII). Encryption is unnecessary.
410
410
411
411
New in version 1.16.0.
412
+
413
+ :param boolean instance_discovery:
414
+ Historically, MSAL would connect to a central endpoint located at
415
+ ``https://login.microsoftonline.com`` to acquire some metadata,
416
+ especially when using an unfamiliar authority.
417
+ This behavior is known as Instance Discovery.
418
+
419
+ This parameter defaults to None, which enables the Instance Discovery.
420
+
421
+ If you know some authorities which you allow MSAL to operate with as-is,
422
+ without involving any Instance Discovery, the recommended pattern is::
423
+
424
+ known_authorities = frozenset([ # Treat your known authorities as const
425
+ "https://contoso.com/adfs", "https://login.azs/foo"])
426
+ ...
427
+ authority = "https://contoso.com/adfs" # Assuming your app will use this
428
+ app1 = PublicClientApplication(
429
+ "client_id",
430
+ authority=authority,
431
+ # Conditionally disable Instance Discovery for known authorities
432
+ instance_discovery=authority not in known_authorities,
433
+ )
434
+
435
+ If you do not know some authorities beforehand,
436
+ yet still want MSAL to accept any authority that you will provide,
437
+ you can use a ``False`` to unconditionally disable Instance Discovery.
438
+
439
+ New in version 1.19.0.
412
440
"""
413
441
self .client_id = client_id
414
442
self .client_credential = client_credential
415
443
self .client_claims = client_claims
416
444
self ._client_capabilities = client_capabilities
445
+ self ._instance_discovery = instance_discovery
417
446
418
447
if exclude_scopes and not isinstance (exclude_scopes , list ):
419
448
raise ValueError (
@@ -453,18 +482,24 @@ def __init__(
453
482
454
483
# Here the self.authority will not be the same type as authority in input
455
484
try :
485
+ authority_to_use = authority or "https://{}/common/" .format (WORLD_WIDE )
456
486
self .authority = Authority (
457
- authority or "https://login.microsoftonline.com/common/" ,
458
- self .http_client , validate_authority = validate_authority )
487
+ authority_to_use ,
488
+ self .http_client ,
489
+ validate_authority = validate_authority ,
490
+ instance_discovery = self ._instance_discovery ,
491
+ )
459
492
except ValueError : # Those are explicit authority validation errors
460
493
raise
461
494
except Exception : # The rest are typically connection errors
462
495
if validate_authority and azure_region :
463
496
# Since caller opts in to use region, here we tolerate connection
464
497
# errors happened during authority validation at non-region endpoint
465
498
self .authority = Authority (
466
- authority or "https://login.microsoftonline.com/common/" ,
467
- self .http_client , validate_authority = False )
499
+ authority_to_use ,
500
+ self .http_client ,
501
+ instance_discovery = False ,
502
+ )
468
503
else :
469
504
raise
470
505
@@ -526,16 +561,19 @@ def _get_regional_authority(self, central_authority):
526
561
if region_to_use :
527
562
regional_host = ("{}.r.login.microsoftonline.com" .format (region_to_use )
528
563
if central_authority .instance in (
529
- # The list came from https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/358/files#r629400328
564
+ # The list came from point 3 of the algorithm section in this internal doc
565
+ # https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=/PinAuthToRegion/AAD%20SDK%20Proposal%20to%20Pin%20Auth%20to%20region.md&anchor=algorithm&_a=preview
530
566
"login.microsoftonline.com" ,
567
+ "login.microsoft.com" ,
531
568
"login.windows.net" ,
532
569
"sts.windows.net" ,
533
570
)
534
571
else "{}.{}" .format (region_to_use , central_authority .instance ))
535
- return Authority (
572
+ return Authority ( # The central_authority has already been validated
536
573
"https://{}/{}" .format (regional_host , central_authority .tenant ),
537
574
self .http_client ,
538
- validate_authority = False ) # The central_authority has already been validated
575
+ instance_discovery = False ,
576
+ )
539
577
return None
540
578
541
579
def _build_client (self , client_credential , authority , skip_regional_client = False ):
@@ -787,7 +825,8 @@ def get_authorization_request_url(
787
825
# Multi-tenant app can use new authority on demand
788
826
the_authority = Authority (
789
827
authority ,
790
- self .http_client
828
+ self .http_client ,
829
+ instance_discovery = self ._instance_discovery ,
791
830
) if authority else self .authority
792
831
793
832
client = _ClientWithCcsRoutingInfo (
@@ -1010,14 +1049,23 @@ def _find_msal_accounts(self, environment):
1010
1049
}
1011
1050
return list (grouped_accounts .values ())
1012
1051
1052
+ def _get_instance_metadata (self ): # This exists so it can be mocked in unit test
1053
+ resp = self .http_client .get (
1054
+ "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
1055
+ headers = {'Accept' : 'application/json' })
1056
+ resp .raise_for_status ()
1057
+ return json .loads (resp .text )['metadata' ]
1058
+
1013
1059
def _get_authority_aliases (self , instance ):
1060
+ if self ._instance_discovery is False :
1061
+ return []
1062
+ if self .authority ._is_known_to_developer :
1063
+ # Then it is an ADFS/B2C/known_authority_hosts situation
1064
+ # which may not reach the central endpoint, so we skip it.
1065
+ return []
1014
1066
if not self .authority_groups :
1015
- resp = self .http_client .get (
1016
- "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize" ,
1017
- headers = {'Accept' : 'application/json' })
1018
- resp .raise_for_status ()
1019
1067
self .authority_groups = [
1020
- set (group ['aliases' ]) for group in json . loads ( resp . text )[ 'metadata' ] ]
1068
+ set (group ['aliases' ]) for group in self . _get_instance_metadata () ]
1021
1069
for group in self .authority_groups :
1022
1070
if instance in group :
1023
1071
return [alias for alias in group if alias != instance ]
@@ -1166,6 +1214,7 @@ def acquire_token_silent_with_error(
1166
1214
# the_authority = Authority(
1167
1215
# authority,
1168
1216
# self.http_client,
1217
+ # instance_discovery=self._instance_discovery,
1169
1218
# ) if authority else self.authority
1170
1219
result = self ._acquire_token_silent_from_cache_and_possibly_refresh_it (
1171
1220
scopes , account , self .authority , force_refresh = force_refresh ,
@@ -1187,7 +1236,8 @@ def acquire_token_silent_with_error(
1187
1236
the_authority = Authority (
1188
1237
"https://" + alias + "/" + self .authority .tenant ,
1189
1238
self .http_client ,
1190
- validate_authority = False )
1239
+ instance_discovery = False ,
1240
+ )
1191
1241
result = self ._acquire_token_silent_from_cache_and_possibly_refresh_it (
1192
1242
scopes , account , the_authority , force_refresh = force_refresh ,
1193
1243
claims_challenge = claims_challenge ,
@@ -1340,7 +1390,7 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
1340
1390
reverse = True ):
1341
1391
logger .debug ("Cache attempts an RT" )
1342
1392
headers = telemetry_context .generate_headers ()
1343
- if "home_account_id" in query : # Then use it as CCS Routing info
1393
+ if query . get ( "home_account_id" ) : # Then use it as CCS Routing info
1344
1394
headers ["X-AnchorMailbox" ] = "Oid:{}" .format ( # case-insensitive value
1345
1395
query ["home_account_id" ].replace ("." , "@" ))
1346
1396
response = client .obtain_token_by_refresh_token (
0 commit comments