/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/openiddict/openiddict-core for more information concerning * the license and the contributors participating to this project. */ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; using Polly; namespace OpenIddict.Client.SystemNetHttp; /// /// Contains the methods required to ensure that the OpenIddict client/System.Net.Http integration configuration is valid. /// [EditorBrowsable(EditorBrowsableState.Advanced)] public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions, IConfigureNamedOptions { private readonly IServiceProvider _provider; /// /// Creates a new instance of the class. /// /// The service provider. public OpenIddictClientSystemNetHttpConfiguration(IServiceProvider provider) => _provider = provider ?? throw new ArgumentNullException(nameof(provider)); /// public void Configure(OpenIddictClientOptions options) { if (options is null) { throw new ArgumentNullException(nameof(options)); } // Register the built-in event handlers used by the OpenIddict System.Net.Http client components. options.Handlers.AddRange(OpenIddictClientSystemNetHttpHandlers.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. if (string.IsNullOrEmpty(name) || !TryResolveRegistrationId(name, out string? identifier)) { return; } var service = _provider.GetRequiredService(); // Note: while the client registration should be returned synchronously in most cases, // the retrieval is always offloaded to the thread pool to help prevent deadlocks when // the waiting is blocking and the operation is executed in a synchronization context. var registration = Task.Run(async () => await service.GetClientRegistrationByIdAsync(identifier)).GetAwaiter().GetResult(); var settings = _provider.GetRequiredService>().CurrentValue; options.HttpClientActions.Add(client => { // By default, HttpClient uses a default timeout of 100 seconds and allows payloads of up to 2GB. // To help reduce the effects of malicious responses (e.g responses returned at a very slow pace // or containing an infine amount of data), the default values are amended to use lower values. client.MaxResponseContentBufferSize = 10 * 1024 * 1024; client.Timeout = TimeSpan.FromMinutes(1); }); // Register the user-defined HTTP client actions. foreach (var action in settings.UnfilteredHttpClientActions) { options.HttpClientActions.Add(client => action(registration, client)); } options.HttpMessageHandlerBuilderActions.Add(builder => { #if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER var options = builder.Services.GetRequiredService>(); #else var options = _provider.GetRequiredService>(); #endif if (builder.PrimaryHandler is not HttpClientHandler handler) { throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName)); } // OpenIddict uses IHttpClientFactory to manage the creation of the HTTP clients and // their underlying HTTP message handlers, that are cached for the specified duration // and re-used to process multiple requests during that period. While remote APIs are // typically not expected to return cookies, it is in practice a very frequent case, // which poses a serious security issue when the cookies are shared across multiple // requests (which is the case when the same message handler is cached and re-used). // // To avoid that, cookies support is explicitly disabled here, for security reasons. handler.UseCookies = false; // Unless the HTTP error policy was explicitly disabled in the options, // add the HTTP handler responsible for replaying failed HTTP requests. if (options.CurrentValue.HttpErrorPolicy is IAsyncPolicy policy) { builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); } }); // Register the user-defined HTTP client handler actions. foreach (var action in settings.UnfilteredHttpClientHandlerActions) { options.HttpMessageHandlerBuilderActions.Add(builder => action(registration, builder.PrimaryHandler as HttpClientHandler ?? throw new InvalidOperationException(SR.FormatID0373(typeof(HttpClientHandler).FullName)))); } static bool TryResolveRegistrationId(string name, [NotNullWhen(true)] out string? value) { var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName(); if (!name.StartsWith(assembly.Name!, StringComparison.Ordinal) || name.Length < assembly.Name!.Length + 1 || name[assembly.Name.Length] is not ':') { value = null; return false; } value = name[(assembly.Name.Length + 1)..]; return true; } } }