diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs index 3624017c..889e3750 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Net.Http.Headers; +using System.Net.Mail; using System.Reflection; using OpenIddict.Client.SystemNetHttp; using Polly; @@ -48,6 +49,38 @@ public sealed class OpenIddictClientSystemNetHttpBuilder return this; } + /// + /// Sets the contact address used in the "From" header that is attached + /// to the backchannel HTTP requests sent to the authorization server. + /// + /// The mail address. + /// The instance. + public OpenIddictClientSystemNetHttpBuilder SetContactAddress(MailAddress address) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + return Configure(options => options.ContactAddress = address); + } + + /// + /// Sets the contact address used in the "From" header that is attached + /// to the backchannel HTTP requests sent to the authorization server. + /// + /// The mail address. + /// The instance. + public OpenIddictClientSystemNetHttpBuilder SetContactAddress(string address) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0366), nameof(address)); + } + + return SetContactAddress(new MailAddress(address)); + } + /// /// Replaces the default HTTP error policy used by the OpenIddict client services. /// @@ -64,7 +97,7 @@ public sealed class OpenIddictClientSystemNetHttpBuilder } /// - /// Sets the product information used in the user agent header that is attached + /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server. /// /// The product information. @@ -80,7 +113,7 @@ public sealed class OpenIddictClientSystemNetHttpBuilder } /// - /// Sets the product information used in the user agent header that is attached + /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server. /// /// The product name. diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs index f7b5cf1b..fe7a7737 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Discovery.cs @@ -19,6 +19,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers PrepareGetHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, @@ -38,6 +39,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers PrepareGetHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.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 039698ed..cfae0d9f 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Exchange.cs @@ -22,6 +22,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers PreparePostHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.Descriptor, AttachBasicAuthenticationCredentials.Descriptor, AttachFormParameters.Descriptor, SendHttpRequest.Descriptor, diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs index 546fac3f..37ebe902 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs @@ -22,6 +22,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers PrepareGetHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.Descriptor, AttachBearerAccessToken.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index 1270a3fa..d0df4fec 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -9,6 +9,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO.Compression; using System.Net.Http.Headers; +using System.Net.Mail; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -185,6 +186,47 @@ public static partial class OpenIddictClientSystemNetHttpHandlers } } + /// + /// Contains the logic responsible for attaching the contact address to the HTTP request. + /// + public sealed class AttachFromHeader : IOpenIddictClientHandler where TContext : BaseExternalContext + { + private readonly IOptionsMonitor _options; + + public AttachFromHeader(IOptionsMonitor options) + => _options = options ?? throw new ArgumentNullException(nameof(options)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachUserAgentHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + // 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)); + + // Attach the contact address specified in the options, if available. + request.Headers.From = _options.CurrentValue.ContactAddress?.ToString(); + + return default; + } + } + /// /// Contains the logic responsible for attaching the query string parameters to the HTTP request. /// diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs index 2a77ee30..9a0d7d1f 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Http.Headers; +using System.Net.Mail; using Polly; using Polly.Extensions.Http; @@ -25,7 +26,13 @@ public sealed class OpenIddictClientSystemNetHttpOptions .WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); /// - /// Gets or sets the product information used in the user agent header that is + /// Gets or sets the contact mail address used in the "From" header that is + /// attached to the backchannel HTTP requests sent to the authorization server. + /// + public MailAddress? ContactAddress { get; set; } + + /// + /// Gets or sets the product information used in the "User-Agent" header that is /// attached to the backchannel HTTP requests sent to the authorization server. /// public ProductInfoHeaderValue? ProductInformation { get; set; } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs index b0cc178c..914960f5 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Net.Http.Headers; +using System.Net.Mail; using System.Reflection; using OpenIddict.Validation.SystemNetHttp; using Polly; @@ -48,6 +49,38 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder return this; } + /// + /// Sets the contact address used in the "From" header that is attached + /// to the backchannel HTTP requests sent to the authorization server. + /// + /// The mail address. + /// The instance. + public OpenIddictValidationSystemNetHttpBuilder SetContactAddress(MailAddress address) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + return Configure(options => options.ContactAddress = address); + } + + /// + /// Sets the contact address used in the "From" header that is attached + /// to the backchannel HTTP requests sent to the authorization server. + /// + /// The mail address. + /// The instance. + public OpenIddictValidationSystemNetHttpBuilder SetContactAddress(string address) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0366), nameof(address)); + } + + return SetContactAddress(new MailAddress(address)); + } + /// /// Replaces the default HTTP error policy used by the OpenIddict client services. /// @@ -64,7 +97,7 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder } /// - /// Sets the product information used in the user agent header that is attached + /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server. /// /// The product information. @@ -80,7 +113,7 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder } /// - /// Sets the product information used in the user agent header that is attached + /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server. /// /// The product name. @@ -97,7 +130,7 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder } /// - /// Sets the product information used in the user agent header that is attached + /// Sets the product information used in the "User-Agent" header that is attached /// to the backchannel HTTP requests sent to the authorization server based /// on the identity of the specified .NET assembly (name and version). /// diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs index b69261a2..c461994e 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs @@ -19,6 +19,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers PrepareGetHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.Descriptor, AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, @@ -38,6 +39,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers PrepareGetHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.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 f1582426..aea375ed 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs @@ -22,6 +22,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers PreparePostHttpRequest.Descriptor, AttachJsonAcceptHeaders.Descriptor, AttachUserAgentHeader.Descriptor, + AttachFromHeader.Descriptor, AttachBasicAuthenticationCredentials.Descriptor, AttachFormParameters.Descriptor, SendHttpRequest.Descriptor, diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index 18612b67..a32ab895 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -9,6 +9,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO.Compression; using System.Net.Http.Headers; +using System.Net.Mail; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -186,6 +187,47 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers } } + /// + /// Contains the logic responsible for attaching the contact address to the HTTP request. + /// + public sealed class AttachFromHeader : IOpenIddictValidationHandler where TContext : BaseExternalContext + { + private readonly IOptionsMonitor _options; + + public AttachFromHeader(IOptionsMonitor options) + => _options = options ?? throw new ArgumentNullException(nameof(options)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachUserAgentHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + // 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)); + + // Attach the contact address specified in the options, if available. + request.Headers.From = _options.CurrentValue.ContactAddress?.ToString(); + + return default; + } + } + /// /// Contains the logic responsible for attaching the query string parameters to the HTTP request. /// diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs index 7cf158eb..d50946c6 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Http.Headers; +using System.Net.Mail; using Polly; using Polly.Extensions.Http; @@ -25,7 +26,13 @@ public sealed class OpenIddictValidationSystemNetHttpOptions .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); /// - /// Gets or sets the product information used in the user agent header that is + /// Gets or sets the contact mail address used in the "From" header that is + /// attached to the backchannel HTTP requests sent to the authorization server. + /// + public MailAddress? ContactAddress { get; set; } + + /// + /// Gets or sets the product information used in the "User-Agent" header that is /// attached to the backchannel HTTP requests sent to the authorization server. /// public ProductInfoHeaderValue? ProductInformation { get; set; }