Browse Source

Add System.Text.Json.Nodes support and revamp the OpenIddictParameter primitive

pull/1416/head
Kévin Chalet 4 years ago
parent
commit
ec32cfa6e5
  1. 6
      Directory.Build.targets
  2. 17
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs
  3. 16
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs
  4. 2
      src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj
  5. 2
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  6. 14
      src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs
  7. 46
      src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs
  8. 961
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  9. 16
      src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs
  10. 16
      src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs
  11. 7
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
  12. 19
      src/OpenIddict.Client/OpenIddictClientHandlers.Exchange.cs
  13. 104
      src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs
  14. 1
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  15. 34
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs
  16. 34
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs
  17. 13
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  18. 34
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs
  19. 34
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs
  20. 7
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  21. 128
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
  22. 44
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs
  23. 22
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs
  24. 1084
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs
  25. 34
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs

6
Directory.Build.targets

@ -84,6 +84,12 @@
<DefineConstants>$(DefineConstants);SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS</DefineConstants>
</PropertyGroup>
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_DIRECT_JSON_ELEMENT_SERIALIZATION</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_JSON_NODES</DefineConstants>
</PropertyGroup>
<!--
Note: Entity Framework Core 2.x references System.Interactive.Async 3.x, that includes
its own IAsyncEnumerable. To work around collisions between this type and the new type

17
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs

@ -79,9 +79,11 @@ public class Startup
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the authorization, device, logout, token, userinfo and verification endpoints.
// Enable the authorization, device, introspection,
// logout, token, userinfo and verification endpoints.
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetDeviceEndpointUris("/connect/device")
.SetIntrospectionEndpointUris("/connect/introspect")
.SetLogoutEndpointUris("/connect/logout")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo")
@ -149,6 +151,19 @@ public class Startup
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Instead of validating the token locally by reading it directly,
// introspection can be used to ask a remote authorization server
// to validate the token (and its attached database entry).
//
// options.UseIntrospection()
// .SetIssuer("https://localhost:44395/")
// .SetClientId("resource_server")
// .SetClientSecret("80B552BB-4CD8-48DA-946E-0815E0147DD2");
//
// When introspection is used, System.Net.Http integration must be enabled.
//
// options.UseSystemNetHttp();
// Register the ASP.NET Core host.
options.UseAspNetCore();

16
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs

@ -66,6 +66,22 @@ public class Worker : IHostedService
});
}
// Note: when using introspection instead of local token validation,
// an application entry MUST be created to allow the resource server
// to communicate with OpenIddict's introspection endpoint.
if (await manager.FindByClientIdAsync("resource_server") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "resource_server",
ClientSecret = "80B552BB-4CD8-48DA-946E-0815E0147DD2",
Permissions =
{
Permissions.Endpoints.Introspection
}
});
}
// To test this sample with Postman, use the following settings:
//
// * Authorization URL: https://localhost:44395/connect/authorize

2
src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>net461;net6.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateResxSourceEmitFormatMethods>true</GenerateResxSourceEmitFormatMethods>
</PropertyGroup>

2
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -506,7 +506,7 @@ This may indicate that the event handler responsible for processing OpenID Conne
<value>The ASP.NET Core HTTP request cannot be resolved.</value>
</data>
<data name="ID0115" xml:space="preserve">
<value>Only strings, booleans, integers, arrays of strings and instances of type 'OpenIddictParameter' or 'JsonElement' can be returned as custom parameters.</value>
<value>Only strings, booleans, integers, arrays of strings and instances of type 'OpenIddictParameter' or 'JsonElement' can be returned as custom parameters. On .NET 6.0 and higher, instances of type 'JsonNode' (i.e 'JsonArray', 'JsonObject' and 'JsonValue') are also supported.</value>
</data>
<data name="ID0116" xml:space="preserve">
<value>A distributed cache instance must be registered when enabling request caching.

14
src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs

