Merge 7.2 forward to 7.3

This commit is contained in:
Joe DeCock 2025-07-21 20:52:17 -05:00
commit 61b2f01988
5 changed files with 126 additions and 15 deletions

2
.gitignore vendored
View file

@ -228,4 +228,4 @@ artifacts
*.Artifacts/
reports
reports

View file

@ -58,17 +58,25 @@ internal class ProtectedResourceErrorHttpWriter : IHttpResponseWriter<ProtectedR
errorDescription = "The access token expired";
}
var errorString = string.Format($"error=\"{error}\"");
if (errorDescription.IsMissing())
var values = new List<string>
{
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, new StringValues(new[] { "Bearer realm=\"IdentityServer\"", errorString }));
}
else
"""
Bearer realm="IdentityServer"
""",
$"""
error="{error}"
"""
};
if (!errorDescription.IsMissing())
{
var errorDescriptionString = string.Format($"error_description=\"{errorDescription}\"");
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, new StringValues(new[] { "Bearer realm=\"IdentityServer\"", errorString, errorDescriptionString }));
values.Add($"""
error_description="{errorDescription}"
""");
}
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, string.Join(",", values));
return Task.CompletedTask;
}
}

View file

@ -140,14 +140,17 @@ public class DefaultSessionCoordinationService : ISessionCoordinationService
{
var client = await ClientStore.FindClientByIdAsync(clientId); // i don't think we care if it's an enabled client at this point
var shouldCoordinate =
client.CoordinateLifetimeWithUserSession == true ||
(Options.Authentication.CoordinateClientLifetimesWithUserSession && client.CoordinateLifetimeWithUserSession != false);
if (shouldCoordinate)
if (client != null)
{
// this implies they should also be contacted for backchannel logout below
clientsToCoordinate.Add(clientId);
var shouldCoordinate =
client.CoordinateLifetimeWithUserSession == true ||
(Options.Authentication.CoordinateClientLifetimesWithUserSession && client.CoordinateLifetimeWithUserSession != false);
if (shouldCoordinate)
{
// this implies they should also be contacted for backchannel logout below
clientsToCoordinate.Add(clientId);
}
}
}

View file

@ -0,0 +1,61 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityServer.Endpoints.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
namespace UnitTests.Endpoints.Results;
public class ProtectedResourceErrorResultTests
{
private readonly ProtectedResourceErrorHttpWriter writer = new();
[Fact]
public void WwwAuthenticate_header_with_error_and_description_should_be_a_single_line()
{
var context = new DefaultHttpContext();
writer.WriteHttpResponse(
new ProtectedResourceErrorResult("oops", "big oops"),
context
);
var wwwAuthHeader = context.Response.Headers[HeaderNames.WWWAuthenticate].ToString();
wwwAuthHeader.ShouldBe(
"""
Bearer realm="IdentityServer",error="oops",error_description="big oops"
""");
}
[Fact]
public void WwwAuthenticate_header_with_error_should_be_a_single_line()
{
var context = new DefaultHttpContext();
writer.WriteHttpResponse(
new ProtectedResourceErrorResult("oops"),
context
);
var wwwAuthHeader = context.Response.Headers[HeaderNames.WWWAuthenticate].ToString();
wwwAuthHeader.ShouldBe(
"""
Bearer realm="IdentityServer",error="oops"
""");
}
[Fact]
public void WwwAuthenticate_header_should_always_be_a_single_string_value()
{
var context = new DefaultHttpContext();
writer.WriteHttpResponse(
new ProtectedResourceErrorResult("oops", "big oops"),
context
);
var wwwAuthHeader = context.Response.Headers[HeaderNames.WWWAuthenticate];
wwwAuthHeader.Count.ShouldBe(1);
}
}

View file

@ -0,0 +1,39 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Stores;
using Microsoft.Extensions.Logging.Abstractions;
using UnitTests.Endpoints.EndSession;
namespace UnitTests.Services.Default;
public class DefaultSessionCoordinationServiceTests
{
public DefaultSessionCoordinationService Service;
[Fact]
public async Task Handles_missing_client_null_reference()
{
var stubBackChannelLogoutClient = new StubBackChannelLogoutClient();
Service = new DefaultSessionCoordinationService(
new IdentityServerOptions(),
new InMemoryPersistedGrantStore(),
new InMemoryClientStore([]),
stubBackChannelLogoutClient,
new NullLogger<DefaultSessionCoordinationService>());
await Service.ProcessExpirationAsync(new UserSession
{
ClientIds = ["not_found"],
SessionId = "1",
SubjectId = "1"
});
stubBackChannelLogoutClient
.SendLogoutsWasCalled
.ShouldBeFalse();
}
}