Skip to content

Commit 2b12418

Browse files
committed
updated with new regional cloud URL's
1 parent b75d3ce commit 2b12418

File tree

2 files changed

+95
-14
lines changed

2 files changed

+95
-14
lines changed

msal/authority.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"login.microsoftonline.us",
4040
"login.usgovcloudapi.net",
4141
"login-us.microsoftonline.com",
42+
"https://login.sovcloud-identity.fr", # AzureBleu
43+
"https://login.sovcloud-identity.de", # AzureDelos
44+
"https://login.sovcloud-identity.sg", # AzureGovSG
4245
])
4346

4447
WELL_KNOWN_B2C_HOSTS = [
@@ -215,7 +218,7 @@ def has_valid_issuer(self):
215218
- It has the same scheme and host as the authority (path can be different)
216219
- The issuer host is a well-known Microsoft authority host
217220
- The issuer host is a regional variant of a well-known host (e.g., westus2.login.microsoft.com)
218-
- For CIAM, the issuer follows the pattern of {tenant}.ciamlogin.com
221+
- For CIAM, hosts that end with well-known B2C hosts (e.g., tenant.b2clogin.com) are accepted as valid issuers
219222
"""
220223
if not self._issuer or not self._oidc_authority_url:
221224
return False
@@ -240,25 +243,25 @@ def has_valid_issuer(self):
240243
dot_index = issuer_host.find(".")
241244
if dot_index > 0:
242245
potential_base = issuer_host[dot_index + 1:]
243-
if potential_base in TRUSTED_ISSUER_HOSTS and "." not in issuer_host[:dot_index]:
244-
return True
246+
if "." not in issuer_host[:dot_index]:
247+
# 3a: Base host is a trusted Microsoft host
248+
if potential_base in TRUSTED_ISSUER_HOSTS:
249+
return True
250+
# 3b: Issuer has a region prefix on the authority host
251+
# e.g. issuer=us.someweb.com, authority=someweb.com
252+
authority_host = authority_parsed.hostname.lower() if authority_parsed.hostname else ""
253+
if potential_base == authority_host:
254+
return True
245255

246256
# Case 4: Same scheme and host (path can differ)
247257
if (authority_parsed.scheme == issuer_parsed.scheme and
248258
authority_parsed.netloc == issuer_parsed.netloc):
249259
return True
260+
261+
# Case 5: Check if issuer host ends with any well-known B2C host (e.g., tenant.b2clogin.com)
262+
if any(issuer_host.endswith(h) for h in WELL_KNOWN_B2C_HOSTS):
263+
return True
250264

251-
# Case 5: CIAM scenario - issuer follows pattern {tenant}.ciamlogin.com
252-
if issuer_host.endswith(_CIAM_DOMAIN_SUFFIX):
253-
authority_host = authority_parsed.hostname.lower() if authority_parsed.hostname else ""
254-
if authority_host.endswith(_CIAM_DOMAIN_SUFFIX):
255-
tenant = authority_host[:-len(_CIAM_DOMAIN_SUFFIX)]
256-
else:
257-
parts = authority_parsed.path.split('/')
258-
tenant = parts[1] if len(parts) >= 2 and parts[1] else None
259-
260-
if tenant and issuer_host == tenant + _CIAM_DOMAIN_SUFFIX:
261-
return True
262265
return False
263266

264267
def canonicalize(authority_or_auth_endpoint):

tests/test_authority.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,81 @@ def test_issuer_case_sensitivity_host(self, tenant_discovery_mock):
526526
self.assertTrue(authority.has_valid_issuer(),
527527
"Host comparison should be case-insensitive")
528528

529+
# Case 3b: Regional prefix on authority host tests
530+
@patch("msal.authority.tenant_discovery")
531+
def test_regional_issuer_matching_authority_host(self, tenant_discovery_mock):
532+
"""Test issuer with region prefix on the authority host: us.someweb.com -> someweb.com"""
533+
authority_url = "https://someweb.com/tenant"
534+
issuer = "https://us.someweb.com/tenant"
535+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
536+
self.assertTrue(authority.has_valid_issuer(),
537+
"Issuer with region prefix on authority host should be valid")
538+
539+
@patch("msal.authority.tenant_discovery")
540+
def test_regional_issuer_westus2_on_custom_authority(self, tenant_discovery_mock):
541+
"""Test issuer westus2.myidp.example.com with authority myidp.example.com"""
542+
authority_url = "https://myidp.example.com/tenant"
543+
issuer = "https://westus2.myidp.example.com/tenant"
544+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
545+
self.assertTrue(authority.has_valid_issuer(),
546+
"Regional prefix westus2 on custom authority host should be valid")
547+
548+
@patch("msal.authority.tenant_discovery")
549+
def test_regional_issuer_does_not_match_different_authority(self, tenant_discovery_mock):
550+
"""Test issuer us.someweb.com should NOT match authority otherdomain.com"""
551+
authority_url = "https://otherdomain.com/tenant"
552+
issuer = "https://us.someweb.com/tenant"
553+
tenant_discovery_mock.return_value = {
554+
"authorization_endpoint": "https://example.com/oauth2/authorize",
555+
"token_endpoint": "https://example.com/oauth2/token",
556+
"issuer": issuer,
557+
}
558+
with self.assertRaises(ValueError):
559+
Authority(None, self.http_client, oidc_authority_url=authority_url)
560+
561+
@patch("msal.authority.tenant_discovery")
562+
def test_regional_issuer_on_authority_with_different_path(self, tenant_discovery_mock):
563+
"""Test issuer eastus.someweb.com/v2 with authority someweb.com/tenant"""
564+
authority_url = "https://someweb.com/tenant"
565+
issuer = "https://eastus.someweb.com/v2"
566+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
567+
self.assertTrue(authority.has_valid_issuer(),
568+
"Regional issuer with different path should still match by host")
569+
570+
# Case 5: B2C host suffix tests
571+
@patch("msal.authority.tenant_discovery")
572+
def test_b2c_issuer_host(self, tenant_discovery_mock):
573+
"""Test issuer from a well-known B2C host: tenant.b2clogin.com"""
574+
authority_url = "https://custom-domain.com/tenant"
575+
issuer = "https://tenant.b2clogin.com/tenant/v2.0"
576+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
577+
self.assertTrue(authority.has_valid_issuer(),
578+
"Issuer ending with b2clogin.com should be valid")
579+
580+
@patch("msal.authority.tenant_discovery")
581+
def test_b2c_china_issuer_host(self, tenant_discovery_mock):
582+
"""Test issuer from B2C China host: tenant.b2clogin.cn"""
583+
authority_url = "https://custom-domain.com/tenant"
584+
issuer = "https://tenant.b2clogin.cn/tenant/v2.0"
585+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
586+
self.assertTrue(authority.has_valid_issuer(),
587+
"Issuer ending with b2clogin.cn should be valid")
588+
589+
@patch("msal.authority.tenant_discovery")
590+
def test_b2c_us_gov_issuer_host(self, tenant_discovery_mock):
591+
"""Test issuer from B2C US Government host: tenant.b2clogin.us"""
592+
authority_url = "https://custom-domain.com/tenant"
593+
issuer = "https://tenant.b2clogin.us/tenant/v2.0"
594+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
595+
self.assertTrue(authority.has_valid_issuer(),
596+
"Issuer ending with b2clogin.us should be valid")
597+
598+
@patch("msal.authority.tenant_discovery")
599+
def test_ciam_issuer_host_via_b2c_check(self, tenant_discovery_mock):
600+
"""Test issuer from ciamlogin.com host is accepted via B2C check"""
601+
authority_url = "https://custom-domain.com/tenant"
602+
issuer = "https://mytenant.ciamlogin.com/tenant"
603+
authority = self._create_authority_with_issuer(authority_url, issuer, tenant_discovery_mock)
604+
self.assertTrue(authority.has_valid_issuer(),
605+
"Issuer ending with ciamlogin.com should be valid")
606+

0 commit comments

Comments
 (0)