@ -10,7 +10,7 @@ using System.Text.Json.Serialization;
namespace OpenIddict.Abstractions;
/// <summary>
/// Represents a JSON.NET converter able to convert OpenIddict primitives.
/// Represents a JSON converter able to convert OpenIddict primitives.
/// </summary>
public class OpenIddictConverter : JsonConverter<OpenIddictMessage>
{
@ -20,11 +20,9 @@ public class OpenIddictConverter : JsonConverter<OpenIddictMessage>
/// <param name="typeToConvert">The type to convert.</param>
/// <returns><see langword="true"/> if the type is supported, <see langword="false"/> otherwise.</returns>
public override bool CanConvert(Type typeToConvert!!)
{
return typeToConvert == typeof(OpenIddictMessage) ||
typeToConvert == typeof(OpenIddictRequest) ||
typeToConvert == typeof(OpenIddictResponse);
}
=> typeToConvert == typeof(OpenIddictMessage) ||
typeToConvert == typeof(OpenIddictRequest) ||
typeToConvert == typeof(OpenIddictResponse);
/// <summary>
/// Deserializes an <see cref="OpenIddictMessage"/> instance.
@ -50,7 +48,5 @@ public class OpenIddictConverter : JsonConverter<OpenIddictMessage>
/// <param name="value">The instance.</param>
/// <param name="options">The JSON serializer options.</param>
public override void Write(Utf8JsonWriter writer!!, OpenIddictMessage value!!, JsonSerializerOptions options)
{
value.WriteTo(writer);
}
=> value.WriteTo(writer);
}

46
src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs

@ -12,6 +12,10 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Primitives;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Abstractions;
/// <summary>
@ -64,6 +68,34 @@ public class OpenIddictMessage
}
}
#if SUPPORTS_JSON_NODES
/// <summary>
/// Initializes a new OpenIddict message.
/// </summary>
/// <param name="parameters">The message parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictMessage(JsonObject parameters!!)
{
foreach (var parameter in parameters)
{
// Ignore parameters whose name is null or empty.
if (string.IsNullOrEmpty(parameter.Key))
{
continue;
}
// While generally discouraged, JSON objects can contain multiple properties with
// the same name. In this case, the last occurrence replaces the previous ones.
if (HasParameter(parameter.Key))
{
RemoveParameter(parameter.Key);
}
AddParameter(parameter.Key, parameter.Value);
}
}
#endif
/// <summary>
/// Initializes a new OpenIddict message.
/// </summary>
@ -220,19 +252,7 @@ public class OpenIddictMessage
/// <param name="name">The parameter name.</param>
/// <returns>The parameter value, or <see langword="null"/> if it cannot be found.</returns>
public OpenIddictParameter? GetParameter(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name));
}
if (Parameters.TryGetValue(name, out OpenIddictParameter value))
{
return value;
}
return null;
}
=> TryGetParameter(name, out var parameter) ? parameter : (OpenIddictParameter?) null;
/// <summary>
/// Gets all the parameters associated with this instance.

961
src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs

File diff suppressed because it is too large

16
src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs

@ -9,6 +9,10 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Primitives;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Abstractions;
/// <summary>
@ -41,6 +45,18 @@ public class OpenIddictRequest : OpenIddictMessage
{
}
#if SUPPORTS_JSON_NODES
/// <summary>
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictRequest(JsonObject parameters)
: base(parameters)
{
}
#endif
/// <summary>
/// Initializes a new OpenIddict request.
/// </summary>

16
src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs

@ -9,6 +9,10 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Primitives;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Abstractions;
/// <summary>
@ -41,6 +45,18 @@ public class OpenIddictResponse : OpenIddictMessage
{
}
#if SUPPORTS_JSON_NODES
/// <summary>
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictResponse(JsonObject parameters)
: base(parameters)
{
}
#endif
/// <summary>
/// Initializes a new OpenIddict response.
/// </summary>

7
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs

@ -17,6 +17,10 @@ using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Properties = OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreConstants.Properties;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Client.AspNetCore;
[EditorBrowsable(EditorBrowsableState.Never)]
@ -447,6 +451,9 @@ public static partial class OpenIddictClientAspNetCoreHandlers
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}

19
src/OpenIddict.Client/OpenIddictClientHandlers.Exchange.cs

