diff --git a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index 4b1e1c42..b1c85eec 100644 --- a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using System.Text; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; @@ -74,6 +75,103 @@ internal static class OpenIddictHelpers } } + /// + /// Adds a query string parameter to the specified . + /// + /// The address, to which the query string parameter will be appended. + /// The name of the query string parameter to append. + /// The value of the query string parameter to append. + /// The final instance, with the specified parameter appended. + public static Uri AddQueryStringParameter(Uri address, string name, string? value) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + var builder = new StringBuilder(address.Query); + if (builder.Length > 0) + { + builder.Append('&'); + } + + builder.Append(Uri.EscapeDataString(name)); + + if (!string.IsNullOrEmpty(value)) + { + builder.Append('='); + builder.Append(Uri.EscapeDataString(value)); + } + + return new UriBuilder(address) { Query = builder.ToString() }.Uri; + } + + /// + /// Adds query string parameters to the specified . + /// + /// The address, to which the query string parameters will be appended. + /// The query string parameters to append. + /// The final instance, with the specified parameters appended. + /// is . + /// is . + public static Uri AddQueryStringParameters(Uri address, IReadOnlyDictionary parameters) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (parameters is null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (parameters.Count is 0) + { + return address; + } + + var builder = new StringBuilder(address.Query); + + foreach (var parameter in parameters) + { + // If the parameter doesn't include any string value, + // only append the parameter key to the query string. + if (parameter.Value.Count is 0) + { + if (builder.Length > 0) + { + builder.Append('&'); + } + + builder.Append(Uri.EscapeDataString(parameter.Key)); + } + + // Otherwise, iterate the string values and create + // a new "name=value" pair for each iterated value. + else + { + foreach (var value in parameter.Value) + { + if (builder.Length > 0) + { + builder.Append('&'); + } + + builder.Append(Uri.EscapeDataString(parameter.Key)); + + if (!string.IsNullOrEmpty(value)) + { + builder.Append('='); + builder.Append(Uri.EscapeDataString(value)); + } + } + } + } + + return new UriBuilder(address) { Query = builder.ToString() }.Uri; + } + /// /// Extracts the parameters from the specified query string. /// diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj b/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj index e8f9bd3e..01c39ecb 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj @@ -38,6 +38,10 @@ + + + + diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index 6dc5c3bc..5d973d8c 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -10,10 +10,10 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO.Compression; using System.Net.Http.Headers; -using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; +using OpenIddict.Extensions; using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpConstants; namespace OpenIddict.Client.SystemNetHttp; @@ -226,28 +226,10 @@ public static partial class OpenIddictClientSystemNetHttpHandlers return default; } - var builder = new StringBuilder(); - - foreach (var (key, value) in - from parameter in context.Transaction.Request.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - if (builder.Length > 0) - { - builder.Append('&'); - } - - builder.Append(Uri.EscapeDataString(key)); - builder.Append('='); - builder.Append(Uri.EscapeDataString(value)); - } - - // Compute the final request URI using the base address and the query string. - request.RequestUri = new UriBuilder(request.RequestUri) { Query = builder.ToString() }.Uri; + request.RequestUri = OpenIddictHelpers.AddQueryStringParameters(request.RequestUri, + context.Transaction.Request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => new StringValues((string?[]?) parameter.Value))); return default; } @@ -286,11 +268,10 @@ public static partial class OpenIddictClientSystemNetHttpHandlers request.Content = new FormUrlEncodedContent( from parameter in context.Transaction.Request.GetParameters() - let values = (string[]?) parameter.Value + let values = (string?[]?) parameter.Value where values is not null from value in values - where !string.IsNullOrEmpty(value) - select new KeyValuePair(parameter.Key, value)); + select new KeyValuePair(parameter.Key, value)); return default; } diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj b/src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj index 18dd3dca..756051da 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj +++ b/src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj @@ -27,6 +27,10 @@ + + + + diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs index e8e7955f..7e7ec84a 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Text.Json; using static OpenIddict.Client.OpenIddictClientHandlers.Userinfo; +using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlerFilters; using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlers.Userinfo; using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; @@ -39,6 +40,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() .UseSingletonHandler() .SetOrder(AttachBearerAccessToken.Descriptor.Order + 250) .SetType(OpenIddictClientHandlerType.BuiltIn) @@ -82,6 +84,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() .UseSingletonHandler() .SetOrder(PopulateClaims.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj b/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj index dd958561..eb75f378 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj @@ -38,6 +38,10 @@ + + + + diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index f017716b..b0b045bc 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -10,10 +10,10 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO.Compression; using System.Net.Http.Headers; -using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; +using OpenIddict.Extensions; using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpConstants; namespace OpenIddict.Validation.SystemNetHttp; @@ -227,28 +227,10 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers return default; } - var builder = new StringBuilder(); - - foreach (var (key, value) in - from parameter in context.Transaction.Request.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - if (builder.Length > 0) - { - builder.Append('&'); - } - - builder.Append(Uri.EscapeDataString(key)); - builder.Append('='); - builder.Append(Uri.EscapeDataString(value)); - } - - // Compute the final request URI using the base address and the query string. - request.RequestUri = new UriBuilder(request.RequestUri) { Query = builder.ToString() }.Uri; + request.RequestUri = OpenIddictHelpers.AddQueryStringParameters(request.RequestUri, + context.Transaction.Request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => new StringValues((string?[]?) parameter.Value))); return default; } @@ -287,11 +269,10 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers request.Content = new FormUrlEncodedContent( from parameter in context.Transaction.Request.GetParameters() - let values = (string[]?) parameter.Value + let values = (string?[]?) parameter.Value where values is not null from value in values - where !string.IsNullOrEmpty(value) - select new KeyValuePair(parameter.Key, value)); + select new KeyValuePair(parameter.Key, value)); return default; } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj b/test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj index 2c94cec3..4e8a7a8c 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj @@ -33,6 +33,10 @@ + + + + diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs index e32e720d..d0c2ed3e 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs @@ -5,10 +5,9 @@ */ using System.Net.Http.Json; -using System.Text; -using System.Text.Encodings.Web; using AngleSharp.Html.Parser; using Microsoft.Extensions.Primitives; +using OpenIddict.Extensions; namespace OpenIddict.Server.IntegrationTests; @@ -235,71 +234,28 @@ public class OpenIddictServerIntegrationTestClient : IAsyncDisposable private HttpRequestMessage CreateRequestMessage(OpenIddictRequest request, HttpMethod method, Uri uri) { - // Note: a dictionary is deliberately not used here to allow multiple parameters with the - // same name to be specified. While initially not allowed by the core OAuth2 specification, - // this is required for derived drafts like the OAuth2 token exchange specification. - var parameters = new List>(); - - foreach (var parameter in request.GetParameters()) + if (!uri.IsAbsoluteUri) { - // If the parameter is null or empty, send an empty value. - if (OpenIddictParameter.IsNullOrEmpty(parameter.Value)) - { - parameters.Add(new KeyValuePair(parameter.Key, string.Empty)); - - continue; - } - - var values = (string?[]?) parameter.Value; - if (values is not { Length: > 0 }) - { - continue; - } - - foreach (var value in values) - { - parameters.Add(new KeyValuePair(parameter.Key, value)); - } + uri = new Uri(HttpClient.BaseAddress!, uri); } - if (method == HttpMethod.Get && parameters.Count is not 0) + var message = new HttpRequestMessage(method, uri); + if (message.Method == HttpMethod.Get && request.Count is not 0) { - var builder = new StringBuilder(); - - foreach (var parameter in parameters) - { - if (string.IsNullOrEmpty(parameter.Key)) - { - continue; - } - - if (builder.Length is not 0) - { - builder.Append('&'); - } - - builder.Append(UrlEncoder.Default.Encode(parameter.Key)); - - if (!string.IsNullOrEmpty(parameter.Value)) - { - builder.Append('='); - builder.Append(UrlEncoder.Default.Encode(parameter.Value)); - } - } - - if (!uri.IsAbsoluteUri) - { - uri = new Uri(HttpClient.BaseAddress!, uri); - } - - uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri; + message.RequestUri = OpenIddictHelpers.AddQueryStringParameters(message.RequestUri!, + request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => new StringValues((string?[]?) parameter.Value))); } - var message = new HttpRequestMessage(method, uri); - - if (method != HttpMethod.Get) + if (message.Method != HttpMethod.Get) { - message.Content = new FormUrlEncodedContent(parameters); + message.Content = new FormUrlEncodedContent( + from parameter in request.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + select new KeyValuePair(parameter.Key, value)); } return message; diff --git a/test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj b/test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj index f2ea1e0e..0b2c218a 100644 --- a/test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj +++ b/test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj @@ -32,6 +32,10 @@ + + + + diff --git a/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTestClient.cs b/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTestClient.cs index afcc5f70..d1c2ae19 100644 --- a/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTestClient.cs +++ b/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTestClient.cs @@ -5,10 +5,9 @@ */ using System.Net.Http.Json; -using System.Text; -using System.Text.Encodings.Web; using AngleSharp.Html.Parser; using Microsoft.Extensions.Primitives; +using OpenIddict.Extensions; namespace OpenIddict.Validation.IntegrationTests; @@ -235,71 +234,28 @@ public class OpenIddictValidationIntegrationTestClient : IAsyncDisposable private HttpRequestMessage CreateRequestMessage(OpenIddictRequest request, HttpMethod method, Uri uri) { - // Note: a dictionary is deliberately not used here to allow multiple parameters with the - // same name to be specified. While initially not allowed by the core OAuth2 specification, - // this is required for derived drafts like the OAuth2 token exchange specification. - var parameters = new List>(); - - foreach (var parameter in request.GetParameters()) + if (!uri.IsAbsoluteUri) { - // If the parameter is null or empty, send an empty value. - if (OpenIddictParameter.IsNullOrEmpty(parameter.Value)) - { - parameters.Add(new KeyValuePair(parameter.Key, string.Empty)); - - continue; - } - - var values = (string?[]?) parameter.Value; - if (values is not { Length: > 0 }) - { - continue; - } - - foreach (var value in values) - { - parameters.Add(new KeyValuePair(parameter.Key, value)); - } + uri = new Uri(HttpClient.BaseAddress!, uri); } - if (method == HttpMethod.Get && parameters.Count is not 0) + var message = new HttpRequestMessage(method, uri); + if (message.Method == HttpMethod.Get && request.Count is not 0) { - var builder = new StringBuilder(); - - foreach (var parameter in parameters) - { - if (string.IsNullOrEmpty(parameter.Key)) - { - continue; - } - - if (builder.Length is not 0) - { - builder.Append('&'); - } - - builder.Append(UrlEncoder.Default.Encode(parameter.Key)); - - if (!string.IsNullOrEmpty(parameter.Value)) - { - builder.Append('='); - builder.Append(UrlEncoder.Default.Encode(parameter.Value)); - } - } - - if (!uri.IsAbsoluteUri) - { - uri = new Uri(HttpClient.BaseAddress!, uri); - } - - uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri; + message.RequestUri = OpenIddictHelpers.AddQueryStringParameters(message.RequestUri!, + request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => new StringValues((string?[]?) parameter.Value))); } - var message = new HttpRequestMessage(method, uri); - - if (method != HttpMethod.Get) + if (message.Method != HttpMethod.Get) { - message.Content = new FormUrlEncodedContent(parameters); + message.Content = new FormUrlEncodedContent( + from parameter in request.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + select new KeyValuePair(parameter.Key, value)); } return message;