Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ internal static class AppContextSwitches
private static bool? _useCapitalizedXMLTypeAttr;
internal static bool UseCapitalizedXMLTypeAttr => _useCapitalizedXMLTypeAttr ??= (AppContext.TryGetSwitch(UseCapitalizedXMLTypeAttrSwitch, out bool useCapitalizedXMLTypeAttr) && useCapitalizedXMLTypeAttr);

/// <summary>
/// When enabled, telemetry will use the full metadata address instead of just the domain name for IdentityModelConfiguration metrics.
/// By default (when disabled), only the domain name is used for successful operations to reduce OpenTelemetry cardinality.
/// </summary>
internal const string UseFullMetadataAddressForTelemetrySwitch = "Switch.Microsoft.IdentityModel.UseFullMetadataAddressForTelemetry";
private static bool? _useFullMetadataAddressForTelemetry;
internal static bool UseFullMetadataAddressForTelemetry => _useFullMetadataAddressForTelemetry ??= (AppContext.TryGetSwitch(UseFullMetadataAddressForTelemetrySwitch, out bool useFullMetadataAddressForTelemetry) && useFullMetadataAddressForTelemetry);

/// <summary>
/// Used for testing to reset all switches to its default value.
/// </summary>
Expand All @@ -123,6 +131,9 @@ internal static void ResetAllSwitches()

_useCapitalizedXMLTypeAttr = null;
AppContext.SetSwitch(UseCapitalizedXMLTypeAttrSwitch, false);

_useFullMetadataAddressForTelemetry = null;
AppContext.SetSwitch(UseFullMetadataAddressForTelemetrySwitch, false);
}
}
}
31 changes: 26 additions & 5 deletions src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,33 @@ internal class TelemetryClient : ITelemetryClient
AppContextSwitches.UpdateConfigAsBlocking.ToString()
);

/// <summary>
/// Extracts the domain name from a metadata address for telemetry purposes.
/// Returns the full address if domain extraction fails or if the UseFullMetadataAddressForTelemetry switch is enabled.
/// In error cases, always returns the full address for better debugging.
/// </summary>
/// <param name="metadataAddress">The full metadata address</param>
/// <param name="isSuccessCase">True if this is a successful operation, false for error cases</param>
/// <returns>Domain name for success cases (when switch is disabled), full address otherwise</returns>
internal static string GetMetadataAddressForTelemetry(string metadataAddress, bool isSuccessCase = true)
{
// Always use full address for error cases or when the switch is enabled
if (!isSuccessCase || AppContextSwitches.UseFullMetadataAddressForTelemetry || string.IsNullOrEmpty(metadataAddress))
return metadataAddress;


if (Uri.TryCreate(metadataAddress, UriKind.Absolute, out Uri result))
return result.Host;

return metadataAddress;
}

public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, string configurationSource)
{
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
{ TelemetryConstants.MetadataAddressTag, metadataAddress },
{ TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: true) },
{ TelemetryConstants.OperationStatusTag, operationStatus },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
_blockingTagValue
Expand All @@ -41,7 +62,7 @@ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress,
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
{ TelemetryConstants.MetadataAddressTag, metadataAddress },
{ TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: false) },
{ TelemetryConstants.OperationStatusTag, operationStatus },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
{ TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() },
Expand All @@ -56,7 +77,7 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, string con
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
{ TelemetryConstants.MetadataAddressTag, metadataAddress },
{ TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: true) },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
};

Expand All @@ -69,7 +90,7 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, string con
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
{ TelemetryConstants.MetadataAddressTag, metadataAddress },
{ TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: false) },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
{ TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() },
_blockingTagValue
Expand All @@ -87,7 +108,7 @@ public void LogBackgroundConfigurationRefreshFailure(
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
{ TelemetryConstants.MetadataAddressTag, metadataAddress },
{ TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: false) },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
{ TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() },
_blockingTagValue
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

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

This test kept failing when ran alongside the new ones introduced by this PR. Putting them in the same collection was the most minimal and only effective solution I found.

Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@