@ -40,7 +40,7 @@ public static partial class OpenIddictClientHandlers
{
foreach (var parameter in context.Response.GetParameters())
{
if (ValidateParameterType(parameter.Key, parameter.Value.Value))
if (ValidateParameterType(parameter.Key, parameter.Value))
{
continue;
}
@ -55,14 +55,21 @@ public static partial class OpenIddictClientHandlers
return default;
static bool ValidateParameterType(string name, object? value) => name switch
// Note: in the typical case, the response parameters should be deserialized from a
// JSON response and thus natively stored as System.Text.Json.JsonElement instances.
//
// In the rare cases where the underlying value wouldn't be a JsonElement instance
// (e.g when custom parameters are manually added to the response), the static
// conversion operator would take care of converting the underlying value to a
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
// The 'expires_in' parameter MUST be formatted as a numeric date value.
Parameters.ExpiresIn => value is long or JsonElement { ValueKind: JsonValueKind.Number },
// The 'access_token', 'id_token' and 'refresh_token' parameters MUST be formatted as unique strings.
Parameters.AccessToken or Parameters.IdToken or Parameters.RefreshToken
=> value is string or JsonElement { ValueKind: JsonValueKind.String },
=> ((JsonElement) value).ValueKind is JsonValueKind.String,
// The 'expires_in' parameter MUST be formatted as a numeric date value.
Parameters.ExpiresIn => ((JsonElement) value).ValueKind is JsonValueKind.Number,
// Parameters that are not in the well-known list can be of any type.
_ => true

104
src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs

@ -5,7 +5,6 @@
*/
using System.Collections.Immutable;
using System.Globalization;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.IdentityModel.JsonWebTokens;
@ -50,7 +49,7 @@ public static partial class OpenIddictClientHandlers
foreach (var parameter in context.Response.GetParameters())
{
if (ValidateClaimType(parameter.Key, parameter.Value.Value))
if (ValidateClaimType(parameter.Key, parameter.Value))
{
continue;
}
@ -65,10 +64,17 @@ public static partial class OpenIddictClientHandlers
return default;
static bool ValidateClaimType(string name, object? value) => name switch
// Note: in the typical case, the response parameters should be deserialized from a
// JSON response and thus natively stored as System.Text.Json.JsonElement instances.
//
// In the rare cases where the underlying value wouldn't be a JsonElement instance
// (e.g when custom parameters are manually added to the response), the static
// conversion operator would take care of converting the underlying value to a
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateClaimType(string name, OpenIddictParameter value) => name switch
{
// The 'sub' parameter MUST be formatted as a string value.
Claims.Subject => value is string or JsonElement { ValueKind: JsonValueKind.String },
// The 'sub' parameter MUST be formatted as a unique string value.
Claims.Subject => ((JsonElement) value).ValueKind is JsonValueKind.String,
// Parameters that are not in the well-known list can be of any type.
_ => true
@ -123,8 +129,8 @@ public static partial class OpenIddictClientHandlers
foreach (var parameter in context.Response.GetParameters())
{
// Always exclude null keys and values, as they can't be represented as valid claims.
if (string.IsNullOrEmpty(parameter.Key) || OpenIddictParameter.IsNullOrEmpty(parameter.Value))
// Always exclude null keys as they can't be represented as valid claims.
if (string.IsNullOrEmpty(parameter.Key))
{
continue;
}
@ -142,53 +148,40 @@ public static partial class OpenIddictClientHandlers
continue;
}
switch (parameter.Value.Value)
// Note: in the typical case, the response parameters should be deserialized from a
// JSON response and thus natively stored as System.Text.Json.JsonElement instances.
//
// In the rare cases where the underlying value wouldn't be a JsonElement instance
// (e.g when custom parameters are manually added to the response), the static
// conversion operator would take care of converting the underlying value to a
// JsonElement instance using the same value type as the original parameter value.
switch ((JsonElement) parameter.Value)
{
// Claims represented as arrays are split and mapped to multiple CLR claims.
case JsonElement { ValueKind: JsonValueKind.Array } value:
foreach (var element in value.EnumerateArray())
// Top-level claims represented as arrays are split and mapped to multiple CLR claims
// to match the logic implemented by IdentityModel for JWT token deserialization.
case { ValueKind: JsonValueKind.Array } value:
foreach (var item in value.EnumerateArray())
{
var item = element.GetString();
if (string.IsNullOrEmpty(item))
{
continue;
}
identity.AddClaim(new Claim(parameter.Key, item,
GetClaimValueType(value.ValueKind), issuer, issuer, identity));
identity.AddClaim(new Claim(
type : parameter.Key,
value : item.ToString()!,
valueType : GetClaimValueType(item.ValueKind),
issuer : issuer,
originalIssuer: issuer,
subject : identity));
}
break;
case JsonElement value:
identity.AddClaim(new Claim(parameter.Key, value.ToString()!,
GetClaimValueType(value.ValueKind), issuer, issuer, identity));
break;
// Note: in the typical case, the introspection parameters should be deserialized from
// a JSON response and thus represented as System.Text.Json.JsonElement instances.
// However, to support responses resolved from custom locations and parameters manually added
// by the application using the events model, the CLR primitive types are also supported.
case bool value:
identity.AddClaim(new Claim(parameter.Key, value.ToString(),
ClaimValueTypes.Boolean, issuer, issuer, identity));
break;
case long value:
identity.AddClaim(new Claim(parameter.Key, value.ToString(CultureInfo.InvariantCulture),
ClaimValueTypes.Integer64, issuer, issuer, identity));
break;
case string value:
identity.AddClaim(new Claim(parameter.Key, value, ClaimValueTypes.String, issuer, issuer, identity));
break;
// Claims represented as arrays are split and mapped to multiple CLR claims.
case string[] value:
for (var index = 0; index < value.Length; index++)
{
identity.AddClaim(new Claim(parameter.Key, value[index], ClaimValueTypes.String, issuer, issuer, identity));
}
// Note: JsonElement.ToString() returns string.Empty for JsonValueKind.Null and
// JsonValueKind.Undefined, which, unlike null strings, is a valid claim value.
case { ValueKind: _ } value:
identity.AddClaim(new Claim(
type : parameter.Key,
value : value.ToString()!,
valueType : GetClaimValueType(value.ValueKind),
issuer : issuer,
originalIssuer: issuer,
subject : identity));
break;
}
}
@ -197,13 +190,12 @@ public static partial class OpenIddictClientHandlers
static string GetClaimValueType(JsonValueKind kind) => kind switch
{
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
};
}
}

1
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -105,6 +105,7 @@ public static partial class OpenIddictClientHandlers
.AddRange(Authentication.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers)
.AddRange(Protection.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);

34
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs

@ -132,16 +132,13 @@ public static partial class OpenIddictServerAspNetCoreHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0117));
}
// Restore the authorization request parameters from the serialized payload.
// Restore the request parameters from the serialized payload.
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Name))
if (!context.Request.HasParameter(parameter.Name))
{
continue;
context.Request.AddParameter(parameter.Name, parameter.Value.Clone());
}
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -207,17 +204,34 @@ public static partial class OpenIddictServerAspNetCoreHandlers
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Build a list of claims matching the parameters extracted from the request.
//
// Note: in most cases, parameters should be representated as strings as requests are
// typically resolved from the query string or the request form, where parameters
// are natively represented as strings. However, requests can also be extracted from
// different places where they can be represented as complex JSON representations
// (e.g requests extracted from a JSON Web Token that may be encrypted and/or signed).
var claims = from parameter in context.Request.GetParameters()
let element = (JsonElement) parameter.Value
let type = element.ValueKind switch
{
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
}
select new Claim(parameter.Key, element.ToString()!, type);
// Store the serialized authorization request parameters in the distributed cache.
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
{
Audience = context.Issuer?.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
EncryptingCredentials = context.Options.EncryptionCredentials.First(),
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity(),
Subject = new ClaimsIdentity(claims, TokenValidationParameters.DefaultAuthenticationType),
TokenType = JsonWebTokenTypes.Private.AuthorizationRequest
});

