diff --git a/build/dependenciesTest.props b/build/dependenciesTest.props
index 65daedcf38..7cc73f438b 100644
--- a/build/dependenciesTest.props
+++ b/build/dependenciesTest.props
@@ -19,4 +19,13 @@
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 8e37d60ac2..b1cea9ebdc 100644
--- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
+++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
@@ -35,6 +35,8 @@ 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.
bool _refreshRequested;
@@ -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;
@@ -295,7 +297,7 @@ private void UpdateCurrentConfiguration()
private void UpdateConfiguration(T configuration)
{
_currentConfiguration = configuration;
- _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, AutomaticRefreshInterval +
+ _syncAfter = DateTimeUtil.Add(_timeProvider.GetUtcNow().UtcDateTime, AutomaticRefreshInterval +
TimeSpan.FromSeconds(new Random().Next((int)AutomaticRefreshInterval.TotalSeconds / 20)));
}
@@ -319,11 +321,12 @@ 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)
{
_isFirstRefreshRequest = false;
_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 f7bba705bc..ebf33d0b68 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;
@@ -19,9 +20,6 @@
namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests
{
- ///
- ///
- ///
public class ConfigurationManagerTests
{
///
@@ -569,7 +567,7 @@ public static TheoryData("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), docRetriever);
@@ -720,6 +718,96 @@ 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 configAfterSecondRefresh = await configManager.GetConfigurationAsync(CancellationToken.None);
+
+ // Second RequestRefresh should not trigger a refresh because the refresh interval has not passed.
+ if (!object.ReferenceEquals(configAfterFirstRefresh, configAfterSecondRefresh))
+ context.Diffs.Add("!object.ReferenceEquals(configAfterFirstRefresh, configAfterSecondRefresh)");
+
+ // Advance time to trigger a refresh.
+ timeProvider.Advance(configManager.RefreshInterval);
+
+ configManager.RequestRefresh();
+
+ var configAfterThirdRefresh = await configManager.GetConfigurationAsync(CancellationToken.None);
+
+ // Third RequestRefresh should trigger a refresh because the refresh interval has passed.
+ if (object.ReferenceEquals(configAfterSecondRefresh, configAfterThirdRefresh))
+ context.Diffs.Add("object.ReferenceEquals(configAfterSecondRefresh, configAfterThirdRefresh)");
+
+ 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 1cbfb9eed3..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
@@ -11,6 +11,12 @@
true
+
+
+ true
+
+
PreserveNewest
@@ -27,6 +33,7 @@
+