namespace Microsoft.IdentityModel.Tokens.Tests
{
[Collection(nameof(ClaimsIdentityFactoryTests))]
[ResetAppContextSwitches]
[Collection("AppContextSwitches")]
public class ClaimsIdentityFactoryTests
{
public ClaimsIdentityFactoryTests()
{
AppContextSwitches.ResetAllSwitches();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand Down Expand Up @@ -47,7 +43,7 @@ public void Create_FromTokenValidationParameters_ReturnsCorrectClaimsIdentity(bo
Assert.Equal(jsonWebToken, ((CaseSensitiveClaimsIdentity)actualClaimsIdentity).SecurityToken);
}

AppContextSwitches.ResetAllSwitches();
AppContext.SetSwitch(AppContextSwitches.UseClaimsIdentityTypeSwitch, false);
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace Microsoft.IdentityModel.Telemetry.Tests;

[ResetAppContextSwitches]
[Collection("AppContextSwitches")]
public class TelemetryClientDomainExtractionTests
{
[Theory]
[InlineData("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", "login.microsoftonline.com")]
[InlineData("https://www.login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", "www.login.microsoftonline.com")]
[InlineData("https://accounts.google.com/.well-known/openid-configuration", "accounts.google.com")]
[InlineData("https://login.windows.net/common/.well-known/openid-configuration", "login.windows.net")]
[InlineData("http://localhost:8080/.well-known/openid-configuration", "localhost")]
[InlineData("https://example.com/path/to/config", "example.com")]
[InlineData("https://subdomain.example.org/config.json", "subdomain.example.org")]
public void GetMetadataAddressForTelemetry_SuccessCase_ReturnsExpectedDomain(string fullAddress, string expectedDomain)
{
// Act
var result = TelemetryClient.GetMetadataAddressForTelemetry(fullAddress, true);

// Assert
Assert.Equal(expectedDomain, result);
}

[Fact]
public void DebugAppContextSwitch()
{
// Check if the AppContext switch is causing issues
var switchValue = AppContextSwitches.UseFullMetadataAddressForTelemetry;

var testUrl = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
var result = TelemetryClient.GetMetadataAddressForTelemetry(testUrl);

// The switch should be false by default, so we should get the host name
Assert.False(switchValue, $"Switch value: {switchValue}");
Assert.Equal("login.microsoftonline.com", result);
}

[Theory]
[InlineData("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")]
[InlineData("https://accounts.google.com/.well-known/openid-configuration")]
[InlineData("https://login.windows.net/common/.well-known/openid-configuration")]
public void GetMetadataAddressForTelemetry_ErrorCase_ReturnsFullAddress(string fullAddress)
{
// Act
var result = TelemetryClient.GetMetadataAddressForTelemetry(fullAddress, false);

// Assert
Assert.Equal(fullAddress, result);
}

[Theory]
[InlineData("invalid-url")]
[InlineData("")]
[InlineData(null)]
public void GetMetadataAddressForTelemetry_InvalidUrl_ReturnsOriginalString(string invalidUrl)
{
// Act
var result = TelemetryClient.GetMetadataAddressForTelemetry(invalidUrl, true);

// Assert
Assert.Equal(invalidUrl, result);
}

[Fact]
public void GetMetadataAddressForTelemetry_WithAppContextSwitch_ReturnsFullAddress()
{
// Arrange
var fullAddress = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";

try
{
// Enable the switch to use full metadata address
AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, true);

// Act
var result = TelemetryClient.GetMetadataAddressForTelemetry(fullAddress, true);

// Assert
Assert.Equal(fullAddress, result);
}
finally
{
// Cleanup is handled by ResetAppContextSwitches attribute
AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, false);
}
}

[Fact]
public void TelemetryClient_Methods_DoNotThrow()
{
// This test ensures that the modified telemetry methods don't break existing functionality
// We cannot easily verify the exact values sent to the telemetry system without complex setup,
// but we can ensure the methods don't throw exceptions when called with valid parameters.

// Arrange
var telemetryClient = new TelemetryClient();
var testAddress = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
var testDuration = TimeSpan.FromMilliseconds(100);
var testException = new InvalidOperationException("Test exception");

// Act & Assert - these should not throw
var ex1 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration));
var ex2 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration, testException));
var ex3 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "FirstRefresh", "Retriever"));
var ex4 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "ConfigurationRetrievalFailed", "Retriever", testException));
var ex5 = Record.Exception(() => telemetryClient.LogBackgroundConfigurationRefreshFailure(testAddress, "Retriever", testException));

Assert.Null(ex1);
Assert.Null(ex2);
Assert.Null(ex3);
Assert.Null(ex4);
Assert.Null(ex5);
}

[Fact]
public void TelemetryClient_WithAppContextSwitch_DoesNotThrow()
{
// Test that telemetry methods work correctly when the backward compatibility switch is enabled
try
{
// Enable the switch to use full metadata address
AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, true);

// Arrange
var telemetryClient = new TelemetryClient();
var testAddress = "https://accounts.google.com/.well-known/openid-configuration";
var testDuration = TimeSpan.FromMilliseconds(150);
var testException = new InvalidOperationException("Test exception");

// Act & Assert - these should not throw even with the switch enabled
var ex1 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration));
var ex2 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration, testException));
var ex3 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "FirstRefresh", "Retriever"));
var ex4 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "ConfigurationRetrievalFailed", "Retriever", testException));
var ex5 = Record.Exception(() => telemetryClient.LogBackgroundConfigurationRefreshFailure(testAddress, "Retriever", testException));

Assert.Null(ex1);
Assert.Null(ex2);
Assert.Null(ex3);
Assert.Null(ex4);
Assert.Null(ex5);
}
finally
{
// Cleanup
AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, false);
}
}
}
Loading