diff --git a/msal/application.py b/msal/application.py index 5cf7e32a..9ca07025 100644 --- a/msal/application.py +++ b/msal/application.py @@ -5,6 +5,7 @@ import sys import warnings from threading import Lock +from typing import Optional # Needed in Python 3.7 & 3.8 import os from .oauth2cli import Client, JwtAssertionCreator @@ -448,11 +449,13 @@ def __init__( Instructs MSAL to use the Entra regional token service. This legacy feature is only available to first-party applications. Only ``acquire_token_for_client()`` is supported. - Supports 3 values: + Supports 4 values: - ``azure_region=None`` - meaning no region is used. This is the default value. + ``azure_region=None`` - This default value means no region is configured. + MSAL will use the region defined in env var ``MSAL_FORCE_REGION``. ``azure_region="some_region"`` - meaning the specified region is used. ``azure_region=True`` - meaning MSAL will try to auto-detect the region. This is not recommended. + ``azure_region=False`` - meaning MSAL will use no region. .. note:: Region auto-discovery has been tested on VMs and on Azure Functions. It is unreliable. @@ -630,7 +633,10 @@ def __init__( except ValueError: # Those are explicit authority validation errors raise except Exception: # The rest are typically connection errors - if validate_authority and azure_region and not oidc_authority: + if validate_authority and not oidc_authority and ( + azure_region # Opted in to use region + or (azure_region is None and os.getenv("MSAL_FORCE_REGION")) # Will use region + ): # Since caller opts in to use region, here we tolerate connection # errors happened during authority validation at non-region endpoint self.authority = Authority( @@ -724,9 +730,11 @@ def _build_telemetry_context( self._telemetry_buffer, self._telemetry_lock, api_id, correlation_id=correlation_id, refresh_reason=refresh_reason) - def _get_regional_authority(self, central_authority): - if not self._region_configured: # User did not opt-in to ESTS-R + def _get_regional_authority(self, central_authority) -> Optional[Authority]: + if self._region_configured is False: # User opts out of ESTS-R return None # Short circuit to completely bypass region detection + if self._region_configured is None: # User did not make an ESTS-R choice + self._region_configured = os.getenv("MSAL_FORCE_REGION") or None self._region_detected = self._region_detected or _detect_region( self.http_client if self._region_configured is not None else None) if (self._region_configured != self.ATTEMPT_REGION_DISCOVERY diff --git a/tests/test_e2e.py b/tests/test_e2e.py index ff35a73e..a0796547 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -1130,11 +1130,23 @@ def _test_acquire_token_for_client(self, configured_region, expected_region): def test_acquire_token_for_client_should_hit_global_endpoint_by_default(self): self._test_acquire_token_for_client(None, None) - def test_acquire_token_for_client_should_ignore_env_var_by_default(self): + def test_acquire_token_for_client_should_ignore_env_var_region_name_by_default(self): os.environ["REGION_NAME"] = "eastus" self._test_acquire_token_for_client(None, None) del os.environ["REGION_NAME"] + @patch.dict(os.environ, {"MSAL_FORCE_REGION": "eastus"}) + def test_acquire_token_for_client_should_use_env_var_msal_force_region_by_default(self): + self._test_acquire_token_for_client(None, "eastus") + + @patch.dict(os.environ, {"MSAL_FORCE_REGION": "eastus"}) + def test_acquire_token_for_client_should_prefer_the_explicit_region(self): + self._test_acquire_token_for_client("westus", "westus") + + @patch.dict(os.environ, {"MSAL_FORCE_REGION": "eastus"}) + def test_acquire_token_for_client_should_allow_opt_out_env_var_msal_force_region(self): + self._test_acquire_token_for_client(False, None) + def test_acquire_token_for_client_should_use_a_specified_region(self): self._test_acquire_token_for_client("westus", "westus")