Fixed compilation errors after rebasing XUnit V3 changes

This commit is contained in:
Brett Hazen 2026-02-19 15:19:42 -06:00
parent e0034d642a
commit 900af2eeab
21 changed files with 91 additions and 93 deletions

View file

@ -2,6 +2,8 @@
"solution": {
"path": "..\\products.slnx",
"projects": [
"conformance-report\\src\\ConformanceReport\\ConformanceReport.csproj",
"conformance-report\\test\\ConformanceReport.Tests\\ConformanceReport.Tests.csproj",
"identity-server\\aspire\\AppHosts\\All\\All.csproj",
"identity-server\\aspire\\AppHosts\\Dev\\Dev.csproj",
"identity-server\\aspire\\ServiceDefaults\\ServiceDefaults.csproj",
@ -40,13 +42,13 @@
"identity-server\\clients\\src\\MvcJarUriJwt\\MvcJarUriJwt.csproj",
"identity-server\\clients\\src\\Web\\Web.csproj",
"identity-server\\clients\\src\\WindowsConsoleSystemBrowser\\WindowsConsoleSystemBrowser.csproj",
"identity-server\\hosts\\AspNetIdentity10\\Host.AspNetIdentity10.csproj",
"identity-server\\hosts\\EntityFramework10\\Host.EntityFramework10.csproj",
"identity-server\\hosts\\Main10\\Host.Main10.csproj",
"identity-server\\hosts\\Shared\\Host.Shared.csproj",
"identity-server\\hosts\\UI\\AspNetIdentity\\UI.AspNetIdentity.csproj",
"identity-server\\hosts\\UI\\EntityFramework\\UI.EntityFramework.csproj",
"identity-server\\hosts\\UI\\Main\\UI.Main.csproj",
"identity-server\\hosts\\AspNetIdentity10\\Host.AspNetIdentity10.csproj",
"identity-server\\hosts\\EntityFramework10\\Host.EntityFramework10.csproj",
"identity-server\\hosts\\Main10\\Host.Main10.csproj",
"identity-server\\migrations\\AspNetIdentityDb\\AspNetIdentityDb.csproj",
"identity-server\\migrations\\IdentityServerDb\\IdentityServerDb.csproj",
"identity-server\\src\\AspNetIdentity\\IdentityServer.AspNetIdentity.csproj",
@ -54,6 +56,7 @@
"identity-server\\src\\Configuration\\IdentityServer.Configuration.csproj",
"identity-server\\src\\EntityFramework.Storage\\IdentityServer.EntityFramework.Storage.csproj",
"identity-server\\src\\EntityFramework\\IdentityServer.EntityFramework.csproj",
"identity-server\\src\\IdentityServer.ConformanceReport\\IdentityServer.ConformanceReport.csproj",
"identity-server\\src\\IdentityServer\\IdentityServer.csproj",
"identity-server\\src\\Storage\\IdentityServer.Storage.csproj",
"identity-server\\templates\\src\\IdentityServerAspNetIdentity\\IdentityServerAspNetIdentity.csproj",
@ -69,4 +72,4 @@
"shared\\Xunit.Playwright\\Xunit.Playwright.csproj"
]
}
}
}

View file

@ -279,8 +279,8 @@ public static class IdentityServerConstants
public const string SamlPathPrefix = "saml";
public const string SamlMetadata = SamlPathPrefix + "/metadata";
public const string SamlSignin = SamlPathPrefix + "/sso";
public const string SamlSigninCallback = SamlSignin + "/callback";
public const string SamlSignin = SamlPathPrefix + "/signin";
public const string SamlSigninCallback = SamlPathPrefix + "/signin_callback";
public const string SamlIdpInitiated = SamlPathPrefix + "/idp-initiated";
public const string SamlLogout = SamlPathPrefix + "/slo";
public const string SamlLogoutCallback = SamlLogout + "/callback";

View file

