From 51ca7fe9bcd3a35f01eb85387afffc2107bc2156 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Thu, 5 Dec 2024 15:38:33 -0600 Subject: [PATCH] Log expiration warning if license expires while running --- src/IdentityServer/Hosting/EndpointRouter.cs | 5 ++ .../Licensing/v2/LicenseExpirationChecker.cs | 2 +- .../LicensingCustomizationTests.cs | 2 +- .../Hosting/LicenseTests.cs | 71 ++++++++++++++----- .../Common/TestLicenseExpirationChecker.cs | 13 ++++ .../Hosting/EndpointRouterTests.cs | 2 +- .../Licensing/v2/FakeLoggerFactory.cs | 24 +++++++ .../v2/LicenseExpirationCheckerTests.cs | 6 +- .../v2/ProtocolRequestCounterTests.cs | 2 +- 9 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 test/IdentityServer.UnitTests/Common/TestLicenseExpirationChecker.cs create mode 100644 test/IdentityServer.UnitTests/Licensing/v2/FakeLoggerFactory.cs diff --git a/src/IdentityServer/Hosting/EndpointRouter.cs b/src/IdentityServer/Hosting/EndpointRouter.cs index 105306d82..7fd2e7e58 100644 --- a/src/IdentityServer/Hosting/EndpointRouter.cs +++ b/src/IdentityServer/Hosting/EndpointRouter.cs @@ -16,17 +16,20 @@ internal class EndpointRouter : IEndpointRouter { private readonly IEnumerable _endpoints; private readonly IProtocolRequestCounter _requestCounter; + private readonly ILicenseExpirationChecker _expirationChecker; private readonly IdentityServerOptions _options; private readonly ILogger _logger; public EndpointRouter( IEnumerable endpoints, IProtocolRequestCounter requestCounter, + ILicenseExpirationChecker expirationChecker, IdentityServerOptions options, ILogger logger) { _endpoints = endpoints; _requestCounter = requestCounter; + _expirationChecker = expirationChecker; _options = options; _logger = logger; } @@ -44,6 +47,8 @@ public IEndpointHandler Find(HttpContext context) _logger.LogDebug("Request path {path} matched to endpoint type {endpoint}", context.Request.Path, endpointName); _requestCounter.Increment(); + _expirationChecker.CheckExpiration(); + return GetEndpointHandler(endpoint, context); } } diff --git a/src/IdentityServer/Licensing/v2/LicenseExpirationChecker.cs b/src/IdentityServer/Licensing/v2/LicenseExpirationChecker.cs index 320c9c28b..0f9d72c97 100644 --- a/src/IdentityServer/Licensing/v2/LicenseExpirationChecker.cs +++ b/src/IdentityServer/Licensing/v2/LicenseExpirationChecker.cs @@ -36,7 +36,7 @@ public void CheckExpiration() if (!_expiredLicenseWarned && !_license.Current.Redistribution && IsExpired) { _expiredLicenseWarned = true; - _logger.LogError("In a future version of IdentityServer, expired licenses will stop the server after {gracePeriod} days.", _gracePeriod.Days); + _logger.LogError("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after {gracePeriod} days.", _gracePeriod.Days); } } diff --git a/test/IdentityServer.IntegrationTests/Extensibility/LicensingCustomizationTests.cs b/test/IdentityServer.IntegrationTests/Extensibility/LicensingCustomizationTests.cs index 0046e6e88..34972f664 100644 --- a/test/IdentityServer.IntegrationTests/Extensibility/LicensingCustomizationTests.cs +++ b/test/IdentityServer.IntegrationTests/Extensibility/LicensingCustomizationTests.cs @@ -52,7 +52,7 @@ public void customization_of_IFeatureManager_is_not_allowed() internal class CustomLicenseAccessor : ILicenseAccessor { - public License Current { get; } + public License Current => new License(); } internal class CustomProtocolRequestCounter : IProtocolRequestCounter diff --git a/test/IdentityServer.IntegrationTests/Hosting/LicenseTests.cs b/test/IdentityServer.IntegrationTests/Hosting/LicenseTests.cs index 5447e359a..6132c3359 100644 --- a/test/IdentityServer.IntegrationTests/Hosting/LicenseTests.cs +++ b/test/IdentityServer.IntegrationTests/Hosting/LicenseTests.cs @@ -103,7 +103,7 @@ public async Task unlicensed_protocol_requests_log_a_warning() [InlineData("6685-enterprise-standard", "Duende_IdentityServer_License.key")] [InlineData("6703-community", "Duende_License.key")] [InlineData("6703-community", "Duende_IdentityServer_License.key")] - public async Task expired_license_warnings_are_logged(string licenseFileName, string destinationFileName) + public async Task expired_license_warnings_are_logged_if_license_expires_while_server_is_running(string licenseFileName, string destinationFileName) { // Copy a test license to the file system where the mock pipeline will see it var contentRoot = Path.GetFullPath(Directory.GetCurrentDirectory()); @@ -112,28 +112,63 @@ public async Task expired_license_warnings_are_logged(string licenseFileName, st var dest = Path.Combine(contentRoot, destinationFileName); File.Copy(src, dest, true); - // Set the time to be after the license expiration + // Start up with an unexpired license var timeProvider = new FakeTimeProvider(); _mockPipeline.OnPreConfigureServices += collection => collection.AddSingleton(timeProvider); - _mockPipeline.Initialize(enableLogging: true); var testLicenseExpiration = new DateTime(2024, 11, 15); // This is the value in all the test license keys + var beforeExpiration = testLicenseExpiration - TimeSpan.FromDays(1); + timeProvider.SetUtcNow(beforeExpiration); + _mockPipeline.Initialize(enableLogging: true); + + // License is not expired yet + _mockPipeline.MockLogger.LogMessages.Should().NotContain("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + + // Advance the clock to after expiration var afterExpiration = testLicenseExpiration + TimeSpan.FromDays(1); timeProvider.SetUtcNow(afterExpiration); + _mockPipeline.MockLogger.LogMessages.Should().NotContain("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); - // Make any protocol request - var data = new Dictionary - { - { "grant_type", "client_credentials" }, - { "client_id", client_id }, - { "client_secret", client_secret }, - { "scope", scope_name }, - }; - var form = new FormUrlEncodedContent(data); - await _mockPipeline.BackChannelClient.PostAsync(IdentityServerPipeline.TokenEndpoint, form); + // Make a protocol request + await _mockPipeline.BackChannelClient.GetAsync(IdentityServerPipeline.DiscoveryEndpoint); + + // Now expect a warning because we handled a request with an expired license + // REMINDER - If this test needs to be updated because the logged message changed, expired_redist_licenses_do_not_log_warnings should also be updated + _mockPipeline.MockLogger.LogMessages.Should().Contain("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + } + + [Theory] + [InlineData("6677-starter-standard", "Duende_License.key")] + [InlineData("6677-starter-standard","Duende_IdentityServer_License.key")] + [InlineData("6678-business-standard", "Duende_License.key")] + [InlineData("6678-business-standard", "Duende_IdentityServer_License.key")] + [InlineData("6680-starter-standard-added-key-management-feature", "Duende_License.key")] + [InlineData("6680-starter-standard-added-key-management-feature", "Duende_IdentityServer_License.key")] + [InlineData("6681-business-standard-added-dynamic-providers-feature", "Duende_License.key")] + [InlineData("6681-business-standard-added-dynamic-providers-feature", "Duende_IdentityServer_License.key")] + [InlineData("6685-enterprise-standard", "Duende_License.key")] + [InlineData("6685-enterprise-standard", "Duende_IdentityServer_License.key")] + [InlineData("6703-community", "Duende_License.key")] + [InlineData("6703-community", "Duende_IdentityServer_License.key")] + public void expired_license_warnings_are_logged_on_startup(string licenseFileName, string destinationFileName) + { + // Copy a test license to the file system where the mock pipeline will see it + var contentRoot = Path.GetFullPath(Directory.GetCurrentDirectory()); + var sourceFileName = Path.Combine("TestLicenses", licenseFileName); + var src = Path.Combine(contentRoot, sourceFileName); + var dest = Path.Combine(contentRoot, destinationFileName); + File.Copy(src, dest, true); - // Expect a warning because the license is expired + // Start up with an expired license + var timeProvider = new FakeTimeProvider(); + _mockPipeline.OnPreConfigureServices += collection => collection.AddSingleton(timeProvider); + var testLicenseExpiration = new DateTime(2024, 11, 15); // This is the value in all the test license keys + var afterExpiration = testLicenseExpiration + TimeSpan.FromDays(1); + timeProvider.SetUtcNow(afterExpiration); + _mockPipeline.Initialize(enableLogging: true); + + // Expect a warning because we handled a request with an expired license // REMINDER - If this test needs to be updated because the logged message changed, expired_redist_licenses_do_not_log_warnings should also be updated - _mockPipeline.MockLogger.LogMessages.Should().Contain("In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + _mockPipeline.MockLogger.LogMessages.Should().Contain("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); } [Theory] @@ -172,7 +207,7 @@ public async Task expired_redist_licenses_do_not_log_warnings(string licenseFile await _mockPipeline.BackChannelClient.PostAsync(IdentityServerPipeline.TokenEndpoint, form); // Expect no warning because the license is a redistribution license - _mockPipeline.MockLogger.LogMessages.Should().NotContain("In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + _mockPipeline.MockLogger.LogMessages.Should().NotContain("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); } [Theory] @@ -216,8 +251,6 @@ public async Task nonexpired_license_warnings_are_not_logged(string licenseFileN await _mockPipeline.BackChannelClient.PostAsync(IdentityServerPipeline.TokenEndpoint, form); // Expect no warning because the license is not expired - _mockPipeline.MockLogger.LogMessages.Should().NotContain("In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + _mockPipeline.MockLogger.LogMessages.Should().NotContain("The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); } - - } \ No newline at end of file diff --git a/test/IdentityServer.UnitTests/Common/TestLicenseExpirationChecker.cs b/test/IdentityServer.UnitTests/Common/TestLicenseExpirationChecker.cs new file mode 100644 index 000000000..b5178e585 --- /dev/null +++ b/test/IdentityServer.UnitTests/Common/TestLicenseExpirationChecker.cs @@ -0,0 +1,13 @@ +// Copyright (c) Duende Software. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using Duende.IdentityServer.Licensing.v2; + +namespace UnitTests.Common; + +public class TestLicenseExpirationChecker : ILicenseExpirationChecker +{ + public void CheckExpiration() + { + } +} \ No newline at end of file diff --git a/test/IdentityServer.UnitTests/Hosting/EndpointRouterTests.cs b/test/IdentityServer.UnitTests/Hosting/EndpointRouterTests.cs index a99f1d0d2..645b6c5de 100644 --- a/test/IdentityServer.UnitTests/Hosting/EndpointRouterTests.cs +++ b/test/IdentityServer.UnitTests/Hosting/EndpointRouterTests.cs @@ -27,7 +27,7 @@ public EndpointRouterTests() _pathMap = new Dictionary(); _endpoints = new List(); _options = new IdentityServerOptions(); - _subject = new EndpointRouter(_endpoints, new TestProtocolRequestCounter(), _options, TestLogger.Create()); + _subject = new EndpointRouter(_endpoints, new TestProtocolRequestCounter(), new TestLicenseExpirationChecker(), _options, TestLogger.Create()); } [Fact] diff --git a/test/IdentityServer.UnitTests/Licensing/v2/FakeLoggerFactory.cs b/test/IdentityServer.UnitTests/Licensing/v2/FakeLoggerFactory.cs new file mode 100644 index 000000000..e0ee0e345 --- /dev/null +++ b/test/IdentityServer.UnitTests/Licensing/v2/FakeLoggerFactory.cs @@ -0,0 +1,24 @@ +// Copyright (c) Duende Software. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using Microsoft.Extensions.Logging; + +namespace IdentityServer.UnitTests.Licensing.v2; + +public class StubLoggerFactory(ILogger logger) : ILoggerFactory +{ + public ILogger CreateLogger(string categoryName) + { + return logger; + } + + public void Dispose() + { + } + + public void AddProvider(ILoggerProvider provider) + { + } + + +} \ No newline at end of file diff --git a/test/IdentityServer.UnitTests/Licensing/v2/LicenseExpirationCheckerTests.cs b/test/IdentityServer.UnitTests/Licensing/v2/LicenseExpirationCheckerTests.cs index 36f391a80..0130fc521 100644 --- a/test/IdentityServer.UnitTests/Licensing/v2/LicenseExpirationCheckerTests.cs +++ b/test/IdentityServer.UnitTests/Licensing/v2/LicenseExpirationCheckerTests.cs @@ -27,7 +27,7 @@ public LicenseExpirationCheckerTests() _license = new TestLicenseAccessor(); _timeProvider = new FakeTimeProvider(); _logger = new FakeLogger(); - _expirationCheck = new LicenseExpirationChecker(_license, _timeProvider, _logger); + _expirationCheck = new LicenseExpirationChecker(_license, _timeProvider, new StubLoggerFactory(_logger)); } [Fact] @@ -41,7 +41,7 @@ public void warning_is_logged_for_expired_license() // REMINDER - If this test needs to change because the log message was updated, so should no_warning_is_logged_for_unexpired_license _logger.Collector.GetSnapshot().Should() .ContainSingle(r => - r.Message == $"In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + r.Message == $"The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); } [Fact] @@ -54,7 +54,7 @@ public void no_warning_is_logged_for_unexpired_license() var expiration = _license.Current.Expiration?.ToString("yyyy-MM-dd"); _logger.Collector.GetSnapshot().Should() .NotContain(r => - r.Message == $"In a future version of IdentityServer, expired licenses will stop the server after 90 days."); + r.Message == $"The IdentityServer license is expired. In a future version of IdentityServer, expired licenses will stop the server after 90 days."); } [Fact] diff --git a/test/IdentityServer.UnitTests/Licensing/v2/ProtocolRequestCounterTests.cs b/test/IdentityServer.UnitTests/Licensing/v2/ProtocolRequestCounterTests.cs index 09a547956..4fa90905c 100644 --- a/test/IdentityServer.UnitTests/Licensing/v2/ProtocolRequestCounterTests.cs +++ b/test/IdentityServer.UnitTests/Licensing/v2/ProtocolRequestCounterTests.cs @@ -15,7 +15,7 @@ public ProtocolRequestCounterTests() { _license = new TestLicenseAccessor(); _logger = new FakeLogger(); - _counter = new ProtocolRequestCounter(_license, _logger); + _counter = new ProtocolRequestCounter(_license, new StubLoggerFactory(_logger)); } [Fact]