diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs index 6c1eda9b..f2c542de 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs @@ -5,7 +5,6 @@ */ using System.Diagnostics; -using System.Net.Http.Headers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; @@ -53,13 +52,6 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions - { - client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue( - productName: assembly.Name!, - productVersion: assembly.Version!.ToString())); - }); - options.HttpMessageHandlerBuilderActions.Add(builder => { #if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs index 64df5c53..f7c9efb9 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs @@ -17,6 +17,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers * Configuration request processing: */ PrepareGetHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, @@ -33,6 +34,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers * Cryptography request processing: */ PrepareGetHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs index 0a8d89f5..52a1041f 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs @@ -20,6 +20,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers * Token request processing: */ PreparePostHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachBasicAuthenticationCredentials.Descriptor, AttachFormParameters.Descriptor, SendHttpRequest.Descriptor, @@ -45,7 +46,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(AttachFormParameters.Descriptor.Order - 1000) + .SetOrder(AttachFormParameters.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs index 77df0ca4..bbb62811 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs @@ -20,6 +20,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers * Userinfo request processing: */ PrepareGetHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachBearerAccessToken.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index 3f9abd42..70871df3 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -109,6 +109,48 @@ public static partial class OpenIddictClientSystemNetHttpHandlers } } + /// + /// Contains the logic responsible for attaching the user agent to the HTTP request. + /// + public class AttachUserAgent : IOpenIddictClientHandler where TContext : BaseExternalContext + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachQueryStringParameters.Descriptor.Order - 1_000) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + + var assembly = typeof(OpenIddictClientSystemNetHttpHandlers).Assembly.GetName(); + + // Attach a user agent based on the assembly version of the System.Net.Http integration. + request.Headers.UserAgent.Add(new ProductInfoHeaderValue( + productName: assembly.Name!, + productVersion: assembly.Version!.ToString())); + + return default; + } + } + /// /// Contains the logic responsible for attaching the query string parameters to the HTTP request. /// diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs index c299bb92..8ed8c4df 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs @@ -16,6 +16,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers /* * Token request preparation: */ - UseProductNameAsUserAgent.Descriptor); + AddProductNameToUserAgentHeader.Descriptor); } } diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs index f6c95415..5330b5b1 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs @@ -19,7 +19,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers /* * Userinfo request preparation: */ - UseProductNameAsUserAgent.Descriptor, + AddProductNameToUserAgentHeader.Descriptor, AttachNonStandardFieldParameter.Descriptor, /* diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index 8ffc9fb1..2025d817 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Net.Http.Headers; using System.Security.Claims; using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlerFilters; +using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlers; using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; namespace OpenIddict.Client.WebIntegration; @@ -201,10 +202,9 @@ public static partial class OpenIddictClientWebIntegrationHandlers } /// - /// Contains the logic responsible for overriding the user agent for providers - /// that are known to require or encourage using custom values (e.g Reddit). + /// Contains the logic responsible for enriching the user agent with an optional product name/product version. /// - public class UseProductNameAsUserAgent : IOpenIddictClientHandler + public class AddProductNameToUserAgentHeader : IOpenIddictClientHandler where TContext : BaseExternalContext { /// @@ -213,8 +213,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 200_000) + .UseSingletonHandler>() + .SetOrder(AttachUserAgent.Descriptor.Order + 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -234,14 +234,18 @@ public static partial class OpenIddictClientWebIntegrationHandlers // A few providers (like Reddit) are known to aggressively check user agents and encourage // developers to use unique user agents. While OpenIddict itself always adds a user agent, // the default value doesn't differ accross applications. To reduce the risks of seeing - // requests blocked by these providers, the user agent is replaced by a custom value - // containing the product name and version set by the user or by the client identifier. - if (context.Registration.GetProviderName() is Providers.Reddit) + // requests blocked by these providers, a more specific user agent header containing the + // product name/version set by the user (or the client identifier if unset) is appended. + var settings = context.Registration.GetProviderSettings(); + if (settings is not null) { - var settings = context.Registration.GetRedditSettings(); - request.Headers.UserAgent.Add(new ProductInfoHeaderValue( - productName: settings.ProductName ?? context.Registration.ClientId!, - productVersion: settings.ProductVersion)); + var name = settings.ProductName ?? context.Registration.ClientId; + if (!string.IsNullOrEmpty(name)) + { + request.Headers.UserAgent.Add(new ProductInfoHeaderValue( + productName: name, + productVersion: settings.ProductVersion)); + } } return default; diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml index bcc013d4..cd4eb360 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml @@ -56,12 +56,6 @@ - - - - diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs index 025aa46a..bfdb5a1b 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs @@ -21,6 +21,16 @@ public abstract partial class OpenIddictClientWebIntegrationSettings /// public virtual string? ClientSecret { get; set; } + /// + /// Gets or sets the product name used in the user agent header. + /// + public string? ProductName { get; set; } + + /// + /// Gets or sets the product version used in the user agent header. + /// + public string? ProductVersion { get; set; } + /// /// Gets or sets the redirection URL. /// diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs index 4af295a0..f083b6bb 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs @@ -5,7 +5,6 @@ */ using System.Diagnostics; -using System.Net.Http.Headers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; @@ -53,13 +52,6 @@ public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions< return; } - options.HttpClientActions.Add(client => - { - client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue( - productName: assembly.Name!, - productVersion: assembly.Version!.ToString())); - }); - options.HttpMessageHandlerBuilderActions.Add(builder => { #if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs index 33e947aa..7405b8d5 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs @@ -17,6 +17,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers * Configuration request processing: */ PrepareGetHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, @@ -33,6 +34,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers * Cryptography request processing: */ PrepareGetHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs index e6fcd2f9..313005e6 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs @@ -20,6 +20,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers * Introspection request processing: */ PreparePostHttpRequest.Descriptor, + AttachUserAgent.Descriptor, AttachBasicAuthenticationCredentials.Descriptor, AttachFormParameters.Descriptor, SendHttpRequest.Descriptor, @@ -45,7 +46,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(AttachFormParameters.Descriptor.Order - 1000) + .SetOrder(AttachFormParameters.Descriptor.Order - 500) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index ec927942..9388f1ef 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -108,6 +108,48 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers } } + /// + /// Contains the logic responsible for attaching the user agent to the HTTP request. + /// + public class AttachUserAgent : IOpenIddictValidationHandler where TContext : BaseExternalContext + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachQueryStringParameters.Descriptor.Order - 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + + var assembly = typeof(OpenIddictValidationSystemNetHttpHandlers).Assembly.GetName(); + + // Attach a user agent based on the assembly version of the System.Net.Http integration. + request.Headers.UserAgent.Add(new ProductInfoHeaderValue( + productName: assembly.Name!, + productVersion: assembly.Version!.ToString())); + + return default; + } + } + /// /// Contains the logic responsible for attaching the query string parameters to the HTTP request. ///