diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs index 499871b43f..8245ff633f 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs @@ -12,6 +12,11 @@ using System.Net; using Microsoft.Identity.Client.ApiConfig.Parameters; using System.Text; +#if SUPPORTS_SYSTEM_TEXT_JSON +using System.Text.Json; +#else +using Microsoft.Identity.Json; +#endif namespace Microsoft.Identity.Client.ManagedIdentity { @@ -29,7 +34,7 @@ protected AbstractManagedIdentity(RequestContext requestContext, ManagedIdentity } public virtual async Task AuthenticateAsync( - AcquireTokenForManagedIdentityParameters parameters, + AcquireTokenForManagedIdentityParameters parameters, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -107,7 +112,7 @@ protected virtual Task HandleResponseAsync( } string message = GetMessageFromErrorResponse(response); - + _requestContext.Logger.Error($"[Managed Identity] request failed, HttpStatusCode: {response.StatusCode} Error message: {message}"); MsalException exception = MsalServiceExceptionFactory.CreateManagedIdentityException( @@ -124,20 +129,39 @@ protected virtual Task HandleResponseAsync( protected ManagedIdentityResponse GetSuccessfulResponse(HttpResponse response) { - ManagedIdentityResponse managedIdentityResponse = JsonHelper.DeserializeFromJson(response.Body); + ManagedIdentityResponse managedIdentityResponse; + try + { + managedIdentityResponse = JsonHelper.DeserializeFromJson(response.Body); + } + catch (JsonException ex) + { + _requestContext.Logger.Error("[Managed Identity] MSI json response failed to parse. " + ex); - if (managedIdentityResponse == null || managedIdentityResponse.AccessToken.IsNullOrEmpty() || managedIdentityResponse.ExpiresOn.IsNullOrEmpty()) + var exception = MsalServiceExceptionFactory.CreateManagedIdentityException( + MsalError.ManagedIdentityResponseParseFailure, + MsalErrorMessage.ManagedIdentityJsonParseFailure, + ex, + _sourceType, + (int)HttpStatusCode.OK); + + throw exception; + } + + if (managedIdentityResponse == null || + managedIdentityResponse.AccessToken.IsNullOrEmpty() || + managedIdentityResponse.ExpiresOn.IsNullOrEmpty()) { _requestContext.Logger.Error("[Managed Identity] Response is either null or insufficient for authentication."); var exception = MsalServiceExceptionFactory.CreateManagedIdentityException( MsalError.ManagedIdentityRequestFailed, MsalErrorMessage.ManagedIdentityInvalidResponse, - null, - _sourceType, - null); + null, + _sourceType, + (int)HttpStatusCode.OK); - throw exception; + throw exception; } return managedIdentityResponse; @@ -158,7 +182,7 @@ internal string GetMessageFromErrorResponse(HttpResponse response) catch { return TryGetMessageFromNestedErrorResponse(response.Body); - } + } } private string ExtractErrorMessageFromManagedIdentityErrorResponse(ManagedIdentityErrorResponse managedIdentityErrorResponse) @@ -218,7 +242,8 @@ private string TryGetMessageFromNestedErrorResponse(string response) { return errorMessage.ToString(); } - } catch + } + catch { // Ignore any exceptions that occur during parsing and send the error message. } @@ -227,8 +252,8 @@ private string TryGetMessageFromNestedErrorResponse(string response) return $"{MsalErrorMessage.ManagedIdentityUnexpectedErrorResponse}. Error response received from the server: {response}."; } - private void HandleException(Exception ex, - ManagedIdentitySource managedIdentitySource = ManagedIdentitySource.None, + private void HandleException(Exception ex, + ManagedIdentitySource managedIdentitySource = ManagedIdentitySource.None, string additionalInfo = null) { ManagedIdentitySource source = managedIdentitySource != ManagedIdentitySource.None ? managedIdentitySource : _sourceType; @@ -254,9 +279,9 @@ private void HandleException(Exception ex, } } - private static void CreateAndThrowException(string errorCode, - string errorMessage, - Exception innerException, + private static void CreateAndThrowException(string errorCode, + string errorMessage, + Exception innerException, ManagedIdentitySource source) { MsalException exception = MsalServiceExceptionFactory.CreateManagedIdentityException( diff --git a/src/client/Microsoft.Identity.Client/MsalError.cs b/src/client/Microsoft.Identity.Client/MsalError.cs index 459cb35134..d57faf37c5 100644 --- a/src/client/Microsoft.Identity.Client/MsalError.cs +++ b/src/client/Microsoft.Identity.Client/MsalError.cs @@ -1110,6 +1110,11 @@ public static class MsalError /// public const string ManagedIdentityRequestFailed = "managed_identity_request_failed"; + /// + /// Managed Identity error response was received. + /// + public const string ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure"; + /// /// Managed Identity endpoint is not reachable. /// diff --git a/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs b/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs index 21c17fcaa5..39ace37dcb 100644 --- a/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs +++ b/src/client/Microsoft.Identity.Client/MsalErrorMessage.cs @@ -414,6 +414,7 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName) public const string ManagedIdentityNoResponseReceived = "[Managed Identity] Authentication unavailable. No response received from the managed identity endpoint."; public const string ManagedIdentityInvalidResponse = "[Managed Identity] Invalid response, the authentication response received did not contain the expected fields."; + public const string ManagedIdentityJsonParseFailure = "[Managed Identity] MSI returned 200 OK, but the response could not be parsed."; public const string ManagedIdentityUnexpectedResponse = "[Managed Identity] Unexpected exception occurred when parsing the response. See the inner exception for details."; public const string ManagedIdentityExactlyOneScopeExpected = "[Managed Identity] To acquire token for managed identity, exactly one scope must be passed."; public const string ManagedIdentityUnexpectedErrorResponse = "[Managed Identity] The error response was either empty or could not be parsed."; 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 074cba040e..7e0d6167a7 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 @@ +const Microsoft.Identity.Client.MsalError.ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure" -> string Microsoft.Identity.Client.AbstractConfidentialClientAcquireTokenParameterBuilder.WithSignedHttpRequestProofOfPossession(Microsoft.Identity.Client.AppConfig.PoPAuthenticationConfiguration popAuthenticationConfiguration) -> T Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithMtlsProofOfPossession() -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder const Microsoft.Identity.Client.MsalError.MissingTenantedAuthority = "missing_tenanted_authority" -> string 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 074cba040e..7e0d6167a7 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 @@ +const Microsoft.Identity.Client.MsalError.ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure" -> string Microsoft.Identity.Client.AbstractConfidentialClientAcquireTokenParameterBuilder.WithSignedHttpRequestProofOfPossession(Microsoft.Identity.Client.AppConfig.PoPAuthenticationConfiguration popAuthenticationConfiguration) -> T Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithMtlsProofOfPossession() -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder const Microsoft.Identity.Client.MsalError.MissingTenantedAuthority = "missing_tenanted_authority" -> string 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 074cba040e..7e0d6167a7 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 @@ +const Microsoft.Identity.Client.MsalError.ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure" -> string Microsoft.Identity.Client.AbstractConfidentialClientAcquireTokenParameterBuilder.WithSignedHttpRequestProofOfPossession(Microsoft.Identity.Client.AppConfig.PoPAuthenticationConfiguration popAuthenticationConfiguration) -> T Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithMtlsProofOfPossession() -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder const Microsoft.Identity.Client.MsalError.MissingTenantedAuthority = "missing_tenanted_authority" -> string 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 074cba040e..7e0d6167a7 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 @@ +const Microsoft.Identity.Client.MsalError.ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure" -> string Microsoft.Identity.Client.AbstractConfidentialClientAcquireTokenParameterBuilder.WithSignedHttpRequestProofOfPossession(Microsoft.Identity.Client.AppConfig.PoPAuthenticationConfiguration popAuthenticationConfiguration) -> T Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithMtlsProofOfPossession() -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder const Microsoft.Identity.Client.MsalError.MissingTenantedAuthority = "missing_tenanted_authority" -> string 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 074cba040e..7e0d6167a7 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 @@ +const Microsoft.Identity.Client.MsalError.ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure" -> string Microsoft.Identity.Client.AbstractConfidentialClientAcquireTokenParameterBuilder.WithSignedHttpRequestProofOfPossession(Microsoft.Identity.Client.AppConfig.PoPAuthenticationConfiguration popAuthenticationConfiguration) -> T Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithMtlsProofOfPossession() -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder const Microsoft.Identity.Client.MsalError.MissingTenantedAuthority = "missing_tenanted_authority" -> string 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 074cba040e..7e0d6167a7 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 @@ +const Microsoft.Identity.Client.MsalError.ManagedIdentityResponseParseFailure = "managed_identity_response_parse_failure" -> string Microsoft.Identity.Client.AbstractConfidentialClientAcquireTokenParameterBuilder.WithSignedHttpRequestProofOfPossession(Microsoft.Identity.Client.AppConfig.PoPAuthenticationConfiguration popAuthenticationConfiguration) -> T Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithMtlsProofOfPossession() -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder const Microsoft.Identity.Client.MsalError.MissingTenantedAuthority = "missing_tenanted_authority" -> string diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index 74af6b8665..91e5c3d268 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -133,13 +133,10 @@ public static string GetMsiSuccessfulResponse(int expiresInHours = 1, bool useIs "\"Bearer\",\"client_id\":\"client_id\"}"; } - public static string GetMsiImdsSuccessfulResponse() + public static string GetMsiErrorBadJson() { - string expiresOn = DateTimeHelpers.DateTimeToUnixTimestamp(DateTime.UtcNow.AddHours(1)); - return - "{\"access_token\":\"" + TestConstants.ATSecret + "\",\"client_id\":\"client-id\"," + - "\"expires_in\":\"12345\",\"expires_on\":\"" + expiresOn + "\",\"resource\":\"https://management.azure.com/\"," + - "\"ext_expires_in\":\"12345\",\"token_type\":\"Bearer\"}"; + string successResponse = GetMsiSuccessfulResponse(); + return successResponse.Replace("{", "|"); } public static string GetMsiErrorResponse(ManagedIdentitySource source) diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 154e4f91f1..f2dfe4449b 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -1073,5 +1073,43 @@ await AssertException.TaskThrowsAsync( .WithForceRefresh(true) .ExecuteAsync(tokenSource.Token)).ConfigureAwait(false); } + + [DataTestMethod] + [DataRow(ManagedIdentitySource.Imds, ImdsEndpoint)] + [DataRow(ManagedIdentitySource.AppService, AppServiceEndpoint)] + [DataRow(ManagedIdentitySource.AzureArc, AzureArcEndpoint)] + [DataRow(ManagedIdentitySource.CloudShell, CloudShellEndpoint)] + [DataRow(ManagedIdentitySource.ServiceFabric, ServiceFabricEndpoint)] + public async Task InvalidJsonResponseHandling(ManagedIdentitySource managedIdentitySource, string endpoint) + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager(isManagedIdentity: true)) + { + SetEnvironmentVariables(managedIdentitySource, endpoint); + + var miBuilder = ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager); + + // Disabling shared cache options to avoid cross test pollution. + miBuilder.Config.AccessorOptions = null; + + var mi = miBuilder.Build(); + + httpManager.AddManagedIdentityMockHandler( + endpoint, + "scope", + MockHelpers.GetMsiErrorBadJson(), + managedIdentitySource); + + MsalServiceException ex = await Assert.ThrowsExceptionAsync(async () => + await mi.AcquireTokenForManagedIdentity("scope") + .ExecuteAsync().ConfigureAwait(false)).ConfigureAwait(false); + + Assert.AreEqual(managedIdentitySource.ToString(), ex.AdditionalExceptionData[MsalException.ManagedIdentitySource]); + Assert.AreEqual(MsalError.ManagedIdentityResponseParseFailure, ex.ErrorCode); + Assert.AreEqual(MsalErrorMessage.ManagedIdentityJsonParseFailure, ex.Message); + } + } } }