diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Core.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Core.cs index fc67fe4be..b60d47643 100644 --- a/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Core.cs +++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Core.cs @@ -17,12 +17,6 @@ using Duende.IdentityServer.Hosting.DynamicProviders; using Duende.IdentityServer.Hosting.FederatedSignOut; using Duende.IdentityServer.Internal; using Duende.IdentityServer.Internal.Saml; -using Duende.IdentityServer.Internal.Saml.Infrastructure; -using Duende.IdentityServer.Internal.Saml.Metadata; -using Duende.IdentityServer.Internal.Saml.SingleLogout; -using Duende.IdentityServer.Internal.Saml.SingleLogout.Models; -using Duende.IdentityServer.Internal.Saml.SingleSignin; -using Duende.IdentityServer.Internal.Saml.SingleSignin.Models; using Duende.IdentityServer.Licensing; using Duende.IdentityServer.Licensing.V2; using Duende.IdentityServer.Licensing.V2.Diagnostics; @@ -135,14 +129,6 @@ public static class IdentityServerBuilderExtensionsCore builder.AddEndpoint(EndpointNames.Token, ProtocolRoutePaths.Token.EnsureLeadingSlash()); builder.AddEndpoint(EndpointNames.UserInfo, ProtocolRoutePaths.UserInfo.EnsureLeadingSlash()); - // SAML 2.0 endpoints - builder.AddEndpoint(EndpointNames.SamlMetadata, ProtocolRoutePaths.SamlMetadata.EnsureLeadingSlash()); - builder.AddEndpoint(EndpointNames.SamlSignin, ProtocolRoutePaths.SamlSignin.EnsureLeadingSlash()); - builder.AddEndpoint(EndpointNames.SamlSigninCallback, ProtocolRoutePaths.SamlSigninCallback.EnsureLeadingSlash()); - builder.AddEndpoint(EndpointNames.SamlIdpInitiated, ProtocolRoutePaths.SamlIdpInitiated.EnsureLeadingSlash()); - builder.AddEndpoint(EndpointNames.SamlLogout, ProtocolRoutePaths.SamlLogout.EnsureLeadingSlash()); - builder.AddEndpoint(EndpointNames.SamlLogoutCallback, ProtocolRoutePaths.SamlLogoutCallback.EnsureLeadingSlash()); - builder.AddHttpWriter(); builder.AddHttpWriter(); builder.AddHttpWriter(); @@ -166,67 +152,6 @@ public static class IdentityServerBuilderExtensionsCore return builder; } - /// - /// Adds SAML 2.0 protocol services. - /// - /// The builder. - /// - public static IIdentityServerBuilder AddSamlServices(this IIdentityServerBuilder builder) - { - // Serializers (Transient) - builder.Services.AddTransient, SamlErrorResponseXmlSerializer>(); - builder.Services.AddTransient, SamlResponse.Serializer>(); - builder.Services.AddTransient, LogoutResponse.Serializer>(); - - // HTTP response writers - builder.AddHttpWriter(); - builder.AddHttpWriter(); - builder.AddHttpWriter(); - - // Processors (Scoped) - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - // Builders (Scoped) - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - // Parsers / Extractors (Scoped) - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - // Infrastructure (Scoped) - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.TryAddScoped(typeof(SamlRequestSignatureValidator<,>)); - - // Interface → Implementation (TryAddScoped for extensibility) - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); - - // State management (Singleton) - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - - // Default no-op service provider store (can be overridden by user) - builder.Services.TryAddTransient(); - - return builder; - } - /// /// Adds an endpoint. /// @@ -287,6 +212,12 @@ public static class IdentityServerBuilderExtensionsCore builder.Services.TryAddTransient(); + // Register no-op SAML services for services used in logout paths + // These are replaced by actual implementations in AddSaml and ISamlServiceProviderStore + // can be replaced with a call to AddSamlServiceProviderStore + builder.Services.TryAddTransient(); + builder.Services.TryAddScoped(); + builder.Services.TryAddSingleton(typeof(IConcurrencyLock<>), typeof(DefaultConcurrencyLock<>)); builder.Services.TryAddTransient(); diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/InMemory.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/InMemory.cs index 40f4fff70..b88d32a69 100644 --- a/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/InMemory.cs +++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/InMemory.cs @@ -190,4 +190,17 @@ public static class IdentityServerBuilderExtensionsInMemory builder.Services.TryAddSingleton(); return builder; } + + /// + /// Adds the in-memory SAML service provider store. + /// + /// The builder. + /// The SAML service providers. + /// + public static IIdentityServerBuilder AddInMemorySamlServiceProviders(this IIdentityServerBuilder builder, IEnumerable serviceProviders) + { + builder.Services.AddSingleton(serviceProviders); + builder.AddSamlServiceProviderStore(); + return builder; + } } diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Saml.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Saml.cs new file mode 100644 index 000000000..402744c87 --- /dev/null +++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/BuilderExtensions/Saml.cs @@ -0,0 +1,134 @@ +// Copyright (c) Duende Software. All rights reserved. +// See LICENSE in the project root for license information. + +#nullable enable + +using Duende.IdentityServer.Configuration; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Internal.Saml; +using Duende.IdentityServer.Internal.Saml.Infrastructure; +using Duende.IdentityServer.Internal.Saml.Metadata; +using Duende.IdentityServer.Internal.Saml.SingleLogout; +using Duende.IdentityServer.Internal.Saml.SingleLogout.Models; +using Duende.IdentityServer.Internal.Saml.SingleSignin; +using Duende.IdentityServer.Internal.Saml.SingleSignin.Models; +using Duende.IdentityServer.Saml; +using Duende.IdentityServer.Stores; +using Microsoft.Extensions.DependencyInjection.Extensions; +using static Duende.IdentityServer.IdentityServerConstants; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Builder extension methods for opting in to SAML 2.0 support. +/// +public static class IdentityServerBuilderExtensionsSaml +{ + /// + /// Adds SAML 2.0 support to IdentityServer. + /// + /// + /// Registers all SAML services and endpoints, and enables the SAML endpoints + /// in . The IdP-initiated SSO endpoint is not + /// enabled by default; set + /// to true explicitly if you need it. + /// + /// The builder. + /// + public static IIdentityServerBuilder AddSaml(this IIdentityServerBuilder builder) + { + builder.AddSamlServices(); + + builder.Services.Configure(options => + { + options.Endpoints.EnableSamlMetadataEndpoint = true; + options.Endpoints.EnableSamlSigninEndpoint = true; + options.Endpoints.EnableSamlSigninCallbackEndpoint = true; + options.Endpoints.EnableSamlLogoutEndpoint = true; + options.Endpoints.EnableSamlLogoutCallbackEndpoint = true; + // EnableSamlIdpInitiatedEndpoint intentionally left false — requires explicit opt-in. + }); + + return builder; + } + + /// + /// Adds SAML 2.0 protocol services. + /// + /// The builder. + /// + private static IIdentityServerBuilder AddSamlServices(this IIdentityServerBuilder builder) + { + // SAML 2.0 endpoints + builder.AddEndpoint(EndpointNames.SamlMetadata, ProtocolRoutePaths.SamlMetadata.EnsureLeadingSlash()); + builder.AddEndpoint(EndpointNames.SamlSignin, ProtocolRoutePaths.SamlSignin.EnsureLeadingSlash()); + builder.AddEndpoint(EndpointNames.SamlSigninCallback, ProtocolRoutePaths.SamlSigninCallback.EnsureLeadingSlash()); + builder.AddEndpoint(EndpointNames.SamlIdpInitiated, ProtocolRoutePaths.SamlIdpInitiated.EnsureLeadingSlash()); + builder.AddEndpoint(EndpointNames.SamlLogout, ProtocolRoutePaths.SamlLogout.EnsureLeadingSlash()); + builder.AddEndpoint(EndpointNames.SamlLogoutCallback, ProtocolRoutePaths.SamlLogoutCallback.EnsureLeadingSlash()); + + // Serializers (Transient) + builder.Services.AddTransient, SamlErrorResponseXmlSerializer>(); + builder.Services.AddTransient, SamlResponse.Serializer>(); + builder.Services.AddTransient, LogoutResponse.Serializer>(); + + // HTTP response writers + builder.AddHttpWriter(); + builder.AddHttpWriter(); + builder.AddHttpWriter(); + + // Processors (Scoped) + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + // Builders (Scoped) + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + // Parsers / Extractors (Scoped) + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + // Infrastructure (Scoped) + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.TryAddScoped(typeof(SamlRequestSignatureValidator<,>)); + + // Interface → Implementation (TryAddScoped for extensibility) + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + // Replace the no-op registered by AddCoreServices with the real implementation. + builder.Services.Replace(ServiceDescriptor.Scoped()); + builder.Services.TryAddScoped(); + + // State management (Singleton) + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + + return builder; + } + + /// + /// Adds a custom SAML service provider store. + /// + /// The type of the implementation. + /// The builder. + /// + public static IIdentityServerBuilder AddSamlServiceProviderStore(this IIdentityServerBuilder builder) + where T : class, ISamlServiceProviderStore + { + builder.Services.AddTransient(); + return builder; + } +} diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/IdentityServerServiceCollectionExtensions.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/IdentityServerServiceCollectionExtensions.cs index 45d90705b..dc9945034 100644 --- a/identity-server/src/IdentityServer/Configuration/DependencyInjection/IdentityServerServiceCollectionExtensions.cs +++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/IdentityServerServiceCollectionExtensions.cs @@ -37,7 +37,6 @@ public static class IdentityServerServiceCollectionExtensions .AddCookieAuthentication() .AddCoreServices() .AddDefaultEndpoints() - .AddSamlServices() .AddPluggableServices() .AddKeyManagement() .AddDynamicProvidersCore() diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/EndpointOptions.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/EndpointOptions.cs index 595de2bb5..78be978f8 100644 --- a/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/EndpointOptions.cs +++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/EndpointOptions.cs @@ -118,7 +118,7 @@ public class EndpointsOptions /// /// true if the SAML metadata endpoint is enabled; otherwise, false. /// - public bool EnableSamlMetadataEndpoint { get; set; } = true; + public bool EnableSamlMetadataEndpoint { get; set; } /// /// Gets or sets a value indicating whether the SAML sign-in (SSO) endpoint is enabled. @@ -126,7 +126,7 @@ public class EndpointsOptions /// /// true if the SAML sign-in endpoint is enabled; otherwise, false. /// - public bool EnableSamlSigninEndpoint { get; set; } = true; + public bool EnableSamlSigninEndpoint { get; set; } /// /// Gets or sets a value indicating whether the SAML sign-in callback endpoint is enabled. @@ -134,7 +134,7 @@ public class EndpointsOptions /// /// true if the SAML sign-in callback endpoint is enabled; otherwise, false. /// - public bool EnableSamlSigninCallbackEndpoint { get; set; } = true; + public bool EnableSamlSigninCallbackEndpoint { get; set; } /// /// Gets or sets a value indicating whether the SAML IdP-initiated SSO endpoint is enabled. @@ -142,7 +142,7 @@ public class EndpointsOptions /// /// true if the SAML IdP-initiated endpoint is enabled; otherwise, false. /// - public bool EnableSamlIdpInitiatedEndpoint { get; set; } = true; + public bool EnableSamlIdpInitiatedEndpoint { get; set; } /// /// Gets or sets a value indicating whether the SAML Single Logout (SLO) endpoint is enabled. @@ -150,7 +150,7 @@ public class EndpointsOptions /// /// true if the SAML logout endpoint is enabled; otherwise, false. /// - public bool EnableSamlLogoutEndpoint { get; set; } = true; + public bool EnableSamlLogoutEndpoint { get; set; } /// /// Gets or sets a value indicating whether the SAML Single Logout callback endpoint is enabled. @@ -158,5 +158,5 @@ public class EndpointsOptions /// /// true if the SAML logout callback endpoint is enabled; otherwise, false. /// - public bool EnableSamlLogoutCallbackEndpoint { get; set; } = true; + public bool EnableSamlLogoutCallbackEndpoint { get; set; } } diff --git a/identity-server/src/IdentityServer/Internal/Saml/EmptySamlServiceProviderStore.cs b/identity-server/src/IdentityServer/Internal/Saml/EmptySamlServiceProviderStore.cs new file mode 100644 index 000000000..eda5b1d3b --- /dev/null +++ b/identity-server/src/IdentityServer/Internal/Saml/EmptySamlServiceProviderStore.cs @@ -0,0 +1,12 @@ +// Copyright (c) Duende Software. All rights reserved. +// See LICENSE in the project root for license information. + +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Stores; + +namespace Duende.IdentityServer.Internal.Saml; + +internal class EmptySamlServiceProviderStore : ISamlServiceProviderStore +{ + public Task FindByEntityIdAsync(string entityId) => Task.FromResult(null); +} diff --git a/identity-server/src/IdentityServer/Internal/Saml/Infrastructure/ISamlRequest.cs b/identity-server/src/IdentityServer/Internal/Saml/Infrastructure/ISamlRequest.cs index 752830650..b09e9b76c 100644 --- a/identity-server/src/IdentityServer/Internal/Saml/Infrastructure/ISamlRequest.cs +++ b/identity-server/src/IdentityServer/Internal/Saml/Infrastructure/ISamlRequest.cs @@ -2,8 +2,6 @@ // See LICENSE in the project root for license information. #nullable enable -using Duende.IdentityServer.Saml.Models; - namespace Duende.IdentityServer.Internal.Saml.Infrastructure; /// diff --git a/identity-server/src/IdentityServer/Internal/Saml/NopSamlLogoutNotificationService.cs b/identity-server/src/IdentityServer/Internal/Saml/NopSamlLogoutNotificationService.cs new file mode 100644 index 000000000..a4991e603 --- /dev/null +++ b/identity-server/src/IdentityServer/Internal/Saml/NopSamlLogoutNotificationService.cs @@ -0,0 +1,15 @@ +// 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.Saml; + +namespace Duende.IdentityServer.Internal.Saml; + +internal class NopSamlLogoutNotificationService : ISamlLogoutNotificationService +{ + public Task> GetSamlFrontChannelLogoutsAsync(LogoutNotificationContext context) => + Task.FromResult(Enumerable.Empty()); +} diff --git a/identity-server/src/IdentityServer/Internal/Saml/SingleLogout/SamlFrontChannelLogoutRequestBuilder.cs b/identity-server/src/IdentityServer/Internal/Saml/SingleLogout/SamlFrontChannelLogoutRequestBuilder.cs index fe9d0d89b..4bbd8f58a 100644 --- a/identity-server/src/IdentityServer/Internal/Saml/SingleLogout/SamlFrontChannelLogoutRequestBuilder.cs +++ b/identity-server/src/IdentityServer/Internal/Saml/SingleLogout/SamlFrontChannelLogoutRequestBuilder.cs @@ -11,7 +11,6 @@ using Duende.IdentityServer.Internal.Saml.SingleLogout.Models; using Duende.IdentityServer.Internal.Saml.SingleSignin.Models; using Duende.IdentityServer.Models; using Duende.IdentityServer.Saml; -using Duende.IdentityServer.Saml.Models; using LogoutRequest = Duende.IdentityServer.Internal.Saml.SingleLogout.Models.LogoutRequest; namespace Duende.IdentityServer.Internal.Saml.SingleLogout; diff --git a/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/RegisteredImplementationsDiagnosticEntry.cs b/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/RegisteredImplementationsDiagnosticEntry.cs index 6205aaa14..29aab2a64 100644 --- a/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/RegisteredImplementationsDiagnosticEntry.cs +++ b/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/RegisteredImplementationsDiagnosticEntry.cs @@ -8,7 +8,6 @@ using Duende.IdentityServer.Hosting.DynamicProviders; using Duende.IdentityServer.Internal; using Duende.IdentityServer.Internal.Saml; using Duende.IdentityServer.Internal.Saml.SingleLogout; -using Duende.IdentityServer.Internal.Saml.SingleSignin; using Duende.IdentityServer.ResponseHandling; using Duende.IdentityServer.Saml; using Duende.IdentityServer.Services; @@ -60,9 +59,9 @@ internal class RegisteredImplementationsDiagnosticEntry(ServiceCollectionAccesso "SAML", [ new(typeof(ISamlClaimsMapper), []), new(typeof(ISamlFrontChannelLogout), [typeof(SamlHttpPostFrontChannelLogout), typeof(SamlHttpRedirectFrontChannelLogout)]), - new(typeof(ISamlInteractionService),[typeof(DefaultSamlInteractionService)]), - new(typeof(ISamlLogoutNotificationService), [typeof(SamlLogoutNotificationService)]), - new(typeof(ISamlSigninInteractionResponseGenerator),[typeof(DefaultSamlSigninInteractionResponseGenerator)]), + new(typeof(ISamlInteractionService),[]), + new(typeof(ISamlLogoutNotificationService), [typeof(NopSamlLogoutNotificationService)]), + new(typeof(ISamlSigninInteractionResponseGenerator),[]), ] }, { @@ -96,7 +95,6 @@ internal class RegisteredImplementationsDiagnosticEntry(ServiceCollectionAccesso new(typeof(IRefreshTokenService), [typeof(DefaultRefreshTokenService)]), new(typeof(IReplayCache), [typeof(DefaultReplayCache)]), new(typeof(IReturnUrlParser), [typeof(OidcReturnUrlParser)]), - new(typeof(ISamlServiceProviderStore), [typeof(InMemorySamlServiceProviderStore)]), new(typeof(IServerUrls), [typeof(DefaultServerUrls)]), new(typeof(ISessionCoordinationService), [typeof(DefaultSessionCoordinationService)]), new(typeof(ISessionManagementService), []), @@ -126,6 +124,7 @@ internal class RegisteredImplementationsDiagnosticEntry(ServiceCollectionAccesso new(typeof(IReferenceTokenStore), [typeof(DefaultReferenceTokenStore)]), new(typeof(IRefreshTokenStore), [typeof(DefaultRefreshTokenStore)]), new(typeof(IResourceStore), [typeof(EmptyResourceStore)]), + new(typeof(ISamlServiceProviderStore), [typeof(EmptySamlServiceProviderStore)]), new(typeof(IServerSideSessionsMarker), []), new(typeof(IServerSideSessionStore),[]), new(typeof(IServerSideTicketStore),[]), diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlEncryptionTests.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlEncryptionTests.cs index 3afe2e677..3cfc8e041 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlEncryptionTests.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlEncryptionTests.cs @@ -9,8 +9,8 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Duende.IdentityModel; using Duende.IdentityServer.Models; -using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers; using Duende.IdentityServer.Saml.Models; +using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers; namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml; diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlFixture.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlFixture.cs index 5e1667bdb..62fb94dc0 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlFixture.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlFixture.cs @@ -121,9 +121,6 @@ internal class SamlFixture : IAsyncLifetime // Configure SAML options services.Configure(ConfigureSamlOptions); - // Register in-memory SAML service provider store with our service providers - services.AddSingleton(new InMemorySamlServiceProviderStore(_serviceProviders)); - // Replace the developer signing credential with our X509 certificate // Remove the ISigningCredentialStore registration added by AddDeveloperSigningCredential var signingCredentialDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(ISigningCredentialStore)); @@ -136,7 +133,8 @@ internal class SamlFixture : IAsyncLifetime services.AddIdentityServerBuilder() .AddSigningCredential(selfSignedCertificate) .AddProfileService() - .AddSamlServices(); + .AddSaml() + .AddInMemorySamlServiceProviders(_serviceProviders); ConfigureServices(services); diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlIdpInitiatedEndpointTests.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlIdpInitiatedEndpointTests.cs index 2c4b51acc..3314d8912 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlIdpInitiatedEndpointTests.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SamlIdpInitiatedEndpointTests.cs @@ -30,6 +30,10 @@ public class SamlIdpInitiatedEndpointTests var sp = Build.SamlServiceProvider(); sp.AllowIdpInitiated = true; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); Fixture.UserToSignIn = @@ -55,6 +59,10 @@ public class SamlIdpInitiatedEndpointTests var sp = Build.SamlServiceProvider(); sp.AllowIdpInitiated = true; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); Fixture.UserToSignIn = @@ -87,6 +95,10 @@ public class SamlIdpInitiatedEndpointTests var sp = Build.SamlServiceProvider(); sp.AllowIdpInitiated = true; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); // Act @@ -106,6 +118,10 @@ public class SamlIdpInitiatedEndpointTests { // Arrange Fixture.ServiceProviders.Add(Build.SamlServiceProvider()); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); // Act @@ -130,6 +146,10 @@ public class SamlIdpInitiatedEndpointTests sp.Enabled = false; sp.AllowIdpInitiated = true; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); // Act @@ -152,6 +172,10 @@ public class SamlIdpInitiatedEndpointTests var sp = Build.SamlServiceProvider(); sp.AllowIdpInitiated = false; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); // Act @@ -178,6 +202,10 @@ public class SamlIdpInitiatedEndpointTests var sp = Build.SamlServiceProvider(); sp.AllowIdpInitiated = true; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); // Act @@ -202,6 +230,10 @@ public class SamlIdpInitiatedEndpointTests sp.AllowIdpInitiated = true; sp.AssertionConsumerServiceUrls = Array.Empty(); Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); // Act @@ -227,6 +259,10 @@ public class SamlIdpInitiatedEndpointTests sp.AllowIdpInitiated = true; sp.AssertionConsumerServiceUrls = [firstAcsUrl, secondAcsUrl]; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); Fixture.UserToSignIn = @@ -257,6 +293,10 @@ public class SamlIdpInitiatedEndpointTests var sp = Build.SamlServiceProvider(); sp.AllowIdpInitiated = true; Fixture.ServiceProviders.Add(sp); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); Fixture.UserToSignIn = @@ -332,6 +372,10 @@ public class SamlIdpInitiatedEndpointTests public async Task missing_sp_entity_id_parameter_returns_bad_request() { Fixture.ServiceProviders.Add(Build.SamlServiceProvider()); + Fixture.ConfigureIdentityServerOptions = options => + { + options.Endpoints.EnableSamlIdpInitiatedEndpoint = true; + }; await Fixture.InitializeAsync(); var result = await Fixture.Client.GetAsync("/saml/idp-initiated", CancellationToken.None); diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SustainSysSamlTestFixture.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SustainSysSamlTestFixture.cs index 90e3ae71e..a933f3649 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SustainSysSamlTestFixture.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Saml/SustainSysSamlTestFixture.cs @@ -9,7 +9,6 @@ using Duende.IdentityModel; using Duende.IdentityServer.IntegrationTests.Common; using Duende.IdentityServer.IntegrationTests.TestFramework; using Duende.IdentityServer.Models; -using Duende.IdentityServer.Stores; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; @@ -95,10 +94,6 @@ internal class SustainSysSamlTestFixture(ITestOutputHelper output) : IAsyncLifet services.AddSingleton(fakeTimeProvider); services.AddSingleton(sp => new FakeDistributedCache(sp.GetRequiredService())); - // Register the mutable service provider list before AddSamlServices - // so TryAdd in AddSamlServices won't overwrite our registration - services.AddSingleton(new InMemorySamlServiceProviderStore(_serviceProviders)); - services.AddIdentityServer(options => { options.UserInteraction.LoginUrl = "/account/login"; @@ -107,7 +102,8 @@ internal class SustainSysSamlTestFixture(ITestOutputHelper output) : IAsyncLifet options.KeyManagement.Enabled = false; }) .AddSigningCredential(selfSignedCertificate) - .AddSamlServices(); + .AddSaml() + .AddInMemorySamlServiceProviders(_serviceProviders); }, app => {