Skip to content

Commit

Permalink
Log expiration warning if license expires while running
Browse files Browse the repository at this point in the history
  • Loading branch information
josephdecock committed Dec 5, 2024
1 parent 35da3cb commit 51ca7fe
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 26 deletions.
5 changes: 5 additions & 0 deletions src/IdentityServer/Hosting/EndpointRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ internal class EndpointRouter : IEndpointRouter
{
private readonly IEnumerable<Endpoint> _endpoints;
private readonly IProtocolRequestCounter _requestCounter;
private readonly ILicenseExpirationChecker _expirationChecker;
private readonly IdentityServerOptions _options;
private readonly ILogger _logger;

public EndpointRouter(
IEnumerable<Endpoint> endpoints,
IProtocolRequestCounter requestCounter,
ILicenseExpirationChecker expirationChecker,
IdentityServerOptions options,
ILogger<EndpointRouter> logger)
{
_endpoints = endpoints;
_requestCounter = requestCounter;
_expirationChecker = expirationChecker;
_options = options;
_logger = logger;
}
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 52 additions & 19 deletions test/IdentityServer.IntegrationTests/Hosting/LicenseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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>(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<string, string>
{
{ "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>(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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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.");
}


}
Original file line number Diff line number Diff line change
@@ -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()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public EndpointRouterTests()
_pathMap = new Dictionary<string, Duende.IdentityServer.Hosting.Endpoint>();
_endpoints = new List<Duende.IdentityServer.Hosting.Endpoint>();
_options = new IdentityServerOptions();
_subject = new EndpointRouter(_endpoints, new TestProtocolRequestCounter(), _options, TestLogger.Create<EndpointRouter>());
_subject = new EndpointRouter(_endpoints, new TestProtocolRequestCounter(), new TestLicenseExpirationChecker(), _options, TestLogger.Create<EndpointRouter>());
}

[Fact]
Expand Down
24 changes: 24 additions & 0 deletions test/IdentityServer.UnitTests/Licensing/v2/FakeLoggerFactory.cs
Original file line number Diff line number Diff line change
@@ -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)
{
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public LicenseExpirationCheckerTests()
_license = new TestLicenseAccessor();
_timeProvider = new FakeTimeProvider();
_logger = new FakeLogger<LicenseExpirationChecker>();
_expirationCheck = new LicenseExpirationChecker(_license, _timeProvider, _logger);
_expirationCheck = new LicenseExpirationChecker(_license, _timeProvider, new StubLoggerFactory(_logger));
}

[Fact]
Expand All @@ -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]
Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public ProtocolRequestCounterTests()
{
_license = new TestLicenseAccessor();
_logger = new FakeLogger<ProtocolRequestCounter>();
_counter = new ProtocolRequestCounter(_license, _logger);
_counter = new ProtocolRequestCounter(_license, new StubLoggerFactory(_logger));
}

[Fact]
Expand Down

0 comments on commit 51ca7fe

Please sign in to comment.