34
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs

@ -129,16 +129,13 @@ public static partial class OpenIddictServerAspNetCoreHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0118));
}
// Restore the authorization request parameters from the serialized payload.
// Restore the request parameters from the serialized payload.
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Name))
if (!context.Request.HasParameter(parameter.Name))
{
continue;
context.Request.AddParameter(parameter.Name, parameter.Value.Clone());
}
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -204,17 +201,34 @@ public static partial class OpenIddictServerAspNetCoreHandlers
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Build a list of claims matching the parameters extracted from the request.
//
// Note: in most cases, parameters should be representated as strings as requests are
// typically resolved from the query string or the request form, where parameters
// are natively represented as strings. However, requests can also be extracted from
// different places where they can be represented as complex JSON representations
// (e.g requests extracted from a JSON Web Token that may be encrypted and/or signed).
var claims = from parameter in context.Request.GetParameters()
let element = (JsonElement) parameter.Value
let type = element.ValueKind switch
{
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
}
select new Claim(parameter.Key, element.ToString()!, type);
// Store the serialized logout request parameters in the distributed cache.
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
{
Audience = context.Issuer?.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
EncryptingCredentials = context.Options.EncryptionCredentials.First(),
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity(),
Subject = new ClaimsIdentity(claims, TokenValidationParameters.DefaultAuthenticationType),
TokenType = JsonWebTokenTypes.Private.LogoutRequest
});

