From 9ac0e2118d50b7cea30df69520d0850d7298e024 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 16:55:35 -0800 Subject: [PATCH 01/12] Revert "App context switch to allow blocking or non-blocking GetConfiguration (#3106)" This reverts commit f56ea1641f4ed6295c68df588a0946f1b9842230. --- .../Configuration/ConfigurationManager.cs | 205 +++----- .../ConfigurationManager_Blocking.cs | 161 ------ .../GlobalSuppressions.cs | 5 - .../InternalAPI.Unshipped.txt | 2 +- .../PublicAPI.Unshipped.txt | 1 - .../AppContextSwitches.cs | 14 - .../Telemetry/ITelemetryClient.cs | 4 - .../Telemetry/TelemetryClient.cs | 32 +- .../Telemetry/TelemetryConstants.cs | 5 - .../Telemetry/TelemetryDataRecorder.cs | 14 +- .../ConfigurationManagerTelemetryTests.cs | 85 +-- .../ConfigurationManagerTests.cs | 497 +++--------------- .../ExtensibilityTests.cs | 5 - .../ResetAppContextSwitchesAttribute.cs | 23 - .../Telemetry/MockTelemetryClient.cs | 8 - 15 files changed, 151 insertions(+), 910 deletions(-) delete mode 100644 src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager_Blocking.cs delete mode 100644 test/Microsoft.IdentityModel.TestUtils/ResetAppContextSwitchesAttribute.cs diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index 200288572e..6d1fb7a5a2 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -18,27 +18,17 @@ namespace Microsoft.IdentityModel.Protocols /// /// The type of . [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] - public partial class ConfigurationManager : BaseConfigurationManager, IConfigurationManager where T : class + public class ConfigurationManager : BaseConfigurationManager, IConfigurationManager where T : class { -#pragma warning disable IDE0044 // Add readonly modifier -#pragma warning disable CS0649 // Unused, it gets used in tests. - internal Action _onBackgroundTaskFinish; -#pragma warning restore CS0649 // Unused -#pragma warning restore IDE0044 // Add readonly modifier - + // To prevent tearing, this needs to be only updated through AtomicUpdateSyncAfter. + // Reads should be done through the property SyncAfter. private DateTime _syncAfter = DateTime.MinValue; - private DateTime SyncAfter - { - get => _syncAfter; - set => AtomicUpdateDateTime(ref _syncAfter, ref value); - } + private DateTime SyncAfter => _syncAfter; + // See comment above, this should only be updated through AtomicUpdateLastRequestRefresh, + // read through LastRequestRefresh. private DateTime _lastRequestRefresh = DateTime.MinValue; - private DateTime LastRequestRefresh - { - get => _lastRequestRefresh; - set => AtomicUpdateDateTime(ref _lastRequestRefresh, ref value); - } + private DateTime LastRequestRefresh => _lastRequestRefresh; private bool _isFirstRefreshRequest = true; private readonly SemaphoreSlim _configurationNullLock = new SemaphoreSlim(1); @@ -63,32 +53,8 @@ private DateTime LastRequestRefresh // requesting a refresh, so it should be done immediately so the next // call to GetConfiguration will return new configuration if the minimum // refresh interval has passed. - private bool _refreshRequested; + bool _refreshRequested; - // Wait handle used to signal a background task to update the configuration. - // Handle starts unset, and AutoResetEvent.Set() sets it, this indicates that - // the background refresh task should immediately run. - private readonly AutoResetEvent _updateMetadataEvent = new(false); - - // Background task that updates the configuration. Signaled with _updateMetadataEvent. - // Task should be started with EnsureBackgroundRefreshTaskIsRunning. - private Task _updateMetadataTask; - - private readonly CancellationTokenSource _backgroundRefreshTaskCancellationTokenSource; - - /// - /// Requests that background tasks be shutdown. - /// This only applies if 'Switch.Microsoft.IdentityModel.UpdateConfigAsBlocking' is not set or set to false. - /// Note that this does not influence . - /// If the background task stops, the next time the task would be signaled, the task will be - /// restarted unless is called. - /// If using a background task, the cannot - /// be used after calling this method. - /// - public void ShutdownBackgroundTask() - { - _backgroundRefreshTaskCancellationTokenSource.Cancel(); - } /// /// Instantiates a new that manages automatic and controls refreshing on configuration data. @@ -151,10 +117,6 @@ public ConfigurationManager(string metadataAddress, IConfigurationRetriever c MetadataAddress = metadataAddress; _docRetriever = docRetriever; _configRetriever = configRetriever; - _backgroundRefreshTaskCancellationTokenSource = new CancellationTokenSource(); - - if (!AppContextSwitches.UpdateConfigAsBlocking) - EnsureBackgroundRefreshTaskIsRunning(); } /// @@ -191,14 +153,8 @@ public ConfigurationManager(string metadataAddress, IConfigurationRetriever c /// /// Obtains an updated version of Configuration. /// - /// Configuration of type . - /// - /// If the time since the last call is less than - /// then is not called and the current Configuration is returned. - /// If the configuration is not able to be updated, but a previous configuration was previously retrieved, the previous configuration is returned. - /// - /// Throw if the configuration is unable to be retrieved. - /// Throw if the configuration fails to be validated by the . + /// Configuration of type T. + /// If the time since the last call is less than then is not called and the current Configuration is returned. public async Task GetConfigurationAsync() { return await GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false); @@ -208,27 +164,13 @@ public async Task GetConfigurationAsync() /// Obtains an updated version of Configuration. /// /// CancellationToken - /// Configuration of type . - /// - /// If the time since the last call is less than - /// then is not called and the current Configuration is returned. - /// If the configuration is not able to be updated, but a previous configuration was previously retrieved, the previous configuration is returned. - /// - /// Throw if the configuration is unable to be retrieved. - /// Throw if the configuration fails to be validated by the . + /// Configuration of type T. + /// If the time since the last call is less than then is not called and the current Configuration is returned. public virtual async Task GetConfigurationAsync(CancellationToken cancel) { if (_currentConfiguration != null && SyncAfter > _timeProvider.GetUtcNow()) return _currentConfiguration; - if (AppContextSwitches.UpdateConfigAsBlocking) - return await GetConfigurationWithBlockingAsync(cancel).ConfigureAwait(false); - else - return await GetConfigurationWithBackgroundTaskUpdatesAsync(cancel).ConfigureAwait(false); - } - - private async Task GetConfigurationWithBackgroundTaskUpdatesAsync(CancellationToken cancel) - { Exception fetchMetadataFailure = null; // LOGIC @@ -304,10 +246,38 @@ private async Task GetConfigurationWithBackgroundTaskUpdatesAsync(Cancellatio { if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) { - if (SyncAfter <= _timeProvider.GetUtcNow()) + if (_refreshRequested) { - EnsureBackgroundRefreshTaskIsRunning(); - _updateMetadataEvent.Set(); + _refreshRequested = false; + + try + { + // Log as manual because RequestRefresh was called + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Manual); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch + { } +#pragma warning restore CA1031 // Do not catch general exception types + + UpdateCurrentConfiguration(); + } + else if (SyncAfter <= _timeProvider.GetUtcNow()) + { + try + { + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Automatic); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch + { } +#pragma warning restore CA1031 // Do not catch general exception types + + _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); } else { @@ -330,53 +300,6 @@ private async Task GetConfigurationWithBackgroundTaskUpdatesAsync(Cancellatio fetchMetadataFailure)); } - private void EnsureBackgroundRefreshTaskIsRunning() - { - if (_backgroundRefreshTaskCancellationTokenSource.IsCancellationRequested) - return; - - if (_updateMetadataTask == null || _updateMetadataTask.Status != TaskStatus.Running) - _updateMetadataTask = Task.Run(UpdateCurrentConfigurationUsingSignals); - } - - private void TelemetryForUpdate() - { - var updateMode = _refreshRequested ? TelemetryConstants.Protocols.Manual : TelemetryConstants.Protocols.Automatic; - - if (_refreshRequested) - _refreshRequested = false; - - try - { - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - updateMode); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch - { } -#pragma warning restore CA1031 // Do not catch general exception types - } - - private void UpdateCurrentConfigurationUsingSignals() - { - try - { - while (!_backgroundRefreshTaskCancellationTokenSource.IsCancellationRequested) - { - if (_updateMetadataEvent.WaitOne(500)) - { - UpdateCurrentConfiguration(); - _onBackgroundTaskFinish?.Invoke(); - } - } - } - catch (Exception ex) - { - TelemetryClient.LogBackgroundConfigurationRefreshFailure(MetadataAddress, ex); - } - } - /// /// This should be called when the configuration needs to be updated either from RequestRefresh or AutomaticRefresh /// The Caller should first check the state checking state using: @@ -384,7 +307,6 @@ private void UpdateCurrentConfigurationUsingSignals() /// private void UpdateCurrentConfiguration() { - TelemetryForUpdate(); #pragma warning disable CA1031 // Do not catch general exception types long startTimestamp = _timeProvider.GetTimestamp(); @@ -446,17 +368,25 @@ private void UpdateConfiguration(T configuration) _currentConfiguration = configuration; var newSyncTime = DateTimeUtil.Add(_timeProvider.GetUtcNow().UtcDateTime, AutomaticRefreshInterval + TimeSpan.FromSeconds(new Random().Next((int)AutomaticRefreshInterval.TotalSeconds / 20))); - SyncAfter = newSyncTime; + AtomicUpdateSyncAfter(newSyncTime); } - private static void AtomicUpdateDateTime(ref DateTime field, ref DateTime value) + private void AtomicUpdateSyncAfter(DateTime syncAfter) { // DateTime's backing data is safe to treat as a long if the Kind is not local. - // time will always be updated to a UTC time. + // _syncAfter will always be updated to a UTC time. // See the implementation of ToBinary on DateTime. Interlocked.Exchange( - ref Unsafe.As(ref field), - Unsafe.As(ref value)); + ref Unsafe.As(ref _syncAfter), + Unsafe.As(ref syncAfter)); + } + + private void AtomicUpdateLastRequestRefresh(DateTime lastRequestRefresh) + { + // See the comment in AtomicUpdateSyncAfter. + Interlocked.Exchange( + ref Unsafe.As(ref _lastRequestRefresh), + Unsafe.As(ref lastRequestRefresh)); } /// @@ -478,32 +408,15 @@ public override async Task GetBaseConfigurationAsync(Cancella /// If == then this method does nothing. /// public override void RequestRefresh() - { - if (AppContextSwitches.UpdateConfigAsBlocking) - { - RequestRefreshBlocking(); - } - else - { - RequestRefreshBackgroundThread(); - } - } - - private void RequestRefreshBackgroundThread() { DateTime now = _timeProvider.GetUtcNow().UtcDateTime; if (now >= DateTimeUtil.Add(LastRequestRefresh, RefreshInterval) || _isFirstRefreshRequest) { _isFirstRefreshRequest = false; - - if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) - { - _refreshRequested = true; - LastRequestRefresh = now; - EnsureBackgroundRefreshTaskIsRunning(); - _updateMetadataEvent.Set(); - } + AtomicUpdateSyncAfter(now); + AtomicUpdateLastRequestRefresh(now); + _refreshRequested = true; } } diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager_Blocking.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager_Blocking.cs deleted file mode 100644 index 8ecf701233..0000000000 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager_Blocking.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading.Tasks; -using System.Threading; -using System; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Protocols.Configuration; -using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Telemetry; - -namespace Microsoft.IdentityModel.Protocols -{ - partial class ConfigurationManager where T : class - { - private readonly SemaphoreSlim _refreshLock = new(1); - - private TimeSpan _bootstrapRefreshInterval = TimeSpan.FromSeconds(1); - - private async Task GetConfigurationWithBlockingAsync(CancellationToken cancel) - { - Exception _fetchMetadataFailure = null; - await _refreshLock.WaitAsync(cancel).ConfigureAwait(false); - - long startTimestamp = _timeProvider.GetTimestamp(); - - try - { - if (SyncAfter <= _timeProvider.GetUtcNow()) - { - 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.. - var configuration = await _configRetriever.GetConfigurationAsync(MetadataAddress, _docRetriever, CancellationToken.None).ConfigureAwait(false); - - var elapsedTime = _timeProvider.GetElapsedTime(startTimestamp); - TelemetryClient.LogConfigurationRetrievalDuration( - MetadataAddress, - elapsedTime); - - if (_configValidator != null) - { - ConfigurationValidationResult result = _configValidator.Validate(configuration); - if (!result.Succeeded) - throw LogHelper.LogExceptionMessage(new InvalidConfigurationException(LogHelper.FormatInvariant(LogMessages.IDX20810, result.ErrorMessage))); - } - - LastRequestRefresh = _timeProvider.GetUtcNow().UtcDateTime; - TelemetryForUpdateBlocking(); - UpdateConfiguration(configuration); - } - catch (Exception ex) - { - _fetchMetadataFailure = ex; - - if (_currentConfiguration == null) // Throw an exception if there's no configuration to return. - { - if (_bootstrapRefreshInterval < RefreshInterval) - { - // Adopt exponential backoff for bootstrap refresh interval with a decorrelated jitter if it is not longer than the refresh interval. - TimeSpan _bootstrapRefreshIntervalWithJitter = TimeSpan.FromSeconds(new Random().Next((int)_bootstrapRefreshInterval.TotalSeconds)); - _bootstrapRefreshInterval += _bootstrapRefreshInterval; - _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, _bootstrapRefreshIntervalWithJitter); - } - else - { - _syncAfter = DateTimeUtil.Add( - _timeProvider.GetUtcNow().UtcDateTime, - AutomaticRefreshInterval < RefreshInterval ? AutomaticRefreshInterval : RefreshInterval); - } - - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.FirstRefresh, - ex); - - throw LogHelper.LogExceptionMessage( - new InvalidOperationException( - LogHelper.FormatInvariant(LogMessages.IDX20803, LogHelper.MarkAsNonPII(MetadataAddress ?? "null"), LogHelper.MarkAsNonPII(_syncAfter), LogHelper.MarkAsNonPII(ex)), ex)); - } - else - { - _syncAfter = DateTimeUtil.Add( - _timeProvider.GetUtcNow().UtcDateTime, - AutomaticRefreshInterval < RefreshInterval ? AutomaticRefreshInterval : RefreshInterval); - - var elapsedTime = _timeProvider.GetElapsedTime(startTimestamp); - - TelemetryClient.LogConfigurationRetrievalDuration( - MetadataAddress, - elapsedTime, - ex); - - LogHelper.LogExceptionMessage( - new InvalidOperationException( - LogHelper.FormatInvariant(LogMessages.IDX20806, LogHelper.MarkAsNonPII(MetadataAddress ?? "null"), LogHelper.MarkAsNonPII(ex)), ex)); - } - } - } - - // Stale metadata is better than no metadata - if (_currentConfiguration != null) - return _currentConfiguration; - else - throw LogHelper.LogExceptionMessage( - new InvalidOperationException( - LogHelper.FormatInvariant( - LogMessages.IDX20803, - LogHelper.MarkAsNonPII(MetadataAddress ?? "null"), - LogHelper.MarkAsNonPII(_syncAfter), - LogHelper.MarkAsNonPII(_fetchMetadataFailure)), - _fetchMetadataFailure)); - } - finally - { - _refreshLock.Release(); - } - } - - private void RequestRefreshBlocking() - { - DateTime now = _timeProvider.GetUtcNow().UtcDateTime; - - if (now >= DateTimeUtil.Add(LastRequestRefresh, RefreshInterval) || _isFirstRefreshRequest) - { - _refreshRequested = true; - _syncAfter = now; - _isFirstRefreshRequest = false; - } - } - - private void TelemetryForUpdateBlocking() - { - string updateMode; - - if (_currentConfiguration is null) - { - updateMode = TelemetryConstants.Protocols.FirstRefresh; - } - else - { - updateMode = _refreshRequested ? TelemetryConstants.Protocols.Manual : TelemetryConstants.Protocols.Automatic; - - if (_refreshRequested) - _refreshRequested = false; - } - - try - { - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - updateMode); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch - { } -#pragma warning restore CA1031 // Do not catch general exception types - } - } -} diff --git a/src/Microsoft.IdentityModel.Protocols/GlobalSuppressions.cs b/src/Microsoft.IdentityModel.Protocols/GlobalSuppressions.cs index 9efe0a190c..901dc16ac8 100644 --- a/src/Microsoft.IdentityModel.Protocols/GlobalSuppressions.cs +++ b/src/Microsoft.IdentityModel.Protocols/GlobalSuppressions.cs @@ -12,8 +12,3 @@ #if NET6_0_OR_GREATER [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Protocols.AuthenticationProtocolMessage.BuildRedirectUrl~System.String")] #endif - -[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", - Justification = "Background thread needs to never throw an unhandled exception.", - Scope = "member", - Target = "~M:Microsoft.IdentityModel.Protocols.ConfigurationManager`1.UpdateCurrentConfigurationUsingSignals")] diff --git a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt index 06f99d657b..98597bed68 100644 --- a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt @@ -1,2 +1,2 @@ Microsoft.IdentityModel.Protocols.ConfigurationManager.TelemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient -Microsoft.IdentityModel.Protocols.ConfigurationManager._onBackgroundTaskFinish -> System.Action +Microsoft.IdentityModel.Protocols.ConfigurationManager.TimeProvider -> System.TimeProvider diff --git a/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt index 29c5255246..5c617a3121 100644 --- a/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt @@ -1,3 +1,2 @@ Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.HttpVersion.get -> System.Version Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.HttpVersion.set -> void -Microsoft.IdentityModel.Protocols.ConfigurationManager.ShutdownBackgroundTask() -> void diff --git a/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs b/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs index eddf948ce5..0105963f08 100644 --- a/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs +++ b/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs @@ -70,17 +70,6 @@ internal static class AppContextSwitches internal static bool UseRfcDefinitionOfEpkAndKid => _useRfcDefinitionOfEpkAndKid ??= (AppContext.TryGetSwitch(UseRfcDefinitionOfEpkAndKidSwitch, out bool isEnabled) && isEnabled); - /// - /// Enabling this switch will cause the configuration manager to block other requests to GetConfigurationAsync if a request is already in progress. - /// The default configuration refresh behavior is if a request is already in progress, the current configuration will be returned until the ongoing request is completed on - /// a background thread. - /// - internal const string UpdateConfigAsBlockingSwitch = "Switch.Microsoft.IdentityModel.UpdateConfigAsBlocking"; - - private static bool? _updateConfigAsBlockingCall; - - internal static bool UpdateConfigAsBlocking => _updateConfigAsBlockingCall ??= (AppContext.TryGetSwitch(UpdateConfigAsBlockingSwitch, out bool blockingCall) && blockingCall); - /// /// Used for testing to reset all switches to its default value. /// @@ -97,9 +86,6 @@ internal static void ResetAllSwitches() _useRfcDefinitionOfEpkAndKid = null; AppContext.SetSwitch(UseRfcDefinitionOfEpkAndKidSwitch, false); - - _updateConfigAsBlockingCall = null; - AppContext.SetSwitch(UpdateConfigAsBlockingSwitch, false); } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs index 0ab0c083ff..656fb6bdf5 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs @@ -24,9 +24,5 @@ internal void IncrementConfigurationRefreshRequestCounter( string metadataAddress, string operationStatus, Exception exception); - - internal void LogBackgroundConfigurationRefreshFailure( - string metadataAddress, - Exception exception); } } diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs index 15d1404486..20cbfcf15b 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Diagnostics; using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; namespace Microsoft.IdentityModel.Telemetry { @@ -16,19 +14,13 @@ internal class TelemetryClient : ITelemetryClient { public string ClientVer = IdentityModelTelemetryUtil.ClientVer; - private KeyValuePair _blockingTagValue = new( - TelemetryConstants.BlockingTypeTag, - AppContextSwitches.UpdateConfigAsBlocking.ToString() - ); - public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) { var tagList = new TagList() { { TelemetryConstants.IdentityModelVersionTag, ClientVer }, { TelemetryConstants.MetadataAddressTag, metadataAddress }, - { TelemetryConstants.OperationStatusTag, operationStatus }, - _blockingTagValue + { TelemetryConstants.OperationStatusTag, operationStatus } }; TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(tagList); @@ -41,8 +33,7 @@ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, { TelemetryConstants.IdentityModelVersionTag, ClientVer }, { TelemetryConstants.MetadataAddressTag, metadataAddress }, { TelemetryConstants.OperationStatusTag, operationStatus }, - { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() }, - _blockingTagValue + { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() } }; TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(tagList); @@ -54,7 +45,6 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan o { { TelemetryConstants.IdentityModelVersionTag, ClientVer }, { TelemetryConstants.MetadataAddressTag, metadataAddress }, - _blockingTagValue }; long durationInMilliseconds = (long)operationDuration.TotalMilliseconds; @@ -67,27 +57,11 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan o { { TelemetryConstants.IdentityModelVersionTag, ClientVer }, { TelemetryConstants.MetadataAddressTag, metadataAddress }, - { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() }, - _blockingTagValue + { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() } }; long durationInMilliseconds = (long)operationDuration.TotalMilliseconds; TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(durationInMilliseconds, tagList); } - - public void LogBackgroundConfigurationRefreshFailure( - string metadataAddress, - Exception exception) - { - var tagList = new TagList() - { - { TelemetryConstants.IdentityModelVersionTag, ClientVer }, - { TelemetryConstants.MetadataAddressTag, metadataAddress }, - { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() }, - _blockingTagValue - }; - - TelemetryDataRecorder.IncrementBackgroundConfigurationRefreshFailureCounter(tagList); - } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs index 643b603305..a4d9449e75 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs @@ -27,11 +27,6 @@ internal static class TelemetryConstants /// public const string ExceptionTypeTag = "ExceptionType"; - /// - /// Telemetry tag indicating if the update was blocking. - /// - public const string BlockingTypeTag = "Blocking"; - public static class Protocols { // Configuration manager refresh statuses diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs index cafcbe9b33..5023606081 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs @@ -26,16 +26,9 @@ internal class TelemetryDataRecorder /// /// Counter to capture configuration refresh requests to ConfigurationManager. /// - internal static readonly Counter ConfigurationManagerCounter = IdentityModelMeter.CreateCounter(IdentityModelConfigurationManagerCounterName, description: IdentityModelConfigurationManagerCounterDescription); internal const string IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager"; internal const string IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations."; - - /// - /// Counter to capture background refresh failures in the ConfigurationManager. - /// - internal static readonly Counter BackgroundConfigurationRefreshFailureCounter = IdentityModelMeter.CreateCounter(BackgroundConfigurationRefreshFailureCounterName, description: BackgroundConfigurationRefreshFailureCounterDescription); - internal const string BackgroundConfigurationRefreshFailureCounterName = "IdentityModelConfigurationManagerBackgroundRefreshFailure"; - internal const string BackgroundConfigurationRefreshFailureCounterDescription = "Counter capturing configuration manager background refresh failures."; + internal static readonly Counter ConfigurationManagerCounter = IdentityModelMeter.CreateCounter(IdentityModelConfigurationManagerCounterName, description: IdentityModelConfigurationManagerCounterDescription); /// /// Histogram to capture total duration of configuration retrieval by ConfigurationManager in milliseconds. @@ -54,10 +47,5 @@ internal static void IncrementConfigurationRefreshRequestCounter(in TagList tagL { ConfigurationManagerCounter.Add(1, tagList); } - - internal static void IncrementBackgroundConfigurationRefreshFailureCounter(in TagList tagList) - { - BackgroundConfigurationRefreshFailureCounter.Add(1, tagList); - } } } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs index e54911535c..583781a2d8 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs @@ -6,36 +6,20 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Time.Testing; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Protocols.OpenIdConnect.Configuration; +using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Telemetry; using Microsoft.IdentityModel.Telemetry.Tests; -using Microsoft.IdentityModel.TestUtils; -using Microsoft.IdentityModel.Tokens; using Xunit; namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests { - [ResetAppContextSwitches] - [Collection(nameof(AppContextSwitches.UpdateConfigAsBlocking))] public class ConfigurationManagerTelemetryTests { [Fact] public async Task RequestRefresh_ExpectedTagsExist() - { - await RequestRefresh_ExpectedTagsBody(); - } - - [Fact] - public async Task RequestRefresh_ExpectedTagsExist_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await RequestRefresh_ExpectedTagsBody(true); - } - - private static async Task RequestRefresh_ExpectedTagsBody(bool blocking = false) { // arrange var testTelemetryClient = new MockTelemetryClient(); @@ -47,20 +31,16 @@ private static async Task RequestRefresh_ExpectedTagsBody(bool blocking = false) { TelemetryClient = testTelemetryClient }; - - AutoResetEvent resetEvent = ConfigurationManagerTests.SetupResetEvent(configurationManager, blocking); + var cancel = new CancellationToken(); // act // Retrieve the configuration for the first time - await configurationManager.GetConfigurationAsync(); + await configurationManager.GetConfigurationAsync(cancel); testTelemetryClient.ClearExportedItems(); // Manually request a config refresh configurationManager.RequestRefresh(); - await configurationManager.GetConfigurationAsync(); - - if (!blocking) - ConfigurationManagerTests.WaitOrFail(resetEvent); + await configurationManager.GetConfigurationAsync(cancel); // assert var expectedCounterTagList = new Dictionary @@ -76,34 +56,12 @@ private static async Task RequestRefresh_ExpectedTagsBody(bool blocking = false) { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer } }; - configurationManager.ShutdownBackgroundTask(); - - await ConfigurationManagerTests.PollForConditionAsync( - () => expectedCounterTagList.Count == testTelemetryClient.ExportedItems.Count && - expectedHistogramTagList.Count == testTelemetryClient.ExportedHistogramItems.Count, - TimeSpan.FromMilliseconds(250), - TimeSpan.FromSeconds(20)); - 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) - { - await GetConfigurationAsync_ExpectedTagList_Body(theoryData); - } - - [Theory, MemberData(nameof(GetConfiguration_ExpectedTagList_TheoryData), DisableDiscoveryEnumeration = true)] - public async Task GetConfigurationAsync_ExpectedTagsExist_Blocking(ConfigurationManagerTelemetryTheoryData theoryData) - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await GetConfigurationAsync_ExpectedTagList_Body(theoryData, true); - } - - private static async Task GetConfigurationAsync_ExpectedTagList_Body( - ConfigurationManagerTelemetryTheoryData theoryData, - bool blocking = false) { var testTelemetryClient = new MockTelemetryClient(); @@ -116,42 +74,21 @@ private static async Task GetConfigurationAsync_ExpectedTagList_Body( TelemetryClient = testTelemetryClient }; - AutoResetEvent resetEvent = ConfigurationManagerTests.SetupResetEvent(configurationManager, blocking); - - var timeProvider = new FakeTimeProvider(); - TestUtilities.SetField(configurationManager, "_timeProvider", timeProvider); - - OpenIdConnectConfiguration firstConfig = null; - OpenIdConnectConfiguration secondConfig = null; - try { - firstConfig = await configurationManager.GetConfigurationAsync(); - if (theoryData.AdjustTime.HasValue) + await configurationManager.GetConfigurationAsync(); + if (theoryData.SyncAfter != null) { testTelemetryClient.ClearExportedItems(); - timeProvider.Advance(theoryData.AdjustTime.Value); - secondConfig = await configurationManager.GetConfigurationAsync(); - - if (!blocking) - ConfigurationManagerTests.WaitOrFail(resetEvent); + TestUtilities.SetField(configurationManager, "_syncAfter", theoryData.SyncAfter); + await configurationManager.GetConfigurationAsync(); } + } catch (Exception) { // Ignore exceptions } - finally - { - configurationManager.ShutdownBackgroundTask(); - } - - await ConfigurationManagerTests.PollForConditionAsync( - () => theoryData.ExpectedTagList.Count == testTelemetryClient.ExportedItems.Count, - TimeSpan.FromMilliseconds(250), - TimeSpan.FromSeconds(20)); - - DateTime syncAfter = (DateTime)TestUtilities.GetField(configurationManager, "_syncAfter"); Assert.Equal(theoryData.ExpectedTagList, testTelemetryClient.ExportedItems); } @@ -201,7 +138,7 @@ public static TheoryData { { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AADCommonUrl }, @@ -223,7 +160,7 @@ public ConfigurationManagerTelemetryTheoryData(string testId) : base(testId) { } public IConfigurationValidator ConfigurationValidator { get; set; } - public TimeSpan? AdjustTime { get; set; } + public DateTime? SyncAfter { get; set; } = null; public Dictionary ExpectedTagList { get; set; } } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index 61ce51ea94..1ee7645570 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -20,8 +20,6 @@ namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests { - [ResetAppContextSwitches] - [Collection(nameof(AppContextSwitches.UpdateConfigAsBlocking))] public class ConfigurationManagerTests { /// @@ -34,17 +32,15 @@ public class ConfigurationManagerTests [Theory, MemberData(nameof(GetPublicMetadataTheoryData), DisableDiscoveryEnumeration = true)] public async Task GetPublicMetadata(ConfigurationManagerTheoryData theoryData) { - var cts = new CancellationTokenSource(); CompareContext context = TestUtilities.WriteHeader($"{this}.GetPublicMetadata", theoryData); - - var configurationManager = new ConfigurationManager( - theoryData.MetadataAddress, - theoryData.ConfigurationRetriever, - theoryData.DocumentRetriever, - theoryData.ConfigurationValidator); - try { + var configurationManager = new ConfigurationManager( + theoryData.MetadataAddress, + theoryData.ConfigurationRetriever, + theoryData.DocumentRetriever, + theoryData.ConfigurationValidator); + var configuration = await configurationManager.GetConfigurationAsync(CancellationToken.None); Assert.NotNull(configuration); @@ -54,10 +50,6 @@ public async Task GetPublicMetadata(ConfigurationManagerTheoryData @@ -182,24 +174,9 @@ public void Defaults() [Fact] public async Task FetchMetadataFailureTest() - { - await FetchMetadataFailureTestBody(); - } - - [Fact] - public async Task FetchMetadataFailureTest_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - - await FetchMetadataFailureTestBody(); - } - - private async Task FetchMetadataFailureTestBody() { var context = new CompareContext($"{this}.FetchMetadataFailureTest"); - var cts = new CancellationTokenSource(); - var documentRetriever = new HttpDocumentRetriever(HttpResponseMessageUtils.SetupHttpClientThatReturns("OpenIdConnectMetadata.json", HttpStatusCode.NotFound)); var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), documentRetriever); @@ -226,10 +203,6 @@ private async Task FetchMetadataFailureTestBody() IdentityComparer.AreEqual(firstFetchMetadataFailure, secondFetchMetadataFailure, context); } } - finally - { - configManager.ShutdownBackgroundTask(); - } TestUtilities.AssertFailIfErrors(context); } @@ -243,14 +216,11 @@ public async Task VerifyInterlockGuardForGetConfigurationAsync() InMemoryDocumentRetriever inMemoryDocumentRetriever = InMemoryDocumentRetrieverWithEvents(waitEvent, signalEvent); waitEvent.Set(); - var cts = new CancellationTokenSource(); - var configurationManager = new ConfigurationManager( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), inMemoryDocumentRetriever); - OpenIdConnectConfiguration configuration = await configurationManager.GetConfigurationAsync(); // InMemoryDocumentRetrieverWithEvents will block until waitEvent.Set() is called. @@ -278,9 +248,6 @@ public async Task VerifyInterlockGuardForGetConfigurationAsync() // Configuration should be AADCommonV1Config configuration = await configurationManager.GetConfigurationAsync(); - - configurationManager.ShutdownBackgroundTask(); - Assert.True(configuration.Issuer.Equals(OpenIdConfigData.AADCommonV1Config.Issuer), $"configuration.Issuer from configurationManager was not as expected," + $" configuration.Issuer: '{configuration.Issuer}' != expected: '{OpenIdConfigData.AADCommonV1Config.Issuer}'."); @@ -294,15 +261,12 @@ public async Task BootstrapRefreshIntervalTest() var documentRetriever = new HttpDocumentRetriever( HttpResponseMessageUtils.SetupHttpClientThatReturns("OpenIdConnectMetadata.json", HttpStatusCode.NotFound)); - var cts = new CancellationTokenSource(); - var configManager = new ConfigurationManager( "OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), documentRetriever) { RefreshInterval = TimeSpan.FromSeconds(2) }; - // ConfigurationManager._syncAfter is set to DateTimeOffset.MinValue on startup // If obtaining the metadata fails due to error, the value should not change try @@ -312,64 +276,14 @@ public async Task BootstrapRefreshIntervalTest() catch (Exception firstFetchMetadataFailure) { // _syncAfter should not have been changed, because the fetch failed. - var syncAfter = TestUtilities.GetField(configManager, "_syncAfter"); - if ((DateTime)syncAfter != DateTime.MinValue) - context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTime.MinValue}'."); + DateTime syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter"); + if (syncAfter != DateTime.MinValue) + context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTimeOffset.MinValue}'."); if (firstFetchMetadataFailure.InnerException == null) context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); - // Fetch metadata again during refresh interval, the exception should be same from above. - try - { - configManager.RequestRefresh(); - var configuration = await configManager.GetConfigurationAsync(); - } - catch (Exception secondFetchMetadataFailure) - { - if (secondFetchMetadataFailure.InnerException == null) - context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); - - // _syncAfter should not have been changed, because the fetch failed. - syncAfter = TestUtilities.GetField(configManager, "_syncAfter"); - if ((DateTime)syncAfter != DateTime.MinValue) - context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTime.MinValue}'."); - - IdentityComparer.AreEqual(firstFetchMetadataFailure, secondFetchMetadataFailure, context); - } - } - finally - { - configManager.ShutdownBackgroundTask(); - } - - TestUtilities.AssertFailIfErrors(context); - } - - [Fact] - public async Task BootstrapRefreshIntervalTest_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - - var context = new CompareContext($"{this}.BootstrapRefreshIntervalTest_Blocking"); - - var documentRetriever = new HttpDocumentRetriever(HttpResponseMessageUtils.SetupHttpClientThatReturns("OpenIdConnectMetadata.json", HttpStatusCode.NotFound)); - var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), documentRetriever) { RefreshInterval = TimeSpan.FromSeconds(2) }; - - // First time to fetch metadata. - try - { - var configuration = await configManager.GetConfigurationAsync(); - } - catch (Exception firstFetchMetadataFailure) - { - // Refresh interval is BootstrapRefreshInterval - var syncAfter = configManager.GetType().GetField("_syncAfter", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(configManager); - if ((DateTime)syncAfter > DateTime.UtcNow + TimeSpan.FromSeconds(2)) - context.AddDiff($"Expected the refresh interval is longer than 2 seconds."); - - if (firstFetchMetadataFailure.InnerException == null) - context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); + DateTime requestTime = DateTime.UtcNow; // Fetch metadata again during refresh interval, the exception should be same from above. try @@ -382,11 +296,11 @@ public async Task BootstrapRefreshIntervalTest_Blocking() if (secondFetchMetadataFailure.InnerException == null) context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); - syncAfter = configManager.GetType().GetField("_syncAfter", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(configManager); + // _syncAfter should not have been changed, because the fetch failed. + syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter"); - // Refresh interval is RefreshInterval - if ((DateTime)syncAfter > DateTime.UtcNow + configManager.RefreshInterval) - context.AddDiff($"Expected the refresh interval is longer than 2 seconds."); + if (!IdentityComparer.AreDatesEqualWithEpsilon(requestTime, syncAfter, 1)) + context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal be within 1 second of '{requestTime}'."); IdentityComparer.AreEqual(firstFetchMetadataFailure, secondFetchMetadataFailure, context); } @@ -398,8 +312,6 @@ public async Task BootstrapRefreshIntervalTest_Blocking() [Fact] public void GetSets() { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - TestUtilities.WriteHeader($"{this}.GetSets", "GetSets", true); int ExpectedPropertyCount = 7; @@ -433,36 +345,21 @@ public void GetSets() [Theory, MemberData(nameof(AutomaticIntervalTestCases), DisableDiscoveryEnumeration = true)] public async Task AutomaticRefreshInterval(ConfigurationManagerTheoryData theoryData) - { - await AutomaticRefreshIntervalBody(theoryData); - } - - [Theory, MemberData(nameof(AutomaticIntervalTestCases), DisableDiscoveryEnumeration = true)] - public async Task AutomaticRefreshInterval_Blocking(ConfigurationManagerTheoryData theoryData) - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await AutomaticRefreshIntervalBody(theoryData, true); - } - - private async Task AutomaticRefreshIntervalBody(ConfigurationManagerTheoryData theoryData, bool blocking = false) { var context = new CompareContext($"{this}.AutomaticRefreshInterval"); - AutoResetEvent resetEvent = SetupResetEvent(theoryData.ConfigurationManager, blocking); try { + var configuration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); IdentityComparer.AreEqual(configuration, theoryData.ExpectedConfiguration, context); theoryData.ConfigurationManager.MetadataAddress = theoryData.UpdatedMetadataAddress; TestUtilities.SetField(theoryData.ConfigurationManager, "_syncAfter", theoryData.SyncAfter.UtcDateTime); var updatedConfiguration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); - - if (!blocking && theoryData.SyncAfter < DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(1))) - WaitOrFail(resetEvent); - + // we wait 100 ms here to make the task is finished. + Thread.Sleep(100); updatedConfiguration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); - IdentityComparer.AreEqual(updatedConfiguration, theoryData.ExpectedUpdatedConfiguration, context); theoryData.ExpectedException.ProcessNoException(context); @@ -471,44 +368,23 @@ private async Task AutomaticRefreshIntervalBody(ConfigurationManagerTheoryData configurationManager, bool blocking) - { - var resetEvent = new AutoResetEvent(false); - - if (!blocking) - { - Action _waitAction = () => resetEvent.Set(); - TestUtilities.SetField(configurationManager, "_onBackgroundTaskFinish", _waitAction); - } - - return resetEvent; - } - public static TheoryData> AutomaticIntervalTestCases { get { var theoryData = new TheoryData>(); - var cts = new CancellationTokenSource(); - // Failing to get metadata returns existing. theoryData.Add(new ConfigurationManagerTheoryData("HttpFault_ReturnExisting") { ConfigurationManager = new ConfigurationManager( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), - InMemoryDocumentRetriever) - { - }, + InMemoryDocumentRetriever), ExpectedConfiguration = OpenIdConfigData.AADCommonV1Config, ExpectedUpdatedConfiguration = OpenIdConfigData.AADCommonV1Config, SyncAfter = DateTime.UtcNow - TimeSpan.FromDays(2), @@ -521,9 +397,7 @@ public static TheoryData( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), - InMemoryDocumentRetriever) - { - }, + InMemoryDocumentRetriever), ExpectedConfiguration = OpenIdConfigData.AADCommonV1Config, ExpectedUpdatedConfiguration = OpenIdConfigData.AADCommonV1Config, SyncAfter = DateTime.UtcNow + TimeSpan.FromDays(2), @@ -536,9 +410,7 @@ public static TheoryData( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), - InMemoryDocumentRetriever) - { - }, + InMemoryDocumentRetriever), ExpectedConfiguration = OpenIdConfigData.AADCommonV1Config, ExpectedUpdatedConfiguration = OpenIdConfigData.AADCommonV2Config, SyncAfter = DateTime.UtcNow, @@ -551,54 +423,33 @@ public static TheoryData theoryData) - { - await RequestRefreshBody(theoryData); - } - - - [Theory, MemberData(nameof(RequestRefreshTestCases), DisableDiscoveryEnumeration = true)] - public async Task RequestRefresh_Blocking(ConfigurationManagerTheoryData theoryData) - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await RequestRefreshBody(theoryData, true); - } - - private async Task RequestRefreshBody(ConfigurationManagerTheoryData theoryData, bool blocking = false) { var context = new CompareContext($"{this}.RequestRefresh"); + var configuration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); IdentityComparer.AreEqual(configuration, theoryData.ExpectedConfiguration, context); - AutoResetEvent resetEvent = SetupResetEvent(theoryData.ConfigurationManager, blocking); - - var timeProvider = new FakeTimeProvider(); - TestUtilities.SetField(theoryData.ConfigurationManager, "_timeProvider", timeProvider); - // the first call to RequestRefresh will trigger a refresh with ConfigurationManager.RefreshInterval being ignored. // Testing RefreshInterval requires a two calls, the second call will trigger a refresh with ConfigurationManager.RefreshInterval being used. if (theoryData.RequestRefresh) { theoryData.ConfigurationManager.RequestRefresh(); - if (!blocking) - WaitOrFail(resetEvent); - configuration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); } + if (theoryData.SleepTimeInMs > 0) + Thread.Sleep(theoryData.SleepTimeInMs); + theoryData.ConfigurationManager.RefreshInterval = theoryData.RefreshInterval; theoryData.ConfigurationManager.MetadataAddress = theoryData.UpdatedMetadataAddress; - timeProvider.Advance(TimeSpan.FromMilliseconds(theoryData.SleepTimeInMs)); - theoryData.ConfigurationManager.RequestRefresh(); - if (!blocking && theoryData.RefreshInterval != TimeSpan.MaxValue) - WaitOrFail(resetEvent); + if (theoryData.SleepTimeInMs > 0) + Thread.Sleep(theoryData.SleepTimeInMs); var updatedConfiguration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); - theoryData.ConfigurationManager.ShutdownBackgroundTask(); - IdentityComparer.AreEqual(updatedConfiguration, theoryData.ExpectedUpdatedConfiguration, context); TestUtilities.AssertFailIfErrors(context); @@ -610,15 +461,13 @@ public static TheoryData>(); - var cts = new CancellationTokenSource(); + // RefreshInterval set to 1 sec should return new config. theoryData.Add(new ConfigurationManagerTheoryData("RequestRefresh_TimeSpan_1000ms") { ConfigurationManager = new ConfigurationManager( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), - InMemoryDocumentRetriever) - { - }, + InMemoryDocumentRetriever), ExpectedConfiguration = OpenIdConfigData.AADCommonV1Config, ExpectedUpdatedConfiguration = OpenIdConfigData.AADCommonV2Config, RefreshInterval = TimeSpan.FromSeconds(1), @@ -627,15 +476,13 @@ public static TheoryData("RequestRefresh_TimeSpan_MaxValue") { ConfigurationManager = new ConfigurationManager( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), - InMemoryDocumentRetriever) - { - }, + InMemoryDocumentRetriever), ExpectedConfiguration = OpenIdConfigData.AADCommonV1Config, ExpectedUpdatedConfiguration = OpenIdConfigData.AADCommonV1Config, RefreshInterval = TimeSpan.MaxValue, @@ -644,18 +491,16 @@ public static TheoryData("RequestRefresh_FirstRefresh") { ConfigurationManager = new ConfigurationManager( "AADCommonV1Json", new OpenIdConnectConfigurationRetriever(), - InMemoryDocumentRetriever) - { - }, + InMemoryDocumentRetriever), ExpectedConfiguration = OpenIdConfigData.AADCommonV1Config, ExpectedUpdatedConfiguration = OpenIdConfigData.AADCommonV2Config, - SleepTimeInMs = 1000, + SleepTimeInMs = 100, UpdatedMetadataAddress = "AADCommonV2Json" }); @@ -671,18 +516,12 @@ public async Task HttpFailures(ConfigurationManagerTheoryData( - "OpenIdConnectMetadata.json", - new OpenIdConnectConfigurationRetriever(), - docRetriever); - - - AutoResetEvent resetEvent = SetupResetEvent(configManager, blocking); + var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); // This is the minimum time that should pass before an automatic refresh occurs // stored in advance to avoid any time drift issues. @@ -763,9 +582,8 @@ private async Task CheckSyncAfterBody(bool blocking = false) // force a refresh by setting internal field TestUtilities.SetField(configManager, "_syncAfter", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))); configuration = await configManager.GetConfigurationAsync(CancellationToken.None); - - if (!blocking) - WaitOrFail(resetEvent); + // wait 1000ms here because update of config is run as a new task. + Thread.Sleep(1000); // check that _syncAfter is greater than DateTimeOffset.UtcNow + AutomaticRefreshInterval DateTime syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter"); @@ -778,74 +596,43 @@ private async Task CheckSyncAfterBody(bool blocking = false) configManager.RequestRefresh(); - if (blocking) - { - bool refreshRequested = (bool)TestUtilities.GetField(configManager, "_refreshRequested"); - if (!refreshRequested) - context.Diffs.Add("Refresh is expected to be requested after RequestRefresh is called"); - } + bool refreshRequested = (bool)TestUtilities.GetField(configManager, "_refreshRequested"); + if (!refreshRequested) + context.Diffs.Add("Refresh is expected to be requested after RequestRefresh is called"); await configManager.GetConfigurationAsync(); - if (blocking) - { - bool refreshRequested = (bool)TestUtilities.GetField(configManager, "_refreshRequested"); - if (refreshRequested) - context.Diffs.Add("Refresh is expected to be requested after RequestRefresh is called"); - } - - if (!blocking) - WaitOrFail(resetEvent); + refreshRequested = (bool)TestUtilities.GetField(configManager, "_refreshRequested"); + if (refreshRequested) + context.Diffs.Add("Refresh is not expected to be requested after GetConfigurationAsync is called"); // check that _syncAfter is greater than DateTimeOffset.UtcNow + AutomaticRefreshInterval syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter"); if (syncAfter < minimumRefreshInterval) context.Diffs.Add($"(RequestRefresh) syncAfter '{syncAfter}' < DateTimeOffset.UtcNow + configManager.AutomaticRefreshInterval: '{minimumRefreshInterval}'."); - configManager.ShutdownBackgroundTask(); - TestUtilities.AssertFailIfErrors(context); } [Fact] public async Task GetConfigurationAsync() { - await GetConfigurationBody(); - } - - [Fact] - public async Task GetConfigurationAsync_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await GetConfigurationBody(); - } - - private async Task GetConfigurationBody() - { - var context = new CompareContext($"{this}.GetConfiguration"); - var cts = new CancellationTokenSource(); - var docRetriever = new FileDocumentRetriever(); - var configManager = new ConfigurationManager( - "OpenIdConnectMetadata.json", - new OpenIdConnectConfigurationRetriever(), - docRetriever); - + var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); + var context = new CompareContext($"{this}.GetConfiguration"); + // Unable to obtain a new configuration, but _currentConfiguration is not null so it should be returned. + configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))); configManager.MetadataAddress = "http://127.0.0.1"; configManager.RequestRefresh(); - - // Unable to obtain a new configuration, but _currentConfiguration is not null so it should be returned. var configuration2 = await configManager.GetConfigurationAsync(CancellationToken.None); IdentityComparer.AreEqual(configuration, configuration2, context); if (!object.ReferenceEquals(configuration, configuration2)) context.Diffs.Add("!object.ReferenceEquals(configuration, configuration2)"); - configManager.ShutdownBackgroundTask(); - // get configuration from http address, should throw // get configuration with unsuccessful HTTP response status code TestUtilities.AssertFailIfErrors(context); @@ -855,18 +642,6 @@ private async Task GetConfigurationBody() // a new LKG is set. [Fact] public void ResetLastKnownGoodLifetime() - { - ResetLastKnownGoodLifetimeBody(); - } - - [Fact] - public void ResetLastKnownGoodLifetime_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - ResetLastKnownGoodLifetimeBody(); - } - - private void ResetLastKnownGoodLifetimeBody() { TestUtilities.WriteHeader($"{this}.ResetLastKnownGoodLifetime"); var context = new CompareContext(); @@ -945,43 +720,21 @@ public void TestConfigurationComparer() [Fact] public async Task RequestRefresh_RespectsRefreshInterval() - { - await RequestRefresh_RespectsRefreshInterval_Body(); - } - - [Fact] - public async Task RequestRefresh_RespectsRefreshInterval_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await RequestRefresh_RespectsRefreshInterval_Body(true); - } - - private async Task RequestRefresh_RespectsRefreshInterval_Body(bool blocking = false) { // This test checks that the _syncAfter field is set correctly after a refresh. var context = new CompareContext($"{this}.RequestRefresh_RespectsRefreshInterval"); - var cts = new CancellationTokenSource(); var timeProvider = new FakeTimeProvider(); var docRetriever = new FileDocumentRetriever(); - var configManager = new ConfigurationManager( - "OpenIdConnectMetadata.json", - new OpenIdConnectConfigurationRetriever(), - docRetriever); - + var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); TestUtilities.SetField(configManager, "_timeProvider", timeProvider); - var resetEvent = SetupResetEvent(configManager, blocking); - // Get the first configuration. var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); configManager.RequestRefresh(); - if (!blocking) - WaitOrFail(resetEvent); - var configAfterFirstRefresh = await configManager.GetConfigurationAsync(CancellationToken.None); // First RequestRefresh triggers a refresh. @@ -1001,9 +754,6 @@ private async Task RequestRefresh_RespectsRefreshInterval_Body(bool blocking = f configManager.RequestRefresh(); - if (!blocking) - WaitOrFail(resetEvent); - var configAfterRefreshInterval = await configManager.GetConfigurationAsync(CancellationToken.None); // Third RequestRefresh should trigger a refresh because the refresh interval has passed. @@ -1013,8 +763,6 @@ private async Task RequestRefresh_RespectsRefreshInterval_Body(bool blocking = f // Advance time just prior to a refresh. timeProvider.Advance(configManager.RefreshInterval.Subtract(TimeSpan.FromSeconds(1))); - configManager.RequestRefresh(); - var configAfterLessThanRefreshInterval = await configManager.GetConfigurationAsync(CancellationToken.None); // Fourth RequestRefresh should not trigger a refresh because the refresh interval has not passed. @@ -1024,52 +772,30 @@ private async Task RequestRefresh_RespectsRefreshInterval_Body(bool blocking = f // Advance time 365 days. timeProvider.Advance(TimeSpan.FromDays(365)); - configManager.RequestRefresh(); - - if (!blocking) - WaitOrFail(resetEvent); - var configAfterOneYear = await configManager.GetConfigurationAsync(CancellationToken.None); // Fifth RequestRefresh should trigger a refresh because the refresh interval has passed. - if (object.ReferenceEquals(configAfterLessThanRefreshInterval, configAfterOneYear)) + if (!object.ReferenceEquals(configAfterLessThanRefreshInterval, configAfterOneYear)) context.Diffs.Add("object.ReferenceEquals(configAfterLessThanRefreshInterval, configAfterOneYear)"); - configManager.ShutdownBackgroundTask(); - TestUtilities.AssertFailIfErrors(context); } [Fact] public async Task GetConfigurationAsync_RespectsRefreshInterval() - { - await GetConfigurationAsync_RespectsRefreshIntervalBody(); - } - - [Fact] - public async Task GetConfigurationAsync_RespectsRefreshInterval_Blocking() - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await GetConfigurationAsync_RespectsRefreshIntervalBody(true); - } - - private async Task GetConfigurationAsync_RespectsRefreshIntervalBody(bool blocking = false) { var context = new CompareContext($"{this}.GetConfigurationAsync_RespectsRefreshInterval"); var timeProvider = new FakeTimeProvider(); + var docRetriever = new FileDocumentRetriever(); + var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); + TestUtilities.SetField(configManager, "_timeProvider", timeProvider); - var cts = new CancellationTokenSource(); - var configManager = new ConfigurationManager( - "OpenIdConnectMetadata.json", - new OpenIdConnectConfigurationRetriever(), - docRetriever); + TimeSpan advanceInterval = BaseConfigurationManager.DefaultAutomaticRefreshInterval.Add(TimeSpan.FromSeconds(configManager.AutomaticRefreshInterval.TotalSeconds / 20)); TestUtilities.SetField(configManager, "_timeProvider", timeProvider); - TimeSpan advanceInterval = BaseConfigurationManager.DefaultAutomaticRefreshInterval.Add(TimeSpan.FromSeconds(configManager.AutomaticRefreshInterval.TotalSeconds)); - // Get the first configuration. var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); @@ -1084,58 +810,29 @@ private async Task GetConfigurationAsync_RespectsRefreshIntervalBody(bool blocki var configAfterTimeIsAdvanced = await configManager.GetConfigurationAsync(CancellationToken.None); - if (!blocking) - { - var resetEvent = SetupResetEvent(configManager, blocking); - // Same config, but a task is queued to update the configuration. - if (!object.ReferenceEquals(configNoAdvanceInTime, configAfterTimeIsAdvanced)) - context.Diffs.Add("!object.ReferenceEquals(configuration, configAfterTimeIsAdvanced)"); + // Same config, but a task is queued to update the configuration. + if (!object.ReferenceEquals(configNoAdvanceInTime, configAfterTimeIsAdvanced)) + context.Diffs.Add("!object.ReferenceEquals(configuration, configAfterTimeIsAdvanced)"); - // Need to wait for background task to finish. - WaitOrFail(resetEvent); + // Need to wait for background task to finish. + Thread.Sleep(250); - var configAfterBackgroundTask = await configManager.GetConfigurationAsync(CancellationToken.None); + var configAfterBackgroundTask = await configManager.GetConfigurationAsync(CancellationToken.None); - // Configuration should be updated after the background task finishes. - if (object.ReferenceEquals(configAfterTimeIsAdvanced, configAfterBackgroundTask)) - context.Diffs.Add("object.ReferenceEquals(configuration, configAfterBackgroundTask)"); - } - else - { - if (object.ReferenceEquals(configAfterTimeIsAdvanced, configuration)) - context.Diffs.Add("object.ReferenceEquals(configAfterTimeIsAdvanced, configuration)"); - } - - configManager.ShutdownBackgroundTask(); + // Configuration should be updated after the background task finishes. + if (object.ReferenceEquals(configAfterTimeIsAdvanced, configAfterBackgroundTask)) + context.Diffs.Add("object.ReferenceEquals(configuration, configAfterBackgroundTask)"); TestUtilities.AssertFailIfErrors(context); } - [Theory, MemberData(nameof(ValidateOpenIdConnectConfigurationTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateOpenIdConnectConfigurationTests_Blocking(ConfigurationManagerTheoryData theoryData) - { - AppContext.SetSwitch(AppContextSwitches.UpdateConfigAsBlockingSwitch, true); - await ValidateOIDCConfigurationBody(theoryData, true); - } - [Theory, MemberData(nameof(ValidateOpenIdConnectConfigurationTestCases), DisableDiscoveryEnumeration = true)] public async Task ValidateOpenIdConnectConfigurationTests(ConfigurationManagerTheoryData theoryData) - { - await ValidateOIDCConfigurationBody(theoryData); - } - - private async Task ValidateOIDCConfigurationBody(ConfigurationManagerTheoryData theoryData, bool blocking = false) { TestUtilities.WriteHeader($"{this}.ValidateOpenIdConnectConfigurationTests"); var context = new CompareContext(); OpenIdConnectConfiguration configuration; - var configurationManager = new ConfigurationManager( - theoryData.MetadataAddress, - theoryData.ConfigurationRetriever, - theoryData.DocumentRetriever, - theoryData.ConfigurationValidator); - - var resetEvent = SetupResetEvent(configurationManager, blocking); + var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetriever, theoryData.DocumentRetriever, theoryData.ConfigurationValidator); if (theoryData.PresetCurrentConfiguration) TestUtilities.SetField(configurationManager, "_currentConfiguration", new OpenIdConnectConfiguration() { Issuer = Default.Issuer }); @@ -1143,21 +840,11 @@ private async Task ValidateOIDCConfigurationBody(ConfigurationManagerTheoryData< try { //create a listener and enable it for logs - using var listener = TestUtils.SampleListener.CreateLoggerListener(EventLevel.Warning); - + var listener = TestUtils.SampleListener.CreateLoggerListener(EventLevel.Warning); configuration = await configurationManager.GetConfigurationAsync(); - if (!blocking && theoryData.ExpectedException is null && string.IsNullOrEmpty(theoryData.ExpectedErrorMessage)) - WaitOrFail(resetEvent); - - // Need to wait for the events on the listener to be processed. - if (!string.IsNullOrEmpty(theoryData.ExpectedErrorMessage)) - { - var success = await PollForConditionAsync( - () => listener.TraceBuffer.Contains(theoryData.ExpectedErrorMessage), - TimeSpan.FromMilliseconds(100), - TimeSpan.FromSeconds(10)); - } + // we need to sleep here to make sure the task that updates configuration has finished. + Thread.Sleep(250); if (!string.IsNullOrEmpty(theoryData.ExpectedErrorMessage) && !listener.TraceBuffer.Contains(theoryData.ExpectedErrorMessage)) context.AddDiff($"Expected exception to contain: '{theoryData.ExpectedErrorMessage}'.{Environment.NewLine}Log is:{Environment.NewLine}'{listener.TraceBuffer}'"); @@ -1171,36 +858,10 @@ private async Task ValidateOIDCConfigurationBody(ConfigurationManagerTheoryData< theoryData.ExpectedException.ProcessException(ex, context); } - finally - { - configurationManager.ShutdownBackgroundTask(); - } TestUtilities.AssertFailIfErrors(context); } - internal static async Task PollForConditionAsync(Func condition, TimeSpan interval, TimeSpan timeout) - { - var startTime = DateTime.UtcNow; - - while (DateTime.UtcNow - startTime < timeout) - { - if (condition()) - return true; - - try - { - await Task.Delay(interval); - } - catch (TaskCanceledException) - { - return false; - } - } - - return false; - } - public static TheoryData> ValidateOpenIdConnectConfigurationTestCases { get @@ -1216,7 +877,7 @@ public static TheoryData @@ -1226,7 +887,7 @@ public static TheoryData @@ -1237,7 +898,7 @@ public static TheoryData @@ -1247,7 +908,7 @@ public static TheoryData @@ -1258,7 +919,7 @@ public static TheoryData @@ -1268,7 +929,7 @@ public static TheoryData @@ -1279,7 +940,7 @@ public static TheoryData @@ -1289,7 +950,7 @@ public static TheoryData @@ -1300,7 +961,7 @@ public static TheoryData : TheoryDataBase where T : class { public ConfigurationManager ConfigurationManager { get; set; } diff --git a/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs b/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs index 93216034c2..71cf3e66d8 100644 --- a/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs @@ -77,14 +77,11 @@ public async Task ConfigurationManagerUsingCustomClass() if (!IdentityComparer.AreEqual(configuration.Issuer, configuration2.Issuer, context)) context.Diffs.Add("!IdentityComparer.AreEqual(configuration, configuration2)"); - configManager.ShutdownBackgroundTask(); - // AutomaticRefreshInterval should pick up new bits. configManager = new ConfigurationManager("IssuerMetadata.json", new IssuerConfigurationRetriever(), docRetriever); configManager.RequestRefresh(); configuration = await configManager.GetConfigurationAsync(); TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))); - configManager.MetadataAddress = "IssuerMetadata2.json"; // Wait for the refresh to complete. @@ -104,8 +101,6 @@ public async Task ConfigurationManagerUsingCustomClass() if (IdentityComparer.AreEqual(configuration.Issuer, configuration2.Issuer)) context.Diffs.Add($"Expected: {configuration.Issuer}, to be different from: {configuration2.Issuer}"); - configManager.ShutdownBackgroundTask(); - TestUtilities.AssertFailIfErrors(context); } diff --git a/test/Microsoft.IdentityModel.TestUtils/ResetAppContextSwitchesAttribute.cs b/test/Microsoft.IdentityModel.TestUtils/ResetAppContextSwitchesAttribute.cs deleted file mode 100644 index 57e1d92231..0000000000 --- a/test/Microsoft.IdentityModel.TestUtils/ResetAppContextSwitchesAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Reflection; -using Microsoft.IdentityModel.Tokens; -using Xunit.Sdk; - -namespace Microsoft.IdentityModel.TestUtils -{ - /// - public class ResetAppContextSwitchesAttribute : BeforeAfterTestAttribute - { - public override void Before(MethodInfo methodUnderTest) - { - AppContextSwitches.ResetAllSwitches(); - } - - public override void After(MethodInfo methodUnderTest) - { - AppContextSwitches.ResetAllSwitches(); - } - } -} diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs index fa3d7a4134..ba76c4c0fe 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs @@ -15,7 +15,6 @@ public class MockTelemetryClient : ITelemetryClient public void ClearExportedItems() { ExportedItems.Clear(); - ExportedHistogramItems.Clear(); } public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) @@ -45,12 +44,5 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan o ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); ExportedHistogramItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString()); } - - void ITelemetryClient.LogBackgroundConfigurationRefreshFailure(string metadataAddress, Exception exception) - { - ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); - ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); - ExportedItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString()); - } } } From 1801cf43722f2d784f4425c61c1fcba5253e438b Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 17:00:00 -0800 Subject: [PATCH 02/12] Revert "Add additional condition before requesting update. (#3095)" This reverts commit 0108a96e1fbf511a0976cc4666bcd6fa7d96d69e. --- .../Configuration/ConfigurationManager.cs | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index 6d1fb7a5a2..b5e6bf0eba 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -248,41 +248,22 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) { if (_refreshRequested) { - _refreshRequested = false; - - try - { - // Log as manual because RequestRefresh was called - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Manual); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch - { } -#pragma warning restore CA1031 // Do not catch general exception types + // Log as manual because RequestRefresh was called + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Manual); UpdateCurrentConfiguration(); + _refreshRequested = false; } - else if (SyncAfter <= _timeProvider.GetUtcNow()) + else { - try - { - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Automatic); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch - { } -#pragma warning restore CA1031 // Do not catch general exception types + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Automatic); _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); } - else - { - Interlocked.Exchange(ref _configurationRetrieverState, ConfigurationRetrieverIdle); - } } } From 2389294cfb925d585d0a6015cb999f9df5047146 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 17:01:19 -0800 Subject: [PATCH 03/12] Revert "Add request count and duration telemetry (#3022)" This reverts commit 9e91a6408b9d2e5e7d56c6a903d9eaaedbdac46b. --- build/dependencies.props | 1 - build/dependenciesTest.props | 1 - .../InternalAPI.Unshipped.txt | 3 - .../JsonWebTokenHandler.ValidateToken.cs | 7 - .../Configuration/ConfigurationManager.cs | 53 +----- .../InternalAPI.Unshipped.txt | 2 - .../InternalsVisibleTo.cs | 4 - .../InternalAPI.Unshipped.txt | 69 -------- .../Microsoft.IdentityModel.Tokens.csproj | 4 - .../Telemetry/ITelemetryClient.cs | 28 --- .../Telemetry/TelemetryClient.cs | 67 ------- .../Telemetry/TelemetryConstants.cs | 67 ------- .../Telemetry/TelemetryDataRecorder.cs | 51 ------ .../InternalAPI.Unshipped.txt | 3 +- .../JwtSecurityTokenHandler.cs | 7 - .../JsonWebTokenHandlerTelemetryTests.cs | 69 -------- ...crosoft.IdentityModel.Logging.Tests.csproj | 2 - .../ConfigurationManagerTelemetryTests.cs | 167 ------------------ ...Model.Protocols.OpenIdConnect.Tests.csproj | 1 - .../Telemetry/MockTelemetryClient.cs | 48 ----- .../JwtSecurityTokenHandlerTelemetryTests.cs | 65 ------- ...stem.IdentityModel.Tokens.Jwt.Tests.csproj | 2 - 22 files changed, 8 insertions(+), 713 deletions(-) delete mode 100644 src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs delete mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs delete mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs delete mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs delete mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs delete mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs delete mode 100644 test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs delete mode 100644 test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs delete mode 100644 test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs diff --git a/build/dependencies.props b/build/dependencies.props index aa3c765a75..9438d4d70e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -8,7 +8,6 @@ 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 6bcbbd9361..7cc73f438b 100644 --- a/build/dependenciesTest.props +++ b/build/dependenciesTest.props @@ -7,7 +7,6 @@ 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 ff770358f8..20d7ce969e 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -1,6 +1,3 @@ -Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient -override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> -override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> 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 203a13339f..f730094055 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs @@ -10,7 +10,6 @@ using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Telemetry; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; namespace Microsoft.IdentityModel.JsonWebTokens @@ -18,8 +17,6 @@ namespace Microsoft.IdentityModel.JsonWebTokens /// This partial class contains methods and logic related to the validation of tokens. public partial class JsonWebTokenHandler : TokenHandler { - internal Telemetry.ITelemetryClient _telemetryClient = new TelemetryClient(); - /// /// Returns a value that indicates if this handler can validate a . /// @@ -519,10 +516,6 @@ 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.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index b5e6bf0eba..fe6d70ff31 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -9,7 +9,6 @@ using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Telemetry; namespace Microsoft.IdentityModel.Protocols { @@ -46,7 +45,6 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM private int _configurationRetrieverState = ConfigurationRetrieverIdle; private readonly TimeProvider _timeProvider = TimeProvider.System; - internal ITelemetryClient TelemetryClient = new TelemetryClient(); // If a refresh is requested, then do the refresh as a blocking operation // not on a background thread. RequestRefresh signals that the app is explicitly @@ -55,7 +53,6 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM // refresh interval has passed. bool _refreshRequested; - /// /// Instantiates a new that manages automatic and controls refreshing on configuration data. /// @@ -193,7 +190,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 its own timeouts, etc. + // The transport should have it's own timeouts, etc. T configuration = await _configRetriever.GetConfigurationAsync( MetadataAddress, _docRetriever, @@ -204,29 +201,18 @@ 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) - { - var ex = new InvalidConfigurationException( - LogHelper.FormatInvariant( - LogMessages.IDX20810, - result.ErrorMessage)); - - throw LogHelper.LogExceptionMessage(ex); - } + throw LogHelper.LogExceptionMessage( + new InvalidConfigurationException( + LogHelper.FormatInvariant( + LogMessages.IDX20810, + result.ErrorMessage))); } - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.FirstRefresh); - UpdateConfiguration(configuration); } catch (Exception ex) { fetchMetadataFailure = ex; - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.FirstRefresh, - ex); LogHelper.LogExceptionMessage( new InvalidOperationException( @@ -248,22 +234,11 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) { if (_refreshRequested) { - // Log as manual because RequestRefresh was called - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Manual); - UpdateCurrentConfiguration(); _refreshRequested = false; } else - { - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Automatic); - _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); - } } } @@ -289,8 +264,6 @@ 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( @@ -298,11 +271,6 @@ private void UpdateCurrentConfiguration() _docRetriever, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - var elapsedTime = _timeProvider.GetElapsedTime(startTimestamp); - TelemetryClient.LogConfigurationRetrievalDuration( - MetadataAddress, - elapsedTime); - if (_configValidator == null) { UpdateConfiguration(configuration); @@ -323,12 +291,6 @@ private void UpdateCurrentConfiguration() } catch (Exception ex) { - var elapsedTime = _timeProvider.GetElapsedTime(startTimestamp); - TelemetryClient.LogConfigurationRetrievalDuration( - MetadataAddress, - elapsedTime, - ex); - LogHelper.LogExceptionMessage( new InvalidOperationException( LogHelper.FormatInvariant( @@ -374,7 +336,7 @@ ref Unsafe.As(ref _lastRequestRefresh), /// 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) { @@ -391,7 +353,6 @@ public override async Task GetBaseConfigurationAsync(Cancella public override void RequestRefresh() { DateTime now = _timeProvider.GetUtcNow().UtcDateTime; - if (now >= DateTimeUtil.Add(LastRequestRefresh, RefreshInterval) || _isFirstRefreshRequest) { _isFirstRefreshRequest = false; diff --git a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt index 98597bed68..e69de29bb2 100644 --- a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.IdentityModel.Protocols.ConfigurationManager.TelemetryClient -> Microsoft.IdentityModel.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 deleted file mode 100644 index 5e00856ea6..0000000000 --- a/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs +++ /dev/null @@ -1,4 +0,0 @@ -// 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 8ce71ead77..3e7673b9a5 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,16 +1,3 @@ -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.ExceptionTypeTag = "ExceptionType" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.IdentityModelVersionTag = "IdentityModelVersion" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.MetadataAddressTag = "MetadataAddress" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.OperationStatusTag = "OperationStatus" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Automatic = "Automatic" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationInvalid = "ConfigurationInvalid" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.FirstRefresh = "FirstRefresh" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Lkg = "LastKnownGood" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Manual = "Manual" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations." -> string -const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS" -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception." -> string @@ -21,36 +8,7 @@ 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.Telemetry.TelemetryConstants.ExceptionTypeTag = "ExceptionType" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.IdentityModelVersionTag = "IdentityModelVersion" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.MetadataAddressTag = "MetadataAddress" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.OperationStatusTag = "OperationStatus" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Automatic = "Automatic" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationInvalid = "ConfigurationInvalid" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed" -> string -const Microsoft.IdentityModel.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.Telemetry.TelemetryConstants.Protocols.Lkg = "LastKnownGood" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Manual = "Manual" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations." -> string -const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager" -> string -const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS" -> string -Microsoft.IdentityModel.Telemetry.ITelemetryClient -Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void -Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void -Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient -Microsoft.IdentityModel.Telemetry.TelemetryClient.ClientVer -> string -Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.TelemetryClient() -> void -Microsoft.IdentityModel.Telemetry.TelemetryConstants -Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols -Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder -Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TelemetryDataRecorder() -> void 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 @@ -79,25 +37,9 @@ 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.Telemetry.ITelemetryClient -Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void -Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void -Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient -Microsoft.IdentityModel.Telemetry.TelemetryClient.ClientVer -> string -Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void -Microsoft.IdentityModel.Telemetry.TelemetryClient.TelemetryClient() -> void -Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder -Microsoft.IdentityModel.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.Telemetry.TelemetryConstants -Microsoft.IdentityModel.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 @@ -129,26 +71,15 @@ override Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.GetExcep override Microsoft.IdentityModel.Tokens.SignatureValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception -static Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(in System.Diagnostics.TagList tagList) -> void -static Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(long requestDurationInMs, in System.Diagnostics.TagList tagList) -> void 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.Telemetry.TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(in System.Diagnostics.TagList tagList) -> void -static Microsoft.IdentityModel.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 static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedString(System.Collections.Generic.IList strings) -> string static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame -static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter -static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogram -> System.Diagnostics.Metrics.Histogram -static readonly Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration -> Microsoft.IdentityModel.Tokens.IssuerValidationSource -static readonly Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters -> Microsoft.IdentityModel.Tokens.IssuerValidationSource -static readonly Microsoft.IdentityModel.Tokens.IssuerValidationSource.NotValidated -> Microsoft.IdentityModel.Tokens.IssuerValidationSource 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.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter -static readonly Microsoft.IdentityModel.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/Microsoft.IdentityModel.Tokens.csproj b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj index 25eb2bbc34..b342d4f1f2 100644 --- a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj +++ b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj @@ -53,10 +53,6 @@ - - - - diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs deleted file mode 100644 index 656fb6bdf5..0000000000 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.IdentityModel.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 deleted file mode 100644 index 20cbfcf15b..0000000000 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Diagnostics; -using Microsoft.IdentityModel.Logging; - -namespace Microsoft.IdentityModel.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 deleted file mode 100644 index a4d9449e75..0000000000 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.IdentityModel.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 deleted file mode 100644 index 5023606081..0000000000 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Diagnostics; -using System.Diagnostics.Metrics; - -namespace Microsoft.IdentityModel.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 102ca896eb..47d5d10352 100644 --- a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt +++ b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt @@ -1,3 +1,2 @@ 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 -System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.TelemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient +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 diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs index 7888083c47..2061bcf261 100644 --- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs +++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs @@ -15,7 +15,6 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Telemetry; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; namespace System.IdentityModel.Tokens.Jwt @@ -37,8 +36,6 @@ public class JwtSecurityTokenHandler : SecurityTokenHandler private static string _shortClaimType = _namespace + "/ShortTypeName"; private bool _mapInboundClaims = DefaultMapInboundClaims; - internal Microsoft.IdentityModel.Telemetry.ITelemetryClient TelemetryClient = new TelemetryClient(); - /// /// Default claim type mapping for inbound claims. /// @@ -891,10 +888,6 @@ 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 deleted file mode 100644 index e63b48e9fb..0000000000 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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.Telemetry; -using Microsoft.IdentityModel.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 6ed8578f22..0d29cd28cd 100644 --- a/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj +++ b/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj @@ -23,8 +23,6 @@ - - diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs deleted file mode 100644 index 583781a2d8..0000000000 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -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.Telemetry; -using Microsoft.IdentityModel.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 - }; - var cancel = new CancellationToken(); - - // act - // Retrieve the configuration for the first time - await configurationManager.GetConfigurationAsync(cancel); - testTelemetryClient.ClearExportedItems(); - - // Manually request a config refresh - configurationManager.RequestRefresh(); - await configurationManager.GetConfigurationAsync(cancel); - - // 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 DateTime? 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 aa78636b2d..8d266dc7ce 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 @@ -25,7 +25,6 @@ - diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs deleted file mode 100644 index ba76c4c0fe..0000000000 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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.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 deleted file mode 100644 index ddef0bde36..0000000000 --- a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Telemetry; -using Microsoft.IdentityModel.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 55c2caee3c..e68913b3b0 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,10 +13,8 @@ - - From 809c09017799cfbdf81ebcdb126e621112fdcbdf Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 17:01:38 -0800 Subject: [PATCH 04/12] Revert "Make updates to syncAfter and lastRefreshRequest atomic (#3090)" This reverts commit 075a6275a03757f0f136214838a10640cf83945f. --- .../Configuration/ConfigurationManager.cs | 46 +++--------- .../ConfigurationManagerTests.cs | 72 +++++++++---------- .../ExtensibilityTests.cs | 2 +- 3 files changed, 46 insertions(+), 74 deletions(-) diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index fe6d70ff31..beec3992c2 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -3,7 +3,6 @@ using System; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Logging; @@ -19,16 +18,8 @@ namespace Microsoft.IdentityModel.Protocols [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] public class ConfigurationManager : BaseConfigurationManager, IConfigurationManager where T : class { - // To prevent tearing, this needs to be only updated through AtomicUpdateSyncAfter. - // Reads should be done through the property SyncAfter. - private DateTime _syncAfter = DateTime.MinValue; - private DateTime SyncAfter => _syncAfter; - - // See comment above, this should only be updated through AtomicUpdateLastRequestRefresh, - // read through LastRequestRefresh. - private DateTime _lastRequestRefresh = DateTime.MinValue; - private DateTime LastRequestRefresh => _lastRequestRefresh; - + private DateTimeOffset _syncAfter = DateTimeOffset.MinValue; + private DateTimeOffset _lastRequestRefresh = DateTimeOffset.MinValue; private bool _isFirstRefreshRequest = true; private readonly SemaphoreSlim _configurationNullLock = new SemaphoreSlim(1); @@ -165,7 +156,7 @@ public async Task GetConfigurationAsync() /// If the time since the last call is less than then is not called and the current Configuration is returned. public virtual async Task GetConfigurationAsync(CancellationToken cancel) { - if (_currentConfiguration != null && SyncAfter > _timeProvider.GetUtcNow()) + if (_currentConfiguration != null && _syncAfter > _timeProvider.GetUtcNow()) return _currentConfiguration; Exception fetchMetadataFailure = null; @@ -251,7 +242,7 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) LogHelper.FormatInvariant( LogMessages.IDX20803, LogHelper.MarkAsNonPII(MetadataAddress ?? "null"), - LogHelper.MarkAsNonPII(SyncAfter), + LogHelper.MarkAsNonPII(_syncAfter), LogHelper.MarkAsNonPII(fetchMetadataFailure)), fetchMetadataFailure)); } @@ -309,27 +300,8 @@ private void UpdateCurrentConfiguration() private void UpdateConfiguration(T configuration) { _currentConfiguration = configuration; - var newSyncTime = DateTimeUtil.Add(_timeProvider.GetUtcNow().UtcDateTime, AutomaticRefreshInterval + + _syncAfter = DateTimeUtil.Add(_timeProvider.GetUtcNow().UtcDateTime, AutomaticRefreshInterval + TimeSpan.FromSeconds(new Random().Next((int)AutomaticRefreshInterval.TotalSeconds / 20))); - AtomicUpdateSyncAfter(newSyncTime); - } - - private void AtomicUpdateSyncAfter(DateTime syncAfter) - { - // DateTime's backing data is safe to treat as a long if the Kind is not local. - // _syncAfter will always be updated to a UTC time. - // See the implementation of ToBinary on DateTime. - Interlocked.Exchange( - ref Unsafe.As(ref _syncAfter), - Unsafe.As(ref syncAfter)); - } - - private void AtomicUpdateLastRequestRefresh(DateTime lastRequestRefresh) - { - // See the comment in AtomicUpdateSyncAfter. - Interlocked.Exchange( - ref Unsafe.As(ref _lastRequestRefresh), - Unsafe.As(ref lastRequestRefresh)); } /// @@ -352,12 +324,12 @@ public override async Task GetBaseConfigurationAsync(Cancella /// public override void RequestRefresh() { - DateTime now = _timeProvider.GetUtcNow().UtcDateTime; - if (now >= DateTimeUtil.Add(LastRequestRefresh, RefreshInterval) || _isFirstRefreshRequest) + DateTimeOffset now = _timeProvider.GetUtcNow(); + if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest) { _isFirstRefreshRequest = false; - AtomicUpdateSyncAfter(now); - AtomicUpdateLastRequestRefresh(now); + _syncAfter = now; + _lastRequestRefresh = now; _refreshRequested = true; } } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index 1ee7645570..caa7e0b6a8 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// Ignore Spelling: Metadata Validator +// Ignore Spelling: Metadata Validator Retreiver using System; using System.Collections.Generic; @@ -37,7 +37,7 @@ public async Task GetPublicMetadata(ConfigurationManagerTheoryData( theoryData.MetadataAddress, - theoryData.ConfigurationRetriever, + theoryData.ConfigurationRetreiver, theoryData.DocumentRetriever, theoryData.ConfigurationValidator); @@ -60,7 +60,7 @@ public static TheoryData("AccountsGoogleCom") { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = new HttpDocumentRetriever(), MetadataAddress = OpenIdConfigData.AccountsGoogle @@ -68,7 +68,7 @@ public static TheoryData("AADCommonUrl") { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = new HttpDocumentRetriever(), MetadataAddress = OpenIdConfigData.AADCommonUrl @@ -76,7 +76,7 @@ public static TheoryData("AADCommonUrlV1") { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = new HttpDocumentRetriever(), MetadataAddress = OpenIdConfigData.AADCommonUrlV1 @@ -84,7 +84,7 @@ public static TheoryData("AADCommonUrlV2") { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = new HttpDocumentRetriever(), MetadataAddress = OpenIdConfigData.AADCommonUrlV2 @@ -99,7 +99,7 @@ public void OpenIdConnectConstructor(ConfigurationManagerTheoryData(theoryData.MetadataAddress, theoryData.ConfigurationRetriever, theoryData.DocumentRetriever, theoryData.ConfigurationValidator); + var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetreiver, theoryData.DocumentRetriever, theoryData.ConfigurationValidator); theoryData.ExpectedException.ProcessNoException(); } catch (Exception ex) @@ -118,7 +118,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = new HttpDocumentRetriever(), ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), @@ -129,7 +129,7 @@ public static TheoryData { - ConfigurationRetriever = null, + ConfigurationRetreiver = null, ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = new HttpDocumentRetriever(), ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), @@ -139,7 +139,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = new OpenIdConnectConfigurationValidator(), DocumentRetriever = null, ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), @@ -149,7 +149,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = null, DocumentRetriever = new HttpDocumentRetriever(), ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), @@ -232,7 +232,7 @@ public async Task VerifyInterlockGuardForGetConfigurationAsync() waitEvent.Reset(); signalEvent.Reset(); - TestUtilities.SetField(configurationManager, "_syncAfter", DateTime.MinValue); + TestUtilities.SetField(configurationManager, "_syncAfter", DateTimeOffset.MinValue); await configurationManager.GetConfigurationAsync(CancellationToken.None); // InMemoryDocumentRetrieverWithEvents will signal when it is OK to change the MetadataAddress @@ -276,8 +276,8 @@ public async Task BootstrapRefreshIntervalTest() catch (Exception firstFetchMetadataFailure) { // _syncAfter should not have been changed, because the fetch failed. - DateTime syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter"); - if (syncAfter != DateTime.MinValue) + DateTimeOffset syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); + if (syncAfter != DateTimeOffset.MinValue) context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTimeOffset.MinValue}'."); if (firstFetchMetadataFailure.InnerException == null) @@ -297,10 +297,10 @@ public async Task BootstrapRefreshIntervalTest() context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); // _syncAfter should not have been changed, because the fetch failed. - syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter"); + syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); - if (!IdentityComparer.AreDatesEqualWithEpsilon(requestTime, syncAfter, 1)) - context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal be within 1 second of '{requestTime}'."); + if (!IdentityComparer.AreDatesEqualWithEpsilon(requestTime, syncAfter.UtcDateTime, 1)) + context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter.UtcDateTime}' should equal be within 1 second of '{requestTime}'."); IdentityComparer.AreEqual(firstFetchMetadataFailure, secondFetchMetadataFailure, context); } @@ -355,10 +355,10 @@ public async Task AutomaticRefreshInterval(ConfigurationManagerTheoryData("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); - TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))); + TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1)); configManager.MetadataAddress = "http://127.0.0.1"; configManager.RequestRefresh(); var configuration2 = await configManager.GetConfigurationAsync(CancellationToken.None); @@ -832,7 +832,7 @@ public async Task ValidateOpenIdConnectConfigurationTests(ConfigurationManagerTh TestUtilities.WriteHeader($"{this}.ValidateOpenIdConnectConfigurationTests"); var context = new CompareContext(); OpenIdConnectConfiguration configuration; - var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetriever, theoryData.DocumentRetriever, theoryData.ConfigurationValidator); + var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetreiver, theoryData.DocumentRetriever, theoryData.ConfigurationValidator); if (theoryData.PresetCurrentConfiguration) TestUtilities.SetField(configurationManager, "_currentConfiguration", new OpenIdConnectConfiguration() { Issuer = Default.Issuer }); @@ -872,7 +872,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator, DocumentRetriever = new FileDocumentRetriever(), First = true, @@ -882,7 +882,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX21818:", typeof(InvalidConfigurationException)), @@ -892,7 +892,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), PresetCurrentConfiguration = true, @@ -903,7 +903,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX10810:", typeof(InvalidConfigurationException)), @@ -913,7 +913,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), PresetCurrentConfiguration = true, @@ -924,7 +924,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX21817:", typeof(InvalidConfigurationException)), @@ -934,7 +934,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), PresetCurrentConfiguration = true, @@ -945,7 +945,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX10814:", typeof(InvalidConfigurationException)), @@ -955,7 +955,7 @@ public static TheoryData { - ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(), + ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(), ConfigurationValidator = openIdConnectConfigurationValidator2, DocumentRetriever = new FileDocumentRetriever(), PresetCurrentConfiguration = true, @@ -1001,7 +1001,7 @@ public ConfigurationManagerTheoryData(string testId) : base(testId) { } public TimeSpan AutomaticRefreshInterval { get; set; } - public IConfigurationRetriever ConfigurationRetriever { get; set; } + public IConfigurationRetriever ConfigurationRetreiver { get; set; } public IConfigurationValidator ConfigurationValidator { get; set; } diff --git a/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs b/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs index 71cf3e66d8..c37f5d04ce 100644 --- a/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs @@ -81,7 +81,7 @@ public async Task ConfigurationManagerUsingCustomClass() configManager = new ConfigurationManager("IssuerMetadata.json", new IssuerConfigurationRetriever(), docRetriever); configManager.RequestRefresh(); configuration = await configManager.GetConfigurationAsync(); - TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))); + TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1)); configManager.MetadataAddress = "IssuerMetadata2.json"; // Wait for the refresh to complete. From ad9bb20c55c48e88bb41683c485a49b575eede45 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 17:01:59 -0800 Subject: [PATCH 05/12] Revert "RequestRefresh back to a signal for GetConfigurationAsync (#3087)" This reverts commit 42b0c2cb3cbbfcee9ce9a016db2a58d2ff7318ea. --- build/dependenciesTest.props | 9 - .../Configuration/ConfigurationManager.cs | 32 +-- .../ConfigurationManagerTests.cs | 184 +++++------------- ...Model.Protocols.OpenIdConnect.Tests.csproj | 7 - 4 files changed, 64 insertions(+), 168 deletions(-) diff --git a/build/dependenciesTest.props b/build/dependenciesTest.props index 7cc73f438b..65daedcf38 100644 --- a/build/dependenciesTest.props +++ b/build/dependenciesTest.props @@ -19,13 +19,4 @@ 3.0.0-pre.49 - - - - 9.0.0 - - - 8.10.0 - - diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index beec3992c2..ed3d4b7e67 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -35,15 +35,6 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM private const int ConfigurationRetrieverRunning = 1; private int _configurationRetrieverState = ConfigurationRetrieverIdle; - private readonly TimeProvider _timeProvider = TimeProvider.System; - - // If a refresh is requested, then do the refresh as a blocking operation - // not on a background thread. RequestRefresh signals that the app is explicitly - // requesting a refresh, so it should be done immediately so the next - // call to GetConfiguration will return new configuration if the minimum - // refresh interval has passed. - bool _refreshRequested; - /// /// Instantiates a new that manages automatic and controls refreshing on configuration data. /// @@ -156,7 +147,7 @@ public async Task GetConfigurationAsync() /// If the time since the last call is less than then is not called and the current Configuration is returned. public virtual async Task GetConfigurationAsync(CancellationToken cancel) { - if (_currentConfiguration != null && _syncAfter > _timeProvider.GetUtcNow()) + if (_currentConfiguration != null && _syncAfter > DateTimeOffset.UtcNow) return _currentConfiguration; Exception fetchMetadataFailure = null; @@ -223,13 +214,7 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) { if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) { - if (_refreshRequested) - { - UpdateCurrentConfiguration(); - _refreshRequested = false; - } - else - _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); + _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); } } @@ -300,7 +285,7 @@ private void UpdateCurrentConfiguration() private void UpdateConfiguration(T configuration) { _currentConfiguration = configuration; - _syncAfter = DateTimeUtil.Add(_timeProvider.GetUtcNow().UtcDateTime, AutomaticRefreshInterval + + _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, AutomaticRefreshInterval + TimeSpan.FromSeconds(new Random().Next((int)AutomaticRefreshInterval.TotalSeconds / 20))); } @@ -324,13 +309,16 @@ public override async Task GetBaseConfigurationAsync(Cancella /// public override void RequestRefresh() { - DateTimeOffset now = _timeProvider.GetUtcNow(); + DateTimeOffset now = DateTimeOffset.UtcNow; + if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest) { _isFirstRefreshRequest = false; - _syncAfter = now; - _lastRequestRefresh = now; - _refreshRequested = true; + if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) + { + UpdateCurrentConfiguration(); + _lastRequestRefresh = now; + } } } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index caa7e0b6a8..2fba523106 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -11,7 +11,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Time.Testing; using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Protocols.OpenIdConnect.Configuration; using Microsoft.IdentityModel.TestUtils; @@ -20,6 +19,9 @@ namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests { + /// + /// + /// public class ConfigurationManagerTests { /// @@ -207,6 +209,48 @@ public async Task FetchMetadataFailureTest() TestUtilities.AssertFailIfErrors(context); } + [Fact] + public async Task VerifyInterlockGuardForRequestRefresh() + { + ManualResetEvent waitEvent = new ManualResetEvent(false); + ManualResetEvent signalEvent = new ManualResetEvent(false); + InMemoryDocumentRetriever inMemoryDocumentRetriever = InMemoryDocumentRetrieverWithEvents(waitEvent, signalEvent); + + var configurationManager = new ConfigurationManager( + "AADCommonV1Json", + new OpenIdConnectConfigurationRetriever(), + inMemoryDocumentRetriever); + + // populate the configurationManager with AADCommonV1Config + TestUtilities.SetField(configurationManager, "_currentConfiguration", OpenIdConfigData.AADCommonV1Config); + + // InMemoryDocumentRetrieverWithEvents will block until waitEvent.Set() is called. + // The first RequestRefresh will not have finished before the next RequestRefresh() is called. + // The guard '_lastRequestRefresh' will not block as we set it to DateTimeOffset.MinValue. + // Interlocked guard will block. + // Configuration should be AADCommonV1Config + signalEvent.Reset(); + _ = Task.Run(() => configurationManager.RequestRefresh()); + + // InMemoryDocumentRetrieverWithEvents will signal when it is OK to change the MetadataAddress + // otherwise, it may be the case that the MetadataAddress is changed before the previous Task has finished. + signalEvent.WaitOne(); + + // AADCommonV1Json would have been passed to the the previous retriever, which is blocked on an event. + configurationManager.MetadataAddress = "AADCommonV2Json"; + TestUtilities.SetField(configurationManager, "_lastRequestRefresh", DateTimeOffset.MinValue); + _ = Task.Run(() => configurationManager.RequestRefresh()); + + // Set the event to release the lock and let the previous retriever finish. + waitEvent.Set(); + + // Configuration should be AADCommonV1Config + var configuration = await configurationManager.GetConfigurationAsync(); + Assert.True(configuration.Issuer.Equals(OpenIdConfigData.AADCommonV1Config.Issuer), + $"configuration.Issuer from configurationManager was not as expected," + + $"configuration.Issuer: '{configuration.Issuer}' != expected '{OpenIdConfigData.AADCommonV1Config.Issuer}'."); + } + [Fact] public async Task VerifyInterlockGuardForGetConfigurationAsync() { @@ -276,15 +320,13 @@ public async Task BootstrapRefreshIntervalTest() catch (Exception firstFetchMetadataFailure) { // _syncAfter should not have been changed, because the fetch failed. - DateTimeOffset syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); - if (syncAfter != DateTimeOffset.MinValue) + var syncAfter = TestUtilities.GetField(configManager, "_syncAfter"); + if ((DateTimeOffset)syncAfter != DateTimeOffset.MinValue) context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTimeOffset.MinValue}'."); if (firstFetchMetadataFailure.InnerException == null) context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); - DateTime requestTime = DateTime.UtcNow; - // Fetch metadata again during refresh interval, the exception should be same from above. try { @@ -297,10 +339,9 @@ public async Task BootstrapRefreshIntervalTest() context.AddDiff($"Expected exception to contain inner exception for fetch metadata failure."); // _syncAfter should not have been changed, because the fetch failed. - syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); - - if (!IdentityComparer.AreDatesEqualWithEpsilon(requestTime, syncAfter.UtcDateTime, 1)) - context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter.UtcDateTime}' should equal be within 1 second of '{requestTime}'."); + syncAfter = TestUtilities.GetField(configManager, "_syncAfter"); + if ((DateTimeOffset)syncAfter != DateTimeOffset.MinValue) + context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTimeOffset.MinValue}'."); IdentityComparer.AreEqual(firstFetchMetadataFailure, secondFetchMetadataFailure, context); } @@ -564,10 +605,10 @@ public static TheoryData("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); @@ -593,18 +634,9 @@ public async Task CheckSyncAfterAndRefreshRequested() // make same check for RequestRefresh // force a refresh by setting internal field TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1)); - configManager.RequestRefresh(); - - bool refreshRequested = (bool)TestUtilities.GetField(configManager, "_refreshRequested"); - if (!refreshRequested) - context.Diffs.Add("Refresh is expected to be requested after RequestRefresh is called"); - - await configManager.GetConfigurationAsync(); - - refreshRequested = (bool)TestUtilities.GetField(configManager, "_refreshRequested"); - if (refreshRequested) - context.Diffs.Add("Refresh is not expected to be requested after GetConfigurationAsync is called"); + // wait 1000ms here because update of config is run as a new task. + Thread.Sleep(1000); // check that _syncAfter is greater than DateTimeOffset.UtcNow + AutomaticRefreshInterval syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); @@ -718,114 +750,6 @@ public void TestConfigurationComparer() TestUtilities.AssertFailIfErrors(context); } - [Fact] - public async Task RequestRefresh_RespectsRefreshInterval() - { - // This test checks that the _syncAfter field is set correctly after a refresh. - var context = new CompareContext($"{this}.RequestRefresh_RespectsRefreshInterval"); - - var timeProvider = new FakeTimeProvider(); - - var docRetriever = new FileDocumentRetriever(); - var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); - TestUtilities.SetField(configManager, "_timeProvider", timeProvider); - - // Get the first configuration. - var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); - - configManager.RequestRefresh(); - - var configAfterFirstRefresh = await configManager.GetConfigurationAsync(CancellationToken.None); - - // First RequestRefresh triggers a refresh. - if (object.ReferenceEquals(configuration, configAfterFirstRefresh)) - context.Diffs.Add("object.ReferenceEquals(configuration, configAfterFirstRefresh)"); - - configManager.RequestRefresh(); - - var configAfterNoTimePassed = await configManager.GetConfigurationAsync(CancellationToken.None); - - // Second RequestRefresh should not trigger a refresh because the refresh interval has not passed. - if (!object.ReferenceEquals(configAfterFirstRefresh, configAfterNoTimePassed)) - context.Diffs.Add("!object.ReferenceEquals(configAfterFirstRefresh, configAfterNoTimePassed)"); - - // Advance time to trigger a refresh. - timeProvider.Advance(configManager.RefreshInterval); - - configManager.RequestRefresh(); - - var configAfterRefreshInterval = await configManager.GetConfigurationAsync(CancellationToken.None); - - // Third RequestRefresh should trigger a refresh because the refresh interval has passed. - if (object.ReferenceEquals(configAfterNoTimePassed, configAfterRefreshInterval)) - context.Diffs.Add("object.ReferenceEquals(configAfterNoTimePassed, configAfterRefreshInterval)"); - - // Advance time just prior to a refresh. - timeProvider.Advance(configManager.RefreshInterval.Subtract(TimeSpan.FromSeconds(1))); - - var configAfterLessThanRefreshInterval = await configManager.GetConfigurationAsync(CancellationToken.None); - - // Fourth RequestRefresh should not trigger a refresh because the refresh interval has not passed. - if (!object.ReferenceEquals(configAfterRefreshInterval, configAfterLessThanRefreshInterval)) - context.Diffs.Add("object.ReferenceEquals(configAfterRefreshInterval, configAfterLessThanRefreshInterval)"); - - // Advance time 365 days. - timeProvider.Advance(TimeSpan.FromDays(365)); - - var configAfterOneYear = await configManager.GetConfigurationAsync(CancellationToken.None); - - // Fifth RequestRefresh should trigger a refresh because the refresh interval has passed. - if (!object.ReferenceEquals(configAfterLessThanRefreshInterval, configAfterOneYear)) - context.Diffs.Add("object.ReferenceEquals(configAfterLessThanRefreshInterval, configAfterOneYear)"); - - TestUtilities.AssertFailIfErrors(context); - } - - [Fact] - public async Task GetConfigurationAsync_RespectsRefreshInterval() - { - var context = new CompareContext($"{this}.GetConfigurationAsync_RespectsRefreshInterval"); - - var timeProvider = new FakeTimeProvider(); - - var docRetriever = new FileDocumentRetriever(); - var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); - TestUtilities.SetField(configManager, "_timeProvider", timeProvider); - - TimeSpan advanceInterval = BaseConfigurationManager.DefaultAutomaticRefreshInterval.Add(TimeSpan.FromSeconds(configManager.AutomaticRefreshInterval.TotalSeconds / 20)); - - TestUtilities.SetField(configManager, "_timeProvider", timeProvider); - - // Get the first configuration. - var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); - - var configNoAdvanceInTime = await configManager.GetConfigurationAsync(CancellationToken.None); - - // First GetConfigurationAsync should not trigger a refresh because the refresh interval has not passed. - if (!object.ReferenceEquals(configuration, configNoAdvanceInTime)) - context.Diffs.Add("!object.ReferenceEquals(configuration, configNoAdvanceInTime)"); - - // Advance time to trigger a refresh. - timeProvider.Advance(advanceInterval); - - var configAfterTimeIsAdvanced = await configManager.GetConfigurationAsync(CancellationToken.None); - - // Same config, but a task is queued to update the configuration. - if (!object.ReferenceEquals(configNoAdvanceInTime, configAfterTimeIsAdvanced)) - context.Diffs.Add("!object.ReferenceEquals(configuration, configAfterTimeIsAdvanced)"); - - // Need to wait for background task to finish. - Thread.Sleep(250); - - var configAfterBackgroundTask = await configManager.GetConfigurationAsync(CancellationToken.None); - - // Configuration should be updated after the background task finishes. - if (object.ReferenceEquals(configAfterTimeIsAdvanced, configAfterBackgroundTask)) - context.Diffs.Add("object.ReferenceEquals(configuration, configAfterBackgroundTask)"); - - TestUtilities.AssertFailIfErrors(context); - } - [Theory, MemberData(nameof(ValidateOpenIdConnectConfigurationTestCases), DisableDiscoveryEnumeration = true)] public async Task ValidateOpenIdConnectConfigurationTests(ConfigurationManagerTheoryData theoryData) { 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 8d266dc7ce..1cbfb9eed3 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 @@ -11,12 +11,6 @@ true - - - true - - PreserveNewest @@ -33,7 +27,6 @@ - From db55b8cead728779e81e1387734d20dc38c7fa75 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 17:02:29 -0800 Subject: [PATCH 06/12] Revert "Revert change to make RequestRefresh run in the background (#3083)" This reverts commit 3f2fbf19a2544030d5b89082990fe874bf18df39. --- .../Configuration/ConfigurationManager.cs | 2 +- .../ConfigurationManagerTests.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index ed3d4b7e67..1f38022f33 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -316,7 +316,7 @@ public override void RequestRefresh() _isFirstRefreshRequest = false; if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) { - UpdateCurrentConfiguration(); + _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); _lastRequestRefresh = now; } } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index 2fba523106..83d7f5d69c 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -230,7 +230,7 @@ public async Task VerifyInterlockGuardForRequestRefresh() // Interlocked guard will block. // Configuration should be AADCommonV1Config signalEvent.Reset(); - _ = Task.Run(() => configurationManager.RequestRefresh()); + configurationManager.RequestRefresh(); // InMemoryDocumentRetrieverWithEvents will signal when it is OK to change the MetadataAddress // otherwise, it may be the case that the MetadataAddress is changed before the previous Task has finished. @@ -239,7 +239,7 @@ public async Task VerifyInterlockGuardForRequestRefresh() // AADCommonV1Json would have been passed to the the previous retriever, which is blocked on an event. configurationManager.MetadataAddress = "AADCommonV2Json"; TestUtilities.SetField(configurationManager, "_lastRequestRefresh", DateTimeOffset.MinValue); - _ = Task.Run(() => configurationManager.RequestRefresh()); + configurationManager.RequestRefresh(); // Set the event to release the lock and let the previous retriever finish. waitEvent.Set(); @@ -658,13 +658,14 @@ public async Task GetConfigurationAsync() var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1)); - configManager.MetadataAddress = "http://127.0.0.1"; configManager.RequestRefresh(); + configManager.MetadataAddress = "http://127.0.0.1"; var configuration2 = await configManager.GetConfigurationAsync(CancellationToken.None); IdentityComparer.AreEqual(configuration, configuration2, context); if (!object.ReferenceEquals(configuration, configuration2)) context.Diffs.Add("!object.ReferenceEquals(configuration, configuration2)"); + // get configuration from http address, should throw // get configuration with unsuccessful HTTP response status code TestUtilities.AssertFailIfErrors(context); From 9173f9175fc1ef5ce604e2faa1c79ddd28260176 Mon Sep 17 00:00:00 2001 From: Westin Musser <127992899+westin-m@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:00:59 -0800 Subject: [PATCH 07/12] Add request count and duration telemetry (#3022) * Add request count and duration telemetry * Add request count and duration telemetry * small fix * Feedback, remodel timing and counters * Use interface for logging, move constants to class * some interface changes, rework tests * fix naming * address interface feedback * Interface and namespace changes * avoid friend assemblies * add metadata * doc comments for tags * PR feedback * Apply suggestions from code review Co-authored-by: Keegan * change class name and add clarifying comments * Change namespace, move package reference --------- Co-authored-by: George Krechar Co-authored-by: jennyf19 Co-authored-by: Keegan --- build/dependencies.props | 1 + build/dependenciesTest.props | 1 + .../InternalAPI.Unshipped.txt | 3 + .../JsonWebTokenHandler.ValidateToken.cs | 7 + .../Configuration/ConfigurationManager.cs | 52 +++++- .../InternalAPI.Unshipped.txt | 2 + .../InternalsVisibleTo.cs | 4 + .../InternalAPI.Unshipped.txt | 66 +++++++ .../Microsoft.IdentityModel.Tokens.csproj | 4 + .../Telemetry/ITelemetryClient.cs | 28 +++ .../Telemetry/TelemetryClient.cs | 67 +++++++ .../Telemetry/TelemetryConstants.cs | 67 +++++++ .../Telemetry/TelemetryDataRecorder.cs | 51 ++++++ .../InternalAPI.Unshipped.txt | 3 +- .../JwtSecurityTokenHandler.cs | 7 + .../JsonWebTokenHandlerTelemetryTests.cs | 69 +++++++ ...crosoft.IdentityModel.Logging.Tests.csproj | 2 + .../ConfigurationManagerTelemetryTests.cs | 171 ++++++++++++++++++ ...Model.Protocols.OpenIdConnect.Tests.csproj | 1 + .../Telemetry/MockTelemetryClient.cs | 49 +++++ .../JwtSecurityTokenHandlerTelemetryTests.cs | 65 +++++++ ...stem.IdentityModel.Tokens.Jwt.Tests.csproj | 2 + 22 files changed, 713 insertions(+), 9 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs create mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs create mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs create mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs create mode 100644 src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs create mode 100644 test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs create mode 100644 test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs 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 20d7ce969e..ff770358f8 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ +Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient +override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> 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 f730094055..203a13339f 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.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 Telemetry.ITelemetryClient _telemetryClient = new TelemetryClient(); + /// /// Returns a value that indicates if this handler can validate a . /// @@ -516,6 +519,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.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index 1f38022f33..fd4608fb6c 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.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; @@ -342,4 +378,4 @@ public override void RequestRefresh() /// public new static readonly TimeSpan MinimumRefreshInterval = BaseConfigurationManager.MinimumRefreshInterval; } -} +} \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt index e69de29bb2..98597bed68 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.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 3e7673b9a5..c8a8b0ba43 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,3 +1,16 @@ +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.ExceptionTypeTag = "ExceptionType" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.IdentityModelVersionTag = "IdentityModelVersion" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.MetadataAddressTag = "MetadataAddress" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.OperationStatusTag = "OperationStatus" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Automatic = "Automatic" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationInvalid = "ConfigurationInvalid" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.FirstRefresh = "FirstRefresh" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Lkg = "LastKnownGood" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Manual = "Manual" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations." -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS" -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception." -> string @@ -8,7 +21,36 @@ 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.Telemetry.TelemetryConstants.ExceptionTypeTag = "ExceptionType" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.IdentityModelVersionTag = "IdentityModelVersion" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.MetadataAddressTag = "MetadataAddress" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.OperationStatusTag = "OperationStatus" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Automatic = "Automatic" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationInvalid = "ConfigurationInvalid" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed" -> string +const Microsoft.IdentityModel.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.Telemetry.TelemetryConstants.Protocols.Lkg = "LastKnownGood" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.Manual = "Manual" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations." -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS" -> string +Microsoft.IdentityModel.Telemetry.ITelemetryClient +Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void +Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void +Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient +Microsoft.IdentityModel.Telemetry.TelemetryClient.ClientVer -> string +Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.TelemetryClient() -> void +Microsoft.IdentityModel.Telemetry.TelemetryConstants +Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols +Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder +Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TelemetryDataRecorder() -> void 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 +79,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.Telemetry.ITelemetryClient +Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void +Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void +Microsoft.IdentityModel.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient +Microsoft.IdentityModel.Telemetry.TelemetryClient.ClientVer -> string +Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.TelemetryClient() -> void +Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder +Microsoft.IdentityModel.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.Telemetry.TelemetryConstants +Microsoft.IdentityModel.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 @@ -71,15 +129,23 @@ override Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.GetExcep override Microsoft.IdentityModel.Tokens.SignatureValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception +static Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(in System.Diagnostics.TagList tagList) -> void +static Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(long requestDurationInMs, in System.Diagnostics.TagList tagList) -> void 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.Telemetry.TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(in System.Diagnostics.TagList tagList) -> void +static Microsoft.IdentityModel.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 static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedString(System.Collections.Generic.IList strings) -> string static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame +static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter +static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogram -> System.Diagnostics.Metrics.Histogram 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.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter +static readonly Microsoft.IdentityModel.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/Microsoft.IdentityModel.Tokens.csproj b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj index b342d4f1f2..25eb2bbc34 100644 --- a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj +++ b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj @@ -53,6 +53,10 @@ + + + + diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs new file mode 100644 index 0000000000..656fb6bdf5 --- /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.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..20cbfcf15b --- /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.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..a4d9449e75 --- /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.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..5023606081 --- /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.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..102ca896eb 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.Telemetry.ITelemetryClient diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs index 2061bcf261..7888083c47 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.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.Telemetry.ITelemetryClient TelemetryClient = new TelemetryClient(); + /// /// Default claim type mapping for inbound claims. /// @@ -888,6 +891,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..e63b48e9fb --- /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.Telemetry; +using Microsoft.IdentityModel.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..5433c6490f --- /dev/null +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +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.Telemetry; +using Microsoft.IdentityModel.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 + }; + var cancel = new CancellationToken(); + + // act + // Retrieve the configuration for the first time + await configurationManager.GetConfigurationAsync(cancel); + testTelemetryClient.ClearExportedItems(); + + // Manually request a config refresh + configurationManager.RequestRefresh(); + await configurationManager.GetConfigurationAsync(cancel); + + Thread.Sleep(1000); + + // 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 + } + + Thread.Sleep(1000); + + 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..74c2cf9312 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,6 +19,7 @@ + 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..8d9dc05262 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs @@ -0,0 +1,49 @@ +// 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.Telemetry.Tests +{ + public class MockTelemetryClient : ITelemetryClient + { + public Dictionary ExportedItems = new Dictionary(); + public Dictionary ExportedHistogramItems = new Dictionary(); + + public void ClearExportedItems() + { + ExportedItems.Clear(); + ExportedHistogramItems.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..ddef0bde36 --- /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.Telemetry; +using Microsoft.IdentityModel.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 @@ + + From 35b1ecfefad5dffe2931fde66b694c6e7a073fe8 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 17:48:03 -0800 Subject: [PATCH 08/12] Fix formatting errors from new version of sdk --- .../Json/JsonWebToken.HeaderClaimSet.cs | 2 +- .../Json/JsonWebToken.PayloadClaimSet.cs | 2 +- .../AuthenticationProtocolMessage.cs | 2 +- src/Microsoft.IdentityModel.Tokens/EventBasedLRUCache.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs index 4694399aa7..2a166298ab 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs @@ -82,7 +82,7 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan) break; else if (!reader.Read()) break; - }; + } return new JsonClaimSet(claims); } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs index dac70e195b..844594ccc5 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs @@ -47,7 +47,7 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan byteSpan) break; else if (!reader.Read()) break; - }; + } return new JsonClaimSet(claims); } diff --git a/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs b/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs index 36cac13e31..d106b7286d 100644 --- a/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs +++ b/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs @@ -210,7 +210,7 @@ public virtual void SetParameters(NameValueCollection nameValueCollection) foreach (string key in nameValueCollection.AllKeys) { SetParameter(key, nameValueCollection[key]); - }; + } } /// diff --git a/src/Microsoft.IdentityModel.Tokens/EventBasedLRUCache.cs b/src/Microsoft.IdentityModel.Tokens/EventBasedLRUCache.cs index 381138019f..5414c89064 100644 --- a/src/Microsoft.IdentityModel.Tokens/EventBasedLRUCache.cs +++ b/src/Microsoft.IdentityModel.Tokens/EventBasedLRUCache.cs @@ -662,7 +662,7 @@ internal void WaitForProcessing() { while (!_eventQueue.IsEmpty) { - }; + } } #endregion From 2028ba709d2663710e4c4eb70bec98dfd50eccb3 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Thu, 13 Feb 2025 18:14:45 -0800 Subject: [PATCH 09/12] Increase test wait time --- .../ConfigurationManagerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index 83d7f5d69c..bfccefa664 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -398,8 +398,8 @@ public async Task AutomaticRefreshInterval(ConfigurationManagerTheoryData Date: Fri, 14 Feb 2025 09:40:42 -0800 Subject: [PATCH 10/12] Telemetry for refresh should only be called when Refresh will happen --- .../Configuration/ConfigurationManager.cs | 10 ++--- .../ConfigurationManagerTelemetryTests.cs | 37 +++++++++++++++++++ .../Telemetry/MockTelemetryClient.cs | 7 ++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index fd4608fb6c..8a316131b1 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -343,12 +343,12 @@ public override void RequestRefresh() { DateTimeOffset now = DateTimeOffset.UtcNow; - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Manual); - if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest) { + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Manual); + _isFirstRefreshRequest = false; if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle) { @@ -378,4 +378,4 @@ public override void RequestRefresh() /// public new static readonly TimeSpan MinimumRefreshInterval = BaseConfigurationManager.MinimumRefreshInterval; } -} \ No newline at end of file +} diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs index 5433c6490f..fe6dea19ee 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs @@ -18,6 +18,43 @@ namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests { public class ConfigurationManagerTelemetryTests { + [Fact] + public async Task RequestRefresh_IntervalHasNotPassed_ExpectedCount() + { + // arrange + var testTelemetryClient = new MockTelemetryClient(); + var configurationManager = new ConfigurationManager( + OpenIdConfigData.AccountsGoogle, + new OpenIdConnectConfigurationRetriever(), + new HttpDocumentRetriever(), + new OpenIdConnectConfigurationValidator()) + { + TelemetryClient = testTelemetryClient + }; + var cancel = new CancellationToken(); + + // act + // Retrieve the configuration for the first time + await configurationManager.GetConfigurationAsync(cancel); + testTelemetryClient.ClearExportedItems(); + + // Manually request a config refresh + configurationManager.RequestRefresh(); + await configurationManager.GetConfigurationAsync(cancel); + + Thread.Sleep(1000); + + // Request a second refresh, but don't wait for the interval to pass + configurationManager.RequestRefresh(); + await configurationManager.GetConfigurationAsync(cancel); + + Thread.Sleep(1000); + + // assert: There should be two calls here, first from the call to GetConfigurationAsync + // the second from RequestRefresh, first request refresh always goes through + Assert.Equal(2, testTelemetryClient.RequestRefreshCounter); + } + [Fact] public async Task RequestRefresh_ExpectedTagsExist() { diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs index 8d9dc05262..d76edf932f 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using Microsoft.IdentityModel.Logging; namespace Microsoft.IdentityModel.Telemetry.Tests @@ -12,6 +13,10 @@ public class MockTelemetryClient : ITelemetryClient public Dictionary ExportedItems = new Dictionary(); public Dictionary ExportedHistogramItems = new Dictionary(); + internal int _requestRefreshCounter; + + public int RequestRefreshCounter => _requestRefreshCounter; + public void ClearExportedItems() { ExportedItems.Clear(); @@ -20,6 +25,7 @@ public void ClearExportedItems() public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) { + Interlocked.Increment(ref _requestRefreshCounter); ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus); @@ -27,6 +33,7 @@ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, Exception exception) { + Interlocked.Increment(ref _requestRefreshCounter); ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus); From d8cdcfbdeb882a102e90ac5ca2f118ec3505ce3e Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Fri, 14 Feb 2025 12:21:50 -0800 Subject: [PATCH 11/12] Remove thread.sleep in tests, add faketimeprovider --- build/commonTest.props | 7 +- build/dependenciesTest.props | 7 ++ .../Configuration/ConfigurationManager.cs | 10 +- .../InternalAPI.Unshipped.txt | 1 + .../ConfigurationManagerTelemetryTests.cs | 33 ++++-- .../ConfigurationManagerTests.cs | 100 +++++++++++++----- .../Microsoft.IdentityModel.TestUtils.csproj | 3 +- 7 files changed, 121 insertions(+), 40 deletions(-) diff --git a/build/commonTest.props b/build/commonTest.props index 74189e5955..eaf97cf0f5 100644 --- a/build/commonTest.props +++ b/build/commonTest.props @@ -51,7 +51,12 @@ - + + true + + + + true diff --git a/build/dependenciesTest.props b/build/dependenciesTest.props index 4e5f755ef4..2fc73e2b67 100644 --- a/build/dependenciesTest.props +++ b/build/dependenciesTest.props @@ -20,4 +20,11 @@ 3.0.0-pre.49 + + + 9.0.0 + + + 8.10.0 + diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index 8a316131b1..4891ade703 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -19,6 +19,8 @@ namespace Microsoft.IdentityModel.Protocols [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] public class ConfigurationManager : BaseConfigurationManager, IConfigurationManager where T : class { + internal Action _onBackgroundTaskFinish; + private DateTimeOffset _syncAfter = DateTimeOffset.MinValue; private DateTimeOffset _lastRequestRefresh = DateTimeOffset.MinValue; private bool _isFirstRefreshRequest = true; @@ -151,7 +153,7 @@ public async Task GetConfigurationAsync() /// If the time since the last call is less than then is not called and the current Configuration is returned. public virtual async Task GetConfigurationAsync(CancellationToken cancel) { - if (_currentConfiguration != null && _syncAfter > DateTimeOffset.UtcNow) + if (_currentConfiguration != null && _syncAfter > TimeProvider.GetUtcNow()) return _currentConfiguration; Exception fetchMetadataFailure = null; @@ -311,13 +313,15 @@ private void UpdateCurrentConfiguration() { Interlocked.Exchange(ref _configurationRetrieverState, ConfigurationRetrieverIdle); } + + _onBackgroundTaskFinish?.Invoke(); #pragma warning restore CA1031 // Do not catch general exception types } private void UpdateConfiguration(T configuration) { _currentConfiguration = configuration; - _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, AutomaticRefreshInterval + + _syncAfter = DateTimeUtil.Add(TimeProvider.GetUtcNow().DateTime, AutomaticRefreshInterval + TimeSpan.FromSeconds(new Random().Next((int)AutomaticRefreshInterval.TotalSeconds / 20))); } @@ -341,7 +345,7 @@ public override async Task GetBaseConfigurationAsync(Cancella /// public override void RequestRefresh() { - DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset now = TimeProvider.GetUtcNow(); if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest) { diff --git a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt index 98597bed68..2d03b12600 100644 --- a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt @@ -1,2 +1,3 @@ Microsoft.IdentityModel.Protocols.ConfigurationManager.TelemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient Microsoft.IdentityModel.Protocols.ConfigurationManager.TimeProvider -> System.TimeProvider +Microsoft.IdentityModel.Protocols.ConfigurationManager._onBackgroundTaskFinish -> System.Action diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs index fe6dea19ee..696d7089c8 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs @@ -6,12 +6,13 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Time.Testing; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Protocols.OpenIdConnect.Configuration; -using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Telemetry; using Microsoft.IdentityModel.Telemetry.Tests; +using Microsoft.IdentityModel.TestUtils; using Xunit; namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests @@ -33,6 +34,11 @@ public async Task RequestRefresh_IntervalHasNotPassed_ExpectedCount() }; var cancel = new CancellationToken(); + AutoResetEvent resetEvent = ConfigurationManagerTests.SetupResetEvent(configurationManager); + + var timeProvider = new FakeTimeProvider(); + configurationManager.TimeProvider = timeProvider; + // act // Retrieve the configuration for the first time await configurationManager.GetConfigurationAsync(cancel); @@ -42,14 +48,12 @@ public async Task RequestRefresh_IntervalHasNotPassed_ExpectedCount() configurationManager.RequestRefresh(); await configurationManager.GetConfigurationAsync(cancel); - Thread.Sleep(1000); + ConfigurationManagerTests.WaitOrFail(resetEvent); // Request a second refresh, but don't wait for the interval to pass configurationManager.RequestRefresh(); await configurationManager.GetConfigurationAsync(cancel); - Thread.Sleep(1000); - // assert: There should be two calls here, first from the call to GetConfigurationAsync // the second from RequestRefresh, first request refresh always goes through Assert.Equal(2, testTelemetryClient.RequestRefreshCounter); @@ -70,6 +74,11 @@ public async Task RequestRefresh_ExpectedTagsExist() }; var cancel = new CancellationToken(); + AutoResetEvent resetEvent = ConfigurationManagerTests.SetupResetEvent(configurationManager); + + var timeProvider = new FakeTimeProvider(); + configurationManager.TimeProvider = timeProvider; + // act // Retrieve the configuration for the first time await configurationManager.GetConfigurationAsync(cancel); @@ -79,7 +88,7 @@ public async Task RequestRefresh_ExpectedTagsExist() configurationManager.RequestRefresh(); await configurationManager.GetConfigurationAsync(cancel); - Thread.Sleep(1000); + ConfigurationManagerTests.WaitOrFail(resetEvent); // assert var expectedCounterTagList = new Dictionary @@ -113,24 +122,28 @@ public async Task GetConfigurationAsync_ExpectedTagsExist(ConfigurationManagerTe TelemetryClient = testTelemetryClient }; + AutoResetEvent resetEvent = ConfigurationManagerTests.SetupResetEvent(configurationManager); + + var timeProvider = new FakeTimeProvider(); + configurationManager.TimeProvider = timeProvider; + try { await configurationManager.GetConfigurationAsync(); if (theoryData.SyncAfter != null) { testTelemetryClient.ClearExportedItems(); - TestUtilities.SetField(configurationManager, "_syncAfter", theoryData.SyncAfter); + timeProvider.Advance((theoryData.SyncAfter - DateTimeOffset.UtcNow).Value); await configurationManager.GetConfigurationAsync(); - } + ConfigurationManagerTests.WaitOrFail(resetEvent); + } } catch (Exception) { // Ignore exceptions } - Thread.Sleep(1000); - Assert.Equal(theoryData.ExpectedTagList, testTelemetryClient.ExportedItems); } @@ -179,7 +192,7 @@ public static TheoryData { { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AADCommonUrl }, diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index bfccefa664..92e3afe66a 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -11,6 +11,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Time.Testing; using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Protocols.OpenIdConnect.Configuration; using Microsoft.IdentityModel.TestUtils; @@ -389,17 +390,20 @@ public async Task AutomaticRefreshInterval(ConfigurationManagerTheoryData 0) - Thread.Sleep(theoryData.SleepTimeInMs); - theoryData.ConfigurationManager.RefreshInterval = theoryData.RefreshInterval; theoryData.ConfigurationManager.MetadataAddress = theoryData.UpdatedMetadataAddress; + timeProvider.Advance(TimeSpan.FromMilliseconds(theoryData.SleepTimeInMs)); + theoryData.ConfigurationManager.RequestRefresh(); - if (theoryData.SleepTimeInMs > 0) - Thread.Sleep(theoryData.SleepTimeInMs); + if (theoryData.WaitForEvent) + WaitOrFail(resetEvent); var updatedConfiguration = await theoryData.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); @@ -514,7 +527,8 @@ public static TheoryData("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever); + AutoResetEvent resetEvent = SetupResetEvent(configManager); + + var timeProvider = new FakeTimeProvider(); + configManager.TimeProvider = timeProvider; + // This is the minimum time that should pass before an automatic refresh occurs // stored in advance to avoid any time drift issues. - DateTimeOffset minimumRefreshInterval = DateTimeOffset.UtcNow + configManager.AutomaticRefreshInterval; + DateTimeOffset minimumRefreshInterval = timeProvider.GetUtcNow() + configManager.AutomaticRefreshInterval; // get the first configuration, internal _syncAfter should be set to a time greater than UtcNow + AutomaticRefreshInterval. var configuration = await configManager.GetConfigurationAsync(CancellationToken.None); // force a refresh by setting internal field - TestUtilities.SetField(configManager, "_syncAfter", DateTimeOffset.UtcNow - TimeSpan.FromHours(1)); + timeProvider.Advance(TimeSpan.FromDays(1)); configuration = await configManager.GetConfigurationAsync(CancellationToken.None); + // wait 1000ms here because update of config is run as a new task. - Thread.Sleep(1000); + WaitOrFail(resetEvent); // check that _syncAfter is greater than DateTimeOffset.UtcNow + AutomaticRefreshInterval DateTimeOffset syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); @@ -635,8 +656,9 @@ public async Task CheckSyncAfter() // force a refresh by setting internal field TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1)); configManager.RequestRefresh(); + // wait 1000ms here because update of config is run as a new task. - Thread.Sleep(1000); + WaitOrFail(resetEvent); // check that _syncAfter is greater than DateTimeOffset.UtcNow + AutomaticRefreshInterval syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter"); @@ -762,14 +784,16 @@ public async Task ValidateOpenIdConnectConfigurationTests(ConfigurationManagerTh if (theoryData.PresetCurrentConfiguration) TestUtilities.SetField(configurationManager, "_currentConfiguration", new OpenIdConnectConfiguration() { Issuer = Default.Issuer }); + AutoResetEvent resetEvent = SetupResetEvent(configurationManager); + try { //create a listener and enable it for logs var listener = TestUtils.SampleListener.CreateLoggerListener(EventLevel.Warning); configuration = await configurationManager.GetConfigurationAsync(); - // we need to sleep here to make sure the task that updates configuration has finished. - Thread.Sleep(250); + if (theoryData.WaitForEvent) + WaitOrFail(resetEvent); if (!string.IsNullOrEmpty(theoryData.ExpectedErrorMessage) && !listener.TraceBuffer.Contains(theoryData.ExpectedErrorMessage)) context.AddDiff($"Expected exception to contain: '{theoryData.ExpectedErrorMessage}'.{Environment.NewLine}Log is:{Environment.NewLine}'{listener.TraceBuffer}'"); @@ -812,7 +836,8 @@ public static TheoryData @@ -823,7 +848,8 @@ public static TheoryData @@ -833,7 +859,8 @@ public static TheoryData @@ -844,7 +871,8 @@ public static TheoryData @@ -854,7 +882,8 @@ public static TheoryData @@ -865,7 +894,8 @@ public static TheoryData @@ -875,7 +905,8 @@ public static TheoryData @@ -886,7 +917,8 @@ public static TheoryData configurationManager) + { + var resetEvent = new AutoResetEvent(false); + + Action _waitAction = () => resetEvent.Set(); + configurationManager._onBackgroundTaskFinish = _waitAction; + + return resetEvent; + } + + internal static void WaitOrFail(AutoResetEvent are) + { + if (!are.WaitOne(30000)) + Assert.Fail("Failed to receive a signal in 30s, failing test"); + } + public class ConfigurationManagerTheoryData : TheoryDataBase where T : class { public ConfigurationManager ConfigurationManager { get; set; } @@ -954,6 +1002,8 @@ public ConfigurationManagerTheoryData(string testId) : base(testId) { } public DateTimeOffset SyncAfter { get; set; } = DateTime.UtcNow; + public bool WaitForEvent { get; set; } + public override string ToString() { return $"{TestId}, {MetadataAddress}, {ExpectedException}"; diff --git a/test/Microsoft.IdentityModel.TestUtils/Microsoft.IdentityModel.TestUtils.csproj b/test/Microsoft.IdentityModel.TestUtils/Microsoft.IdentityModel.TestUtils.csproj index 8ac6f21ecc..ebcc1d0e00 100644 --- a/test/Microsoft.IdentityModel.TestUtils/Microsoft.IdentityModel.TestUtils.csproj +++ b/test/Microsoft.IdentityModel.TestUtils/Microsoft.IdentityModel.TestUtils.csproj @@ -1,4 +1,4 @@ - + @@ -16,6 +16,7 @@ + From 19f8f709ecb6242db8cdb84746ab6c27516d14c4 Mon Sep 17 00:00:00 2001 From: Keegan Caruso Date: Fri, 14 Feb 2025 14:36:06 -0800 Subject: [PATCH 12/12] Cannot remove friend dependencies --- .../AppContextSwitches.cs | 10 ++++++++++ .../Telemetry/ITelemetryClient.cs | 6 ++++++ .../Telemetry/TelemetryClient.cs | 16 ++++++++++++++++ .../Telemetry/TelemetryConstants.cs | 5 +++++ .../Telemetry/TelemetryDataRecorder.cs | 12 ++++++++++++ .../Telemetry/MockTelemetryClient.cs | 7 +++++++ 6 files changed, 56 insertions(+) diff --git a/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs b/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs index 0105963f08..b5e9f18c26 100644 --- a/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs +++ b/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs @@ -70,6 +70,16 @@ internal static class AppContextSwitches internal static bool UseRfcDefinitionOfEpkAndKid => _useRfcDefinitionOfEpkAndKid ??= (AppContext.TryGetSwitch(UseRfcDefinitionOfEpkAndKidSwitch, out bool isEnabled) && isEnabled); + + internal const string UpdateConfigAsBlockingSwitch = "Switch.Microsoft.IdentityModel.UpdateConfigAsBlocking"; + + private static bool? _updateConfigAsBlockingCall; + + /// + /// Unused, part of a previous release. This is a friend, so we cannot remove. + /// + internal static bool UpdateConfigAsBlocking => _updateConfigAsBlockingCall ??= (AppContext.TryGetSwitch(UpdateConfigAsBlockingSwitch, out bool blockingCall) && blockingCall); + /// /// Used for testing to reset all switches to its default value. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs index 656fb6bdf5..2640ec7357 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs @@ -24,5 +24,11 @@ internal void IncrementConfigurationRefreshRequestCounter( string metadataAddress, string operationStatus, Exception exception); + + // Unused, this was part of a previous release, since it is a friend, + // it cannot be removed. + internal void LogBackgroundConfigurationRefreshFailure( + string metadataAddress, + Exception exception); } } diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs index 20cbfcf15b..9df1e3e0a9 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; namespace Microsoft.IdentityModel.Telemetry { @@ -63,5 +64,20 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan o long durationInMilliseconds = (long)operationDuration.TotalMilliseconds; TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(durationInMilliseconds, tagList); } + + public void LogBackgroundConfigurationRefreshFailure( + string metadataAddress, + Exception exception) + { + var tagList = new TagList() + { + { TelemetryConstants.IdentityModelVersionTag, ClientVer }, + { TelemetryConstants.MetadataAddressTag, metadataAddress }, + { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() }, + { TelemetryConstants.BlockingTypeTag, AppContextSwitches.UpdateConfigAsBlocking.ToString() } + }; + + TelemetryDataRecorder.IncrementBackgroundConfigurationRefreshFailureCounter(tagList); + } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs index a4d9449e75..643b603305 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs @@ -27,6 +27,11 @@ internal static class TelemetryConstants /// public const string ExceptionTypeTag = "ExceptionType"; + /// + /// Telemetry tag indicating if the update was blocking. + /// + public const string BlockingTypeTag = "Blocking"; + public static class Protocols { // Configuration manager refresh statuses diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs index 5023606081..604ec0bda0 100644 --- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs +++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs @@ -30,6 +30,13 @@ internal class TelemetryDataRecorder internal const string IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations."; internal static readonly Counter ConfigurationManagerCounter = IdentityModelMeter.CreateCounter(IdentityModelConfigurationManagerCounterName, description: IdentityModelConfigurationManagerCounterDescription); + /// + /// Counter to capture background refresh failures in the ConfigurationManager. + /// + internal static readonly Counter BackgroundConfigurationRefreshFailureCounter = IdentityModelMeter.CreateCounter(BackgroundConfigurationRefreshFailureCounterName, description: BackgroundConfigurationRefreshFailureCounterDescription); + internal const string BackgroundConfigurationRefreshFailureCounterName = "IdentityModelConfigurationManagerBackgroundRefreshFailure"; + internal const string BackgroundConfigurationRefreshFailureCounterDescription = "Counter capturing configuration manager background refresh failures."; + /// /// Histogram to capture total duration of configuration retrieval by ConfigurationManager in milliseconds. /// @@ -47,5 +54,10 @@ internal static void IncrementConfigurationRefreshRequestCounter(in TagList tagL { ConfigurationManagerCounter.Add(1, tagList); } + + internal static void IncrementBackgroundConfigurationRefreshFailureCounter(in TagList tagList) + { + BackgroundConfigurationRefreshFailureCounter.Add(1, tagList); + } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs index d76edf932f..87c1fd67f5 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs @@ -52,5 +52,12 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan o ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); ExportedHistogramItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString()); } + + void ITelemetryClient.LogBackgroundConfigurationRefreshFailure(string metadataAddress, Exception exception) + { + ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer); + ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress); + ExportedItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString()); + } } }