Browse Source

Update the System.Net.Http integrations to support sending empty parameters in GET and POST requests

pull/1549/head
Kévin Chalet 3 years ago
parent
commit
ac5e3dd62b
  1. 98
      shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs
  2. 4
      src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj
  3. 33
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
  4. 4
      src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj
  5. 3
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
  6. 4
      src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj
  7. 33
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  8. 4
      test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj
  9. 76
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs
  10. 4
      test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj
  11. 76
      test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTestClient.cs

98
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
}
}
/// <summary>
/// Adds a query string parameter to the specified <see cref="Uri"/>.
/// </summary>
/// <param name="address">The address, to which the query string parameter will be appended.</param>
/// <param name="name">The name of the query string parameter to append.</param>
/// <param name="value">The value of the query string parameter to append.</param>
/// <returns>The final <see cref="Uri"/> instance, with the specified parameter appended.</returns>
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;
}
/// <summary>
/// Adds query string parameters to the specified <see cref="Uri"/>.
/// </summary>
/// <param name="address">The address, to which the query string parameters will be appended.</param>
/// <param name="parameters">The query string parameters to append.</param>
/// <returns>The final <see cref="Uri"/> instance, with the specified parameters appended.</returns>
/// <exception cref="ArgumentNullException"><paramref name="address"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="parameters"/> is <see langword="null"/>.</exception>
public static Uri AddQueryStringParameters(Uri address, IReadOnlyDictionary<string, StringValues> 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;
}
/// <summary>
/// Extracts the parameters from the specified query string.
/// </summary>

4
src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj

@ -38,6 +38,10 @@
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net.Http.Json" />
<Using Include="OpenIddict.Abstractions" />

33
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<string, string>(parameter.Key, value));
select new KeyValuePair<string?, string?>(parameter.Key, value));
return default;
}

4
src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj

@ -27,6 +27,10 @@
<AdditionalFiles Include="**\*.xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />

3
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
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<PrepareUserinfoRequestContext>()
.AddFilter<RequireHttpMetadataAddress>()
.UseSingletonHandler<AttachAccessTokenParameter>()
.SetOrder(AttachBearerAccessToken.Descriptor.Order + 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
@ -82,6 +84,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<HandleUserinfoResponseContext>()
.AddFilter<RequireHttpMetadataAddress>()
.UseSingletonHandler<UnwrapUserinfoResponse>()
.SetOrder(PopulateClaims.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)

4
src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj

@ -38,6 +38,10 @@
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net.Http.Json" />
<Using Include="OpenIddict.Abstractions" />

33
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<string, string>(parameter.Key, value));
select new KeyValuePair<string?, string?>(parameter.Key, value));
return default;
}

4
test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj

@ -33,6 +33,10 @@
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />

76
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<KeyValuePair<string?, string?>>();
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<string?, string?>(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<string?, string?>(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<string?, string?>(parameter.Key, value));
}
return message;

4
test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj

@ -32,6 +32,10 @@
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />

76
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<KeyValuePair<string?, string?>>();
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<string?, string?>(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<string?, string?>(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<string?, string?>(parameter.Key, value));
}
return message;

Loading…
Cancel
Save