Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions src/client/Microsoft.Identity.Client/Internal/ClaimsHelper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Identity.Client.Utils;
#if SUPPORTS_SYSTEM_TEXT_JSON
using System.Text;
Expand All @@ -29,10 +31,21 @@ internal static string GetMergedClaimsAndClientCapabilities(
{
if (clientCapabilities != null && clientCapabilities.Any())
{
JObject capabilitiesJson = CreateClientCapabilitiesRequestJson(clientCapabilities);
JObject mergedClaimsAndCapabilities = MergeClaimsIntoCapabilityJson(claims, capabilitiesJson);
try
{
JObject capabilitiesJson = CreateClientCapabilitiesRequestJson(clientCapabilities);
JObject mergedClaimsAndCapabilities = MergeClaimsIntoCapabilityJson(claims, capabilitiesJson);

return JsonHelper.JsonObjectToString(mergedClaimsAndCapabilities);
return JsonHelper.JsonObjectToString(mergedClaimsAndCapabilities);
}
catch (PlatformNotSupportedException pns)
{
throw CreateJsonEncoderException(pns);
}
catch (TypeInitializationException tie) when (tie.InnerException is PlatformNotSupportedException)
{
throw CreateJsonEncoderException(tie.InnerException as PlatformNotSupportedException);
}
}

return claims;
Expand Down Expand Up @@ -91,5 +104,18 @@ private static JObject CreateClientCapabilitiesRequestJson(IEnumerable<string> c
}
};
}

private static MsalClientException CreateJsonEncoderException(PlatformNotSupportedException innerException)
{
string processArchitecture = RuntimeInformation.ProcessArchitecture.ToString();
bool is64BitProcess = Environment.Is64BitProcess;
string hwIntrinsicEnvValue = Environment.GetEnvironmentVariable("DOTNET_EnableHWIntrinsic")
?? Environment.GetEnvironmentVariable("COMPlus_EnableHWIntrinsic");

return new MsalClientException(
MsalError.JsonEncoderIntrinsicsUnsupported,
MsalErrorMessage.JsonEncoderIntrinsicsUnsupported(processArchitecture, is64BitProcess, hwIntrinsicEnvValue),
innerException);
}
}
}
6 changes: 6 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,12 @@ public static class MsalError
/// </summary>
public const string InvalidJsonClaimsFormat = "invalid_json_claims_format";

/// <summary>
/// <para>What happens?</para>The JSON encoder failed due to unavailable hardware intrinsics (SIMD/SSSE3). This typically occurs on 32-bit processes or systems with hardware intrinsics disabled.
/// <para>Mitigation</para>Run the process as 64-bit, update the runtime, or set the environment variable DOTNET_EnableHWIntrinsic=0 to force the non-SIMD code path.
/// </summary>
public const string JsonEncoderIntrinsicsUnsupported = "json_encoder_intrinsics_unsupported";

/// <summary>
/// <para>What happens?</para>The authority configured at the application level is different than the authority configured at the request level
/// <para>Mitigation</para>Ensure the same authority type is used
Expand Down
9 changes: 9 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -445,5 +445,14 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName)
public const string ForceRefreshAndTokenHasNotCompatible = "Cannot specify ForceRefresh and AccessTokenSha256ToRefresh in the same request.";
public const string RequestTimeOut = "Request to the endpoint timed out.";
public const string MalformedOidcAuthorityFormat = "Possible cause: When using Entra External ID, you didn't append /v2.0, for example {0}/v2.0\"";

public static string JsonEncoderIntrinsicsUnsupported(string processArchitecture, bool is64BitProcess, string hwIntrinsicEnvValue)
{
return $"JSON encoding failed due to unavailable hardware intrinsics (SIMD/SSSE3). " +
$"Process architecture: {processArchitecture}, " +
$"Is 64-bit process: {is64BitProcess}, " +
$"DOTNET_EnableHWIntrinsic: {hwIntrinsicEnvValue ?? "(not set)"}. " +
"Mitigation: Run as 64-bit process, update runtime, or set environment variable DOTNET_EnableHWIntrinsic=0 to force the non-SIMD code path.";
}
}
}
70 changes: 55 additions & 15 deletions src/client/Microsoft.Identity.Client/Utils/JsonHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Internal;
Expand Down Expand Up @@ -145,7 +147,21 @@ internal static long ExtractParsedIntOrZero(JObject json, string key)
}

#if SUPPORTS_SYSTEM_TEXT_JSON
internal static string JsonObjectToString(JsonObject jsonObject) => jsonObject.ToJsonString();
internal static string JsonObjectToString(JsonObject jsonObject)
{
try
{
return jsonObject.ToJsonString();
}
catch (PlatformNotSupportedException pns)
{
throw CreateJsonEncoderException(pns);
}
catch (TypeInitializationException tie) when (tie.InnerException is PlatformNotSupportedException)
{
throw CreateJsonEncoderException(tie.InnerException as PlatformNotSupportedException);
}
}

internal static JsonObject ParseIntoJsonObject(string json) => JsonNode.Parse(json).AsObject();

