diff --git a/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs b/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs index 02adac45f3..a991e721a3 100644 --- a/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs +++ b/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs @@ -78,46 +78,46 @@ public async Task GetAzureRegionAsync(RequestContext requestContext) requestContext.ApiEvent != null, "Do not call GetAzureRegionAsync outside of a request. This can happen if you perform instance discovery outside a request, for example as part of validating input params."); + if (!IsAutoDiscoveryRequested(azureRegionConfig)) + { + // For a user-provided region (WithAzureRegion or the MSAL_FORCE_REGION env variable), + // validate the format before using it. An invalid region (e.g. one containing a host, + // path, or other special characters) must never be prefixed onto the trusted + // "{region}.login.microsoft.com" suffix, as that would redirect the request to a + // tampered host. Consistent with region handling elsewhere, an invalid value falls + // back to the global (non-regional) endpoint rather than failing the request. + if (!IsValidRegionName(azureRegionConfig)) + { + logger.Error($"[Region discovery] User provided region '{azureRegionConfig}' is invalid. Falling back to the global endpoint. {DateTime.UtcNow}"); + return null; + } + + logger.Info(() => $"[Region discovery] Returning user provided region: {azureRegionConfig}."); + requestContext.ApiEvent.RegionUsed = azureRegionConfig; + requestContext.ApiEvent.RegionOutcome = RegionOutcome.UserProvided; + return azureRegionConfig; + } + IRetryPolicyFactory retryPolicyFactory = requestContext.ServiceBundle.Config.RetryPolicyFactory; IRetryPolicy retryPolicy = retryPolicyFactory.GetRetryPolicy(RequestType.RegionDiscovery); - // MSAL always performs region auto-discovery, even if the user configured an actual region - // in order to detect inconsistencies and report via telemetry var discoveredRegion = await DiscoverAndCacheAsync(logger, requestContext.UserCancellationToken, retryPolicy).ConfigureAwait(false); - RecordTelemetry(requestContext.ApiEvent, azureRegionConfig, discoveredRegion); + RecordTelemetry(requestContext.ApiEvent, discoveredRegion); - if (IsAutoDiscoveryRequested(azureRegionConfig)) + if (discoveredRegion.RegionSource != RegionAutodetectionSource.FailedAutoDiscovery) { - if (discoveredRegion.RegionSource != RegionAutodetectionSource.FailedAutoDiscovery) - { - logger.Verbose(() => $"[Region discovery] Discovered Region {discoveredRegion.Region}"); - requestContext.ApiEvent.RegionUsed = discoveredRegion.Region; - requestContext.ApiEvent.AutoDetectedRegion = discoveredRegion.Region; - return discoveredRegion.Region; - } - else - { - logger.Verbose(() => $"[Region discovery] {s_regionDiscoveryDetails}"); - requestContext.ApiEvent.RegionDiscoveryFailureReason = s_regionDiscoveryDetails; - return null; - } + logger.Verbose(() => $"[Region discovery] Discovered Region {discoveredRegion.Region}"); + requestContext.ApiEvent.RegionUsed = discoveredRegion.Region; + requestContext.ApiEvent.AutoDetectedRegion = discoveredRegion.Region; + return discoveredRegion.Region; } - - // For a user-provided region (WithAzureRegion or the MSAL_FORCE_REGION env variable), - // validate the format before using it. An invalid region (e.g. one containing a host, - // path, or other special characters) must never be prefixed onto the trusted - // "{region}.login.microsoft.com" suffix, as that would redirect the request to a - // tampered host. Consistent with region handling elsewhere, an invalid value falls - // back to the global (non-regional) endpoint rather than failing the request. - if (!IsValidRegionName(azureRegionConfig)) + else { - logger.Error($"[Region discovery] User provided region '{azureRegionConfig}' is invalid. Falling back to the global endpoint. {DateTime.UtcNow}"); + logger.Verbose(() => $"[Region discovery] {s_regionDiscoveryDetails}"); + requestContext.ApiEvent.RegionDiscoveryFailureReason = s_regionDiscoveryDetails; return null; } - - logger.Info(() => $"[Region discovery] Returning user provided region: {azureRegionConfig}."); - return azureRegionConfig; } internal static void ResetStaticCacheForTest() @@ -132,7 +132,7 @@ private static bool IsAutoDiscoveryRequested(string azureRegionConfig) return string.Equals(azureRegionConfig, ConfidentialClientApplication.AttemptRegionDiscovery); } - private static void RecordTelemetry(ApiEvent apiEvent, string azureRegionConfig, RegionInfo discoveredRegion) + private static void RecordTelemetry(ApiEvent apiEvent, RegionInfo discoveredRegion) { // already emitted telemetry for this request, don't emit again as it will overwrite with "from cache" if (IsTelemetryRecorded(apiEvent)) @@ -140,33 +140,11 @@ private static void RecordTelemetry(ApiEvent apiEvent, string azureRegionConfig, return; } - bool isAutoDiscoveryRequested = IsAutoDiscoveryRequested(azureRegionConfig); apiEvent.RegionAutodetectionSource = discoveredRegion.RegionSource; - - if (isAutoDiscoveryRequested) - { - apiEvent.RegionUsed = discoveredRegion.Region; - apiEvent.RegionOutcome = discoveredRegion.RegionSource == RegionAutodetectionSource.FailedAutoDiscovery ? - RegionOutcome.FallbackToGlobal : - RegionOutcome.AutodetectSuccess; - } - else - { - apiEvent.RegionUsed = azureRegionConfig; - apiEvent.RegionDiscoveryFailureReason = discoveredRegion.RegionDetails; - - if (discoveredRegion.RegionSource == RegionAutodetectionSource.FailedAutoDiscovery) - { - apiEvent.RegionOutcome = RegionOutcome.UserProvidedAutodetectionFailed; - } - - if (!string.IsNullOrEmpty(discoveredRegion.Region)) - { - apiEvent.RegionOutcome = string.Equals(discoveredRegion.Region, azureRegionConfig, StringComparison.OrdinalIgnoreCase) ? - RegionOutcome.UserProvidedValid : - RegionOutcome.UserProvidedInvalid; - } - } + apiEvent.RegionUsed = discoveredRegion.Region; + apiEvent.RegionOutcome = discoveredRegion.RegionSource == RegionAutodetectionSource.FailedAutoDiscovery ? + RegionOutcome.FallbackToGlobal : + RegionOutcome.AutodetectSuccess; } private static bool IsTelemetryRecorded(ApiEvent apiEvent) diff --git a/src/client/Microsoft.Identity.Client/Instance/Region/RegionOutcome.cs b/src/client/Microsoft.Identity.Client/Instance/Region/RegionOutcome.cs index 71f9e13253..42c5ffac3d 100644 --- a/src/client/Microsoft.Identity.Client/Instance/Region/RegionOutcome.cs +++ b/src/client/Microsoft.Identity.Client/Instance/Region/RegionOutcome.cs @@ -11,23 +11,26 @@ namespace Microsoft.Identity.Client.Region public enum RegionOutcome { /// - /// Indicates that the API .WithAzureRegion() was not used + /// Indicates that no region outcome was recorded. /// None = 0, /// - /// Region provided by the user, matches auto detected region + /// Region provided by the user, matches auto detected region. /// + [Obsolete("MSAL no longer performs auto-discovery for explicitly configured regions. Use UserProvided instead.", false)] UserProvidedValid = 1, /// - /// Region provided by the user, auto detection cannot be done + /// Region provided by the user, auto detection cannot be done. /// + [Obsolete("MSAL no longer performs auto-discovery for explicitly configured regions. Use UserProvided instead.", false)] UserProvidedAutodetectionFailed = 2, /// - /// Region provided by the user, does not match auto detected region + /// Region provided by the user, does not match auto detected region. /// + [Obsolete("MSAL no longer performs auto-discovery for explicitly configured regions. Use UserProvided instead.", false)] UserProvidedInvalid = 3, /// @@ -38,6 +41,11 @@ public enum RegionOutcome /// /// Region autodetect requested but failed. Fallback to global /// - FallbackToGlobal = 5 + FallbackToGlobal = 5, + + /// + /// Region provided by the user and used without auto-detection. + /// + UserProvided = 6 } } diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 415f79fda3..4848d1f85c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.Identity.Client.Region.RegionOutcome.UserProvided = 6 -> Microsoft.Identity.Client.Region.RegionOutcome const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string Microsoft.Identity.Client.AppConfig.PoPOptions Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 415f79fda3..4848d1f85c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.Identity.Client.Region.RegionOutcome.UserProvided = 6 -> Microsoft.Identity.Client.Region.RegionOutcome const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string Microsoft.Identity.Client.AppConfig.PoPOptions Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 415f79fda3..4848d1f85c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.Identity.Client.Region.RegionOutcome.UserProvided = 6 -> Microsoft.Identity.Client.Region.RegionOutcome const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string Microsoft.Identity.Client.AppConfig.PoPOptions Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 415f79fda3..4848d1f85c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.Identity.Client.Region.RegionOutcome.UserProvided = 6 -> Microsoft.Identity.Client.Region.RegionOutcome const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string Microsoft.Identity.Client.AppConfig.PoPOptions Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 415f79fda3..4848d1f85c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.Identity.Client.Region.RegionOutcome.UserProvided = 6 -> Microsoft.Identity.Client.Region.RegionOutcome const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string Microsoft.Identity.Client.AppConfig.PoPOptions Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 415f79fda3..4848d1f85c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.Identity.Client.Region.RegionOutcome.UserProvided = 6 -> Microsoft.Identity.Client.Region.RegionOutcome const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string Microsoft.Identity.Client.AppConfig.PoPOptions Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength diff --git a/tests/Microsoft.Identity.Test.Unit/CoreTests/RegionDiscoveryProviderTests.cs b/tests/Microsoft.Identity.Test.Unit/CoreTests/RegionDiscoveryProviderTests.cs index e171943456..2e518c6951 100644 --- a/tests/Microsoft.Identity.Test.Unit/CoreTests/RegionDiscoveryProviderTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/CoreTests/RegionDiscoveryProviderTests.cs @@ -197,18 +197,8 @@ public async Task FetchRegionFromLocalImdsThenGetMetadataFromCacheAsync() } [TestMethod] - [DataRow(HttpStatusCode.NotFound, 0, TestConstants.RegionAutoDetectNotFoundFailureMessage)] // No retries for 404 errors - [DataRow(HttpStatusCode.InternalServerError, TestRegionDiscoveryRetryPolicy.NumRetries, TestConstants.RegionAutoDetectInternalServerErrorFailureMessage)] - public async Task SuccessfulResponseFromUserProvidedRegionAsync( - HttpStatusCode statusCode, - int expectedRetries, - string expectedFailureMessage) + public async Task SuccessfulResponseFromUserProvidedRegionDoesNotCallImdsAsync() { - for (int i = 0; i < (1 + expectedRetries); i++) - { - AddMockedResponse(MockHelpers.CreateNullMessage(statusCode)); - } - _testRequestContext.ServiceBundle.Config.AzureRegion = TestConstants.Region; RegionManager.ResetStaticCacheForTest(); IRegionDiscoveryProvider regionDiscoveryProvider = new RegionAndMtlsDiscoveryProvider(_httpManager); @@ -219,48 +209,12 @@ public async Task SuccessfulResponseFromUserProvidedRegionAsync( Assert.AreEqual($"centralus.{RegionAndMtlsDiscoveryProvider.PublicEnvForRegional}", regionalMetadata.PreferredNetwork); Assert.AreEqual(TestConstants.Region, _testRequestContext.ApiEvent.RegionUsed); - Assert.AreEqual(RegionAutodetectionSource.FailedAutoDiscovery, _testRequestContext.ApiEvent.RegionAutodetectionSource); - Assert.AreEqual(RegionOutcome.UserProvidedAutodetectionFailed, _testRequestContext.ApiEvent.RegionOutcome); - Assert.Contains(expectedFailureMessage, _testRequestContext.ApiEvent.RegionDiscoveryFailureReason); - - // Verify all mock responses were consumed - Assert.AreEqual(0, _httpManager.QueueSize); - } - - [TestMethod] - public async Task ResponseFromUserProvidedRegionSameAsRegionDetectedAsync() - { - Environment.SetEnvironmentVariable(TestConstants.RegionName, TestConstants.Region); - _testRequestContext.ServiceBundle.Config.AzureRegion = TestConstants.Region; - - // IRegionDiscoveryProvider regionDiscoveryProvider = new RegionDiscoveryProvider(_httpManager); - InstanceDiscoveryMetadataEntry regionalMetadata = await _regionDiscoveryProvider.GetMetadataAsync(new Uri("https://login.microsoftonline.com/common/"), _testRequestContext).ConfigureAwait(false); - - Assert.IsNotNull(regionalMetadata); - Assert.AreEqual($"centralus.{RegionAndMtlsDiscoveryProvider.PublicEnvForRegional}", regionalMetadata.PreferredNetwork); - Assert.AreEqual(TestConstants.Region, _testRequestContext.ApiEvent.RegionUsed); - Assert.AreEqual(RegionAutodetectionSource.EnvVariable, _testRequestContext.ApiEvent.RegionAutodetectionSource); - Assert.AreEqual(RegionOutcome.UserProvidedValid, _testRequestContext.ApiEvent.RegionOutcome); + Assert.AreEqual(RegionAutodetectionSource.None, _testRequestContext.ApiEvent.RegionAutodetectionSource); + Assert.AreEqual(RegionOutcome.UserProvided, _testRequestContext.ApiEvent.RegionOutcome); Assert.IsNull(_testRequestContext.ApiEvent.RegionDiscoveryFailureReason); - } - - [TestMethod] - public async Task ResponseFromUserProvidedRegionDifferentFromRegionDetectedAsync() - { - Environment.SetEnvironmentVariable(TestConstants.RegionName, "detectedregion"); - _testRequestContext.ServiceBundle.Config.AzureRegion = "userregion"; - - //IRegionDiscoveryProvider regionDiscoveryProvider = new RegionDiscoveryProvider(_httpManager, new NetworkCacheMetadataProvider()); - InstanceDiscoveryMetadataEntry regionalMetadata = await _regionDiscoveryProvider.GetMetadataAsync( - new Uri("https://login.microsoftonline.com/common/"), - _testRequestContext).ConfigureAwait(false); - Assert.IsNotNull(regionalMetadata); - Assert.AreEqual($"userregion.{RegionAndMtlsDiscoveryProvider.PublicEnvForRegional}", regionalMetadata.PreferredNetwork); - Assert.AreEqual("userregion", _testRequestContext.ApiEvent.RegionUsed); - Assert.AreEqual(RegionAutodetectionSource.EnvVariable, _testRequestContext.ApiEvent.RegionAutodetectionSource); - Assert.AreEqual(RegionOutcome.UserProvidedInvalid, _testRequestContext.ApiEvent.RegionOutcome); - Assert.IsNull(_testRequestContext.ApiEvent.RegionDiscoveryFailureReason); + // Verify no IMDS request was made for the explicit region. + Assert.AreEqual(0, _httpManager.QueueSize); } [TestMethod] diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithRegionTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithRegionTests.cs index aca90b38eb..513bedf948 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithRegionTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithRegionTests.cs @@ -308,7 +308,6 @@ public async Task MsalForceRegionIsSet_RegionIsUsed() Environment.SetEnvironmentVariable( ConfidentialClientApplicationBuilder.ForceRegionEnvVariable, TestConstants.Region); - httpManager.AddRegionDiscoveryMockHandler(TestConstants.Region); httpManager.AddMockHandler(CreateTokenResponseHttpHandler(expectRegional: true)); var cca = ConfidentialClientApplicationBuilder @@ -323,10 +322,10 @@ public async Task MsalForceRegionIsSet_RegionIsUsed() .ConfigureAwait(false); Assert.AreEqual(TestConstants.Region, result.ApiEvent.RegionUsed); - Assert.AreEqual(RegionAutodetectionSource.Imds, result.ApiEvent.RegionAutodetectionSource); + Assert.AreEqual(RegionAutodetectionSource.None, result.ApiEvent.RegionAutodetectionSource); Assert.AreEqual(TestConstants.Region, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); } } @@ -426,7 +425,6 @@ public async Task MsalForceRegionIsSet_WithRegionIsSet_WithRegionWins() Environment.SetEnvironmentVariable( ConfidentialClientApplicationBuilder.ForceRegionEnvVariable, "someOtherRegion"); - httpManager.AddRegionDiscoveryMockHandler(TestConstants.Region); httpManager.AddMockHandler(CreateTokenResponseHttpHandler(expectRegional: true)); var cca = ConfidentialClientApplicationBuilder @@ -442,10 +440,10 @@ public async Task MsalForceRegionIsSet_WithRegionIsSet_WithRegionWins() .ConfigureAwait(false); Assert.AreEqual(TestConstants.Region, result.ApiEvent.RegionUsed); - Assert.AreEqual(RegionAutodetectionSource.Imds, result.ApiEvent.RegionAutodetectionSource); + Assert.AreEqual(RegionAutodetectionSource.None, result.ApiEvent.RegionAutodetectionSource); Assert.AreEqual(TestConstants.Region, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); } } @@ -514,7 +512,6 @@ public async Task WithAzureRegionWithInvalidFormatFallsBackToGlobalAsync(string using (var harness = base.CreateTestHarness()) { var httpManager = harness.HttpManager; - httpManager.AddRegionDiscoveryMockHandler(TestConstants.Region); httpManager.AddInstanceDiscoveryMockHandler(); httpManager.AddMockHandler(CreateTokenResponseHttpHandler(expectRegional: false)); @@ -542,9 +539,7 @@ public async Task ForceRegionEnvVariableWithInvalidFormatFallsBackToGlobalAsync( using (var harness = base.CreateTestHarness()) { Environment.SetEnvironmentVariable(ConfidentialClientApplicationBuilder.ForceRegionEnvVariable, "fake.com/x"); - var httpManager = harness.HttpManager; - httpManager.AddRegionDiscoveryMockHandler(TestConstants.Region); httpManager.AddInstanceDiscoveryMockHandler(); httpManager.AddMockHandler(CreateTokenResponseHttpHandler(expectRegional: false)); @@ -575,7 +570,6 @@ public async Task WithAzureRegionWithValidFormatRoutesRegionallyAsync() using (var harness = base.CreateTestHarness()) { var httpManager = harness.HttpManager; - httpManager.AddRegionDiscoveryMockHandler(TestConstants.Region); httpManager.AddMockHandler(CreateTokenResponseHttpHandler(expectRegional: true)); IConfidentialClientApplication cca = CreateCca(httpManager, TestConstants.Region); @@ -598,17 +592,11 @@ public async Task WithAzureRegionWithValidFormatRoutesRegionallyAsync() // regression: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2686 public async Task OtherCloudWithAuthorityValidationAsync() { - const string imdsError = "IMDS call failed with exception"; - const string autoDiscoveryError = "Auto-discovery failed in the past. Not trying again. IMDS call failed"; - using (var harness = base.CreateTestHarness()) { harness.ServiceBundle.Config.RetryPolicyFactory = new TestRetryPolicyFactory(); var httpManager = harness.HttpManager; - // for simplicity, return 404 so retry is not triggered - httpManager.AddRegionDiscoveryMockHandlerWithError(HttpStatusCode.NotFound); - var discoveryHandler = MockHelpers.CreateInstanceDiscoveryMockHandler( "https://login.microsoftonline.com/common/discovery/instance", TestConstants.DiscoveryJsonResponse); @@ -645,8 +633,8 @@ public async Task OtherCloudWithAuthorityValidationAsync() discoveryHandler.ActualRequestMessage.RequestUri.AbsoluteUri, "Authority validation is made on https://login.microsoftonline.com/ and it validates the auth_endpoint of the non-regional authority"); Assert.AreEqual(EastUsRegion, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedAutodetectionFailed, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); - Assert.Contains(imdsError, result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); result = await app .AcquireTokenForClient(TestConstants.s_scope) @@ -658,8 +646,8 @@ public async Task OtherCloudWithAuthorityValidationAsync() Assert.AreEqual(EastUsRegion, result.ApiEvent.RegionUsed); Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); Assert.AreEqual(EastUsRegion, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedAutodetectionFailed, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); - Assert.Contains(autoDiscoveryError, result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); } } @@ -744,7 +732,7 @@ public async Task OtherCloud_WithValidation_Async(bool validateAuthority, bool a Assert.AreEqual(EastUsRegion, result.ApiEvent.RegionUsed); Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); Assert.AreEqual(EastUsRegion, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); if (validateAuthority) @@ -831,13 +819,12 @@ public async Task PublicAndSovereignCloud_UsesPreferredNetwork_AndNoDiscovery_As [TestMethod] [Description("Test with a user configured region.")] - public async Task UserRegion_DiscoveryHappensOnce_Async() + public async Task UserRegion_DoesNotCallRegionDiscovery_Async() { using (var harness = base.CreateTestHarness()) { var httpManager = harness.HttpManager; - httpManager.AddRegionDiscoveryMockHandler(TestConstants.Region); httpManager.AddMockHandler(CreateTokenResponseHttpHandler(true)); IConfidentialClientApplication app = CreateCca( @@ -850,11 +837,11 @@ public async Task UserRegion_DiscoveryHappensOnce_Async() .ConfigureAwait(false); Assert.AreEqual(TestConstants.Region, result.ApiEvent.RegionUsed); - Assert.AreEqual(RegionAutodetectionSource.Imds, result.ApiEvent.RegionAutodetectionSource); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.ApiEvent.RegionOutcome); + Assert.AreEqual(RegionAutodetectionSource.None, result.ApiEvent.RegionAutodetectionSource); + Assert.AreEqual(RegionOutcome.UserProvided, result.ApiEvent.RegionOutcome); Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); Assert.AreEqual(TestConstants.Region, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); @@ -866,7 +853,7 @@ public async Task UserRegion_DiscoveryHappensOnce_Async() Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); Assert.IsNull(result.AuthenticationResultMetadata.RegionDetails.AutoDetectionError); Assert.AreEqual(TestConstants.Region, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); + Assert.AreEqual(RegionOutcome.UserProvided, result.AuthenticationResultMetadata.RegionDetails.RegionOutcome); } } diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsPopTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsPopTests.cs index 3e58fc528c..2827030acc 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsPopTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsPopTests.cs @@ -540,7 +540,6 @@ public async Task MtlsPop_KnownRegionAsync() using (var httpManager = new MockHttpManager()) { - httpManager.AddRegionDiscoveryMockHandler(region); httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(tokenType: "mtls_pop"); var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) @@ -557,7 +556,7 @@ public async Task MtlsPop_KnownRegionAsync() Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(region, result.AuthenticationResultMetadata.RegionDetails.RegionUsed); - Assert.AreEqual(RegionOutcome.UserProvidedValid, result.ApiEvent.RegionOutcome); + Assert.AreEqual(RegionOutcome.UserProvided, result.ApiEvent.RegionOutcome); Assert.AreEqual(expectedTokenEndpoint, result.AuthenticationResultMetadata.TokenEndpoint); } } @@ -572,7 +571,6 @@ public async Task MtlsPop_RegionalTokenCacheInterchangeabilityAsync() using (var httpManager = new MockHttpManager()) { - httpManager.AddRegionDiscoveryMockHandler(region); httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(tokenType: "mtls_pop"); IConfidentialClientApplication regionalApp1 = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs index 4eeae139c5..58d768b410 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs @@ -130,71 +130,27 @@ public async Task TelemetryAutoDiscoveryFailsTestsAsync() } [TestMethod] - public async Task TelemetryUserProvidedRegionAutoDiscoveryFailsTestsAsync() + public async Task TelemetryUserProvidedRegionSkipsAutoDiscoveryTestsAsync() { - Trace.WriteLine("Acquire token for client with region provided by user and region detection fails."); - - // for simplicity, return 404 so retry is not triggered - _harness.HttpManager.AddMockHandlerContentNotFound(HttpMethod.Get, TestConstants.ImdsUrl); + Trace.WriteLine("Acquire token for client with region provided by user skips region detection."); var result = await RunAcquireTokenForClientAsync(AcquireTokenForClientOutcome.UserProvidedRegion).ConfigureAwait(false); AssertCurrentTelemetry( result.HttpRequest, ApiIds.AcquireTokenForClient, - RegionAutodetectionSource.FailedAutoDiscovery.ToString("D"), - RegionOutcome.UserProvidedAutodetectionFailed.ToString("D"), + string.Empty, + RegionOutcome.UserProvided.ToString("D"), isCacheSerialized: false, region: TestConstants.Region); - // Verify all mock responses were consumed + // Verify no IMDS request was made for the explicit region. Assert.AreEqual(0, _harness.HttpManager.QueueSize); } - /// - /// Acquire token for client with regionToUse when auto region discovery passes with region same as regionToUse - /// Current_request = 4 | ATC_ID, 0, centralus, 3, 1 | - /// Last_request = 4 | 0 | | | - /// - [TestMethod] - public async Task TelemetryUserProvidedRegionAutoDiscoverRegionSameTestsAsync() - { - Environment.SetEnvironmentVariable(TestConstants.RegionName, TestConstants.Region); - - Trace.WriteLine("Acquire token for client with region provided by user and region detected is same as regionToUse."); - var result = await RunAcquireTokenForClientAsync(AcquireTokenForClientOutcome.UserProvidedRegion).ConfigureAwait(false); - AssertCurrentTelemetry(result.HttpRequest, - ApiIds.AcquireTokenForClient, - RegionAutodetectionSource.EnvVariable.ToString("D"), - RegionOutcome.UserProvidedValid.ToString("D"), - isCacheSerialized: false, - region: TestConstants.Region); - } - - /// - /// Acquire token for client with regionToUse when auto region discovery passes with region different from regionToUse - /// Current_request = 4 | ATC_ID, 0, centralus, 3, 1 | - /// Last_request = 4 | 0 | | | - /// - [TestMethod] - public async Task TelemetryUserProvidedRegionAutoDiscoverRegionMismatchTestsAsync() - { - Environment.SetEnvironmentVariable(TestConstants.RegionName, TestConstants.Region); - - Trace.WriteLine("Acquire token for client with region provided by user and region detected mismatches regionToUse."); - var result = await RunAcquireTokenForClientAsync(AcquireTokenForClientOutcome.UserProvidedInvalidRegion).ConfigureAwait(false); - AssertCurrentTelemetry(result.HttpRequest, - ApiIds.AcquireTokenForClient, - RegionAutodetectionSource.EnvVariable.ToString("D"), - RegionOutcome.UserProvidedInvalid.ToString("D"), - isCacheSerialized: false, - region: TestConstants.InvalidRegion); - } - private enum AcquireTokenForClientOutcome { Success, UserProvidedRegion, - UserProvidedInvalidRegion, AADUnavailableError, FallbackToGlobal } @@ -277,27 +233,6 @@ private enum AcquireTokenForClientOutcome correlationId = authResult.CorrelationId; break; - case AcquireTokenForClientOutcome.UserProvidedInvalidRegion: - - tokenRequestHandler = _harness.HttpManager.AddSuccessTokenResponseMockHandlerForPost - (authority: TestConstants.AuthorityRegionalInvalidRegion, - responseMessage: MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage()); - - var app4 = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) - .WithAuthority(AzureCloudInstance.AzurePublic, TestConstants.TenantId, false) - .WithClientSecret(TestConstants.ClientSecret) - .WithHttpManager(_harness.HttpManager) - .WithAzureRegion(TestConstants.InvalidRegion) - .WithExperimentalFeatures(true) - .BuildConcrete(); - authResult = await app4 - .AcquireTokenForClient(TestConstants.s_scope) - .WithForceRefresh(forceRefresh) - .ExecuteAsync() - .ConfigureAwait(false); - correlationId = authResult.CorrelationId; - break; - case AcquireTokenForClientOutcome.AADUnavailableError: correlationId = Guid.NewGuid();