From 9049fe9570fdc7f28de2a6464cc53f173359e04f Mon Sep 17 00:00:00 2001 From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:40:09 -0700 Subject: [PATCH] Add validation callback and tests (#4818) * initial * Add validation callback and tests * Address comments * Missed to use Lazy HttpClient * Update src/client/Microsoft.Identity.Client/Http/IHttpManager.cs Co-authored-by: Bogdan Gavril * Update src/client/Microsoft.Identity.Client/ManagedIdentity/ServiceFabricManagedIdentitySource.cs Co-authored-by: Bogdan Gavril * Rename * Add retry policy * Address comments * Update tests to test managed identity retry policy as well * Address comments * Update the logic to check for custom http client first. * Fix tests failing due to reflection * Update the service fabric endpoint in tests --------- Co-authored-by: Gladwin Johnson Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> Co-authored-by: Bogdan Gavril --- .../Http/HttpManager.cs | 19 ++++++-- .../Http/IHttpManager.cs | 17 +++++++ .../Instance/Region/RegionManager.cs | 39 ++++++++-------- .../Validation/AdfsAuthorityValidator.cs | 19 ++++---- .../AbstractManagedIdentity.cs | 8 ++++ .../AzureArcManagedIdentitySource.cs | 1 + .../ServiceFabricManagedIdentitySource.cs | 40 ++++++++++++++++- .../OAuth2/OAuth2Client.cs | 2 + .../WsTrust/WsTrustWebRequestManager.cs | 3 ++ .../Core/Helpers/ManagedIdentityTestUtil.cs | 4 +- .../Core/Mocks/MockHttpManager.cs | 7 ++- .../Core/Mocks/MockHttpMessageHandler.cs | 1 - .../Infrastructure/MsiProxyHttpManager.cs | 1 + .../CoreTests/HttpTests/HttpManagerTests.cs | 14 +++++- .../ManagedIdentityTests.cs | 2 +- .../ServiceFabricTests.cs | 44 +++++++++++++++++++ .../ParallelRequestsTests.cs | 1 + .../OTelInstrumentationTests.cs | 6 +-- 18 files changed, 187 insertions(+), 41 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Http/HttpManager.cs b/src/client/Microsoft.Identity.Client/Http/HttpManager.cs index 987ce4a423..f2a0991098 100644 --- a/src/client/Microsoft.Identity.Client/Http/HttpManager.cs +++ b/src/client/Microsoft.Identity.Client/Http/HttpManager.cs @@ -51,6 +51,7 @@ public async Task SendRequestAsync( ILoggerAdapter logger, bool doNotThrow, X509Certificate2 bindingCertificate, + HttpClient customHttpClient, CancellationToken cancellationToken, int retryCount = 0) { @@ -75,6 +76,7 @@ public async Task SendRequestAsync( clonedBody, method, bindingCertificate, + customHttpClient, logger, cancellationToken).ConfigureAwait(false); } @@ -111,6 +113,7 @@ public async Task SendRequestAsync( logger, doNotThrow, bindingCertificate, + customHttpClient, cancellationToken: cancellationToken, retryCount) // Pass the updated retry count .ConfigureAwait(false); @@ -142,8 +145,17 @@ public async Task SendRequestAsync( return response; } - private HttpClient GetHttpClient(X509Certificate2 x509Certificate2) - { + private HttpClient GetHttpClient(X509Certificate2 x509Certificate2, HttpClient customHttpClient) { + if (x509Certificate2 != null && customHttpClient != null) + { + throw new NotImplementedException("Mtls certificate cannot be used with service fabric. A custom http client is used for service fabric managed identity to validate the server certificate."); + } + + if (customHttpClient != null) + { + return customHttpClient; + } + if (_httpClientFactory is IMsalMtlsHttpClientFactory msalMtlsHttpClientFactory) { // If the factory is an IMsalMtlsHttpClientFactory, use it to get an HttpClient with the certificate @@ -175,6 +187,7 @@ private async Task ExecuteAsync( HttpContent body, HttpMethod method, X509Certificate2 bindingCertificate, + HttpClient customHttpClient, ILoggerAdapter logger, CancellationToken cancellationToken = default) { @@ -189,7 +202,7 @@ private async Task ExecuteAsync( Stopwatch sw = Stopwatch.StartNew(); - HttpClient client = GetHttpClient(bindingCertificate); + HttpClient client = GetHttpClient(bindingCertificate, customHttpClient); using (HttpResponseMessage responseMessage = await client.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false)) diff --git a/src/client/Microsoft.Identity.Client/Http/IHttpManager.cs b/src/client/Microsoft.Identity.Client/Http/IHttpManager.cs index 246375cfe0..835631a63c 100644 --- a/src/client/Microsoft.Identity.Client/Http/IHttpManager.cs +++ b/src/client/Microsoft.Identity.Client/Http/IHttpManager.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.Core; +using Microsoft.Identity.Client.Internal; namespace Microsoft.Identity.Client.Http { @@ -15,6 +16,21 @@ internal interface IHttpManager { long LastRequestDurationInMs { get; } + /// + /// Method to send a request to the server using the HttpClient configured in the implementation. + /// + /// The endpoint to send the request to. + /// Headers to send in the http request. + /// Body of the request. + /// Http method. + /// Logger from the request context. + /// Flag to decide if MsalServiceException is thrown or the response is returned in case of 5xx errors. + /// Flag to indicate whether the retries are performed in for specific failures. + /// Certificate used for MTLS authentication. + /// Custom http client which bypasses the HttpClientFactory. + /// This is needed for service fabric managed identity where a cert validation callback is added to the handler. + /// + /// Task SendRequestAsync( Uri endpoint, IDictionary headers, @@ -23,6 +39,7 @@ Task SendRequestAsync( ILoggerAdapter logger, bool doNotThrow, X509Certificate2 mtlsCertificate, + HttpClient customHttpClient, CancellationToken cancellationToken, int retryCount = 0); } diff --git a/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs b/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs index c154f90da0..b8b4a3596e 100644 --- a/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs +++ b/src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs @@ -199,15 +199,16 @@ private async Task DiscoverAsync(ILoggerAdapter logger, Cancellation Uri imdsUri = BuildImdsUri(DefaultApiVersion); HttpResponse response = await _httpManager.SendRequestAsync( - imdsUri, - headers, - body: null, - HttpMethod.Get, - logger: logger, - doNotThrow: false, - mtlsCertificate: null, - GetCancellationToken(requestCancellationToken)) - .ConfigureAwait(false); + imdsUri, + headers, + body: null, + HttpMethod.Get, + logger: logger, + doNotThrow: false, + mtlsCertificate: null, + customHttpClient: null, + GetCancellationToken(requestCancellationToken)) + .ConfigureAwait(false); // A bad request occurs when the version in the IMDS call is no longer supported. if (response.StatusCode == HttpStatusCode.BadRequest) @@ -215,15 +216,16 @@ private async Task DiscoverAsync(ILoggerAdapter logger, Cancellation string apiVersion = await GetImdsUriApiVersionAsync(logger, headers, requestCancellationToken).ConfigureAwait(false); // Get the latest version imdsUri = BuildImdsUri(apiVersion); response = await _httpManager.SendRequestAsync( - imdsUri, - headers, - body: null, - HttpMethod.Get, - logger: logger, - doNotThrow: false, - mtlsCertificate: null, - GetCancellationToken(requestCancellationToken)) - .ConfigureAwait(false); // Call again with updated version + imdsUri, + headers, + body: null, + HttpMethod.Get, + logger: logger, + doNotThrow: false, + mtlsCertificate: null, + customHttpClient: null, + GetCancellationToken(requestCancellationToken)) + .ConfigureAwait(false); // Call again with updated version } if (response.StatusCode == HttpStatusCode.OK && !response.Body.IsNullOrEmpty()) @@ -323,6 +325,7 @@ private async Task GetImdsUriApiVersionAsync(ILoggerAdapter logger, Dict logger: logger, doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, GetCancellationToken(userCancellationToken)) .ConfigureAwait(false); diff --git a/src/client/Microsoft.Identity.Client/Instance/Validation/AdfsAuthorityValidator.cs b/src/client/Microsoft.Identity.Client/Instance/Validation/AdfsAuthorityValidator.cs index 6cd03a3dc8..d98ed4754f 100644 --- a/src/client/Microsoft.Identity.Client/Instance/Validation/AdfsAuthorityValidator.cs +++ b/src/client/Microsoft.Identity.Client/Instance/Validation/AdfsAuthorityValidator.cs @@ -30,15 +30,16 @@ public async Task ValidateAuthorityAsync( string webFingerUrl = Constants.FormatAdfsWebFingerUrl(authorityInfo.Host, resource); Http.HttpResponse httpResponse = await _requestContext.ServiceBundle.HttpManager.SendRequestAsync( - new Uri(webFingerUrl), - null, - body: null, - System.Net.Http.HttpMethod.Get, - logger: _requestContext.Logger, - doNotThrow: false, - mtlsCertificate: null, - _requestContext.UserCancellationToken) - .ConfigureAwait(false); + new Uri(webFingerUrl), + null, + body: null, + System.Net.Http.HttpMethod.Get, + logger: _requestContext.Logger, + doNotThrow: false, + mtlsCertificate: null, + customHttpClient: null, + _requestContext.UserCancellationToken) + .ConfigureAwait(false); if (httpResponse.StatusCode != HttpStatusCode.OK) { diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs index 97444821ca..e89dc0587e 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs @@ -63,6 +63,7 @@ public virtual async Task AuthenticateAsync( logger: _requestContext.Logger, doNotThrow: true, mtlsCertificate: null, + GetHttpClientWithSslValidation(_requestContext), cancellationToken).ConfigureAwait(false); } else @@ -76,6 +77,7 @@ public virtual async Task AuthenticateAsync( logger: _requestContext.Logger, doNotThrow: true, mtlsCertificate: null, + GetHttpClientWithSslValidation(_requestContext), cancellationToken) .ConfigureAwait(false); @@ -90,6 +92,12 @@ public virtual async Task AuthenticateAsync( } } + // This method is internal for testing purposes. + internal virtual HttpClient GetHttpClientWithSslValidation(RequestContext requestContext) + { + return null; + } + protected virtual Task HandleResponseAsync( AcquireTokenForManagedIdentityParameters parameters, HttpResponse response, diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs index 90f99872bd..2b6babecf9 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AzureArcManagedIdentitySource.cs @@ -128,6 +128,7 @@ protected override async Task HandleResponseAsync( logger: _requestContext.Logger, doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, cancellationToken) .ConfigureAwait(false); diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ServiceFabricManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ServiceFabricManagedIdentitySource.cs index 13e2a91c84..4ffd341a20 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ServiceFabricManagedIdentitySource.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ServiceFabricManagedIdentitySource.cs @@ -15,9 +15,9 @@ namespace Microsoft.Identity.Client.ManagedIdentity internal class ServiceFabricManagedIdentitySource : AbstractManagedIdentity { private const string ServiceFabricMsiApiVersion = "2019-07-01-preview"; - private readonly Uri _endpoint; private readonly string _identityHeaderValue; + internal static Lazy _httpClientLazy; public static AbstractManagedIdentity Create(RequestContext requestContext) { @@ -45,8 +45,44 @@ public static AbstractManagedIdentity Create(RequestContext requestContext) return new ServiceFabricManagedIdentitySource(requestContext, endpointUri, EnvironmentVariables.IdentityHeader); } + internal override HttpClient GetHttpClientWithSslValidation(RequestContext requestContext) + { + if (_httpClientLazy == null) + { + _httpClientLazy = new Lazy(() => + { + HttpClientHandler handler = CreateHandlerWithSslValidation(requestContext.Logger); + return new HttpClient(handler); + }); + } + + return _httpClientLazy.Value; + } + + internal HttpClientHandler CreateHandlerWithSslValidation(ILoggerAdapter logger) + { +#if NET471_OR_GREATER || NETSTANDARD || NET + logger.Info(() => "[Managed Identity] Setting up server certificate validation callback."); + return new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, certificate, chain, sslPolicyErrors) => + { + if (sslPolicyErrors != System.Net.Security.SslPolicyErrors.None) + { + return 0 == string.Compare(certificate.Thumbprint, EnvironmentVariables.IdentityServerThumbprint, StringComparison.OrdinalIgnoreCase); + } + return true; + } + }; +#else + logger.Warning("[Managed Identity] Server certificate validation callback is not supported on .NET Framework."); + return new HttpClientHandler(); +#endif + } + + private ServiceFabricManagedIdentitySource(RequestContext requestContext, Uri endpoint, string identityHeaderValue) : - base(requestContext, ManagedIdentitySource.ServiceFabric) + base(requestContext, ManagedIdentitySource.ServiceFabric) { _endpoint = endpoint; _identityHeaderValue = identityHeaderValue; diff --git a/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs b/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs index fac853fa56..7940d4d88d 100644 --- a/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs +++ b/src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs @@ -139,6 +139,7 @@ internal async Task ExecuteRequestAsync( logger: requestContext.Logger, doNotThrow: false, mtlsCertificate: _mtlsCertificate, + customHttpClient: null, requestContext.UserCancellationToken) .ConfigureAwait(false); } @@ -152,6 +153,7 @@ internal async Task ExecuteRequestAsync( logger: requestContext.Logger, doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, requestContext.UserCancellationToken) .ConfigureAwait(false); } diff --git a/src/client/Microsoft.Identity.Client/WsTrust/WsTrustWebRequestManager.cs b/src/client/Microsoft.Identity.Client/WsTrust/WsTrustWebRequestManager.cs index 42e392734d..43bff88831 100644 --- a/src/client/Microsoft.Identity.Client/WsTrust/WsTrustWebRequestManager.cs +++ b/src/client/Microsoft.Identity.Client/WsTrust/WsTrustWebRequestManager.cs @@ -51,6 +51,7 @@ public async Task GetMexDocumentAsync(string federationMetadataUrl, logger: requestContext.Logger, doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, requestContext.UserCancellationToken) .ConfigureAwait(false); @@ -106,6 +107,7 @@ public async Task GetWsTrustResponseAsync( logger: requestContext.Logger, doNotThrow: true, mtlsCertificate: null, + customHttpClient: null, requestContext.UserCancellationToken).ConfigureAwait(false); if (resp.StatusCode != System.Net.HttpStatusCode.OK) @@ -179,6 +181,7 @@ public async Task GetUserRealmAsync( logger: requestContext.Logger, doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, requestContext.UserCancellationToken) .ConfigureAwait(false); diff --git a/tests/Microsoft.Identity.Test.Common/Core/Helpers/ManagedIdentityTestUtil.cs b/tests/Microsoft.Identity.Test.Common/Core/Helpers/ManagedIdentityTestUtil.cs index 3c6e7e4c09..225085029e 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Helpers/ManagedIdentityTestUtil.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Helpers/ManagedIdentityTestUtil.cs @@ -32,7 +32,7 @@ public enum MsiAzureResource ServiceFabric } - public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentitySource, string endpoint, string secret = "secret") + public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentitySource, string endpoint, string secret = "secret", string thumbprint = "thumbprint") { switch (managedIdentitySource) { @@ -57,7 +57,7 @@ public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentity case ManagedIdentitySource.ServiceFabric: Environment.SetEnvironmentVariable("IDENTITY_ENDPOINT", endpoint); Environment.SetEnvironmentVariable("IDENTITY_HEADER", secret); - Environment.SetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT", "thumbprint"); + Environment.SetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT", thumbprint); break; } } diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs index 3383c97f5e..6a68bd3e60 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs @@ -15,6 +15,7 @@ using Microsoft.Identity.Client; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Http; +using Microsoft.Identity.Client.Internal; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Identity.Test.Common.Core.Mocks @@ -29,9 +30,11 @@ internal sealed class MockHttpManager : IHttpManager, public MockHttpManager(string testName = null, bool isManagedIdentity = false, Func messageHandlerFunc = null, + Func validateServerCertificateCallback = null, bool invokeNonMtlsHttpManagerFactory = false) : this(true, testName, isManagedIdentity, messageHandlerFunc, invokeNonMtlsHttpManagerFactory) - { } + { + } public MockHttpManager( bool retry, @@ -110,6 +113,7 @@ public Task SendRequestAsync( ILoggerAdapter logger, bool doNotThrow, X509Certificate2 mtlsCertificate, + HttpClient customHttpClient, CancellationToken cancellationToken, int retryCount = 0) { @@ -121,6 +125,7 @@ public Task SendRequestAsync( logger, doNotThrow, mtlsCertificate, + customHttpClient: null, cancellationToken, retryCount); } diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpMessageHandler.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpMessageHandler.cs index 08ceeebf88..654b819214 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpMessageHandler.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpMessageHandler.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsiProxyHttpManager.cs b/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsiProxyHttpManager.cs index 0d8321c6ec..aada0b10d4 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsiProxyHttpManager.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsiProxyHttpManager.cs @@ -47,6 +47,7 @@ public async Task SendRequestAsync( ILoggerAdapter logger, bool doNotThrow, X509Certificate2 mtlsCertificate, + HttpClient customHttpClient, CancellationToken cancellationToken, int retryCount = 0) { diff --git a/tests/Microsoft.Identity.Test.Unit/CoreTests/HttpTests/HttpManagerTests.cs b/tests/Microsoft.Identity.Test.Unit/CoreTests/HttpTests/HttpManagerTests.cs index 59b9fcaf90..77c51deb74 100644 --- a/tests/Microsoft.Identity.Test.Unit/CoreTests/HttpTests/HttpManagerTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/CoreTests/HttpTests/HttpManagerTests.cs @@ -12,7 +12,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Identity.Test.Common; using NSubstitute; -using Microsoft.Identity.Client.TelemetryCore; using Microsoft.Identity.Test.Common.Core.Helpers; using System.Threading; using System.Security.Cryptography.X509Certificates; @@ -58,6 +57,7 @@ public async Task MtlsCertAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: cert, + customHttpClient: null, default) .ConfigureAwait(false); @@ -84,6 +84,7 @@ public async Task TestSendPostNullHeaderNullBodyAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default) .ConfigureAwait(false); @@ -124,6 +125,7 @@ public async Task TestSendPostNoFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default) .ConfigureAwait(false); @@ -154,6 +156,7 @@ public async Task TestSendGetNoFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default) .ConfigureAwait(false); @@ -190,6 +193,7 @@ await Assert.ThrowsExceptionAsync( logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, cts.Token)) .ConfigureAwait(false); } @@ -213,6 +217,7 @@ public async Task TestSendGetWithRetryFalseHttp500TypeFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default)) .ConfigureAwait(false); @@ -240,6 +245,7 @@ public async Task TestSendGetWithHttp500TypeFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default)) .ConfigureAwait(false); @@ -270,6 +276,7 @@ public async Task NoResiliencyIfRetryAfterHeaderPresentAsync(bool useTimeSpanRet logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default)) .ConfigureAwait(false); @@ -294,6 +301,7 @@ public async Task TestSendGetWithHttp500TypeFailure2Async() logger: Substitute.For(), doNotThrow: true, mtlsCertificate: null, + customHttpClient: null, default).ConfigureAwait(false); Assert.AreEqual(HttpStatusCode.BadGateway, msalHttpResponse.StatusCode); @@ -318,6 +326,7 @@ public async Task TestSendPostWithHttp500TypeFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default)).ConfigureAwait(false); Assert.AreEqual(MsalError.ServiceNotAvailable, exc.ErrorCode); @@ -342,6 +351,7 @@ public async Task TestSendGetWithRetryOnTimeoutFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default)).ConfigureAwait(false); Assert.AreEqual(MsalError.RequestTimeout, exc.ErrorCode); @@ -367,6 +377,7 @@ public async Task TestSendPostWithRetryOnTimeoutFailureAsync() logger: Substitute.For(), doNotThrow: false, mtlsCertificate: null, + customHttpClient: null, default)).ConfigureAwait(false); Assert.AreEqual(MsalError.RequestTimeout, exc.ErrorCode); Assert.IsTrue(exc.InnerException is TaskCanceledException); @@ -406,6 +417,7 @@ public async Task TestRetryConfigWithHttp500TypeFailureAsync(bool retry, bool is logger: Substitute.For(), doNotThrow: true, mtlsCertificate: null, + customHttpClient: null, default).ConfigureAwait(false); Assert.IsNotNull(msalHttpResponse); diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 9d6f8e514d..cda9d22e83 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -32,7 +32,7 @@ public class ManagedIdentityTests : TestBase internal const string ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"; internal const string AzureArcEndpoint = "http://localhost:40342/metadata/identity/oauth2/token"; internal const string CloudShellEndpoint = "http://localhost:40342/metadata/identity/oauth2/token"; - internal const string ServiceFabricEndpoint = "http://localhost:40342/metadata/identity/oauth2/token"; + internal const string ServiceFabricEndpoint = "https://localhost:2377/metadata/identity/oauth2/token"; internal const string ExpectedErrorMessage = "Expected error message."; internal const string ExpectedErrorCode = "ErrorCode"; internal const string ExpectedCorrelationId = "Some GUID"; diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ServiceFabricTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ServiceFabricTests.cs index 52a4855852..9a544005d7 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ServiceFabricTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ServiceFabricTests.cs @@ -4,9 +4,14 @@ using System; using System.Globalization; using System.Net; +using System.Net.Http; +using System.Net.Security; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Identity.Client.AppConfig; +using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.ManagedIdentity; using Microsoft.Identity.Test.Common; using Microsoft.Identity.Test.Common.Core.Helpers; @@ -53,5 +58,44 @@ await mi.AcquireTokenForManagedIdentity(Resource) Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityEndpointInvalidUriError, "IDENTITY_ENDPOINT", "localhost/token", "Service Fabric"), ex.Message); } } + + [DataTestMethod] + [DeploymentItem(@"Resources\testCert.crtfile")] + [DataRow("invalidThumbprint", SslPolicyErrors.None, true, DisplayName = "ServerCertificateValidationCallback_NoSSLErrors_InvalidThumbprint")] + [DataRow("E70C50DA4EA66F94229A594BC112CB4B4FF29EB4", SslPolicyErrors.RemoteCertificateNameMismatch, true, DisplayName = "ServerCertificateValidationCallback_SSLErrors_validThumbprint")] + [DataRow("E70C50DA4EA66F94229A594BC112CB4B4FF29EB4", SslPolicyErrors.None, true, DisplayName = "ServerCertificateValidationCallback_NoSSLErrors_ValidThumbprint")] + [DataRow("invalidThumbprint", SslPolicyErrors.RemoteCertificateNameMismatch, false, DisplayName = "ServerCertificateValidationCallback_SSLErrors_invalidThumbprint")] + public void ValidateServerCertificateCallback_ServerCertificateValidationCallback_ReturnsHttpClientHandlerWithCustomValidationCallback( + string thumbprint, SslPolicyErrors sslPolicyErrors, bool expectedValidationResult) + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager(isManagedIdentity: true)) + { + SetEnvironmentVariables(ManagedIdentitySource.ServiceFabric, "http://localhost:40342/metadata/identity/oauth2/token", thumbprint: thumbprint); + var certificate = new X509Certificate2(ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword); + var chain = new X509Chain(); + + var miBuilder = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager); + + // Disabling the shared cache to avoid the test to pass because of the cache + miBuilder.Config.AccessorOptions = null; + + var mi = miBuilder.BuildConcrete(); + + RequestContext requestContext = new RequestContext(mi.ServiceBundle, Guid.NewGuid()); + + var sf = ServiceFabricManagedIdentitySource.Create(requestContext); + + Assert.IsInstanceOfType(sf, typeof(ServiceFabricManagedIdentitySource)); + HttpClient httpClient = ((ServiceFabricManagedIdentitySource)sf).GetHttpClientWithSslValidation(requestContext); + Assert.IsNotNull(httpClient); + var httpClientHandler = ((ServiceFabricManagedIdentitySource)sf).CreateHandlerWithSslValidation(requestContext.Logger); + Assert.IsNotNull(httpClientHandler.ServerCertificateCustomValidationCallback); + + var validationResult = httpClientHandler.ServerCertificateCustomValidationCallback(null, certificate, chain, sslPolicyErrors); + Assert.AreEqual(expectedValidationResult, validationResult); + } + } } } diff --git a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs index 6b4eb6f9e9..e85af0c20f 100644 --- a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs @@ -271,6 +271,7 @@ public async Task SendRequestAsync( ILoggerAdapter logger, bool doNotThrow, X509Certificate2 mtlsCertificate, + HttpClient customHttpClient, CancellationToken cancellationToken, int retryCount = 0) { diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs index 2d5fda857d..d441921544 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs @@ -105,8 +105,8 @@ public async Task ProactiveTokenRefresh_ValidResponse_ClientCredential_Async() Assert.IsNotNull(result); Assert.AreEqual(0, _harness.HttpManager.QueueSize, "MSAL should have refreshed the token because the original AT was marked for refresh"); - Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.ProactivelyRefreshed); - Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == refreshOn); + Assert.AreEqual(CacheRefreshReason.ProactivelyRefreshed, result.AuthenticationResultMetadata.CacheRefreshReason); + Assert.AreEqual(refreshOn, result.AuthenticationResultMetadata.RefreshOn); Trace.WriteLine("5. ATS - should not perform an RT refresh, as the token is still valid"); result = await _cca.AcquireTokenForClient(TestConstants.s_scope) @@ -115,7 +115,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_ClientCredential_Async() Trace.WriteLine(result.AuthenticationResultMetadata.DurationTotalInMs); - Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.NotApplicable); + Assert.AreEqual(CacheRefreshReason.NotApplicable, result.AuthenticationResultMetadata.CacheRefreshReason); s_meterProvider.ForceFlush(); VerifyMetrics(4, _exportedMetrics, 4, 0);