From ea5617b437b0c9ace1a1868f69fe4ad0e06ec329 Mon Sep 17 00:00:00 2001
From: Joe DeCock <josephdecock@gmail.com>
Date: Wed, 13 Dec 2023 21:01:55 -0600
Subject: [PATCH] Rework fix for logout token issuer bug

Moved the fix from IdentityServerTools into the back channel logout
service to avoid breaking changes in IdentityServerTools. While
technically this is a breaking change in the back channel service too,
this makes the impact much smaller, and more likely to be caught by the
compiler. The previous implementation changed the semantics of a string
parameter in a way that could have been surprising, vs this way adds a
new constructor parameter in a way that will be totally obvious and
caught by the compiler.
---
 src/IdentityServer/IdentityServerTools.cs     | 11 +++++-----
 .../DefaultBackChannelLogoutService.cs        | 11 +++++++++-
 .../DefaultBackChannelLogoutServiceTests.cs   | 20 +++++++++++--------
 3 files changed, 27 insertions(+), 15 deletions(-)

diff --git a/src/IdentityServer/IdentityServerTools.cs b/src/IdentityServer/IdentityServerTools.cs
index 02ed1e09b..c9ca8dc68 100644
--- a/src/IdentityServer/IdentityServerTools.cs
+++ b/src/IdentityServer/IdentityServerTools.cs
@@ -48,23 +48,22 @@ public IdentityServerTools(IServiceProvider serviceProvider, IIssuerNameService
     /// <exception cref="System.ArgumentNullException">claims</exception>
     public virtual async Task<string> IssueJwtAsync(int lifetime, IEnumerable<Claim> claims)
     {
-        var tokenType = OidcConstants.TokenTypes.AccessToken;
         var issuer = await IssuerNameService.GetCurrentAsync();
-        return await IssueJwtAsync(lifetime, issuer, tokenType, claims);
+        return await IssueJwtAsync(lifetime, issuer, claims);
     }
 
     /// <summary>
     /// Issues a JWT.
     /// </summary>
     /// <param name="lifetime">The lifetime.</param>
-    /// <param name="tokenType">The token type.</param>
+    /// <param name="issuer">The issuer.</param>
     /// <param name="claims">The claims.</param>
     /// <returns></returns>
     /// <exception cref="System.ArgumentNullException">claims</exception>
-    public virtual async Task<string> IssueJwtAsync(int lifetime, string tokenType, IEnumerable<Claim> claims)
+    public virtual Task<string> IssueJwtAsync(int lifetime, string issuer, IEnumerable<Claim> claims)
     {
-        var issuer = await IssuerNameService.GetCurrentAsync();
-        return await IssueJwtAsync(lifetime, issuer, tokenType, claims);
+        var tokenType = OidcConstants.TokenTypes.AccessToken;
+        return IssueJwtAsync(lifetime, issuer, tokenType, claims);
     }
 
     /// <summary>
diff --git a/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs b/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs
index dbd9e4ad6..54b8d5e94 100644
--- a/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs
+++ b/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs
@@ -49,6 +49,11 @@ public class DefaultBackChannelLogoutService : IBackChannelLogoutService
     /// </summary>
     protected ILogger<IBackChannelLogoutService> Logger { get; }
 
+    /// <summary>
+    /// Ths issuer name service.
+    /// </summary>
+    protected IIssuerNameService IssuerNameService { get; }
+
     /// <summary>
     /// Constructor.
     /// </summary>
@@ -56,12 +61,14 @@ public class DefaultBackChannelLogoutService : IBackChannelLogoutService
     /// <param name="tools"></param>
     /// <param name="logoutNotificationService"></param>
     /// <param name="backChannelLogoutHttpClient"></param>
+    /// <param name="issuerNameService"></param>
     /// <param name="logger"></param>
     public DefaultBackChannelLogoutService(
         ISystemClock clock,
         IdentityServerTools tools,
         ILogoutNotificationService logoutNotificationService,
         IBackChannelLogoutHttpClient backChannelLogoutHttpClient,
+        IIssuerNameService issuerNameService,
         ILogger<IBackChannelLogoutService> logger)
     {
         Clock = clock;
@@ -69,6 +76,7 @@ public DefaultBackChannelLogoutService(
         LogoutNotificationService = logoutNotificationService;
         HttpClient = backChannelLogoutHttpClient;
         Logger = logger;
+        IssuerNameService = issuerNameService;
     }
 
     /// <inheritdoc/>
@@ -150,7 +158,8 @@ protected virtual async Task<string> CreateTokenAsync(BackChannelLogoutRequest r
             return await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, request.Issuer, IdentityServerConstants.TokenTypes.LogoutToken, claims);
         }
 
-        return await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, IdentityServerConstants.TokenTypes.LogoutToken, claims);
+        var issuer = await IssuerNameService.GetCurrentAsync();
+        return await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, issuer, IdentityServerConstants.TokenTypes.LogoutToken, claims);
     }
 
     /// <summary>
diff --git a/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs b/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs
index f8228fd36..a864ce618 100644
--- a/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs
+++ b/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs
@@ -24,15 +24,18 @@ public class DefaultBackChannelLogoutServiceTests
 {
     private class ServiceTestHarness : DefaultBackChannelLogoutService
     {
-        public ServiceTestHarness(ISystemClock clock,
-                                  IdentityServerTools tools,
-                                  ILogoutNotificationService logoutNotificationService,
-                                  IBackChannelLogoutHttpClient backChannelLogoutHttpClient,
-                                  ILogger<IBackChannelLogoutService> logger) 
-            : base(clock, tools, logoutNotificationService, backChannelLogoutHttpClient, logger)
+        public ServiceTestHarness(
+            ISystemClock clock,
+            IdentityServerTools tools,
+            ILogoutNotificationService logoutNotificationService,
+            IBackChannelLogoutHttpClient backChannelLogoutHttpClient,
+            IIssuerNameService issuerNameService,
+            ILogger<IBackChannelLogoutService> logger) 
+            : base(clock, tools, logoutNotificationService, backChannelLogoutHttpClient, issuerNameService, logger)
         {
         }
 
+
         // CreateTokenAsync is protected, so we use this wrapper to exercise it in our tests
         public async Task<string> ExerciseCreateTokenAsync(BackChannelLogoutRequest request)
         {
@@ -51,14 +54,15 @@ public async Task CreateTokenAsync_Should_Set_Issuer_Correctly()
 
         var tokenCreation = new DefaultTokenCreationService(new MockClock(), mockKeyMaterialService, TestIdentityServerOptions.Create(), TestLogger.Create<DefaultTokenCreationService>());
 
+        var issuerNameService = new TestIssuerNameService(expected);
         var tools = new IdentityServerTools(
             null, // service provider is unused 
-            new TestIssuerNameService(expected),
+            issuerNameService,
             tokenCreation,
             new MockClock()
         );
 
-        var subject = new ServiceTestHarness(null, tools, null, null, null);
+        var subject = new ServiceTestHarness(null, tools, null, null, issuerNameService, null);
         var rawToken = await subject.ExerciseCreateTokenAsync(new BackChannelLogoutRequest
         {
             ClientId = "test_client",