13
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -17,6 +17,10 @@ using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Properties = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.Properties;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Server.AspNetCore;
[EditorBrowsable(EditorBrowsableState.Never)]
@ -320,6 +324,9 @@ public static partial class OpenIddictServerAspNetCoreHandlers
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}
@ -367,6 +374,9 @@ public static partial class OpenIddictServerAspNetCoreHandlers
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}
@ -414,6 +424,9 @@ public static partial class OpenIddictServerAspNetCoreHandlers
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}

34
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs

@ -129,16 +129,13 @@ public static partial class OpenIddictServerOwinHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0117));
}
// Restore the authorization request parameters from the serialized payload.
// Restore the request parameters from the serialized payload.
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Name))
if (!context.Request.HasParameter(parameter.Name))
{
continue;
context.Request.AddParameter(parameter.Name, parameter.Value.Clone());
}
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -199,17 +196,34 @@ public static partial class OpenIddictServerOwinHandlers
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Build a list of claims matching the parameters extracted from the request.
//
// Note: in most cases, parameters should be representated as strings as requests are
// typically resolved from the query string or the request form, where parameters
// are natively represented as strings. However, requests can also be extracted from
// different places where they can be represented as complex JSON representations
// (e.g requests extracted from a JSON Web Token that may be encrypted and/or signed).
var claims = from parameter in context.Request.GetParameters()
let element = (JsonElement) parameter.Value
let type = element.ValueKind switch
{
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
}
select new Claim(parameter.Key, element.ToString()!, type);
// Store the serialized authorization request parameters in the distributed cache.
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
{
Audience = context.Issuer?.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
EncryptingCredentials = context.Options.EncryptionCredentials.First(),
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity(),
Subject = new ClaimsIdentity(claims, TokenValidationParameters.DefaultAuthenticationType),
TokenType = JsonWebTokenTypes.Private.AuthorizationRequest
});

34
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs

@ -127,16 +127,13 @@ public static partial class OpenIddictServerOwinHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0118));
}
// Restore the authorization request parameters from the serialized payload
// Restore the request parameters from the serialized payload.
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Name))
if (!context.Request.HasParameter(parameter.Name))
{
continue;
context.Request.AddParameter(parameter.Name, parameter.Value.Clone());
}
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -197,17 +194,34 @@ public static partial class OpenIddictServerOwinHandlers
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Build a list of claims matching the parameters extracted from the request.
//
// Note: in most cases, parameters should be representated as strings as requests are
// typically resolved from the query string or the request form, where parameters
// are natively represented as strings. However, requests can also be extracted from
// different places where they can be represented as complex JSON representations
// (e.g requests extracted from a JSON Web Token that may be encrypted and/or signed).
var claims = from parameter in context.Request.GetParameters()
let element = (JsonElement) parameter.Value
let type = element.ValueKind switch
{
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
}
select new Claim(parameter.Key, element.ToString()!, type);
// Store the serialized logout request parameters in the distributed cache.
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
{
Audience = context.Issuer?.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
EncryptingCredentials = context.Options.EncryptionCredentials.First(),
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity(),
Subject = new ClaimsIdentity(claims, TokenValidationParameters.DefaultAuthenticationType),
TokenType = JsonWebTokenTypes.Private.LogoutRequest
});

7
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs

@ -17,6 +17,10 @@ using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Properties = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants.Properties;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Validation.AspNetCore;
[EditorBrowsable(EditorBrowsableState.Never)]
@ -317,6 +321,9 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
string value => new OpenIddictParameter(value),
string[] value => new OpenIddictParameter(value),
#if SUPPORTS_JSON_NODES
JsonNode value => new OpenIddictParameter(value),
#endif
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115))
};
}

128
src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs

