/* * 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; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Threading.Tasks; using OpenIddict.Abstractions; using static OpenIddict.Validation.OpenIddictValidationEvents; using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; namespace OpenIddict.Validation.SystemNetHttp { [EditorBrowsable(EditorBrowsableState.Never)] public static partial class OpenIddictValidationSystemNetHttpHandlers { public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create() .AddRange(Discovery.DefaultHandlers) .AddRange(Introspection.DefaultHandlers); /// /// Contains the logic responsible of preparing an HTTP GET request message. /// public class PrepareGetHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler>() .SetOrder(int.MinValue + 100_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(TContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var request = new HttpRequestMessage(HttpMethod.Get, context.Address); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); // Store the HttpRequestMessage in the transaction properties. context.Transaction.Properties[typeof(HttpRequestMessage).FullName!] = request; return default; } } /// /// Contains the logic responsible of preparing an HTTP POST request message. /// public class PreparePostHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler>() .SetOrder(PrepareGetHttpRequest.Descriptor.Order + 1_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(TContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var request = new HttpRequestMessage(HttpMethod.Post, context.Address); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); // Store the HttpRequestMessage in the transaction properties. context.Transaction.Properties[typeof(HttpRequestMessage).FullName!] = request; return default; } } /// /// Contains the logic responsible of attaching the query string parameters to the HTTP request. /// public class AttachQueryStringParameters : IOpenIddictValidationHandler where TContext : BaseExternalContext { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler>() .SetOrder(AttachFormParameters.Descriptor.Order - 100_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Transaction.Request != null, SR.GetResourceString(SR.ID5008)); // 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(); if (request == null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID1172)); } // Note: System.Net.Http doesn't expose convenient methods allowing to create // query strings from existing key/value pairs. To work around this limitation, // a FormUrlEncodedContent is instantiated and used to manually create the URL. using var content = new FormUrlEncodedContent( from parameter in context.Transaction.Request.GetParameters() let values = (string[]?) parameter.Value where values != null from value in values select new KeyValuePair(parameter.Key, value)); var builder = new UriBuilder(request.RequestUri) { Query = await content.ReadAsStringAsync() }; request.RequestUri = builder.Uri; } } /// /// Contains the logic responsible of attaching the form parameters to the HTTP request. /// public class AttachFormParameters : IOpenIddictValidationHandler where TContext : BaseExternalContext { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler>() .SetOrder(int.MaxValue - 100_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(TContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Transaction.Request != null, SR.GetResourceString(SR.ID5008)); // 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(); if (request == null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID1172)); } request.Content = new FormUrlEncodedContent( from parameter in context.Transaction.Request.GetParameters() let values = (string[]?) parameter.Value where values != null from value in values select new KeyValuePair(parameter.Key, value)); return default; } } /// /// Contains the logic responsible of sending the HTTP request to the remote server. /// public class SendHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext { private readonly IHttpClientFactory _factory; public SendHttpRequest(IHttpClientFactory factory) => _factory = factory; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler>() .SetOrder(int.MaxValue - 100_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { if (context == 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(); if (request == null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID1172)); } var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName(); using var client = _factory.CreateClient(assembly.Name); if (client == null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID1173)); } var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead); if (response == null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID1174)); } // Store the HttpResponseMessage in the transaction properties. context.Transaction.Properties[typeof(HttpResponseMessage).FullName!] = response; } } /// /// Contains the logic responsible of extracting the response from the JSON-encoded HTTP body. /// public class ExtractJsonHttpResponse : IOpenIddictValidationHandler where TContext : BaseExternalContext { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler>() .SetOrder(int.MaxValue - 100_000) .SetType(OpenIddictValidationHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, // this may indicate that the request was incorrectly processed by another client stack. var response = context.Transaction.GetHttpResponseMessage(); if (response == null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID1172)); } // The status code is deliberately not validated to ensure even errored responses // (typically in the 4xx range) can be deserialized and handled by the event handlers. // Note: ReadFromJsonAsync() automatically validates the content type and the content encoding // and transcode the response stream if a non-UTF-8 response is returned by the remote server. context.Transaction.Response = await response.Content.ReadFromJsonAsync(); } } } }