Expand All @@ -168,23 +184,34 @@ internal static long ExtractParsedIntOrZero(JObject json, string key)
/// </remarks>
internal static JObject Merge(JObject originalJson, JObject newContent)
{
// Output buffer to store the merged JSON
var outputBuffer = new ArrayBufferWriter<byte>();

// Parse the original and new JSON content
using (JsonDocument jDoc1 = JsonDocument.Parse(originalJson.ToJsonString()))
using (JsonDocument jDoc2 = JsonDocument.Parse(newContent.ToJsonString()))
using (var jsonWriter = new Utf8JsonWriter(outputBuffer, new JsonWriterOptions { Indented = true }))
try
{
// Merge the JSON elements
MergeJsonElements(jsonWriter, jDoc1.RootElement, jDoc2.RootElement);
}
// Output buffer to store the merged JSON
var outputBuffer = new ArrayBufferWriter<byte>();

// Convert the merged JSON to a UTF-8 encoded string
string mergedJsonString = Encoding.UTF8.GetString(outputBuffer.WrittenSpan);
// Parse the original and new JSON content
using (JsonDocument jDoc1 = JsonDocument.Parse(originalJson.ToJsonString()))
using (JsonDocument jDoc2 = JsonDocument.Parse(newContent.ToJsonString()))
using (var jsonWriter = new Utf8JsonWriter(outputBuffer, new JsonWriterOptions { Indented = true }))
{
// Merge the JSON elements
MergeJsonElements(jsonWriter, jDoc1.RootElement, jDoc2.RootElement);
}

// Convert the merged JSON to a UTF-8 encoded string
string mergedJsonString = Encoding.UTF8.GetString(outputBuffer.WrittenSpan);

// Parse the merged JSON string to a JObject
return ParseIntoJsonObject(mergedJsonString);
// Parse the merged JSON string to a JObject
return ParseIntoJsonObject(mergedJsonString);
}
catch (PlatformNotSupportedException pns)
{
throw CreateJsonEncoderException(pns);
}
catch (TypeInitializationException tie) when (tie.InnerException is PlatformNotSupportedException)
{
throw CreateJsonEncoderException(tie.InnerException as PlatformNotSupportedException);
}
}

// Merges two JSON elements based on their value kind
Expand Down Expand Up @@ -284,6 +311,19 @@ private static void MergeArrays(Utf8JsonWriter jsonWriter, JsonElement root1, Js
// End writing the merged array
jsonWriter.WriteEndArray();
}

private static MsalClientException CreateJsonEncoderException(PlatformNotSupportedException innerException)
{
string processArchitecture = RuntimeInformation.ProcessArchitecture.ToString();
bool is64BitProcess = Environment.Is64BitProcess;
string hwIntrinsicEnvValue = Environment.GetEnvironmentVariable("DOTNET_EnableHWIntrinsic")
?? Environment.GetEnvironmentVariable("COMPlus_EnableHWIntrinsic");

return new MsalClientException(
MsalError.JsonEncoderIntrinsicsUnsupported,
MsalErrorMessage.JsonEncoderIntrinsicsUnsupported(processArchitecture, is64BitProcess, hwIntrinsicEnvValue),
innerException);
}
#else
internal static string JsonObjectToString(JObject jsonObject) => jsonObject.ToString(Formatting.None);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,38 @@ public void ClaimsMerge_Test(string claims, string[] capabilities, string expect
var mergedJson = ClaimsHelper.GetMergedClaimsAndClientCapabilities(claims, capabilities);
Assert.AreEqual(expectedMergedJson, mergedJson);
}

[TestMethod]
public void ClaimsHelper_HandlesPlatformNotSupportedException_FromJsonEncoder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test can be better here?

{
// This test verifies that PlatformNotSupportedException thrown during JSON encoding
// is caught and wrapped in MsalClientException with proper diagnostic information.

// Note: We cannot easily simulate a real PlatformNotSupportedException from System.Text.Json
// without running on an unsupported platform, so this test verifies the error handling
// code compiles and the error constants are properly defined.

// Verify error code constant exists
Assert.AreEqual("json_encoder_intrinsics_unsupported", MsalError.JsonEncoderIntrinsicsUnsupported);

// Verify error message method generates a proper message
string errorMsg = MsalErrorMessage.JsonEncoderIntrinsicsUnsupported("X64", true, "0");
Assert.IsTrue(errorMsg.Contains("JSON encoding failed"));
Assert.IsTrue(errorMsg.Contains("X64"));
Assert.IsTrue(errorMsg.Contains("Is 64-bit process: True"));
Assert.IsTrue(errorMsg.Contains("DOTNET_EnableHWIntrinsic: 0"));
Assert.IsTrue(errorMsg.Contains("SIMD"));
}

[TestMethod]
public void ClaimsHelper_ErrorMessage_HandlesNullEnvVariable()
{
// Test that the error message handles null environment variable correctly
string errorMsg = MsalErrorMessage.JsonEncoderIntrinsicsUnsupported("X86", false, null);
Assert.IsTrue(errorMsg.Contains("JSON encoding failed"));
Assert.IsTrue(errorMsg.Contains("X86"));
Assert.IsTrue(errorMsg.Contains("Is 64-bit process: False"));
Assert.IsTrue(errorMsg.Contains("DOTNET_EnableHWIntrinsic: (not set)"));
}
}
}