@ -5,7 +5,6 @@
*/
using System.Collections.Immutable;
using System.Globalization;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.IdentityModel.JsonWebTokens;
@ -152,7 +151,7 @@ public static partial class OpenIddictValidationHandlers
{
foreach (var parameter in context.Response.GetParameters())
{
if (ValidateClaimType(parameter.Key, parameter.Value.Value))
if (ValidateClaimType(parameter.Key, parameter.Value))
{
continue;
}
@ -167,32 +166,39 @@ public static partial class OpenIddictValidationHandlers
return default;
static bool ValidateClaimType(string name, object? value) => name switch
// Note: in the typical case, the response parameters should be deserialized from a
// JSON response and thus natively stored as System.Text.Json.JsonElement instances.
//
// In the rare cases where the underlying value wouldn't be a JsonElement instance
// (e.g when custom parameters are manually added to the response), the static
// conversion operator would take care of converting the underlying value to a
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateClaimType(string name, OpenIddictParameter value) => name switch
{
// The 'aud' claim MUST be represented either as a unique string or as an array of multiple strings.
Claims.Audience when value is string or string[] => true,
Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.String } => true,
Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.Array } element &&
ValidateArrayChildren(element, JsonValueKind.String) => true,
Claims.Audience => false,
// The 'jti', 'iss', 'scope' and 'token_usage' claims MUST be formatted as a unique string.
Claims.JwtId or Claims.Issuer or Claims.Scope or Claims.TokenUsage
=> ((JsonElement) value).ValueKind is JsonValueKind.String,
// The 'aud' claim MUST be represented either as a unique string or as an array of strings.
//
// Note: empty arrays and arrays that contain a single value are also considered valid.
Claims.Audience => ((JsonElement) value) is JsonElement element &&
element.ValueKind is JsonValueKind.String ||
(element.ValueKind is JsonValueKind.Array && ValidateStringArray(element)),
// The 'exp', 'iat' and 'nbf' claims MUST be formatted as numeric date values.
Claims.ExpiresAt or Claims.IssuedAt or Claims.NotBefore
=> value is long or JsonElement { ValueKind: JsonValueKind.Number },
// The 'jti', 'iss', 'scope' and 'token_usage' claims MUST be formatted as a unique string.
Claims.JwtId or Claims.Issuer or Claims.Scope or Claims.TokenUsage
=> value is string or JsonElement { ValueKind: JsonValueKind.String },
=> ((JsonElement) value).ValueKind is JsonValueKind.Number,
// Claims that are not in the well-known list can be of any type.
_ => true
};
static bool ValidateArrayChildren(JsonElement element, JsonValueKind kind)
static bool ValidateStringArray(JsonElement element)
{
foreach (var child in element.EnumerateArray())
foreach (var item in element.EnumerateArray())
{
if (child.ValueKind != kind)
if (item.ValueKind is not JsonValueKind.String)
{
return false;
}
@ -348,8 +354,8 @@ public static partial class OpenIddictValidationHandlers
foreach (var parameter in context.Response.GetParameters())
{
// Always exclude null keys and values, as they can't be represented as valid claims.
if (string.IsNullOrEmpty(parameter.Key) || OpenIddictParameter.IsNullOrEmpty(parameter.Value))
// Always exclude null keys as they can't be represented as valid claims.
if (string.IsNullOrEmpty(parameter.Key))
{
continue;
}
@ -367,53 +373,40 @@ public static partial class OpenIddictValidationHandlers
continue;
}
switch (parameter.Value.Value)
// Note: in the typical case, the response parameters should be deserialized from a
// JSON response and thus natively stored as System.Text.Json.JsonElement instances.
//
// In the rare cases where the underlying value wouldn't be a JsonElement instance
// (e.g when custom parameters are manually added to the response), the static
// conversion operator would take care of converting the underlying value to a
// JsonElement instance using the same value type as the original parameter value.
switch ((JsonElement) parameter.Value)
{
// Claims represented as arrays are split and mapped to multiple CLR claims.
case JsonElement { ValueKind: JsonValueKind.Array } value:
foreach (var element in value.EnumerateArray())
// Top-level claims represented as arrays are split and mapped to multiple CLR claims
// to match the logic implemented by IdentityModel for JWT token deserialization.
case { ValueKind: JsonValueKind.Array } value:
foreach (var item in value.EnumerateArray())
{
var item = element.GetString();
if (string.IsNullOrEmpty(item))
{
continue;
}
identity.AddClaim(new Claim(parameter.Key, item,
GetClaimValueType(value.ValueKind), issuer, issuer, identity));
identity.AddClaim(new Claim(
type : parameter.Key,
value : item.ToString()!,
valueType : GetClaimValueType(item.ValueKind),
issuer : issuer,
originalIssuer: issuer,
subject : identity));
}
break;
case JsonElement value:
identity.AddClaim(new Claim(parameter.Key, value.ToString()!,
GetClaimValueType(value.ValueKind), issuer, issuer, identity));
break;
// Note: in the typical case, the introspection parameters should be deserialized from
// a JSON response and thus represented as System.Text.Json.JsonElement instances.
// However, to support responses resolved from custom locations and parameters manually added
// by the application using the events model, the CLR primitive types are also supported.
case bool value:
identity.AddClaim(new Claim(parameter.Key, value.ToString(),
ClaimValueTypes.Boolean, issuer, issuer, identity));
break;
case long value:
identity.AddClaim(new Claim(parameter.Key, value.ToString(CultureInfo.InvariantCulture),
ClaimValueTypes.Integer64, issuer, issuer, identity));
break;
case string value:
identity.AddClaim(new Claim(parameter.Key, value, ClaimValueTypes.String, issuer, issuer, identity));
break;
// Claims represented as arrays are split and mapped to multiple CLR claims.
case string[] value:
for (var index = 0; index < value.Length; index++)
{
identity.AddClaim(new Claim(parameter.Key, value[index], ClaimValueTypes.String, issuer, issuer, identity));
}
// Note: JsonElement.ToString() returns string.Empty for JsonValueKind.Null and
// JsonValueKind.Undefined, which, unlike null strings, is a valid claim value.
case { ValueKind: _ } value:
identity.AddClaim(new Claim(
type : parameter.Key,
value : value.ToString()!,
valueType : GetClaimValueType(value.ValueKind),
issuer : issuer,
originalIssuer: issuer,
subject : identity));
break;
}
}
@ -422,13 +415,12 @@ public static partial class OpenIddictValidationHandlers
static string GetClaimValueType(JsonValueKind kind) => kind switch
{
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
JsonValueKind.String => ClaimValueTypes.String,
JsonValueKind.Number => ClaimValueTypes.Integer64,
JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean,
JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull,
JsonValueKind.Array => JsonClaimValueTypes.JsonArray,
JsonValueKind.Object or _ => JsonClaimValueTypes.Json
};
}
}

