diff --git a/build/dependencies.props b/build/dependencies.props index 9438d4d70e..aa3c765a75 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -8,6 +8,7 @@ 1.0.0 2.0.3 13.0.3 + 6.0.2 4.5.5 4.5.0 8.0.5 diff --git a/build/dependenciesTest.props b/build/dependenciesTest.props index 65daedcf38..4e5f755ef4 100644 --- a/build/dependenciesTest.props +++ b/build/dependenciesTest.props @@ -7,6 +7,7 @@ 17.11.1 2.0.3 13.0.3 + 1.6.0 4.3.4 4.3.0 8.0.5 diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt index cb23d777b2..2cfd5e0431 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateToken(string payload, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, string compressionAlgorithm, System.Collections.Generic.IDictionary additionalHeaderClaims, string tokenType, bool includeKeyIdInHeader) -> string static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs index 918788932f..c37fb5d876 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs @@ -10,6 +10,7 @@ using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Telemetry; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; namespace Microsoft.IdentityModel.JsonWebTokens @@ -17,6 +18,8 @@ namespace Microsoft.IdentityModel.JsonWebTokens /// This partial class contains methods and logic related to the validation of tokens. public partial class JsonWebTokenHandler : TokenHandler { + internal Tokens.Telemetry.ITelemetryClient _telemetryClient = new TelemetryClient(); + /// /// Returns a value that indicates if this handler can validate a . /// @@ -511,6 +514,10 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration) // where a new valid configuration was somehow published during validation time. if (currentConfiguration != null) { + _telemetryClient.IncrementConfigurationRefreshRequestCounter( + validationParameters.ConfigurationManager.MetadataAddress, + TelemetryConstants.Protocols.Lkg); + validationParameters.ConfigurationManager.RequestRefresh(); validationParameters.RefreshBeforeValidation = true; var lastConfig = currentConfiguration; diff --git a/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj b/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj index e6a1110b05..4b9733a6f5 100644 --- a/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj +++ b/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj @@ -32,6 +32,10 @@ + + + + diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index 1f38022f33..1e5455861d 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -8,6 +8,7 @@ using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Telemetry; namespace Microsoft.IdentityModel.Protocols { @@ -35,6 +36,9 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM private const int ConfigurationRetrieverRunning = 1; private int _configurationRetrieverState = ConfigurationRetrieverIdle; + internal TimeProvider TimeProvider = TimeProvider.System; + internal ITelemetryClient TelemetryClient = new TelemetryClient(); + /// /// Instantiates a new that manages automatic and controls refreshing on configuration data. /// @@ -172,7 +176,7 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) try { // Don't use the individual CT here, this is a shared operation that shouldn't be affected by an individual's cancellation. - // The transport should have it's own timeouts, etc. + // The transport should have its own timeouts, etc. T configuration = await _configRetriever.GetConfigurationAsync( MetadataAddress, _docRetriever, @@ -183,18 +187,29 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) ConfigurationValidationResult result = _configValidator.Validate(configuration); // in this case we have never had a valid configuration, so we will throw an exception if the validation fails if (!result.Succeeded) - throw LogHelper.LogExceptionMessage( - new InvalidConfigurationException( - LogHelper.FormatInvariant( - LogMessages.IDX20810, - result.ErrorMessage))); + { + var ex = new InvalidConfigurationException( + LogHelper.FormatInvariant( + LogMessages.IDX20810, + result.ErrorMessage)); + + throw LogHelper.LogExceptionMessage(ex); + } } + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.FirstRefresh); + UpdateConfiguration(configuration); } catch (Exception ex) { fetchMetadataFailure = ex; + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.FirstRefresh, + ex); LogHelper.LogExceptionMessage( new InvalidOperationException( @@ -214,6 +229,10 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) { if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) { + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Automatic); + _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); } } @@ -240,6 +259,8 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) private void UpdateCurrentConfiguration() { #pragma warning disable CA1031 // Do not catch general exception types + long startTimestamp = TimeProvider.GetTimestamp(); + try { T configuration = _configRetriever.GetConfigurationAsync( @@ -247,6 +268,11 @@ private void UpdateCurrentConfiguration() _docRetriever, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); + var elapsedTime = TimeProvider.GetElapsedTime(startTimestamp); + TelemetryClient.LogConfigurationRetrievalDuration( + MetadataAddress, + elapsedTime); + if (_configValidator == null) { UpdateConfiguration(configuration); @@ -267,6 +293,12 @@ private void UpdateCurrentConfiguration() } catch (Exception ex) { + var elapsedTime = TimeProvider.GetElapsedTime(startTimestamp); + TelemetryClient.LogConfigurationRetrievalDuration( + MetadataAddress, + elapsedTime, + ex); + LogHelper.LogExceptionMessage( new InvalidOperationException( LogHelper.FormatInvariant( @@ -293,7 +325,7 @@ private void UpdateConfiguration(T configuration) /// Obtains an updated version of Configuration. /// /// CancellationToken - /// Configuration of type BaseConfiguration . + /// Configuration of type BaseConfiguration. /// If the time since the last call is less than then is not called and the current Configuration is returned. public override async Task GetBaseConfigurationAsync(CancellationToken cancel) { @@ -311,6 +343,10 @@ public override void RequestRefresh() { DateTimeOffset now = DateTimeOffset.UtcNow; + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Manual); + if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest) { _isFirstRefreshRequest = false; diff --git a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt index e69de29bb2..5d0bb6b3c4 100644 --- a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Protocols.ConfigurationManager.TelemetryClient -> Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient +Microsoft.IdentityModel.Protocols.ConfigurationManager.TimeProvider -> System.TimeProvider diff --git a/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs new file mode 100644 index 0000000000..5e00856ea6 --- /dev/null +++ b/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 14d2d3493a..d3311f516f 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -8,7 +8,20 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10273 = "IDX10273: Algorithm const Microsoft.IdentityModel.Tokens.LogMessages.IDX10274 = "IDX10274: IssuerSigningKeyValidationDelegate threw an exception, see inner exception." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10276 = "IDX10276: TokenReplayValidationDelegate threw an exception, see inner exception." -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.ExceptionTypeTag = "ExceptionType" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.IdentityModelVersionTag = "IdentityModelVersion" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.MetadataAddressTag = "MetadataAddress" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.OperationStatusTag = "OperationStatus" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.Automatic = "Automatic" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.ConfigurationInvalid = "ConfigurationInvalid" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.FirstRefresh = "FirstRefresh" -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10277 = "IDX10277: RequireAudience property on ValidationParameters is set to false. Exiting without validating the audience." -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.Lkg = "LastKnownGood" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.Manual = "Manual" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations." -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager" -> string +const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS" -> string Microsoft.IdentityModel.Tokens.AlgorithmValidationError Microsoft.IdentityModel.Tokens.AlgorithmValidationError.AlgorithmValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string @@ -37,9 +50,25 @@ Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTo Microsoft.IdentityModel.Tokens.SignatureValidationError Microsoft.IdentityModel.Tokens.SignatureValidationError.InnerValidationError.get -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.SignatureValidationError.SignatureValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.ValidationError innerValidationError = null, System.Exception innerException = null) -> void +Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient +Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void +Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void +Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void +Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.ClientVer -> string +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.TelemetryClient() -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.TelemetryDataRecorder() -> void Microsoft.IdentityModel.Tokens.TokenReplayValidationError Microsoft.IdentityModel.Tokens.TokenReplayValidationError.ExpirationTime.get -> System.DateTime? Microsoft.IdentityModel.Tokens.TokenReplayValidationError.TokenReplayValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? expirationTime, System.Exception innerException = null) -> void +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants +Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols Microsoft.IdentityModel.Tokens.TokenTypeValidationError Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> string Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType, System.Exception innerException = null) -> void @@ -63,6 +92,8 @@ override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.GetException( override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception static Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError static Microsoft.IdentityModel.Tokens.SignatureValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.SignatureValidationError +static Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(in System.Diagnostics.TagList tagList) -> void +static Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(long requestDurationInMs, in System.Diagnostics.TagList tagList) -> void static Microsoft.IdentityModel.Tokens.TokenReplayValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenReplayValidationError static Microsoft.IdentityModel.Tokens.TokenTypeValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenTypeValidationError static Microsoft.IdentityModel.Tokens.Base64UrlEncoder.Decode(System.ReadOnlySpan strSpan, System.Span output) -> int @@ -70,6 +101,8 @@ static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedStr static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationFailed -> Microsoft.Extensions.Logging.EventId static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationSucceeded -> Microsoft.Extensions.Logging.EventId +static readonly Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter +static readonly Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.TotalDurationHistogram -> System.Diagnostics.Metrics.Histogram static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AlgorithmValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AudienceValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerSigningKeyValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs new file mode 100644 index 0000000000..350a0076ae --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.IdentityModel.Tokens.Telemetry +{ + internal interface ITelemetryClient + { + internal void LogConfigurationRetrievalDuration( + string metadataAddress, + TimeSpan operationDuration); + + internal void LogConfigurationRetrievalDuration( + string metadataAddress, + TimeSpan operationDuration, + Exception exception); + + internal void IncrementConfigurationRefreshRequestCounter( + string metadataAddress, + string operationStatus); + + internal void IncrementConfigurationRefreshRequestCounter( + string metadataAddress, + string operationStatus, + Exception exception); + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs new file mode 100644 index 0000000000..92026976cc --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens.Telemetry +{ + /// + /// Prepares s using the provided data and sends them to for recording. + /// + internal class TelemetryClient : ITelemetryClient + { + public string ClientVer = IdentityModelTelemetryUtil.ClientVer; + + public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) + { + var tagList = new TagList() + { + { TelemetryConstants.IdentityModelVersionTag, ClientVer }, + { TelemetryConstants.MetadataAddressTag, metadataAddress }, + { TelemetryConstants.OperationStatusTag, operationStatus } + }; + + TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(tagList); + } + + public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, Exception exception) + { + var tagList = new TagList() + { + { TelemetryConstants.IdentityModelVersionTag, ClientVer }, + { TelemetryConstants.MetadataAddressTag, metadataAddress }, + { TelemetryConstants.OperationStatusTag, operationStatus }, + { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() } + }; + + TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(tagList); + } + + public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration) + { + var tagList = new TagList() + { + { TelemetryConstants.IdentityModelVersionTag, ClientVer }, + { TelemetryConstants.MetadataAddressTag, metadataAddress }, + }; + + long durationInMilliseconds = (long)operationDuration.TotalMilliseconds; + TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(durationInMilliseconds, tagList); + } + + public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration, Exception exception) + { + var tagList = new TagList() + { + { TelemetryConstants.IdentityModelVersionTag, ClientVer }, + { TelemetryConstants.MetadataAddressTag, metadataAddress }, + { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() } + }; + + long durationInMilliseconds = (long)operationDuration.TotalMilliseconds; + TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(durationInMilliseconds, tagList); + } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs new file mode 100644 index 0000000000..2e49b8774e --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.IdentityModel.Tokens.Telemetry +{ + internal static class TelemetryConstants + { + // Static attribute tags + + /// + /// Telemetry tag indicating the version of the IdentityModel library. + /// + public const string IdentityModelVersionTag = "IdentityModelVersion"; + + /// + /// Telemetry tag indicating the endpoint from which a configuration is retrieved. + /// + public const string MetadataAddressTag = "MetadataAddress"; + + /// + /// Telemetry tag describing the operation being performed. + /// + public const string OperationStatusTag = "OperationStatus"; + + /// + /// Telemetry tag indicating the type of exception that occurred. + /// + public const string ExceptionTypeTag = "ExceptionType"; + + public static class Protocols + { + // Configuration manager refresh statuses + + /// + /// Telemetry tag indicating configuration retrieval after the refresh interval has expired. + /// + public const string Automatic = "Automatic"; + + /// + /// Telemetry tag indicating configuration retrieval per a call to RequestRefresh. + /// + public const string Manual = "Manual"; + + /// + /// Telemetry tag indicating configuration retrieval when there is no previously cached configuration. + /// + public const string FirstRefresh = "FirstRefresh"; + + /// + /// Telemetry tag indicating configuration retrieval when the last known good configuration is needed. + /// + public const string Lkg = "LastKnownGood"; + + // Configuration manager exception types + + /// + /// Telemetry tag indicating that configuration could not be sucessfully validated after retrieval. + /// + public const string ConfigurationInvalid = "ConfigurationInvalid"; + + /// + /// Telemetry tag indicating that configuration could not be retrieved successfully. + /// + public const string ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed"; + } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs new file mode 100644 index 0000000000..dbf449baf9 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.Diagnostics.Metrics; + +namespace Microsoft.IdentityModel.Tokens.Telemetry +{ + /// + /// Pushes telemetry data to the configured or . + /// + internal class TelemetryDataRecorder + { + /// + /// Meter name for MicrosoftIdentityModel. + /// + private const string MeterName = "MicrosoftIdentityModel_Meter"; + + /// + /// The meter responsible for creating instruments. + /// + private static readonly Meter IdentityModelMeter = new(MeterName, "1.0.0"); + + internal const string TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS"; + + /// + /// Counter to capture configuration refresh requests to ConfigurationManager. + /// + internal const string IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager"; + internal const string IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations."; + internal static readonly Counter ConfigurationManagerCounter = IdentityModelMeter.CreateCounter(IdentityModelConfigurationManagerCounterName, description: IdentityModelConfigurationManagerCounterDescription); + + /// + /// Histogram to capture total duration of configuration retrieval by ConfigurationManager in milliseconds. + /// + internal static readonly Histogram TotalDurationHistogram = IdentityModelMeter.CreateHistogram( + TotalDurationHistogramName, + unit: "ms", + description: "Configuration retrieval latency during configuration manager operations."); + + internal static void RecordConfigurationRetrievalDurationHistogram(long requestDurationInMs, in TagList tagList) + { + TotalDurationHistogram.Record(requestDurationInMs, tagList); + } + + internal static void IncrementConfigurationRefreshRequestCounter(in TagList tagList) + { + ConfigurationManagerCounter.Add(1, tagList); + } + } +} diff --git a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt index 47d5d10352..bfbb59b541 100644 --- a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt +++ b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt @@ -1,2 +1,3 @@ System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalHeaderClaims, bool includeKeyIdInHeader) -> void -System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalInnerHeaderClaims, bool includeKeyIdInHeader) -> void \ No newline at end of file +System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalInnerHeaderClaims, bool includeKeyIdInHeader) -> void +System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.TelemetryClient -> Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs index f108b9dbf5..23ab48108d 100644 --- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs +++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs @@ -15,6 +15,7 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Telemetry; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; namespace System.IdentityModel.Tokens.Jwt @@ -36,6 +37,8 @@ public class JwtSecurityTokenHandler : SecurityTokenHandler private static string _shortClaimType = _namespace + "/ShortTypeName"; private bool _mapInboundClaims = DefaultMapInboundClaims; + internal Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient TelemetryClient = new TelemetryClient(); + /// /// Default claim type mapping for inbound claims. /// @@ -887,6 +890,10 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken, // where a new valid configuration was somehow published during validation time. if (currentConfiguration != null) { + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + validationParameters.ConfigurationManager.MetadataAddress, + TelemetryConstants.Protocols.Lkg); + validationParameters.ConfigurationManager.RequestRefresh(); validationParameters.RefreshBeforeValidation = true; var lastConfig = currentConfiguration; diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs new file mode 100644 index 0000000000..556e182a41 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Telemetry; +using Microsoft.IdentityModel.Tokens.Telemetry.Tests; +using Microsoft.IdentityModel.Validators; +using Xunit; + +namespace Microsoft.IdentityModel.JsonWebTokens.Tests +{ + public class JsonWebTokenHandlerTelemetryTests + { + [Fact] + public async Task ValidateJwsWithConfigAsync_ExpectedTagsExist() + { + var invalidIssuerConfig = new OpenIdConnectConfiguration() + { + TokenEndpoint = Default.Issuer + "oauth/token", + Issuer = Default.Issuer + "2" + }; + invalidIssuerConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); + + var validationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(invalidIssuerConfig), + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = false + }; + + var testTelemetryClient = new MockTelemetryClient(); + try + { + var handler = new JsonWebTokenHandler() + { + _telemetryClient = testTelemetryClient + }; + var jwt = handler.ReadJsonWebToken(Default.AsymmetricJws); + AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).ConfigurationManagerV1 = validationParameters.ConfigurationManager; + var validationResult = await handler.ValidateTokenAsync(jwt, validationParameters); + var rawTokenValidationResult = await handler.ValidateTokenAsync(Default.AsymmetricJws, validationParameters); + } + catch (Exception) + { + // ignore exceptions + } + + var expectedCounterTagList = new Dictionary + { + // metadata address is null because the configuration manager is made using an invalid config to trigger an exception + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.MetadataAddressTag, null }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Lkg } + }; + + Assert.Equal(expectedCounterTagList, testTelemetryClient.ExportedItems); + } + } +} diff --git a/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj b/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj index 0d29cd28cd..6ed8578f22 100644 --- a/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj +++ b/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj @@ -23,6 +23,8 @@ + + diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs new file mode 100644 index 0000000000..6d61e16b19 --- /dev/null +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.Configuration; +using Microsoft.IdentityModel.Protocols.OpenIdConnect.Configuration; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens.Telemetry; +using Microsoft.IdentityModel.Tokens.Telemetry.Tests; +using Xunit; + +namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests +{ + public class ConfigurationManagerTelemetryTests + { + [Fact] + public async Task RequestRefresh_ExpectedTagsExist() + { + // arrange + var testTelemetryClient = new MockTelemetryClient(); + var configurationManager = new ConfigurationManager( + OpenIdConfigData.AccountsGoogle, + new OpenIdConnectConfigurationRetriever(), + new HttpDocumentRetriever(), + new OpenIdConnectConfigurationValidator()) + { + TelemetryClient = testTelemetryClient + }; + + // act + configurationManager.RequestRefresh(); + //// Wait for UpdateCurrentConfiguration to complete + while (TestUtilities.GetField(configurationManager, "_currentConfiguration") == null) + { + await Task.Delay(100); + } + + // assert + var expectedCounterTagList = new Dictionary + { + { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AccountsGoogle }, + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Manual }, + }; + + var expectedHistogramTagList = new Dictionary + { + { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AccountsGoogle }, + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer } + }; + + Assert.Equal(expectedCounterTagList, testTelemetryClient.ExportedItems); + Assert.Equal(expectedHistogramTagList, testTelemetryClient.ExportedHistogramItems); + } + + [Theory, MemberData(nameof(GetConfiguration_ExpectedTagList_TheoryData), DisableDiscoveryEnumeration = true)] + public async Task GetConfigurationAsync_ExpectedTagsExist(ConfigurationManagerTelemetryTheoryData theoryData) + { + var testTelemetryClient = new MockTelemetryClient(); + + var configurationManager = new ConfigurationManager( + theoryData.MetadataAddress, + new OpenIdConnectConfigurationRetriever(), + theoryData.DocumentRetriever, + theoryData.ConfigurationValidator) + { + TelemetryClient = testTelemetryClient + }; + + try + { + await configurationManager.GetConfigurationAsync(); + if (theoryData.SyncAfter != null) + { + testTelemetryClient.ClearExportedItems(); + TestUtilities.SetField(configurationManager, "_syncAfter", theoryData.SyncAfter); + await configurationManager.GetConfigurationAsync(); + } + + } + catch (Exception) + { + // Ignore exceptions + } + + Assert.Equal(theoryData.ExpectedTagList, testTelemetryClient.ExportedItems); + } + + public static TheoryData> GetConfiguration_ExpectedTagList_TheoryData() + { + return new TheoryData> + { + new ConfigurationManagerTelemetryTheoryData("Success-retrieve from endpoint") + { + MetadataAddress = OpenIdConfigData.AccountsGoogle, + ConfigurationValidator = new OpenIdConnectConfigurationValidator(), + ExpectedTagList = new Dictionary + { + { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AccountsGoogle }, + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.FirstRefresh }, + } + }, + new ConfigurationManagerTelemetryTheoryData("Failure-invalid metadata address") + { + MetadataAddress = OpenIdConfigData.HttpsBadUri, + ConfigurationValidator = new OpenIdConnectConfigurationValidator(), + ExpectedTagList = new Dictionary + { + { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.HttpsBadUri }, + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.FirstRefresh }, + { TelemetryConstants.ExceptionTypeTag, new IOException().GetType().ToString() }, + } + }, + new ConfigurationManagerTelemetryTheoryData("Failure-invalid config") + { + MetadataAddress = OpenIdConfigData.JsonFile, + DocumentRetriever = new FileDocumentRetriever(), + // The config being loaded has two keys; require three to force invalidity + ConfigurationValidator = new OpenIdConnectConfigurationValidator() { MinimumNumberOfKeys = 3 }, + ExpectedTagList = new Dictionary + { + { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.JsonFile }, + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.FirstRefresh }, + { TelemetryConstants.ExceptionTypeTag, new InvalidConfigurationException().GetType().ToString() }, + } + }, + new ConfigurationManagerTelemetryTheoryData("Success-refresh") + { + MetadataAddress = OpenIdConfigData.AADCommonUrl, + ConfigurationValidator = new OpenIdConnectConfigurationValidator(), + SyncAfter = DateTime.UtcNow - TimeSpan.FromDays(2), + ExpectedTagList = new Dictionary + { + { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AADCommonUrl }, + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Automatic }, + } + }, + }; + } + } + + public class ConfigurationManagerTelemetryTheoryData : TheoryDataBase where T : class + { + public ConfigurationManagerTelemetryTheoryData(string testId) : base(testId) { } + + public string MetadataAddress { get; set; } + + public IDocumentRetriever DocumentRetriever { get; set; } = new HttpDocumentRetriever(); + + public IConfigurationValidator ConfigurationValidator { get; set; } + + public DateTimeOffset? SyncAfter { get; set; } = null; + + public Dictionary ExpectedTagList { get; set; } + } +} diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj index 1cbfb9eed3..c7913cbd45 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj @@ -19,14 +19,15 @@ + - - + + diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs new file mode 100644 index 0000000000..c7cd1b71fc --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens.Telemetry.Tests +{ + public class MockTelemetryClient : ITelemetryClient + { + public Dictionary ExportedItems = new Dictionary(); + public Dictionary ExportedHistogramItems = new Dictionary(); + + public void ClearExportedItems() + { + ExportedItems.Clear(); + } + + public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) + { + ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); + ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); + ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus); + } + + public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, Exception exception) + { + ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); + ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); + ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus); + ExportedItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString()); + } + + public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration) + { + ExportedHistogramItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); + ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); + } + + public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration, Exception exception) + { + ExportedHistogramItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); + ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); + ExportedHistogramItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString()); + } + } +} diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs new file mode 100644 index 0000000000..f181c00961 --- /dev/null +++ b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Telemetry; +using Microsoft.IdentityModel.Tokens.Telemetry.Tests; +using Microsoft.IdentityModel.Validators; +using Xunit; + +namespace System.IdentityModel.Tokens.Jwt.Tests +{ + public class JwtSecurityTokenHandlerTelemetryTests + { + [Fact] + public void ValidateToken_ExpectedTagsExist() + { + var invalidIssuerConfig = new OpenIdConnectConfiguration() + { + TokenEndpoint = Default.Issuer + "oauth/token", + Issuer = Default.Issuer + "2" + }; + invalidIssuerConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); + + var validationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(invalidIssuerConfig), + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = false + }; + + var testTelemetryClient = new MockTelemetryClient(); + try + { + AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).ConfigurationManagerV1 = validationParameters.ConfigurationManager; + var handler = new JwtSecurityTokenHandler() + { + TelemetryClient = testTelemetryClient + }; + handler.ValidateToken(Default.AsymmetricJws, validationParameters, out _); + } + catch (Exception) + { + // ignore exceptions + } + + var expectedCounterTagList = new Dictionary + { + // metadata address is null because the configuration manager is made using an invalid config to trigger an exception + { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }, + { TelemetryConstants.MetadataAddressTag, null }, + { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Lkg }, + }; + + Assert.Equal(expectedCounterTagList, testTelemetryClient.ExportedItems); + } + } +} diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj b/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj index e68913b3b0..55c2caee3c 100644 --- a/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj +++ b/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj @@ -13,8 +13,10 @@ + +