From 11c5b7ee9e35108d89b09b34ee01cae8b3f95a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 22 Dec 2022 13:52:05 +0100 Subject: [PATCH] =?UTF-8?q?Add=20Pro=20Sant=C3=A9=20Connect=20to=20the=20l?= =?UTF-8?q?ist=20of=20supported=20providers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...IddictClientWebIntegrationConfiguration.cs | 77 ++++++++++++++++++- ...penIddictClientWebIntegrationExtensions.cs | 4 +- .../OpenIddictClientWebIntegrationHandlers.cs | 10 +++ ...penIddictClientWebIntegrationProviders.xml | 22 ++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs index b1ee934b..04a82e2c 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs @@ -5,7 +5,12 @@ */ using System.ComponentModel; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; +using OpenIddict.Client.SystemNetHttp; +using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; namespace OpenIddict.Client.WebIntegration; @@ -13,8 +18,20 @@ namespace OpenIddict.Client.WebIntegration; /// Contains the methods required to ensure that the OpenIddict client Web integration configuration is valid. /// [EditorBrowsable(EditorBrowsableState.Advanced)] -public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfigureOptions +public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfigureOptions, + IConfigureNamedOptions { +#if !SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + /// The service provider. + public OpenIddictClientWebIntegrationConfiguration(IServiceProvider provider) + => _provider = provider ?? throw new ArgumentNullException(nameof(provider)); +#endif + /// public void Configure(OpenIddictClientOptions options) { @@ -26,4 +43,62 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfi // Register the built-in event handlers used by the OpenIddict client Web components. options.Handlers.AddRange(OpenIddictClientWebIntegrationHandlers.DefaultHandlers); } + + /// + public void Configure(HttpClientFactoryOptions options) => Configure(Options.DefaultName, options); + + /// + public void Configure(string? name, HttpClientFactoryOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + // Only amend the HTTP client factory options if the instance is managed by OpenIddict + // and contains the name of a provider managed by OpenIddict.Client.WebIntegration. + var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName(); + if (string.IsNullOrEmpty(name) || !name.StartsWith(assembly.Name!, StringComparison.Ordinal) || + name.Length < assembly.Name!.Length + 1 || name[assembly.Name.Length] is not ':') + { + return; + } + + // Note: while not enforced yet, Pro Santé Connect's specification requires sending a TLS + // client certificate when communicating with its backchannel OpenID Connect endpoints. + // + // For that, the primary HTTP handler must be altered or replaced by an instance that + // includes the client certificate set in the options in its certificate collection. + // + // For more information, see EXI PSC 24 in the annex part of + // https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000045551195. + if (name.AsSpan(assembly.Name.Length + 1) is Providers.ProSantéConnect) + { + options.HttpMessageHandlerBuilderActions.Add(builder => + { + // Note: the client registration instance is not available here, + // so the provider options must be resolved via the DI container. +#if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER + var options = builder.Services.GetRequiredService>().CurrentValue; +#else + var options = _provider.GetRequiredService>().CurrentValue; +#endif + // If a client certificate was specified, alter the HTTP handler. + if (options.ClientCertificate is X509Certificate certificate) + { + // If the primary HTTP handler is not an instance of HttpClientHandler, replace it by + // a new instance of HttpClientHandler as it required to attach the client certificate. + if (builder.PrimaryHandler is not HttpClientHandler handler) + { + builder.PrimaryHandler = handler = new HttpClientHandler(); + } + + handler.ClientCertificates.Add(certificate); + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + } + }); + } + } } diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs index 592d3503..74beb3e4 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs @@ -5,6 +5,7 @@ */ using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; using OpenIddict.Client; using OpenIddict.Client.WebIntegration; @@ -40,7 +41,8 @@ public static class OpenIddictClientWebIntegrationExtensions // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. builder.Services.TryAddEnumerable(new[] { - ServiceDescriptor.Singleton, OpenIddictClientWebIntegrationConfiguration>() + ServiceDescriptor.Singleton, OpenIddictClientWebIntegrationConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictClientWebIntegrationConfiguration>() }); return new OpenIddictClientWebIntegrationBuilder(builder.Services); diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index 765247d7..b0ea5c1e 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -545,6 +545,16 @@ public static partial class OpenIddictClientWebIntegrationHandlers context.Request["access_type"] = options.AccessType; } + // Pro Santé Connect's specification requires sending an acr_values parameter containing + // the desired level of authentication (currently, only "eidas1" is supported). For more + // information, see https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000045551195. + else if (context.Registration.ProviderName is Providers.ProSantéConnect) + { + var options = context.Registration.GetProSantéConnectOptions(); + + context.Request.AcrValues = options.AuthenticationLevel; + } + // By default, Reddit doesn't return a refresh token but // allows sending a "duration" parameter to retrieve one. else if (context.Registration.ProviderName is Providers.Reddit) diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml index b804c11a..bef30175 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml @@ -130,6 +130,28 @@ + + + + + + + + + + + + + + + +