44
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs

@ -8,6 +8,10 @@ using System.Text;
using System.Text.Json;
using Xunit;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Abstractions.Tests.Primitives;
public class OpenIddictConverterTests
@ -129,6 +133,11 @@ public class OpenIddictConverterTests
Assert.Null((long?) message.GetParameter("long"));
Assert.Equal(JsonValueKind.Null, ((JsonElement) message.GetParameter("array")).ValueKind);
Assert.Equal(JsonValueKind.Null, ((JsonElement) message.GetParameter("object")).ValueKind);
#if SUPPORTS_JSON_NODES
Assert.Null((JsonNode?) message.GetParameter("array"));
Assert.Null((JsonNode?) message.GetParameter("object"));
#endif
}
[Fact]
@ -149,6 +158,11 @@ public class OpenIddictConverterTests
Assert.Empty((string?) message.GetParameter("string"));
Assert.NotNull((JsonElement?) message.GetParameter("array"));
Assert.NotNull((JsonElement?) message.GetParameter("object"));
#if SUPPORTS_JSON_NODES
Assert.NotNull((JsonNode?) message.GetParameter("array"));
Assert.NotNull((JsonNode?) message.GetParameter("object"));
#endif
}
[Fact]
@ -213,7 +227,11 @@ public class OpenIddictConverterTests
message.AddParameter("string", new OpenIddictParameter((string?) null));
message.AddParameter("bool", new OpenIddictParameter((bool?) null));
message.AddParameter("long", new OpenIddictParameter((long?) null));
message.AddParameter("node", new OpenIddictParameter(default(JsonElement)));
message.AddParameter("element", new OpenIddictParameter(default(JsonElement)));
#if SUPPORTS_JSON_NODES
message.AddParameter("node", new OpenIddictParameter((JsonNode?) null));
#endif
// Act
converter.Write(writer, value: message, options: null!);
@ -221,7 +239,12 @@ public class OpenIddictConverterTests
// Assert
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""node"":null}", reader.ReadToEnd());
#if SUPPORTS_JSON_NODES
Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""element"":null,""node"":null}", reader.ReadToEnd());
#else
Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""element"":null}", reader.ReadToEnd());
#endif
}
[Fact]
@ -235,8 +258,14 @@ public class OpenIddictConverterTests
var message = new OpenIddictMessage();
message.AddParameter("string", new OpenIddictParameter(string.Empty));
message.AddParameter("array", new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
message.AddParameter("object", new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("{}")));
message.AddParameter("element_array", new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
message.AddParameter("element_object", new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("{}")));
#if SUPPORTS_JSON_NODES
message.AddParameter("node_array", new OpenIddictParameter(new JsonArray()));
message.AddParameter("node_object", new OpenIddictParameter(new JsonObject()));
message.AddParameter("node_value", new OpenIddictParameter(JsonValue.Create(new { })));
#endif
// Act
converter.Write(writer, value: message, options: null!);
@ -244,7 +273,12 @@ public class OpenIddictConverterTests
// Assert
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
Assert.Equal(@"{""string"":"""",""array"":[],""object"":{}}", reader.ReadToEnd());
#if SUPPORTS_JSON_NODES
Assert.Equal(@"{""string"":"""",""element_array"":[],""element_object"":{},""node_array"":[],""node_object"":{},""node_value"":{}}", reader.ReadToEnd());
#else
Assert.Equal(@"{""string"":"""",""element_array"":[],""element_object"":{}}", reader.ReadToEnd());
#endif
}
[Fact]

22
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs

@ -9,6 +9,10 @@ using System.Text.Encodings.Web;
using System.Text.Json;
using Xunit;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Abstractions.Tests.Primitives;
public class OpenIddictMessageTests
@ -185,11 +189,23 @@ public class OpenIddictMessageTests
message.AddParameter("value", JsonSerializer.Deserialize<JsonElement>(
@"{""property"":""""}").GetProperty("property").GetString());
#if SUPPORTS_JSON_NODES
message.AddParameter("node_array", new JsonArray());
message.AddParameter("node_object", new JsonObject());
message.AddParameter("node_value", JsonValue.Create(string.Empty));
#endif
// Assert
Assert.Empty((string?) message.GetParameter("string"));
Assert.NotNull((JsonElement?) message.GetParameter("array"));
Assert.NotNull((JsonElement?) message.GetParameter("object"));
Assert.NotNull((JsonElement?) message.GetParameter("value"));
#if SUPPORTS_JSON_NODES
Assert.NotNull((JsonNode?) message.GetParameter("node_array"));
Assert.NotNull((JsonNode?) message.GetParameter("node_object"));
Assert.NotNull((JsonNode?) message.GetParameter("node_value"));
#endif
}
[Theory]
@ -384,6 +400,12 @@ public class OpenIddictMessageTests
message.SetParameter("value", JsonSerializer.Deserialize<JsonElement>(
@"{""property"":""""}").GetProperty("property").GetString());
#if SUPPORTS_JSON_NODES
message.SetParameter("node_array", new JsonArray());
message.SetParameter("node_object", new JsonObject());
message.SetParameter("node_value", JsonValue.Create(string.Empty));
#endif
// Assert
Assert.Empty(message.GetParameters());
}

1084
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs

File diff suppressed because it is too large

34
test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs

@ -22,6 +22,10 @@ using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
#if SUPPORTS_JSON_NODES
using System.Text.Json.Nodes;
#endif
namespace OpenIddict.Server.AspNetCore.IntegrationTests;
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests
@ -167,10 +171,13 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind);
Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]);
Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind);
Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_parameter"]);
Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind);
Assert.Equal("value", (string?) response["object_parameter"]?["parameter"]);
Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind);
#if SUPPORTS_JSON_NODES
Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["node_array_parameter"]);
Assert.IsType<JsonArray>((JsonNode?) response["node_array_parameter"]);
Assert.Equal("value", (string?) response["node_object_parameter"]?["parameter"]);
Assert.IsType<JsonObject>((JsonNode?) response["node_object_parameter"]);
#endif
}
[Fact]
@ -762,6 +769,13 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind);
Assert.Equal("value", (string?) response["object_parameter"]?["parameter"]);
Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind);
#if SUPPORTS_JSON_NODES
Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["node_array_parameter"]);
Assert.IsType<JsonArray>((JsonNode?) response["node_array_parameter"]);
Assert.Equal("value", (string?) response["node_object_parameter"]?["parameter"]);
Assert.IsType<JsonObject>((JsonNode?) response["node_object_parameter"]);
#endif
}
[Fact]
@ -893,7 +907,11 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
["integer_parameter"] = 42,
["string_parameter"] = "Bob l'Eponge",
["array_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"),
["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}")
["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"),
#if SUPPORTS_JSON_NODES
["node_array_parameter"] = new JsonArray("Contoso", "Fabrikam"),
["node_object_parameter"] = new JsonObject { ["parameter"] = "value" }
#endif
});
await context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal, properties);
@ -942,7 +960,11 @@ public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServ
["integer_parameter"] = 42,
["string_parameter"] = "Bob l'Eponge",
["array_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"),
["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}")
["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"),
#if SUPPORTS_JSON_NODES
["node_array_parameter"] = new JsonArray("Contoso", "Fabrikam"),
["node_object_parameter"] = new JsonObject { ["parameter"] = "value" }
#endif
});
await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties);

Loading…
Cancel
Save