diff --git a/benchmark/Microsoft.IdentityModel.Benchmarks/Microsoft.IdentityModel.Benchmarks.csproj b/benchmark/Microsoft.IdentityModel.Benchmarks/Microsoft.IdentityModel.Benchmarks.csproj
index c7d383a7e1..2cbc222427 100644
--- a/benchmark/Microsoft.IdentityModel.Benchmarks/Microsoft.IdentityModel.Benchmarks.csproj
+++ b/benchmark/Microsoft.IdentityModel.Benchmarks/Microsoft.IdentityModel.Benchmarks.csproj
@@ -47,4 +47,9 @@
+
+
+ true
+
+
diff --git a/build/commonTest.props b/build/commonTest.props
index 488f6ef6f3..74189e5955 100644
--- a/build/commonTest.props
+++ b/build/commonTest.props
@@ -49,5 +49,10 @@
+
+
+
+ true
+
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..6bcbbd9361 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
@@ -19,4 +20,13 @@
3.0.0-pre.49
+
+
+
+ 9.0.0
+
+
+ 8.10.0
+
+
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt
index 3242b527e7..3c1f13320b 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt
@@ -1,3 +1,4 @@
+Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string header, string payload, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string jwtEncodedString, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(System.ReadOnlyMemory encodedTokenMemory, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs
index 918788932f..d01751bd4c 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 .
///
@@ -511,6 +514,10 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration)
// where a new valid configuration was somehow published during validation time.
if (currentConfiguration != null)
{
+ _telemetryClient.IncrementConfigurationRefreshRequestCounter(
+ validationParameters.ConfigurationManager.MetadataAddress,
+ TelemetryConstants.Protocols.Lkg);
+
validationParameters.ConfigurationManager.RequestRefresh();
validationParameters.RefreshBeforeValidation = true;
var lastConfig = currentConfiguration;
diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
index 1f38022f33..b5e6bf0eba 100644
--- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
+++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
@@ -3,11 +3,13 @@
using System;
using System.Net.Http;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols.Configuration;
using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Telemetry;
namespace Microsoft.IdentityModel.Protocols
{
@@ -18,8 +20,16 @@ namespace Microsoft.IdentityModel.Protocols
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
public class ConfigurationManager : BaseConfigurationManager, IConfigurationManager where T : class
{
- private DateTimeOffset _syncAfter = DateTimeOffset.MinValue;
- private DateTimeOffset _lastRequestRefresh = DateTimeOffset.MinValue;
+ // 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 bool _isFirstRefreshRequest = true;
private readonly SemaphoreSlim _configurationNullLock = new SemaphoreSlim(1);
@@ -35,6 +45,17 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM
private const int ConfigurationRetrieverRunning = 1;
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
+ // 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.
///
@@ -147,7 +168,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;
@@ -172,7 +193,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 +204,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,7 +246,24 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel)
{
if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle)
{
- _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None);
+ 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);
+ }
}
}
@@ -227,7 +276,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));
}
@@ -240,6 +289,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 +298,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 +323,12 @@ private void UpdateCurrentConfiguration()
}
catch (Exception ex)
{
+ var elapsedTime = _timeProvider.GetElapsedTime(startTimestamp);
+ TelemetryClient.LogConfigurationRetrievalDuration(
+ MetadataAddress,
+ elapsedTime,
+ ex);
+
LogHelper.LogExceptionMessage(
new InvalidOperationException(
LogHelper.FormatInvariant(
@@ -285,15 +347,34 @@ private void UpdateCurrentConfiguration()
private void UpdateConfiguration(T configuration)
{
_currentConfiguration = configuration;
- _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, AutomaticRefreshInterval +
+ var newSyncTime = 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));
}
///
/// 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)
{
@@ -309,16 +390,14 @@ public override async Task GetBaseConfigurationAsync(Cancella
///
public override void RequestRefresh()
{
- DateTimeOffset now = DateTimeOffset.UtcNow;
+ DateTime now = _timeProvider.GetUtcNow().UtcDateTime;
- if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest)
+ if (now >= DateTimeUtil.Add(LastRequestRefresh, RefreshInterval) || _isFirstRefreshRequest)
{
_isFirstRefreshRequest = false;
- if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle)
- {
- _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None);
- _lastRequestRefresh = now;
- }
+ AtomicUpdateSyncAfter(now);
+ AtomicUpdateLastRequestRefresh(now);
+ _refreshRequested = true;
}
}
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/AsymmetricAdapter.cs b/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs
index 7b09c89324..8aa11d9318 100644
--- a/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs
+++ b/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs
@@ -241,6 +241,9 @@ private void InitializeUsingRsaSecurityKey(RsaSecurityKey rsaSecurityKey, string
{
#if NET472 || NET6_0_OR_GREATER
var rsa = RSA.Create(rsaSecurityKey.Parameters);
+#elif NET462
+ var rsa = new RSACng();
+ rsa.ImportParameters(rsaSecurityKey.Parameters);
#else
var rsa = RSA.Create();
rsa.ImportParameters(rsaSecurityKey.Parameters);
diff --git a/src/Microsoft.IdentityModel.Tokens/Encryption/AuthenticatedEncryptionProvider.cs b/src/Microsoft.IdentityModel.Tokens/Encryption/AuthenticatedEncryptionProvider.cs
index a8e38b0b1f..6965c1f0b6 100644
--- a/src/Microsoft.IdentityModel.Tokens/Encryption/AuthenticatedEncryptionProvider.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Encryption/AuthenticatedEncryptionProvider.cs
@@ -152,7 +152,7 @@ private AuthenticatedEncryptionResult EncryptWithAesCbc(byte[] plaintext, byte[]
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogHelper.FormatInvariant(LogMessages.IDX10654, ex)));
}
- byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8);
+ byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8L);
byte[] macBytes = new byte[authenticatedData.Length + aes.IV.Length + ciphertext.Length + al.Length];
Array.Copy(authenticatedData, 0, macBytes, 0, authenticatedData.Length);
Array.Copy(aes.IV, 0, macBytes, authenticatedData.Length, aes.IV.Length);
@@ -173,7 +173,7 @@ private byte[] DecryptWithAesCbc(byte[] ciphertext, byte[] authenticatedData, by
throw LogHelper.LogExceptionMessage(new SecurityTokenDecryptionFailedException(
LogHelper.FormatInvariant(LogMessages.IDX10625, authenticationTag.Length, expectedTagLength, Base64UrlEncoder.Encode(authenticationTag), Algorithm)));
- byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8);
+ byte[] al = Utility.ConvertToBigEndian(authenticatedData.Length * 8L);
byte[] macBytes = new byte[authenticatedData.Length + iv.Length + ciphertext.Length + al.Length];
Array.Copy(authenticatedData, 0, macBytes, 0, authenticatedData.Length);
Array.Copy(iv, 0, macBytes, authenticatedData.Length, iv.Length);
diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
index 3e5486ba41..864be35a34 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
@@ -38,9 +80,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
@@ -64,15 +122,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/JsonWebKeyConverter.cs b/src/Microsoft.IdentityModel.Tokens/JsonWebKeyConverter.cs
index 786d6c1b25..ae41d800b0 100644
--- a/src/Microsoft.IdentityModel.Tokens/JsonWebKeyConverter.cs
+++ b/src/Microsoft.IdentityModel.Tokens/JsonWebKeyConverter.cs
@@ -199,8 +199,16 @@ public static JsonWebKey ConvertFromECDsaSecurityKey(ECDsaSecurityKey key)
}
#endif
- internal static bool TryConvertToSecurityKey(JsonWebKey webKey, out SecurityKey key)
+ ///
+ /// This will attempt to convert the to a .
+ ///
+ ///
+ ///
+ public static bool TryConvertToSecurityKey(JsonWebKey webKey, out SecurityKey key)
{
+ if (webKey == null)
+ throw LogHelper.LogArgumentNullException(nameof(webKey));
+
if (webKey.ConvertedSecurityKey != null)
{
key = webKey.ConvertedSecurityKey;
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/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt
index d2c20a77d4..0743567f72 100644
--- a/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt
@@ -1,2 +1,3 @@
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.get -> bool
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.set -> void
+static Microsoft.IdentityModel.Tokens.JsonWebKeyConverter.TryConvertToSecurityKey(Microsoft.IdentityModel.Tokens.JsonWebKey webKey, out Microsoft.IdentityModel.Tokens.SecurityKey key) -> bool
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 f108b9dbf5..9c52fef4aa 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.
///
@@ -887,6 +890,10 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken,
// where a new valid configuration was somehow published during validation time.
if (currentConfiguration != null)
{
+ TelemetryClient.IncrementConfigurationRefreshRequestCounter(
+ validationParameters.ConfigurationManager.MetadataAddress,
+ TelemetryConstants.Protocols.Lkg);
+
validationParameters.ConfigurationManager.RequestRefresh();
validationParameters.RefreshBeforeValidation = true;
var lastConfig = currentConfiguration;
diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs
new file mode 100644
index 0000000000..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..583781a2d8
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs
@@ -0,0 +1,167 @@
+// 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/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs
index 83d7f5d69c..1ee7645570 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 Retreiver
+// Ignore Spelling: Metadata Validator
using System;
using System.Collections.Generic;
@@ -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;
@@ -19,9 +20,6 @@
namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests
{
- ///
- ///
- ///
public class ConfigurationManagerTests
{
///
@@ -39,7 +37,7 @@ public async Task GetPublicMetadata(ConfigurationManagerTheoryData(
theoryData.MetadataAddress,
- theoryData.ConfigurationRetreiver,
+ theoryData.ConfigurationRetriever,
theoryData.DocumentRetriever,
theoryData.ConfigurationValidator);
@@ -62,7 +60,7 @@ public static TheoryData("AccountsGoogleCom")
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = new HttpDocumentRetriever(),
MetadataAddress = OpenIdConfigData.AccountsGoogle
@@ -70,7 +68,7 @@ public static TheoryData("AADCommonUrl")
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = new HttpDocumentRetriever(),
MetadataAddress = OpenIdConfigData.AADCommonUrl
@@ -78,7 +76,7 @@ public static TheoryData("AADCommonUrlV1")
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = new HttpDocumentRetriever(),
MetadataAddress = OpenIdConfigData.AADCommonUrlV1
@@ -86,7 +84,7 @@ public static TheoryData("AADCommonUrlV2")
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = new HttpDocumentRetriever(),
MetadataAddress = OpenIdConfigData.AADCommonUrlV2
@@ -101,7 +99,7 @@ public void OpenIdConnectConstructor(ConfigurationManagerTheoryData(theoryData.MetadataAddress, theoryData.ConfigurationRetreiver, theoryData.DocumentRetriever, theoryData.ConfigurationValidator);
+ var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetriever, theoryData.DocumentRetriever, theoryData.ConfigurationValidator);
theoryData.ExpectedException.ProcessNoException();
}
catch (Exception ex)
@@ -120,7 +118,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = new HttpDocumentRetriever(),
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"),
@@ -131,7 +129,7 @@ public static TheoryData
{
- ConfigurationRetreiver = null,
+ ConfigurationRetriever = null,
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = new HttpDocumentRetriever(),
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"),
@@ -141,7 +139,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
DocumentRetriever = null,
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"),
@@ -151,7 +149,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = null,
DocumentRetriever = new HttpDocumentRetriever(),
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"),
@@ -209,48 +207,6 @@ 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();
- 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);
- 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,7 +232,7 @@ public async Task VerifyInterlockGuardForGetConfigurationAsync()
waitEvent.Reset();
signalEvent.Reset();
- TestUtilities.SetField(configurationManager, "_syncAfter", DateTimeOffset.MinValue);
+ TestUtilities.SetField(configurationManager, "_syncAfter", DateTime.MinValue);
await configurationManager.GetConfigurationAsync(CancellationToken.None);
// InMemoryDocumentRetrieverWithEvents will signal when it is OK to change the MetadataAddress
@@ -320,13 +276,15 @@ public async Task BootstrapRefreshIntervalTest()
catch (Exception firstFetchMetadataFailure)
{
// _syncAfter should not have been changed, because the fetch failed.
- var syncAfter = TestUtilities.GetField(configManager, "_syncAfter");
- if ((DateTimeOffset)syncAfter != DateTimeOffset.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.");
+ DateTime requestTime = DateTime.UtcNow;
+
// Fetch metadata again during refresh interval, the exception should be same from above.
try
{
@@ -339,9 +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 = TestUtilities.GetField(configManager, "_syncAfter");
- if ((DateTimeOffset)syncAfter != DateTimeOffset.MinValue)
- context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal '{DateTimeOffset.MinValue}'.");
+ syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter");
+
+ if (!IdentityComparer.AreDatesEqualWithEpsilon(requestTime, syncAfter, 1))
+ context.AddDiff($"ConfigurationManager._syncAfter: '{syncAfter}' should equal be within 1 second of '{requestTime}'.");
IdentityComparer.AreEqual(firstFetchMetadataFailure, secondFetchMetadataFailure, context);
}
@@ -396,10 +355,10 @@ public async Task AutomaticRefreshInterval(ConfigurationManagerTheoryData("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever);
@@ -621,25 +580,34 @@ public async Task CheckSyncAfter()
var configuration = await configManager.GetConfigurationAsync(CancellationToken.None);
// force a refresh by setting internal field
- TestUtilities.SetField(configManager, "_syncAfter", DateTimeOffset.UtcNow - TimeSpan.FromHours(1));
+ TestUtilities.SetField(configManager, "_syncAfter", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1)));
configuration = await configManager.GetConfigurationAsync(CancellationToken.None);
// 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
- DateTimeOffset syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter");
+ DateTime syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter");
if (syncAfter < minimumRefreshInterval)
context.Diffs.Add($"(AutomaticRefreshInterval) syncAfter '{syncAfter}' < DateTimeOffset.UtcNow + configManager.AutomaticRefreshInterval: '{minimumRefreshInterval}'.");
// make same check for RequestRefresh
// force a refresh by setting internal field
- TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1));
+ TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1)));
+
configManager.RequestRefresh();
- // wait 1000ms here because update of config is run as a new task.
- Thread.Sleep(1000);
+
+ 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");
// check that _syncAfter is greater than DateTimeOffset.UtcNow + AutomaticRefreshInterval
- syncAfter = (DateTimeOffset)TestUtilities.GetField(configManager, "_syncAfter");
+ syncAfter = (DateTime)TestUtilities.GetField(configManager, "_syncAfter");
if (syncAfter < minimumRefreshInterval)
context.Diffs.Add($"(RequestRefresh) syncAfter '{syncAfter}' < DateTimeOffset.UtcNow + configManager.AutomaticRefreshInterval: '{minimumRefreshInterval}'.");
@@ -657,15 +625,14 @@ public async Task GetConfigurationAsync()
configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever);
var configuration = await configManager.GetConfigurationAsync(CancellationToken.None);
- TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTimeOffset.UtcNow - TimeSpan.FromHours(1));
- configManager.RequestRefresh();
+ TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1)));
configManager.MetadataAddress = "http://127.0.0.1";
+ configManager.RequestRefresh();
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);
@@ -751,13 +718,121 @@ 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)
{
TestUtilities.WriteHeader($"{this}.ValidateOpenIdConnectConfigurationTests");
var context = new CompareContext();
OpenIdConnectConfiguration configuration;
- var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetreiver, theoryData.DocumentRetriever, theoryData.ConfigurationValidator);
+ var configurationManager = new ConfigurationManager(theoryData.MetadataAddress, theoryData.ConfigurationRetriever, theoryData.DocumentRetriever, theoryData.ConfigurationValidator);
if (theoryData.PresetCurrentConfiguration)
TestUtilities.SetField(configurationManager, "_currentConfiguration", new OpenIdConnectConfiguration() { Issuer = Default.Issuer });
@@ -797,7 +872,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator,
DocumentRetriever = new FileDocumentRetriever(),
First = true,
@@ -807,7 +882,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX21818:", typeof(InvalidConfigurationException)),
@@ -817,7 +892,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
PresetCurrentConfiguration = true,
@@ -828,7 +903,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX10810:", typeof(InvalidConfigurationException)),
@@ -838,7 +913,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
PresetCurrentConfiguration = true,
@@ -849,7 +924,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX21817:", typeof(InvalidConfigurationException)),
@@ -859,7 +934,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
PresetCurrentConfiguration = true,
@@ -870,7 +945,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
ExpectedException = new ExpectedException(typeof(InvalidOperationException), "IDX10814:", typeof(InvalidConfigurationException)),
@@ -880,7 +955,7 @@ public static TheoryData
{
- ConfigurationRetreiver = new OpenIdConnectConfigurationRetriever(),
+ ConfigurationRetriever = new OpenIdConnectConfigurationRetriever(),
ConfigurationValidator = openIdConnectConfigurationValidator2,
DocumentRetriever = new FileDocumentRetriever(),
PresetCurrentConfiguration = true,
@@ -926,7 +1001,7 @@ public ConfigurationManagerTheoryData(string testId) : base(testId) { }
public TimeSpan AutomaticRefreshInterval { get; set; }
- public IConfigurationRetriever ConfigurationRetreiver { get; set; }
+ public IConfigurationRetriever ConfigurationRetriever { get; set; }
public IConfigurationValidator ConfigurationValidator { 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..aa78636b2d 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,6 +11,12 @@
true
+
+
+ true
+
+
PreserveNewest
@@ -19,6 +25,7 @@
+
@@ -27,6 +34,7 @@
+
diff --git a/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs b/test/Microsoft.IdentityModel.Protocols.Tests/ExtensibilityTests.cs
index c37f5d04ce..71cf3e66d8 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", DateTimeOffset.UtcNow - TimeSpan.FromHours(1));
+ TestUtilities.SetField(configManager, "_lastRequestRefresh", DateTime.UtcNow.Subtract(TimeSpan.FromHours(1)));
configManager.MetadataAddress = "IssuerMetadata2.json";
// Wait for the refresh to complete.
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs
index 1afd38f4a4..28825138c2 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs
@@ -1307,7 +1307,7 @@ internal static void AddSignUsingOffsets(byte[] bytes, SecurityKey securityKey,
{
Bytes = bytes,
Count = -1,
- ExpectedException = ExpectedException.ArgumentException(),
+ ExpectedException = prefix == "RSA" ? ExpectedException.ArgumentOutOfRangeException() : ExpectedException.ArgumentException(),
Offset = 0,
SignatureProvider = CreateProvider(securityKey, algorithm)
});
@@ -1316,7 +1316,7 @@ internal static void AddSignUsingOffsets(byte[] bytes, SecurityKey securityKey,
{
Bytes = bytes,
Count = bytes.Length + 1,
- ExpectedException = ExpectedException.ArgumentException(),
+ ExpectedException = prefix == "RSA" ? ExpectedException.ArgumentOutOfRangeException() : ExpectedException.ArgumentException(),
Offset = 0,
SignatureProvider = CreateProvider(securityKey, algorithm)
});
@@ -1325,7 +1325,7 @@ internal static void AddSignUsingOffsets(byte[] bytes, SecurityKey securityKey,
{
Bytes = bytes,
Count = 10,
- ExpectedException = ExpectedException.ArgumentException(),
+ ExpectedException = prefix == "RSA" ? ExpectedException.ArgumentOutOfRangeException() : ExpectedException.ArgumentException(),
Offset = bytes.Length - 1,
SignatureProvider = CreateProvider(securityKey, algorithm)
});
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..ba76c4c0fe
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Telemetry.Tests
+{
+ public class MockTelemetryClient : ITelemetryClient
+ {
+ public Dictionary ExportedItems = new Dictionary();
+ public Dictionary ExportedHistogramItems = new Dictionary();
+
+ public void ClearExportedItems()
+ {
+ ExportedItems.Clear();
+ }
+
+ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus)
+ {
+ ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus);
+ }
+
+ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, Exception exception)
+ {
+ ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus);
+ ExportedItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString());
+ }
+
+ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration)
+ {
+ ExportedHistogramItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ }
+
+ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration, Exception exception)
+ {
+ ExportedHistogramItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ ExportedHistogramItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString());
+ }
+ }
+}
diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs
new file mode 100644
index 0000000000..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 @@
+
+