diff --git a/src/IdentityServer/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStore.cs b/src/IdentityServer/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStore.cs index bfa04987e..b8c4c3aff 100644 --- a/src/IdentityServer/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStore.cs +++ b/src/IdentityServer/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStore.cs @@ -21,7 +21,6 @@ public class PostConfigureApplicationCookieTicketStore : IPostConfigureOptions _logger; /// @@ -38,7 +37,6 @@ public PostConfigureApplicationCookieTicketStore( ILogger logger) { _httpContextAccessor = httpContextAccessor; - _licenseUsage = httpContextAccessor.HttpContext?.RequestServices.GetRequiredService(); _logger = logger; _scheme = identityServerOptions.Authentication.CookieAuthenticationScheme ?? @@ -69,7 +67,8 @@ public void PostConfigure(string name, CookieAuthenticationOptions options) } IdentityServerLicenseValidator.Instance.ValidateServerSideSessions(); - _licenseUsage.FeatureUsed(LicenseFeature.ServerSideSessions); + var licenseUsage = _httpContextAccessor.HttpContext?.RequestServices.GetRequiredService(); + licenseUsage.FeatureUsed(LicenseFeature.ServerSideSessions); var sessionStore = _httpContextAccessor.HttpContext!.RequestServices.GetService(); if (sessionStore is InMemoryServerSideSessionStore) diff --git a/test/IdentityServer.UnitTests/Common/MockHttpContextAccessor.cs b/test/IdentityServer.UnitTests/Common/MockHttpContextAccessor.cs index 6ce802a4f..b9d7f2525 100644 --- a/test/IdentityServer.UnitTests/Common/MockHttpContextAccessor.cs +++ b/test/IdentityServer.UnitTests/Common/MockHttpContextAccessor.cs @@ -2,6 +2,7 @@ // See LICENSE in the project root for license information. +using System; using Duende.IdentityServer; using Duende.IdentityServer.Configuration; using Duende.IdentityServer.Models; @@ -24,7 +25,8 @@ public MockHttpContextAccessor( IdentityServerOptions options = null, IUserSession userSession = null, IMessageStore endSessionStore = null, - IServerUrls urls = null) + IServerUrls urls = null, + Action configureServices = null) { options = options ?? TestIdentityServerOptions.Create(); @@ -63,6 +65,11 @@ public MockHttpContextAccessor( services.AddSingleton(urls); } + if (configureServices != null) + { + configureServices(services); + } + _context.RequestServices = services.BuildServiceProvider(); } diff --git a/test/IdentityServer.UnitTests/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStoreTests.cs b/test/IdentityServer.UnitTests/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStoreTests.cs new file mode 100644 index 000000000..59dd07471 --- /dev/null +++ b/test/IdentityServer.UnitTests/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStoreTests.cs @@ -0,0 +1,53 @@ +using Duende.IdentityServer.Configuration; +using Duende.IdentityServer.Licensing.V2; +using FluentAssertions; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using UnitTests.Common; +using Xunit; + +namespace UnitTests.Configuration.DependencyInjection; + +public class PostConfigureApplicationCookieTicketStoreTests +{ + + [Fact] + public void can_be_constructed_without_httpcontext_and_used_later_with_httpcontext() + { + // Register the dependencies of the usage tracker so that we can resolve it in PostConfigure + var httpContextAccessor = new MockHttpContextAccessor(configureServices: sp => + { + sp.AddSingleton(TestLogger.Create()); + sp.AddSingleton(); + sp.AddSingleton(); + }); + + // The mock http context accessor has a convenient HttpContext, but + // initially we simulate not having it by stashing it away and setting + // the accessor's context to null. + var savedContext = httpContextAccessor.HttpContext; + httpContextAccessor.HttpContext = null; + + var sut = new PostConfigureApplicationCookieTicketStore( + httpContextAccessor, + new IdentityServerOptions + { + Authentication = new AuthenticationOptions + { + // This is needed so that we operate on the correct scheme + CookieAuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme + } + }, + Options.Create(new()), + TestLogger.Create() + ); + + // Now that we've constructed, we can bring back the http context and run PostConfigure + httpContextAccessor.HttpContext = savedContext; + var cookieOpts = new CookieAuthenticationOptions(); + sut.PostConfigure(CookieAuthenticationDefaults.AuthenticationScheme, cookieOpts); + + cookieOpts.SessionStore.Should().BeOfType(); + } +} \ No newline at end of file