From d8fadf967f9f7d4616e62bd19b8987e15d226033 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Sat, 4 Jan 2025 23:13:49 -0600 Subject: [PATCH] fix(is): service resolution from HttpContext Fix service resolution in PostConfigureApplicationCookieTicketStore so that it can be constructed without an http context available. --- ...stConfigureApplicationCookieTicketStore.cs | 3 +- .../Common/MockHttpContextAccessor.cs | 9 +++- ...figureApplicationCookieTicketStoreTests.cs | 53 +++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 test/IdentityServer.UnitTests/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStoreTests.cs diff --git a/src/IdentityServer/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStore.cs b/src/IdentityServer/Configuration/DependencyInjection/PostConfigureApplicationCookieTicketStore.cs index bfa04987e..2e0a6fb34 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,6 +67,7 @@ public void PostConfigure(string name, CookieAuthenticationOptions options) } IdentityServerLicenseValidator.Instance.ValidateServerSideSessions(); + var _licenseUsage = _httpContextAccessor.HttpContext?.RequestServices.GetRequiredService(); _licenseUsage.FeatureUsed(LicenseFeature.ServerSideSessions); var sessionStore = _httpContextAccessor.HttpContext!.RequestServices.GetService(); 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..9b7f259e7 --- /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 has a convenient HttpContext, but initially we + // simulate not having it by stashing it away and setting the accessor + // 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