@ -8,16 +8,15 @@ using Duende.IdentityModel;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Saml;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlClaimsMappingTests(ITestOutputHelper output)
public class SamlClaimsMappingTests
{
private const string Category = "SAML Claims Mapping";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlDataBuilder Build => Fixture.Builder;
[Fact]

View file

@ -9,17 +9,16 @@ using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Duende.IdentityModel;
using Duende.IdentityServer.Models;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlEncryptionTests(ITestOutputHelper output)
public class SamlEncryptionTests
{
private const string Category = "SAML Encryption";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlData Data => Fixture.Data;
private SamlDataBuilder Build => Fixture.Builder;

View file

@ -13,11 +13,10 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
internal class SamlFixture(ITestOutputHelper output) : IAsyncLifetime
internal class SamlFixture : IAsyncLifetime
{
public SamlData Data = new SamlData();
public SamlDataBuilder Builder => new SamlDataBuilder(Data);
@ -78,7 +77,7 @@ internal class SamlFixture(ITestOutputHelper output) : IAsyncLifetime
public T Get<T>() where T : notnull => Host!.Resolve<T>();
public async Task InitializeAsync()
public async ValueTask InitializeAsync()
{
var selfSignedCertificate = X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(StableSigningCert), null);
@ -178,7 +177,7 @@ internal class SamlFixture(ITestOutputHelper output) : IAsyncLifetime
NonRedirectingClient = Host!.Server.CreateClient();
}
public async Task DisposeAsync()
public async ValueTask DisposeAsync()
{
if (Host != null)
{

View file

@ -8,16 +8,15 @@ using System.Web;
using Duende.IdentityModel;
using Duende.IdentityServer.Internal.Saml;
using Microsoft.AspNetCore.Mvc;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlIdpInitiatedEndpointTests(ITestOutputHelper output)
public class SamlIdpInitiatedEndpointTests
{
private const string Category = "SAML IdP-Initiated Endpoint";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlData Data => Fixture.Data;
@ -293,7 +292,7 @@ public class SamlIdpInitiatedEndpointTests(ITestOutputHelper output)
var samlResponse = await ExtractSamlSuccessFromPostAsync(callbackResult, CancellationToken.None);
samlResponse.ShouldNotBeNull();
samlResponse.Issuer.ShouldBe(Fixture.Host!.Uri());
samlResponse.Issuer.ShouldBe(Fixture.Host!.Url());
samlResponse.Destination.ShouldBe(Data.AcsUrl.ToString());
samlResponse.StatusCode.ShouldBe("urn:oasis:names:tc:SAML:2.0:status:Success");

View file

@ -4,15 +4,14 @@
using System.Net;
using System.Xml.Linq;
using Duende.IdentityServer.Internal.Saml;
using Xunit.Abstractions;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlMetadataEndpointTests(ITestOutputHelper output)
public class SamlMetadataEndpointTests
{
private const string Category = "SAML Metadata Endpoint";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
[Fact]
[Trait("Category", Category)]
@ -30,7 +29,7 @@ public class SamlMetadataEndpointTests(ITestOutputHelper output)
var content = await result.Content.ReadAsStringAsync(CancellationToken.None);
var settings = new VerifySettings();
var hostUri = Fixture.Host!.Uri();
var hostUri = Fixture.Host!.Url();
settings.AddScrubber(sb =>
{
sb.Replace(hostUri, "https://localhost");
@ -189,7 +188,7 @@ public class SamlMetadataEndpointTests(ITestOutputHelper output)
public async Task metadata_urls_should_handle_edge_case_route_configurations()
{
// Verify that default configuration produces clean URLs
// Edge cases (empty routes, whitespace, etc.) are comprehensively tested
// Edge cases (empty routes, whitespace, etc.) are comprehensively tested
// at the unit level in BuildServiceUrl unit tests
await Fixture.InitializeAsync();

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><EntityDescriptor entityID="http://localhost" validUntil="2000-01-09T03:04:05Z" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><KeyDescriptor use="signing"><KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><X509Data><X509Certificate>MIICojCCAYqgAwIBAgIIIjGqKDo3ME4wDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAxMGZm9vYmFyMB4XDTI1MTExMDE0MTEyNloXDTMwMTExMDE0MTEyNlowETEPMA0GA1UEAxMGZm9vYmFyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu/fcM55jlB810lyxGpgk0Zhw83Liqz80l3zLLAZgJ/IUdBx9VFD28BeO37eByHDXxBIQdHFYXQj+lv2g3KFRxVzfZhiFUrb1UydJYFZ951sQUEsP4T/Fpbyb95HNrwG2NwE5/fk1MXr9no4ydsQTZA6EWOfbxn6o2YQs/8QdDykhCzpZcWYbk5AKS/G6nYLpwuW4UsyMQ6ur9ZQXtwDS/hGyP3RjK8pjqkckbQG9ZapI+hWezIJkGmkXcuIx+FpZbdjjwu/SIcNNrBIXLbrbWyxoWt4y2jWfDixanBAubBLtx6tCg69trJ3M5gZkFZBR3CVqs78fYZUThKBTS20afQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCxB08tE2bDWpF5mR14kQvRUA/2hZKeC6CYYGEwOu1hbh5m3rVj4T9GPgOh+s6tX+rCb0IoV1uD9iSeTd3XaJ/1sSFkgVD/PaA6NRgzKVeDXLl9rZGAnOmp/Es3Pz35FbPxZKTe8UDyFHySbioLaLvtODhzX7SeGP3BcRpp8rZLvggMYiqo3w39+qZcgZPIBP4yRSulBYb3r9qagQ/n//gp7SmenCQmjA5L7pTn7QggFQsSQmB6dyNS54cUk0niUsTihT9oqpMnXmsXonXf5cv3tnaydreiB4aPea+OjjY3oy8hvHUH6FuQQX7t3RllZlPGJQFZe61rYMVmRRjlHWTA</X509Certificate></X509Data></KeyInfo></KeyDescriptor><NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat><NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat><NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat><NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost/saml/signin" /><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost/saml/signin" /><SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost/saml/logout" /><SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost/saml/logout" /></IDPSSODescriptor></EntityDescriptor>

View file

@ -10,17 +10,16 @@ using Duende.IdentityModel;
using Duende.IdentityServer.Internal.Saml.SingleSignin;
using Duende.IdentityServer.Models;
using Microsoft.AspNetCore.Mvc;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
using StateId = Duende.IdentityServer.Internal.Saml.SingleSignin.Models.StateId;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlSigninCallbackEndpointTests(ITestOutputHelper output)
public class SamlSigninCallbackEndpointTests
{
private const string Category = "SAML Signin Callback Endpoint";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlData Data => Fixture.Data;

View file

@ -15,19 +15,18 @@ using Duende.IdentityServer.Internal.Saml;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Saml.Models;
using Microsoft.AspNetCore.Mvc;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlSigninEndpointTests(ITestOutputHelper output)
public class SamlSigninEndpointTests
{
private const string Category = "SAML Signin Endpoint";
private readonly CancellationToken _ct = CancellationToken.None;
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlData Data => Fixture.Data;
@ -106,7 +105,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
errorResponse.StatusCode.ShouldBe("urn:oasis:names:tc:SAML:2.0:status:VersionMismatch");
errorResponse.StatusMessage.ShouldNotBeNull();
errorResponse.StatusMessage.ShouldContain("Only Version 2.0 is supported");
errorResponse.Issuer.ShouldBe(Fixture.Host!.Uri());
errorResponse.Issuer.ShouldBe(Fixture.Host!.Url());
errorResponse.InResponseTo.ShouldNotBeNullOrEmpty();
errorResponse.AssertionConsumerServiceUrl.ShouldBe(Data.AcsUrl.ToString());
}
@ -223,7 +222,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
errorResponse.StatusCode.ShouldBe("urn:oasis:names:tc:SAML:2.0:status:VersionMismatch");
errorResponse.StatusMessage.ShouldNotBeNull();
errorResponse.StatusMessage.ShouldContain("Only Version 2.0 is supported");
errorResponse.Issuer.ShouldBe(Fixture.Host!.Uri());
errorResponse.Issuer.ShouldBe(Fixture.Host!.Url());
errorResponse.InResponseTo.ShouldNotBeNullOrEmpty();
errorResponse.AssertionConsumerServiceUrl.ShouldBe(Data.AcsUrl.ToString());
}
@ -316,7 +315,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
errorResponse.StatusCode.ShouldBe("urn:oasis:names:tc:SAML:2.0:status:Requester");
errorResponse.StatusMessage.ShouldBe("Request IssueInstant is in the future");
errorResponse.Issuer.ShouldBe(Fixture.Host!.Uri());
errorResponse.Issuer.ShouldBe(Fixture.Host!.Url());
errorResponse.InResponseTo.ShouldNotBeNullOrEmpty();
errorResponse.AssertionConsumerServiceUrl.ShouldBe(Data.AcsUrl.ToString());
}
@ -498,7 +497,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
await Fixture.InitializeAsync();
// Use the correct Destination attribute
var correctDestination = new Uri(Fixture.Host!.Uri() + "/saml/signin");
var correctDestination = new Uri(Fixture.Host!.Url() + "/saml/signin");
var authnRequestXml = Build.AuthNRequestXml(destination: correctDestination);
var urlEncoded = await EncodeRequest(authnRequestXml);
@ -540,7 +539,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
successResponse.ResponseId.ShouldNotBeNullOrEmpty();
successResponse.Destination.ShouldBe(Fixture.Data.AcsUrl.ToString());
successResponse.IssueInstant.ShouldBe(Data.FakeTimeProvider.GetUtcNow().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
successResponse.Issuer.ShouldBe(Fixture.Host?.Uri());
successResponse.Issuer.ShouldBe(Fixture.Host?.Url());
successResponse.InResponseTo.ShouldBe(Data.RequestId);
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
@ -550,7 +549,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
assertion.Id.ShouldNotBeNullOrEmpty();
assertion.Version.ShouldBe("2.0");
assertion.IssueInstant.ShouldBe(Data.FakeTimeProvider.GetUtcNow().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
assertion.Issuer.ShouldBe(Fixture.Host?.Uri());
assertion.Issuer.ShouldBe(Fixture.Host?.Url());
var subject = assertion.Subject;
subject.ShouldNotBeNull();
@ -789,7 +788,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
errorResponse.StatusCode.ShouldBe("urn:oasis:names:tc:SAML:2.0:status:NoPassive");
errorResponse.StatusMessage.ShouldBe("The user is not currently logged in and passive login was requested.");
errorResponse.Issuer.ShouldBe(Fixture.Host!.Uri());
errorResponse.Issuer.ShouldBe(Fixture.Host!.Url());
errorResponse.InResponseTo.ShouldNotBeNullOrEmpty();
errorResponse.AssertionConsumerServiceUrl.ShouldBe(Data.AcsUrl.ToString());
}
@ -814,7 +813,7 @@ public class SamlSigninEndpointTests(ITestOutputHelper output)
errorResponse.StatusCode.ShouldBe("urn:oasis:names:tc:SAML:2.0:status:NoPassive");
errorResponse.StatusMessage.ShouldBe("The user is not currently logged in");
errorResponse.Issuer.ShouldBe(Fixture.Host!.Uri());
errorResponse.Issuer.ShouldBe(Fixture.Host!.Url());
errorResponse.InResponseTo.ShouldNotBeNullOrEmpty();
errorResponse.AssertionConsumerServiceUrl.ShouldBe(Data.AcsUrl.ToString());
}

View file

@ -5,15 +5,14 @@ using System.Net;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Saml.Models;
using Duende.IdentityServer.Stores;
using Xunit.Abstractions;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlSingleLogoutCallbackEndpointTests(ITestOutputHelper output)
public class SamlSingleLogoutCallbackEndpointTests
{
private const string Category = "SAML single logout callback endpoint";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlData Data => Fixture.Data;

View file

@ -10,16 +10,15 @@ using Duende.IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Saml.Models;
using Microsoft.AspNetCore.Mvc;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
public class SamlSingleLogoutEndpointTests
{
private const string Category = "SAML single logout endpoint";
private SamlFixture Fixture = new(output);
private SamlFixture Fixture = new();
private SamlData Data => Fixture.Data;
@ -151,7 +150,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
await Fixture.Client.GetAsync("/__signin", CancellationToken.None);
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: "session123");
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -176,7 +175,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
await Fixture.InitializeAsync();
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
version: "1.0");
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -200,7 +199,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
var futureTime = Data.Now.AddMinutes(10);
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
issueInstant: futureTime,
sessionIndex: "session123");
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -226,7 +225,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
var oldTime = Data.Now.AddMinutes(-10);
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
issueInstant: oldTime,
sessionIndex: "session123");
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -261,7 +260,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
// Assert
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
logoutResponse.StatusMessage.ShouldBe($"Invalid destination. Expected '{Fixture.Host!.Uri()}/saml/logout'");
logoutResponse.StatusMessage.ShouldBe($"Invalid destination. Expected '{Fixture.Host!.Url()}/saml/logout'");
}
[Fact]
@ -275,7 +274,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
await Fixture.InitializeAsync();
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: "session123");
var urlEncoded = await EncodeRequest(logoutRequestXml, CancellationToken.None);
@ -301,7 +300,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
// Create a logout request without a signature
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: "session123");
var urlEncoded = await EncodeRequest(logoutRequestXml, CancellationToken.None);
@ -352,7 +351,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
// Don't sign in a user - no authenticated session
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: "session123");
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -385,7 +384,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
// Use a different service provider than what was established
var logoutRequestXml = Build.LogoutRequestXml(
issuer: anotherSp.EntityId, // Use a different SP so session will not be found
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"));
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"));
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
// Act
@ -413,7 +412,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
// Use a different session index than what was established
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: "wrong-session-index");
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -444,7 +443,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
var sessionIndex = await PerformSigninAndExtractSessionIndex(sp);
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: sessionIndex);
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -482,7 +481,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
var sessionIndex = await PerformSigninAndExtractSessionIndex(sp);
var logoutRequestXml = Build.LogoutRequestXml(
destination: new Uri($"{Fixture.Host!.Uri()}/saml/logout"),
destination: new Uri($"{Fixture.Host!.Url()}/saml/logout"),
sessionIndex: sessionIndex);
var urlEncoded = await EncodeAndSignRequest(logoutRequestXml, sp, CancellationToken.None);
@ -495,7 +494,7 @@ public class SamlSingleLogoutEndpointTests(ITestOutputHelper output)
// Verify user can no longer access protected resource and is redirected to login
var finalProtectedResourceResult = await Fixture.Client.GetAsync("__protected-resource", CancellationToken.None);
finalProtectedResourceResult.StatusCode.ShouldBe(HttpStatusCode.OK);
finalProtectedResourceResult.RequestMessage?.RequestUri?.AbsoluteUri.ShouldStartWith($"{Fixture.Host!.Uri()}{Fixture.LoginUrl.ToString()}");
finalProtectedResourceResult.RequestMessage?.RequestUri?.AbsoluteUri.ShouldStartWith($"{Fixture.Host!.Url()}{Fixture.LoginUrl.ToString()}");
}
private static async Task<string> EncodeAndSignRequest(

View file

@ -17,12 +17,11 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Time.Testing;
using Sustainsys.Saml2.AspNetCore2;
using Xunit.Abstractions;
using IdentityProvider = Sustainsys.Saml2.IdentityProvider;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
internal class SustainSysSamlTestFixture(ITestOutputHelper output) : IAsyncLifetime
internal class SustainSysSamlTestFixture : IAsyncLifetime
{
public TestFramework.GenericHost? Host = null!;
public HttpClient? BrowserClient = null!;
@ -30,7 +29,7 @@ internal class SustainSysSamlTestFixture(ITestOutputHelper output) : IAsyncLifet
public Uri IdentityProviderLoginUri => new Uri(new Uri(_samlFixture.Host!.Url()), _samlFixture.LoginUrl);
private readonly SamlFixture _samlFixture = new(output);
private readonly SamlFixture _samlFixture = new();
private bool _shouldGenerateSigningCertificate;
private bool _shouldRequireEncryptedAssertions;
@ -49,7 +48,7 @@ internal class SustainSysSamlTestFixture(ITestOutputHelper output) : IAsyncLifet
public void RequireEncryptedAssertions() => _shouldRequireEncryptedAssertions = true;
public async Task InitializeAsync()
public async ValueTask InitializeAsync()
{
// need to use current time because the SustainSys library does not rely on an abstraction such
// as TimeProvider and times need to be current
@ -152,7 +151,7 @@ internal class SustainSysSamlTestFixture(ITestOutputHelper output) : IAsyncLifet
BrowserClient = Host.HttpClient;
}
public async Task DisposeAsync()
public async ValueTask DisposeAsync()
{
if (Host != null)
{

View file

@ -3,16 +3,15 @@
using System.Net;
using System.Web;
using Xunit.Abstractions;
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
public class SustainSysSigninTests(ITestOutputHelper output)
public class SustainSysSigninTests
{
private const string Category = "SustainSys SAML signin";
private SustainSysSamlTestFixture Fixture = new(output);
private SustainSysSamlTestFixture Fixture = new();
[Fact]
[Trait("Category", Category)]

View file

@ -0,0 +1,28 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityServer.Saml;
using Duende.IdentityServer.Saml.Models;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
/// <summary>
/// Test implementation of ISamlClaimsMapper that returns a single custom attribute.
/// Used by both unit and integration tests.
/// </summary>
public class TestSamlClaimsMapper : ISamlClaimsMapper
{
public Task<IEnumerable<SamlAttribute>> MapClaimsAsync(SamlClaimsMappingContext mappingContext)
{
var attributes = new List<SamlAttribute>
{
new()
{
Name = "CUSTOM_MAPPED",
NameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
Values = new List<string> { "custom_value" }
}
};
return Task.FromResult<IEnumerable<SamlAttribute>>(attributes);
}
}

View file

@ -18,6 +18,7 @@
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="AngleSharp" />
<PackageReference Include="Verify.XunitV3" />
</ItemGroup>

View file

@ -1,24 +0,0 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
#nullable enable
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Stores;
namespace UnitTests.Saml;
/// <summary>
/// Simple in-memory implementation of ISamlServiceProviderStore for unit testing.
/// </summary>
internal class InMemoryTestServiceProviderStore : ISamlServiceProviderStore
{
private readonly List<SamlServiceProvider> _providers = [];
public InMemoryTestServiceProviderStore() { }
public InMemoryTestServiceProviderStore(params SamlServiceProvider[] providers) => _providers.AddRange(providers);
public void Add(SamlServiceProvider provider) => _providers.Add(provider);
public Task<SamlServiceProvider?> FindByEntityIdAsync(string entityId)
=> Task.FromResult(_providers.FirstOrDefault(p => p.EntityId == entityId));
}

View file

@ -12,7 +12,6 @@ using Duende.IdentityServer.Internal.Saml.SingleLogout;
using Duende.IdentityServer.Models;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using UnitTests.Common;
namespace UnitTests.Saml;

View file

@ -9,7 +9,6 @@ using Duende.IdentityServer.Services;
using Duende.IdentityServer.Stores;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using UnitTests.Common;
namespace UnitTests.Saml;

View file

@ -7,6 +7,7 @@ using Duende.IdentityServer.Internal.Saml.Infrastructure;
using Duende.IdentityServer.Internal.Saml.SingleLogout;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Saml.Models;
using Duende.IdentityServer.Stores;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using UnitTests.Common;
@ -30,7 +31,7 @@ public class SamlLogoutNotificationServiceTests
return new SamlLogoutNotificationService(
_issuerNameService,
new InMemoryTestServiceProviderStore(samlServiceProviders),
new InMemorySamlServiceProviderStore(samlServiceProviders),
frontChannelLogoutRequestBuilder,
NullLogger<SamlLogoutNotificationService>.Instance);
}

View file

@ -165,6 +165,7 @@
<Project Path="identity-server/src/Configuration/IdentityServer.Configuration.csproj" />
<Project Path="identity-server/src/EntityFramework.Storage/IdentityServer.EntityFramework.Storage.csproj" />
<Project Path="identity-server/src/EntityFramework/IdentityServer.EntityFramework.csproj" />
<Project Path="identity-server/src/IdentityServer.ConformanceReport/IdentityServer.ConformanceReport.csproj" />
<Project Path="identity-server/src/IdentityServer/IdentityServer.csproj" />
<Project Path="identity-server/src/Storage/IdentityServer.Storage.csproj" />
</Folder>