Browse Source

Replace JSON.NET by System.Text.Json, store cached requests as JWE/JWS payloads and set the typ header of JWT tokens

pull/849/head
Kévin Chalet 7 years ago
parent
commit
bef913d002
  1. 4
      eng/Versions.props
  2. 3
      samples/Mvc.Server/Controllers/UserinfoController.cs
  3. 3
      src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj
  4. 14
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  5. 97
      src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs
  6. 17
      src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
  7. 29
      src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs
  8. 579
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  9. 14
      src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs
  10. 6
      src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs
  11. 7
      src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
  12. 7
      src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
  13. 7
      src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
  14. 7
      src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
  15. 63
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  16. 33
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  17. 33
      src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
  18. 23
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  19. 63
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  20. 33
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  21. 33
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  22. 24
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
  23. 15
      src/OpenIddict.MongoDb/Stores/OpenIddictApplicationStore.cs
  24. 15
      src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
  25. 17
      src/OpenIddict.MongoDb/Stores/OpenIddictScopeStore.cs
  26. 15
      src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs
  27. 72
      src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs
  28. 42
      src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs
  29. 42
      src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs
  30. 32
      src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs
  31. 1
      src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj
  32. 6
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs
  33. 80
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs
  34. 83
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs
  35. 89
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  36. 17
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs
  37. 1
      src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj
  38. 6
      src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs
  39. 80
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs
  40. 83
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs
  41. 39
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  42. 4
      src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs
  43. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
  44. 74
      src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs
  45. 17
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  46. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  47. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
  48. 5
      src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
  49. 144
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  50. 29
      src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
  51. 1
      src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj
  52. 53
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  53. 4
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs
  54. 1
      src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj
  55. 53
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs
  56. 10
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  57. 3
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  58. 20
      src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs
  59. 182
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs
  60. 62
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs
  61. 453
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs
  62. 6
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs

4
eng/Versions.props

@ -30,12 +30,11 @@
<PropertyGroup>
<BclAsyncInterfacesVersion>1.0.0</BclAsyncInterfacesVersion>
<BclHashCodeVersion>1.0.0</BclHashCodeVersion>
<BouncyCastleVersion>1.8.5</BouncyCastleVersion>
<DataAnnotationsVersion>4.4.0</DataAnnotationsVersion>
<EntityFrameworkVersion>6.3.0</EntityFrameworkVersion>
<JetBrainsVersion>2019.1.3</JetBrainsVersion>
<JsonNetVersion>12.0.2</JsonNetVersion>
<JsonNetBsonVersion>1.0.2</JsonNetBsonVersion>
<IdentityModelVersion>5.6.0</IdentityModelVersion>
<ImmutableCollectionsVersion>1.5.0</ImmutableCollectionsVersion>
<LinqAsyncVersion>4.0.0</LinqAsyncVersion>
@ -43,6 +42,7 @@
<MoqVersion>4.7.63</MoqVersion>
<NHibernateVersion>5.2.2</NHibernateVersion>
<OwinVersion>4.0.0</OwinVersion>
<SystemTextJsonVersion>4.6.0</SystemTextJsonVersion>
<TasksExtensionsVersion>4.5.3</TasksExtensionsVersion>
</PropertyGroup>

3
samples/Mvc.Server/Controllers/UserinfoController.cs

@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Mvc.Server.Models;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
@ -51,7 +50,7 @@ namespace Mvc.Server.Controllers
if (User.HasClaim(OpenIddictConstants.Claims.Scope, "roles"))
{
claims["roles"] = JArray.FromObject(await _userManager.GetRolesAsync(user));
claims["roles"] = await _userManager.GetRolesAsync(user);
}
// Note: the complete list of standard claims supported by the OpenID Connect specification

3
src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj

@ -12,13 +12,14 @@
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(ExtensionsVersion)" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="$(ExtensionsVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
<PackageReference Include="System.Collections.Immutable" Version="$(ImmutableCollectionsVersion)" />
<PackageReference Include="System.ComponentModel.Annotations" Version="$(DataAnnotationsVersion)" />
<PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="$(BclAsyncInterfacesVersion)" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="$(BclHashCodeVersion)" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(TasksExtensionsVersion)" />
</ItemGroup>

14
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -178,6 +178,20 @@ namespace OpenIddict.Abstractions
public const string RefreshToken = "refresh_token";
}
public static class JsonWebTokenTypes
{
public const string AccessToken = "at+jwt";
public const string IdentityToken = "jwt";
public static class Private
{
public const string AuthorizationCode = "oi_auc+jwt";
public const string DeviceCode = "oi_dvc+jwt";
public const string RefreshToken = "oi_reft+jwt";
public const string UserCode = "oi_usrc+jwt";
}
}
public static class Metadata
{
public const string AcrValuesSupported = "acr_values_supported";

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

@ -5,16 +5,16 @@
*/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace OpenIddict.Abstractions
{
/// <summary>
/// Represents a JSON.NET converter able to convert OpenIddict primitives.
/// </summary>
public class OpenIddictConverter : JsonConverter
public class OpenIddictConverter : JsonConverter<OpenIddictMessage>
{
/// <summary>
/// Determines whether the specified type is supported by this converter.
@ -36,62 +36,50 @@ namespace OpenIddict.Abstractions
/// </summary>
/// <param name="reader">The JSON reader.</param>
/// <param name="type">The type of the deserialized instance.</param>
/// <param name="value">The existing <see cref="OpenIddictMessage"/>, if applicable.</param>
/// <param name="serializer">The JSON serializer.</param>
/// <param name="options">The JSON serializer options.</param>
/// <returns>The deserialized <see cref="OpenIddictMessage"/> instance.</returns>
public override object ReadJson(
[NotNull] JsonReader reader, [NotNull] Type type,
[CanBeNull] object value, [CanBeNull] JsonSerializer serializer)
public override OpenIddictMessage Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
// Note: OpenIddict primitives are always represented as JSON objects.
var payload = JToken.Load(reader) as JObject;
if (payload == null)
var payload = JsonSerializer.Deserialize<JsonElement>(ref reader, options);
if (payload.ValueKind != JsonValueKind.Object)
{
throw new JsonSerializationException("An error occurred while reading the JSON payload.");
throw new JsonException("An error occurred while reading the JSON payload.");
}
// If no existing value was specified, instantiate a
// new request/response depending on the requested type.
var message = value as OpenIddictMessage;
if (message == null)
OpenIddictMessage message;
if (type == typeof(OpenIddictMessage))
{
if (type == typeof(OpenIddictMessage))
{
message = new OpenIddictMessage();
}
message = new OpenIddictMessage();
}
else if (type == typeof(OpenIddictRequest))
{
message = new OpenIddictRequest();
}
else if (type == typeof(OpenIddictRequest))
{
message = new OpenIddictRequest();
}
else if (type == typeof(OpenIddictResponse))
{
message = new OpenIddictResponse();
}
else if (type == typeof(OpenIddictResponse))
{
message = new OpenIddictResponse();
}
if (message != null)
else
{
foreach (var parameter in payload.Properties())
{
message.AddParameter(parameter.Name, parameter.Value);
}
throw new ArgumentException("The specified type is not supported.", nameof(type));
}
return message;
foreach (var parameter in payload.EnumerateObject())
{
message.AddParameter(parameter.Name, parameter.Value);
}
throw new ArgumentException("The specified type is not supported.", nameof(type));
return message;
}
/// <summary>
@ -99,8 +87,8 @@ namespace OpenIddict.Abstractions
/// </summary>
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The instance.</param>
/// <param name="serializer">The JSON serializer.</param>
public override void WriteJson([NotNull] JsonWriter writer, [NotNull] object value, [CanBeNull] JsonSerializer serializer)
/// <param name="options">The JSON serializer options.</param>
public override void Write(Utf8JsonWriter writer, OpenIddictMessage value, JsonSerializerOptions options)
{
if (writer == null)
{
@ -112,31 +100,24 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(value));
}
if (value is OpenIddictMessage message)
writer.WriteStartObject();
foreach (var parameter in value.GetParameters())
{
writer.WriteStartObject();
writer.WritePropertyName(parameter.Key);
foreach (var parameter in message.GetParameters())
var token = (JsonElement?) parameter.Value;
if (token == null)
{
writer.WritePropertyName(parameter.Key);
var token = (JToken) parameter.Value;
if (token == null)
{
writer.WriteNull();
writer.WriteNullValue();
continue;
}
token.WriteTo(writer);
continue;
}
writer.WriteEndObject();
return;
token.Value.WriteTo(writer);
}
throw new ArgumentException("The specified object is not supported.", nameof(value));
writer.WriteEndObject();
}
}
}

17
src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs

@ -11,10 +11,10 @@ using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using JetBrains.Annotations;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Abstractions
@ -538,7 +538,9 @@ namespace OpenIddict.Abstractions
return ImmutableArray.Create<string>();
}
return JArray.Parse(destinations).Values<string>().Distinct(StringComparer.OrdinalIgnoreCase).ToImmutableArray();
return JsonSerializer.Deserialize<IEnumerable<string>>(destinations)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToImmutableArray();
}
/// <summary>
@ -565,7 +567,8 @@ namespace OpenIddict.Abstractions
return false;
}
return JArray.Parse(destinations).Values<string>().Contains(destination, StringComparer.OrdinalIgnoreCase);
return JsonSerializer.Deserialize<IEnumerable<string>>(destinations)
.Contains(destination, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
@ -593,7 +596,11 @@ namespace OpenIddict.Abstractions
}
claim.Properties[Properties.Destinations] =
new JArray(destinations.Distinct(StringComparer.OrdinalIgnoreCase)).ToString(Formatting.None);
JsonSerializer.Serialize(destinations.Distinct(StringComparer.OrdinalIgnoreCase), new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return claim;
}

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

@ -7,13 +7,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace OpenIddict.Abstractions
{
@ -38,7 +38,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict message.
/// </summary>
/// <param name="parameters">The message parameters.</param>
public OpenIddictMessage([NotNull] IEnumerable<KeyValuePair<string, JToken>> parameters)
public OpenIddictMessage([NotNull] IEnumerable<KeyValuePair<string, JsonElement>> parameters)
{
if (parameters == null)
{
@ -316,12 +316,12 @@ namespace OpenIddict.Abstractions
/// <returns>The indented JSON representation corresponding to this message.</returns>
public override string ToString()
{
var builder = new StringBuilder();
using var writer = new JsonTextWriter(new StringWriter(builder, CultureInfo.InvariantCulture))
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Formatting = Formatting.Indented
};
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = true
});
writer.WriteStartObject();
@ -343,26 +343,27 @@ namespace OpenIddict.Abstractions
case OpenIddictConstants.Parameters.RefreshToken:
case OpenIddictConstants.Parameters.Token:
{
writer.WriteValue("[removed for security reasons]");
writer.WriteStringValue("[removed for security reasons]");
continue;
}
}
var token = (JToken) parameter.Value;
var token = (JsonElement?) parameter.Value;
if (token == null)
{
writer.WriteNull();
writer.WriteNullValue();
continue;
}
token.WriteTo(writer);
token.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
return builder.ToString();
return Encoding.UTF8.GetString(stream.ToArray());
}
}
}

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

@ -7,15 +7,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace OpenIddict.Abstractions
{
/// <summary>
/// Represents an OpenID Connect parameter value, that can be either a primitive value,
/// Represents an OpenIddict parameter value, that can be either a primitive value,
/// an array of strings or a complex JSON representation containing child nodes.
/// </summary>
public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
@ -39,7 +40,14 @@ namespace OpenIddict.Abstractions
/// parameter using the specified value.
/// </summary>
/// <param name="value">The parameter value.</param>
public OpenIddictParameter(JToken value) => Value = value;
public OpenIddictParameter(JsonElement value) => Value = value;
/// <summary>
/// Initializes a new OpenID Connect
/// parameter using the specified value.
/// </summary>
/// <param name="value">The parameter value.</param>
public OpenIddictParameter(JsonElement? value) => Value = value;
/// <summary>
/// Initializes a new OpenID Connect
@ -96,33 +104,119 @@ namespace OpenIddict.Abstractions
/// </summary>
/// <param name="parameter">The other object to which to compare this instance.</param>
/// <returns><c>true</c> if the two instances are equal, <c>false</c> otherwise.</returns>
public bool Equals(OpenIddictParameter parameter) => Value switch
public bool Equals(OpenIddictParameter parameter)
{
// If the two parameters reference the same instance, return true.
// Note: true will also be returned if the two parameters are null.
var value when ReferenceEquals(value, parameter.Value) => true,
// If one of the two parameters is null, return false.
null => false,
var _ when parameter.Value == null => false,
// If the two parameters are string arrays, use SequenceEqual().
string[] array when parameter.Value is string[] other => array.SequenceEqual(other),
// If the two parameters are JSON values, use JToken.DeepEquals().
JToken token when parameter.Value is JToken other => JToken.DeepEquals(token, other),
// If the current instance is a JValue, compare the
// underlying value to the other parameter value.
JValue value => value.Value != null && value.Value.Equals(parameter.Value),
// If the other parameter is a JValue, compare the
// underlying value to the current parameter value.
var value when parameter.Value is JValue other => other.Value != null && other.Value.Equals(value),
// Otherwise, directly compare the two underlying values.
_ => Value.Equals(parameter.Value)
};
return Value switch
{
// If the two parameters reference the same instance, return true.
// Note: true will also be returned if the two parameters are null.
var value when ReferenceEquals(value, parameter.Value) => true,
// If one of the two parameters is null, return false.
null => false,
var _ when parameter.Value == null => false,
// If the two parameters are string arrays, use SequenceEqual().
string[] value when parameter.Value is string[] array => value.SequenceEqual(array),
// If the two parameters are JsonElement instances, use the custom comparer.
JsonElement value when parameter.Value is JsonElement element => Equals(value, element),
// When one of the parameters is a bool, compare them as booleans.
JsonElement value when value.ValueKind == JsonValueKind.True
&& parameter.Value is bool boolean => boolean,
JsonElement value when value.ValueKind == JsonValueKind.False
&& parameter.Value is bool boolean => !boolean,
bool value when parameter.Value is JsonElement element
&& element.ValueKind == JsonValueKind.True => value,
bool value when parameter.Value is JsonElement element
&& element.ValueKind == JsonValueKind.False => !value,
// When one of the parameters is a number, compare them as integers.
JsonElement value when value.ValueKind == JsonValueKind.Number
&& parameter.Value is long integer
=> integer == value.GetInt64(),
long value when parameter.Value is JsonElement element
&& element.ValueKind == JsonValueKind.Number
=> value == element.GetInt64(),
// When one of the parameters is a string, compare them as texts.
JsonElement value when value.ValueKind == JsonValueKind.String
&& parameter.Value is string text
=> string.Equals(value.GetString(), text, StringComparison.Ordinal),
string value when parameter.Value is JsonElement element
&& element.ValueKind == JsonValueKind.String
=> string.Equals(value, element.GetString(), StringComparison.Ordinal),
// Otherwise, use direct CLR comparison.
var value => value.Equals(parameter.Value)
};
static bool Equals(JsonElement left, JsonElement right)
{
switch (left.ValueKind)
{
case JsonValueKind.Undefined:
return right.ValueKind == JsonValueKind.Undefined;
case JsonValueKind.Null:
return right.ValueKind == JsonValueKind.Null;
case JsonValueKind.False:
return right.ValueKind == JsonValueKind.False;
case JsonValueKind.True:
return right.ValueKind == JsonValueKind.True;
case JsonValueKind.Number when right.ValueKind == JsonValueKind.Number:
return left.GetInt64() == right.GetInt64();
case JsonValueKind.String when right.ValueKind == JsonValueKind.String:
return string.Equals(left.GetString(), right.GetString(), StringComparison.Ordinal);
case JsonValueKind.Array when right.ValueKind == JsonValueKind.Array:
if (left.GetArrayLength() != right.GetArrayLength())
{
return false;
}
using (var enumerator = left.EnumerateArray())
{
for (var index = 0; enumerator.MoveNext(); index++)
{
if (!Equals(left[index], right[index]))
{
return false;
}
}
}
return true;
case JsonValueKind.Object when right.ValueKind == JsonValueKind.Object:
foreach (var property in left.EnumerateObject())
{
if (!right.TryGetProperty(property.Name, out JsonElement element) ||
property.Value.ValueKind != element.ValueKind)
{
return false;
}
if (!Equals(property.Value, element))
{
return false;
}
}
return true;
default: return false;
}
}
}
/// <summary>
/// Determines whether the current <see cref="OpenIddictParameter"/>
@ -137,9 +231,69 @@ namespace OpenIddict.Abstractions
/// Returns the hash code of the current <see cref="OpenIddictParameter"/> instance.
/// </summary>
/// <returns>The hash code for the current instance.</returns>
// Note: if the value is a JValue, JSON.NET will automatically
// return the hash code corresponding to the underlying value.
public override int GetHashCode() => Value?.GetHashCode() ?? 0;
public override int GetHashCode()
{
return Value switch
{
// When the parameter value is null, return 0.
null => 0,
// When the parameter is a JsonElement, compute its hash code.
JsonElement value => GetHashCode(value),
// Otherwise, use the default hash code method.
var value => value.GetHashCode()
};
static int GetHashCode(JsonElement value)
{
switch (value.ValueKind)
{
case JsonValueKind.Undefined:
case JsonValueKind.Null:
return 0;
case JsonValueKind.False:
return false.GetHashCode();
case JsonValueKind.True:
return true.GetHashCode();
case JsonValueKind.Number:
return value.GetInt64().GetHashCode();
case JsonValueKind.String:
return value.GetString().GetHashCode();
case JsonValueKind.Array:
{
var hash = new HashCode();
foreach (var element in value.EnumerateArray())
{
hash.Add(GetHashCode(element));
}
return hash.ToHashCode();
}
case JsonValueKind.Object:
{
var hash = new HashCode();
foreach (var property in value.EnumerateObject())
{
hash.Add(property.Name);
hash.Add(GetHashCode(property.Value));
}
return hash.ToHashCode();
}
default: return 0;
}
}
}
/// <summary>
/// Gets the child item corresponding to the specified index.
@ -165,26 +319,19 @@ namespace OpenIddict.Abstractions
return new OpenIddictParameter(array[index]);
}
// If the value is not a JSON array, return null.
if (Value is JArray token)
if (Value is JsonElement element && element.ValueKind == JsonValueKind.Array)
{
// If the specified index goes beyond the
// number of items in the array, return null.
if (index >= token.Count)
if (index >= element.GetArrayLength())
{
return null;
}
// If the item doesn't exist, return a null parameter.
var value = token[index];
if (value == null)
{
return null;
}
return new OpenIddictParameter(value);
return new OpenIddictParameter(element[index]);
}
// If the value is not a JSON array, return null.
return null;
}
@ -200,16 +347,15 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The item name cannot be null or empty.", nameof(name));
}
if (Value is JObject dictionary)
if (Value is JsonElement element && element.ValueKind == JsonValueKind.Object)
{
// If the item doesn't exist, return a null parameter.
var value = dictionary[name];
if (value == null)
if (element.TryGetProperty(name, out JsonElement value) && value.ValueKind != JsonValueKind.Null)
{
return null;
return new OpenIddictParameter(value);
}
return new OpenIddictParameter(value);
// If the item doesn't exist, return a null parameter.
return null;
}
return null;
@ -229,18 +375,25 @@ namespace OpenIddict.Abstractions
}
}
if (Value is JToken token)
if (Value is JsonElement element)
{
foreach (var child in token.Children())
switch (element.ValueKind)
{
if (!(child is JProperty property))
{
yield return new KeyValuePair<string, OpenIddictParameter>(null, child);
case JsonValueKind.Array:
foreach (var value in element.EnumerateArray())
{
yield return new KeyValuePair<string, OpenIddictParameter>(null, value);
}
continue;
}
break;
case JsonValueKind.Object:
foreach (var property in element.EnumerateObject())
{
yield return new KeyValuePair<string, OpenIddictParameter>(property.Name, property.Value);
}
yield return new KeyValuePair<string, OpenIddictParameter>(property.Name, property.Value);
break;
}
}
@ -254,12 +407,17 @@ namespace OpenIddict.Abstractions
public override string ToString() => Value switch
{
null => string.Empty,
JValue value when value.Value == null => string.Empty,
string[] array => string.Join(", ", array),
string value => value,
string[] value => string.Join(", ", value),
JValue value => value.Value.ToString(),
JToken token => token.ToString(Formatting.None),
JsonElement value when value.ValueKind == JsonValueKind.Undefined => string.Empty,
JsonElement value when value.ValueKind == JsonValueKind.Null => string.Empty,
JsonElement value when value.ValueKind != JsonValueKind.Array &&
value.ValueKind != JsonValueKind.Object => value.GetString(),
JsonElement value => value.ToString(),
_ => Value.ToString()
};
@ -277,9 +435,10 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The parameter name cannot be null or empty.", nameof(name));
}
if (Value is JObject dictionary && dictionary.TryGetValue(name, out JToken token))
if (Value is JsonElement element && element.ValueKind == JsonValueKind.Object &&
element.TryGetProperty(name, out JsonElement property))
{
value = new OpenIddictParameter(token);
value = new OpenIddictParameter(property);
return true;
}
@ -310,70 +469,234 @@ namespace OpenIddict.Abstractions
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator bool(OpenIddictParameter? parameter) => Convert<bool>(parameter);
public static explicit operator bool(OpenIddictParameter? parameter)
=> ((bool?) parameter).GetValueOrDefault();
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a nullable boolean.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator bool?(OpenIddictParameter? parameter) => Convert<bool?>(parameter);
public static explicit operator bool?(OpenIddictParameter? parameter) => parameter?.Value switch
{
// When the parameter is a null value, return null.
null => null,
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a <see cref="JArray"/>.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator JArray(OpenIddictParameter? parameter) => Convert<JArray>(parameter);
// When the parameter is a boolean value, return it as-is.
bool value => value,
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a <see cref="JObject"/>.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator JObject(OpenIddictParameter? parameter) => Convert<JObject>(parameter);
// When the parameter is a string value, try to parse it.
string value => bool.TryParse(value, out var result) ? (bool?) result : null,
// When the parameter is a JsonElement representing null, return null.
JsonElement value when value.ValueKind == JsonValueKind.Undefined => null,
JsonElement value when value.ValueKind == JsonValueKind.Null => null,
// When the parameter is a JsonElement representing a boolean, return it as-is.
JsonElement value when value.ValueKind == JsonValueKind.False => false,
JsonElement value when value.ValueKind == JsonValueKind.True => true,
// When the parameter is a JsonElement representing a string, try to parse it.
JsonElement value when value.ValueKind == JsonValueKind.String
=> bool.TryParse(value.GetString(), out var result) ? (bool?) result : null,
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a <see cref="JToken"/>.
/// Converts an <see cref="OpenIddictParameter"/> instance to a <see cref="JsonElement"/>.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator JToken(OpenIddictParameter? parameter) => Convert<JToken>(parameter);
public static explicit operator JsonElement(OpenIddictParameter? parameter)
=> ((JsonElement?) parameter).GetValueOrDefault();
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a <see cref="JValue"/>.
/// Converts an <see cref="OpenIddictParameter"/> instance to a nullale <see cref="JsonElement"/>.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator JValue(OpenIddictParameter? parameter) => Convert<JValue>(parameter);
public static explicit operator JsonElement?(OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value, return null.
null => default(JsonElement?),
// When the parameter is a JsonElement representing null, return null.
JsonElement value when value.ValueKind == JsonValueKind.Undefined => null,
JsonElement value when value.ValueKind == JsonValueKind.Null => null,
// When the parameter is already a JsonElement, return it as-is.
JsonElement value => value,
// When the parameter is a string, try to derialize it to get a JsonElement instance.
string value => DeserializeElement(value) ?? DeserializeElement(JsonSerializer.Serialize(value, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
})),
// Otherwise, serialize it to get a JsonElement instance.
var value => DeserializeElement(JsonSerializer.Serialize(value, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
}))
};
static JsonElement? DeserializeElement(string value)
{
try { return JsonSerializer.Deserialize<JsonElement>(value); }
catch (JsonException) { return null; }
}
}
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a long integer.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator long(OpenIddictParameter? parameter) => Convert<long>(parameter);
public static explicit operator long(OpenIddictParameter? parameter)
=> ((long?) parameter).GetValueOrDefault();
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a nullable long integer.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator long?(OpenIddictParameter? parameter) => Convert<long?>(parameter);
public static explicit operator long?(OpenIddictParameter? parameter) => parameter?.Value switch
{
// When the parameter is a null value, return null.
null => null,
// When the parameter is an integer, return it as-is.
long value => value,
// When the parameter is a string value, try to parse it.
string value
=> long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null,
// When the parameter is a JsonElement representing null, return null.
JsonElement value when value.ValueKind == JsonValueKind.Undefined => null,
JsonElement value when value.ValueKind == JsonValueKind.Null => null,
// When the parameter is a JsonElement representing a number, return it as-is.
JsonElement value when value.ValueKind == JsonValueKind.Number
=> value.TryGetInt64(out var result) ? (long?) result : null,
// When the parameter is a JsonElement representing a string, try to parse it.
JsonElement value when value.ValueKind == JsonValueKind.String
=> long.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null,
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to a string.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator string(OpenIddictParameter? parameter) => Convert<string>(parameter);
public static explicit operator string(OpenIddictParameter? parameter) => parameter?.Value switch
{
// When the parameter is a null value, return null.
null => null,
// When the parameter is a string value, return it as-is.
string value => value,
// When the parameter is a boolean value, use its string representation.
bool value => value.ToString(),
// When the parameter is an integer, use its string representation.
long value => value.ToString(CultureInfo.InvariantCulture),
// When the parameter is a JsonElement representing null, return null.
JsonElement value when value.ValueKind == JsonValueKind.Undefined => null,
JsonElement value when value.ValueKind == JsonValueKind.Null => null,
// When the parameter is a JsonElement representing a string, return it as-is.
JsonElement value when value.ValueKind == JsonValueKind.String => value.GetString(),
// When the parameter is a JsonElement that doesn't represent a string, serialize it.
JsonElement value => JsonSerializer.Serialize(value, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
}),
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
/// <summary>
/// Converts an <see cref="OpenIddictParameter"/> instance to an array of strings.
/// </summary>
/// <param name="parameter">The parameter to convert.</param>
/// <returns>The converted value.</returns>
public static explicit operator string[](OpenIddictParameter? parameter) => Convert<string[]>(parameter);
public static explicit operator string[](OpenIddictParameter? parameter)
{
return parameter?.Value switch
{
// When the parameter is a null value, return a null array.
null => null,
// When the parameter is already an array of strings, return it as-is.
string[] value => value,
// When the parameter is a string value, return an array with a single entry.
string value => new string[] { value },
// When the parameter is a boolean value, return an array with its string representation.
bool value => new string[] { value.ToString() },
// When the parameter is an integer, return an array with its string representation.
long value => new string[] { value.ToString(CultureInfo.InvariantCulture) },
// When the parameter is a JsonElement representing null, return null.
JsonElement value when value.ValueKind == JsonValueKind.Undefined => null,
JsonElement value when value.ValueKind == JsonValueKind.Null => null,
// When the parameter is a JsonElement representing a string, return an array with a single entry.
JsonElement value when value.ValueKind == JsonValueKind.String
=> new string[] { value.GetString() },
// When the parameter is a JsonElement representing a number, return an array with a single entry.
JsonElement value when value.ValueKind == JsonValueKind.Number
=> new string[] { value.GetInt64().ToString(CultureInfo.InvariantCulture) },
// When the parameter is a JsonElement representing a boolean, return an array with a single entry.
JsonElement value when value.ValueKind == JsonValueKind.False => new string[] { bool.FalseString },
JsonElement value when value.ValueKind == JsonValueKind.True => new string[] { bool.TrueString },
// When the parameter is a JsonElement representing an array of strings, return it.
JsonElement value when value.ValueKind == JsonValueKind.Array => CreateArray(value),
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
};
static string[] CreateArray(JsonElement value)
{
var array = new string[value.GetArrayLength()];
using var enumerator = value.EnumerateArray();
for (var index = 0; enumerator.MoveNext(); index++)
{
var element = enumerator.Current;
if (element.ValueKind != JsonValueKind.String)
{
return null;
}
array[index] = element.GetString();
}
return array;
}
}
/// <summary>
/// Converts a boolean to an <see cref="OpenIddictParameter"/> instance.
@ -390,11 +713,11 @@ namespace OpenIddict.Abstractions
public static implicit operator OpenIddictParameter(bool? value) => new OpenIddictParameter(value);
/// <summary>
/// Converts a <see cref="JToken"/> to an <see cref="OpenIddictParameter"/> instance.
/// Converts a <see cref="JsonElement"/> to an <see cref="OpenIddictParameter"/> instance.
/// </summary>
/// <param name="value">The value to convert</param>
/// <returns>An <see cref="OpenIddictParameter"/> instance.</returns>
public static implicit operator OpenIddictParameter(JToken value) => new OpenIddictParameter(value);
public static implicit operator OpenIddictParameter(JsonElement value) => new OpenIddictParameter(value);
/// <summary>
/// Converts a long integer to an <see cref="OpenIddictParameter"/> instance.
@ -425,78 +748,36 @@ namespace OpenIddict.Abstractions
public static implicit operator OpenIddictParameter(string[] value) => new OpenIddictParameter(value);
/// <summary>
/// Converts the parameter to the specified generic type.
/// Determines whether an OpenID Connect parameter is null or empty.
/// </summary>
/// <typeparam name="T">The type the parameter will be converted to.</typeparam>
/// <param name="parameter">The <see cref="OpenIddictParameter"/> instance.</param>
/// <returns>The converted parameter.</returns>
private static T Convert<T>(OpenIddictParameter? parameter)
/// <param name="parameter">The OpenID Connect parameter.</param>
/// <returns><c>true</c> if the parameter is null or empty, <c>false</c> otherwise.</returns>
public static bool IsNullOrEmpty(OpenIddictParameter parameter)
{
try
return parameter.Value switch
{
return parameter?.Value switch
{
null => default,
null => true,
T value => value,
string value => string.IsNullOrEmpty(value),
string[] value => value.Length == 0,
string value when typeof(T) == typeof(string[]) => (T) (object) new string[] { value },
JsonElement value when value.ValueKind == JsonValueKind.Undefined => true,
JsonElement value when value.ValueKind == JsonValueKind.Null => true,
// Note: when the parameter is represented as a string, try to
// deserialize it if the requested type is a JArray or a JObject.
string value when typeof(T) == typeof(JArray) => (T) (object) JArray.Parse(value),
JsonElement value when value.ValueKind == JsonValueKind.String
=> string.IsNullOrEmpty(value.GetString()),
string value when typeof(T) == typeof(JObject) => (T) (object) JObject.Parse(value),
JsonElement value when value.ValueKind == JsonValueKind.Array => value.GetArrayLength() == 0,
JsonElement value when value.ValueKind == JsonValueKind.Object => IsEmptyNode(value),
string[] array => new JArray(array).ToObject<T>(),
JValue value when typeof(T) == typeof(string[]) => (T) (object) new string[]
{
value.ToObject<string>()
},
_ => false
};
JToken token => token.ToObject<T>(),
var value when typeof(T) == typeof(string[]) => (T) (object) new string[]
{
new JValue(value).ToObject<string>()
},
_ => new JValue(parameter?.Value).ToObject<T>()
};
}
// Swallow the conversion exceptions thrown by JSON.NET.
catch (Exception exception) when (exception is ArgumentException ||
exception is FormatException ||
exception is InvalidCastException ||
exception is JsonReaderException ||
exception is JsonSerializationException)
static bool IsEmptyNode(JsonElement value)
{
return default;
using var enumerator = value.EnumerateObject();
return !enumerator.MoveNext();
}
// Other exceptions will be automatically re-thrown.
}
/// <summary>
/// Determines whether an OpenID Connect parameter is null or empty.
/// </summary>
/// <param name="parameter">The OpenID Connect parameter.</param>
/// <returns><c>true</c> if the parameter is null or empty, <c>false</c> otherwise.</returns>
public static bool IsNullOrEmpty(OpenIddictParameter parameter) => parameter.Value switch
{
null => true,
string value => string.IsNullOrEmpty(value),
string[] array => array.Length == 0,
JValue value when value.Value is string text => string.IsNullOrEmpty(text),
JArray array => !array.HasValues,
JToken token => !token.HasValues,
_ => false
};
}
}

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

@ -6,10 +6,10 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace OpenIddict.Abstractions
{
@ -35,7 +35,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
public OpenIddictRequest([NotNull] IEnumerable<KeyValuePair<string, JToken>> parameters)
public OpenIddictRequest([NotNull] IEnumerable<KeyValuePair<string, JsonElement>> parameters)
: base(parameters) { }
/// <summary>
@ -105,9 +105,9 @@ namespace OpenIddict.Abstractions
/// <summary>
/// Gets or sets the "claims" parameter.
/// </summary>
public JObject Claims
public JsonElement Claims
{
get => (JObject) GetParameter(OpenIddictConstants.Parameters.Claims);
get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Claims);
set => SetParameter(OpenIddictConstants.Parameters.Claims, value);
}
@ -402,9 +402,9 @@ namespace OpenIddict.Abstractions
/// <summary>
/// Gets or sets the "registration" parameter.
/// </summary>
public JObject Registration
public JsonElement Registration
{
get => (JObject) GetParameter(OpenIddictConstants.Parameters.Registration);
get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Registration);
set => SetParameter(OpenIddictConstants.Parameters.Registration, value);
}

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

@ -6,10 +6,10 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace OpenIddict.Abstractions
{
@ -35,7 +35,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
public OpenIddictResponse([NotNull] IEnumerable<KeyValuePair<string, JToken>> parameters)
public OpenIddictResponse([NotNull] IEnumerable<KeyValuePair<string, JsonElement>> parameters)
: base(parameters) { }
/// <summary>

7
src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs

@ -11,7 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace OpenIddict.Abstractions
{
@ -212,7 +212,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken);
ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the callback addresses associated with an application.
@ -341,7 +341,8 @@ namespace OpenIddict.Abstractions
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken);
ValueTask SetPropertiesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken);
/// <summary>
/// Sets the callback addresses associated with an application.

7
src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs

@ -11,7 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace OpenIddict.Abstractions
{
@ -183,7 +183,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the scopes associated with an authorization.
@ -285,7 +285,8 @@ namespace OpenIddict.Abstractions
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken);
ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken);
/// <summary>
/// Sets the scopes associated with an authorization.

7
src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs

@ -11,7 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace OpenIddict.Abstractions
{
@ -166,7 +166,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose
/// result returns all the additional properties associated with the scope.
/// </returns>
ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken);
ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the resources associated with a scope.
@ -245,7 +245,8 @@ namespace OpenIddict.Abstractions
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken);
ValueTask SetPropertiesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken);
/// <summary>
/// Sets the resources associated with a scope.

7
src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs

@ -11,7 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace OpenIddict.Abstractions
{
@ -232,7 +232,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken);
ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the reference identifier associated with a token.
@ -371,7 +371,8 @@ namespace OpenIddict.Abstractions
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken);
ValueTask SetPropertiesAsync([NotNull] TToken token,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken);
/// <summary>
/// Sets the reference identifier associated with a token.

63
src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs

@ -13,13 +13,13 @@ using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
@ -487,9 +487,7 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(permission => (string) permission)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.Permissions);
});
return new ValueTask<ImmutableArray<string>>(permissions);
@ -524,9 +522,7 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(address => (string) address)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.PostLogoutRedirectUris);
});
return new ValueTask<ImmutableArray<string>>(addresses);
@ -541,7 +537,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -550,7 +546,7 @@ namespace OpenIddict.EntityFramework
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -561,10 +557,10 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(application.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(application.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -596,9 +592,7 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(address => (string) address)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.RedirectUris);
});
return new ValueTask<ImmutableArray<string>>(addresses);
@ -633,9 +627,7 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Requirements)
.Select(element => (string) element)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.Requirements);
});
return new ValueTask<ImmutableArray<string>>(requirements);
@ -836,7 +828,11 @@ namespace OpenIddict.EntityFramework
return default;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
application.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -863,7 +859,11 @@ namespace OpenIddict.EntityFramework
return default;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
application.PostLogoutRedirectUris = JsonSerializer.Serialize(addresses, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -875,21 +875,26 @@ namespace OpenIddict.EntityFramework
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
application.Properties = null;
return default;
}
application.Properties = properties.ToString(Formatting.None);
application.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -916,7 +921,11 @@ namespace OpenIddict.EntityFramework
return default;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
application.RedirectUris = JsonSerializer.Serialize(addresses, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -942,7 +951,11 @@ namespace OpenIddict.EntityFramework
return default;
}
application.Requirements = new JArray(requirements.ToArray()).ToString(Formatting.None);
application.Requirements = JsonSerializer.Serialize(requirements, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

33
src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs

@ -13,13 +13,13 @@ using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
@ -488,7 +488,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -497,7 +497,7 @@ namespace OpenIddict.EntityFramework
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -508,10 +508,10 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(authorization.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(authorization.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -543,9 +543,7 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(authorization.Scopes)
.Select(scope => (string) scope)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(authorization.Scopes);
});
return new ValueTask<ImmutableArray<string>>(scopes);
@ -820,21 +818,26 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
authorization.Properties = null;
return default;
}
authorization.Properties = properties.ToString(Formatting.None);
authorization.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -863,7 +866,11 @@ namespace OpenIddict.EntityFramework
return default;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
authorization.Scopes = JsonSerializer.Serialize(scopes, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

33
src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs

@ -12,13 +12,13 @@ using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
@ -357,7 +357,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -366,7 +366,7 @@ namespace OpenIddict.EntityFramework
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -377,10 +377,10 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(scope.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(scope.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -412,9 +412,7 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(resource => (string) resource)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(scope.Resources);
});
return new ValueTask<ImmutableArray<string>>(resources);
@ -556,21 +554,26 @@ namespace OpenIddict.EntityFramework
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
scope.Properties = null;
return default;
}
scope.Properties = properties.ToString(Formatting.None);
scope.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -596,7 +599,11 @@ namespace OpenIddict.EntityFramework
return default;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
scope.Resources = JsonSerializer.Serialize(resources, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

23
src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

@ -13,13 +13,13 @@ using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
@ -575,7 +575,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -584,7 +584,7 @@ namespace OpenIddict.EntityFramework
if (string.IsNullOrEmpty(token.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -595,10 +595,10 @@ namespace OpenIddict.EntityFramework
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(token.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(token.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -987,21 +987,26 @@ namespace OpenIddict.EntityFramework
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
token.Properties = null;
return default;
}
token.Properties = properties.ToString(Formatting.None);
token.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

63
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -11,6 +11,8 @@ using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -19,8 +21,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
@ -534,9 +534,7 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(permission => (string) permission)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.Permissions);
});
return new ValueTask<ImmutableArray<string>>(permissions);
@ -571,9 +569,7 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(address => (string) address)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.PostLogoutRedirectUris);
});
return new ValueTask<ImmutableArray<string>>(addresses);
@ -588,7 +584,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -597,7 +593,7 @@ namespace OpenIddict.EntityFrameworkCore
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -608,10 +604,10 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(application.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(application.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -643,9 +639,7 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(address => (string) address)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.RedirectUris);
});
return new ValueTask<ImmutableArray<string>>(addresses);
@ -680,9 +674,7 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Requirements)
.Select(element => (string) element)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.Requirements);
});
return new ValueTask<ImmutableArray<string>>(requirements);
@ -883,7 +875,11 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
application.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -910,7 +906,11 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
application.PostLogoutRedirectUris = JsonSerializer.Serialize(addresses, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -922,21 +922,26 @@ namespace OpenIddict.EntityFrameworkCore
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
application.Properties = null;
return default;
}
application.Properties = properties.ToString(Formatting.None);
application.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -963,7 +968,11 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
application.RedirectUris = JsonSerializer.Serialize(addresses, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -989,7 +998,11 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
application.Requirements = new JArray(requirements.ToArray()).ToString(Formatting.None);
application.Requirements = JsonSerializer.Serialize(requirements, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

33
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs

@ -11,6 +11,8 @@ using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -19,8 +21,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
@ -542,7 +542,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -551,7 +551,7 @@ namespace OpenIddict.EntityFrameworkCore
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -562,10 +562,10 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(authorization.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(authorization.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -597,9 +597,7 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(authorization.Scopes)
.Select(scope => (string) scope)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(authorization.Scopes);
});
return new ValueTask<ImmutableArray<string>>(scopes);
@ -888,21 +886,26 @@ namespace OpenIddict.EntityFrameworkCore
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
authorization.Properties = null;
return default;
}
authorization.Properties = properties.ToString(Formatting.None);
authorization.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -929,7 +932,11 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
authorization.Scopes = JsonSerializer.Serialize(scopes, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

33
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs

@ -10,14 +10,14 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
@ -372,7 +372,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -381,7 +381,7 @@ namespace OpenIddict.EntityFrameworkCore
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -392,10 +392,10 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(scope.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(scope.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -427,9 +427,7 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(resource => (string) resource)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(scope.Resources);
});
return new ValueTask<ImmutableArray<string>>(resources);
@ -571,21 +569,26 @@ namespace OpenIddict.EntityFrameworkCore
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
scope.Properties = null;
return default;
}
scope.Properties = properties.ToString(Formatting.None);
scope.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -611,7 +614,11 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
scope.Resources = JsonSerializer.Serialize(resources, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

24
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs

@ -6,10 +6,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -18,8 +21,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
@ -621,7 +622,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -630,7 +631,7 @@ namespace OpenIddict.EntityFrameworkCore
if (string.IsNullOrEmpty(token.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
// Note: parsing the stringified properties is an expensive operation.
@ -641,10 +642,10 @@ namespace OpenIddict.EntityFrameworkCore
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JObject.Parse(token.Properties);
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(token.Properties);
});
return new ValueTask<JObject>((JObject) properties.DeepClone());
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -1068,21 +1069,26 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
token.Properties = null;
return default;
}
token.Properties = properties.ToString(Formatting.None);
token.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

15
src/OpenIddict.MongoDb/Stores/OpenIddictApplicationStore.cs

@ -17,8 +17,6 @@ using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.MongoDb.Models;
@ -448,7 +446,7 @@ namespace OpenIddict.MongoDb
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -457,10 +455,10 @@ namespace OpenIddict.MongoDb
if (application.Properties == null)
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.FromObject(application.Properties.ToDictionary()));
return new ValueTask<ImmutableDictionary<string, object>>(application.Properties.ToDictionary().ToImmutableDictionary());
}
/// <summary>
@ -763,21 +761,22 @@ namespace OpenIddict.MongoDb
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
application.Properties = null;
return default;
}
application.Properties = BsonDocument.Parse(properties.ToString(Formatting.None));
application.Properties = new BsonDocument(properties.ToDictionary(property => property.Key, property => property.Value));
return default;
}

15
src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs

@ -17,8 +17,6 @@ using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.MongoDb.Models;
@ -482,7 +480,7 @@ namespace OpenIddict.MongoDb
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -491,10 +489,10 @@ namespace OpenIddict.MongoDb
if (authorization.Properties == null)
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.FromObject(authorization.Properties.ToDictionary()));
return new ValueTask<ImmutableDictionary<string, object>>(authorization.Properties.ToDictionary().ToImmutableDictionary());
}
/// <summary>
@ -766,21 +764,22 @@ namespace OpenIddict.MongoDb
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
authorization.Properties = null;
return default;
}
authorization.Properties = BsonDocument.Parse(properties.ToString(Formatting.None));
authorization.Properties = new BsonDocument(properties.ToDictionary(property => property.Key, property => property.Value));
return default;
}

17
src/OpenIddict.MongoDb/Stores/OpenIddictScopeStore.cs

@ -10,6 +10,8 @@ using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -17,8 +19,6 @@ using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.MongoDb.Models;
@ -349,7 +349,7 @@ namespace OpenIddict.MongoDb
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -358,10 +358,10 @@ namespace OpenIddict.MongoDb
if (scope.Properties == null)
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.FromObject(scope.Properties.ToDictionary()));
return new ValueTask<ImmutableDictionary<string, object>>(scope.Properties.ToDictionary().ToImmutableDictionary());
}
/// <summary>
@ -541,21 +541,22 @@ namespace OpenIddict.MongoDb
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
scope.Properties = null;
return default;
}
scope.Properties = BsonDocument.Parse(properties.ToString(Formatting.None));
scope.Properties = new BsonDocument(properties.ToDictionary(property => property.Key, property => property.Value));
return default;
}

15
src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs

@ -17,8 +17,6 @@ using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.MongoDb.Models;
@ -547,7 +545,7 @@ namespace OpenIddict.MongoDb
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -556,10 +554,10 @@ namespace OpenIddict.MongoDb
if (token.Properties == null)
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.FromObject(token.Properties.ToDictionary()));
return new ValueTask<ImmutableDictionary<string, object>>(token.Properties.ToDictionary().ToImmutableDictionary());
}
/// <summary>
@ -865,21 +863,22 @@ namespace OpenIddict.MongoDb
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
token.Properties = null;
return default;
}
token.Properties = BsonDocument.Parse(properties.ToString(Formatting.None));
token.Properties = new BsonDocument(properties.ToDictionary(property => property.Key, property => property.Value));
return default;
}

72
src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs

@ -11,13 +11,13 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
@ -474,9 +474,7 @@ namespace OpenIddict.NHibernate
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(permission => (string) permission)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.Permissions);
});
return new ValueTask<ImmutableArray<string>>(permissions);
@ -511,9 +509,7 @@ namespace OpenIddict.NHibernate
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(address => (string) address)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.PostLogoutRedirectUris);
});
return new ValueTask<ImmutableArray<string>>(addresses);
@ -528,7 +524,7 @@ namespace OpenIddict.NHibernate
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -537,10 +533,21 @@ namespace OpenIddict.NHibernate
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.Parse(application.Properties));
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("2e3e9680-5654-48d8-a27d-b8bb4f0f1d50", "\x1e", application.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(application.Properties);
});
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -572,9 +579,7 @@ namespace OpenIddict.NHibernate
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(address => (string) address)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.RedirectUris);
});
return new ValueTask<ImmutableArray<string>>(addresses);
@ -609,9 +614,7 @@ namespace OpenIddict.NHibernate
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Requirements)
.Select(element => (string) element)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(application.Requirements);
});
return new ValueTask<ImmutableArray<string>>(requirements);
@ -828,7 +831,11 @@ namespace OpenIddict.NHibernate
return default;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
application.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -855,7 +862,11 @@ namespace OpenIddict.NHibernate
return default;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
application.PostLogoutRedirectUris = JsonSerializer.Serialize(addresses, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -867,21 +878,26 @@ namespace OpenIddict.NHibernate
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
application.Properties = null;
return default;
}
application.Properties = properties.ToString(Formatting.None);
application.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -908,7 +924,11 @@ namespace OpenIddict.NHibernate
return default;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
application.RedirectUris = JsonSerializer.Serialize(addresses, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -934,7 +954,11 @@ namespace OpenIddict.NHibernate
return default;
}
application.Requirements = new JArray(requirements.ToArray()).ToString(Formatting.None);
application.Requirements = JsonSerializer.Serialize(requirements, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

42
src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs

@ -11,13 +11,13 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
@ -506,7 +506,7 @@ namespace OpenIddict.NHibernate
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -515,10 +515,21 @@ namespace OpenIddict.NHibernate
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.Parse(authorization.Properties));
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("68056e1a-dbcf-412b-9a6a-d791c7dbe726", "\x1e", authorization.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(authorization.Properties);
});
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -550,9 +561,7 @@ namespace OpenIddict.NHibernate
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(authorization.Scopes)
.Select(scope => (string) scope)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(authorization.Scopes);
});
return new ValueTask<ImmutableArray<string>>(scopes);
@ -763,21 +772,26 @@ namespace OpenIddict.NHibernate
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
authorization.Properties = null;
return default;
}
authorization.Properties = properties.ToString(Formatting.None);
authorization.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -804,7 +818,11 @@ namespace OpenIddict.NHibernate
return default;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
authorization.Scopes = JsonSerializer.Serialize(scopes, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

42
src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs

@ -11,13 +11,13 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
@ -390,7 +390,7 @@ namespace OpenIddict.NHibernate
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -399,10 +399,21 @@ namespace OpenIddict.NHibernate
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.Parse(scope.Properties));
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("78d8dfdd-3870-442e-b62e-dc9bf6eaeff7", "\x1e", scope.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(scope.Properties);
});
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -434,9 +445,7 @@ namespace OpenIddict.NHibernate
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(resource => (string) resource)
.ToImmutableArray();
return JsonSerializer.Deserialize<ImmutableArray<string>>(scope.Resources);
});
return new ValueTask<ImmutableArray<string>>(resources);
@ -594,21 +603,26 @@ namespace OpenIddict.NHibernate
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
scope.Properties = null;
return default;
}
scope.Properties = properties.ToString(Formatting.None);
scope.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}
@ -634,7 +648,11 @@ namespace OpenIddict.NHibernate
return default;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
scope.Resources = JsonSerializer.Serialize(resources, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

32
src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs

@ -11,13 +11,13 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
@ -626,7 +626,7 @@ namespace OpenIddict.NHibernate
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual ValueTask<ImmutableDictionary<string, object>> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -635,10 +635,21 @@ namespace OpenIddict.NHibernate
if (string.IsNullOrEmpty(token.Properties))
{
return new ValueTask<JObject>(new JObject());
return new ValueTask<ImmutableDictionary<string, object>>(ImmutableDictionary.Create<string, object>());
}
return new ValueTask<JObject>(JObject.Parse(token.Properties));
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("d0509397-1bbf-40e7-97e1-5e6d7bc2536c", "\x1e", token.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JsonSerializer.Deserialize<ImmutableDictionary<string, object>>(token.Properties);
});
return new ValueTask<ImmutableDictionary<string, object>>(properties);
}
/// <summary>
@ -951,21 +962,26 @@ namespace OpenIddict.NHibernate
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
public virtual ValueTask SetPropertiesAsync([NotNull] TToken token,
[CanBeNull] ImmutableDictionary<string, object> properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
if (properties == null || properties.IsEmpty)
{
token.Properties = null;
return default;
}
token.Properties = properties.ToString(Formatting.None);
token.Properties = JsonSerializer.Serialize(properties, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
return default;
}

1
src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj

@ -25,7 +25,6 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">

6
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs

@ -17,6 +17,12 @@ namespace OpenIddict.Server.AspNetCore
public const string LogoutRequest = "openiddict-logout-request:";
}
public static class JsonWebTokenTypes
{
public const string AuthorizationRequest = "oi_auth_req";
public const string LogoutRequest = "oi_lgt_req";
}
public static class Properties
{
public const string Error = ".error";

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

@ -5,12 +5,15 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore;
@ -18,14 +21,13 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters;
using static OpenIddict.Server.OpenIddictServerEvents;
using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes;
namespace OpenIddict.Server.AspNetCore
{
@ -110,8 +112,8 @@ namespace OpenIddict.Server.AspNetCore
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
var payload = await _cache.GetAsync(Cache.AuthorizationRequest + context.Request.RequestId);
if (payload == null)
var token = await _cache.GetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId);
if (token == null || !context.Options.JsonWebTokenHandler.CanReadToken(token))
{
context.Logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
@ -124,16 +126,45 @@ namespace OpenIddict.Server.AspNetCore
}
// Restore the authorization request parameters from the serialized payload.
using var reader = new BsonDataReader(new MemoryStream(payload));
foreach (var parameter in JObject.Load(reader))
var parameters = new TokenValidationParameters
{
IssuerSigningKeys = context.Options.SigningCredentials.Select(credentials => credentials.Key),
TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
ValidateLifetime = false,
ValidAudience = context.Issuer.AbsoluteUri,
ValidIssuer = context.Issuer.AbsoluteUri,
ValidTypes = new[] { JsonWebTokenTypes.AuthorizationRequest }
};
var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
if (!result.IsValid)
{
context.Logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: Errors.InvalidRequest,
description: "The specified 'request_id' parameter is invalid.");
return;
}
using var document = JsonDocument.Parse(
Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload));
if (document.RootElement.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException("The authorization request payload is malformed.");
}
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Key))
if (context.Request.HasParameter(parameter.Name))
{
continue;
}
context.Request.SetParameter(parameter.Key, parameter.Value);
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -216,19 +247,32 @@ namespace OpenIddict.Server.AspNetCore
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Store the serialized authorization request parameters in the distributed cache.
var stream = new MemoryStream();
using (var writer = new BsonDataWriter(stream))
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(new SecurityTokenDescriptor
{
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Request);
}
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AuthorizationRequest
},
Audience = context.Issuer.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
Issuer = context.Issuer.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity()
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: new Dictionary<string, object>
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AuthorizationRequest
});
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
await _cache.SetAsync(Cache.AuthorizationRequest + context.Request.RequestId,
stream.ToArray(), _options.CurrentValue.AuthorizationEndpointCachingPolicy);
await _cache.SetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId,
token, _options.CurrentValue.AuthorizationEndpointCachingPolicy);
// Create a new GET authorization request containing only the request_id parameter.
var address = QueryHelpers.AddQueryString(

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

@ -5,11 +5,13 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore;
@ -17,14 +19,13 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters;
using static OpenIddict.Server.OpenIddictServerEvents;
using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes;
namespace OpenIddict.Server.AspNetCore
{
@ -109,8 +110,8 @@ namespace OpenIddict.Server.AspNetCore
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
var payload = await _cache.GetAsync(Cache.LogoutRequest + context.Request.RequestId);
if (payload == null)
var token = await _cache.GetStringAsync(Cache.LogoutRequest + context.Request.RequestId);
if (token == null || !context.Options.JsonWebTokenHandler.CanReadToken(token))
{
context.Logger.LogError("The logout request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
@ -122,17 +123,46 @@ namespace OpenIddict.Server.AspNetCore
return;
}
// Restore the logout request parameters from the serialized payload.
using var reader = new BsonDataReader(new MemoryStream(payload));
foreach (var parameter in JObject.Load(reader))
// Restore the authorization request parameters from the serialized payload.
var parameters = new TokenValidationParameters
{
IssuerSigningKeys = context.Options.SigningCredentials.Select(credentials => credentials.Key),
TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
ValidateLifetime = false,
ValidAudience = context.Issuer.AbsoluteUri,
ValidIssuer = context.Issuer.AbsoluteUri,
ValidTypes = new[] { JsonWebTokenTypes.LogoutRequest }
};
var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
if (!result.IsValid)
{
context.Logger.LogError("The logout request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: Errors.InvalidRequest,
description: "The specified 'request_id' parameter is invalid.");
return;
}
using var document = JsonDocument.Parse(
Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload));
if (document.RootElement.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException("The logout request payload is malformed.");
}
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Key))
if (context.Request.HasParameter(parameter.Name))
{
continue;
}
context.Request.SetParameter(parameter.Key, parameter.Value);
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -215,19 +245,32 @@ namespace OpenIddict.Server.AspNetCore
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Store the serialized logout request parameters in the distributed cache.
var stream = new MemoryStream();
using (var writer = new BsonDataWriter(stream))
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(new SecurityTokenDescriptor
{
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Request);
}
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.LogoutRequest
},
Audience = context.Issuer.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
Issuer = context.Issuer.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity()
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: new Dictionary<string, object>
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AuthorizationRequest
});
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
await _cache.SetAsync(Cache.LogoutRequest + context.Request.RequestId,
stream.ToArray(), _options.CurrentValue.LogoutEndpointCachingPolicy);
await _cache.SetStringAsync(Cache.LogoutRequest + context.Request.RequestId,
token, _options.CurrentValue.AuthorizationEndpointCachingPolicy);
// Create a new GET logout request containing only the request_id parameter.
var address = QueryHelpers.AddQueryString(

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

@ -10,6 +10,8 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore;
@ -18,7 +20,6 @@ using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters;
@ -834,61 +835,59 @@ namespace OpenIddict.Server.AspNetCore
context.Logger.LogInformation("The response was successfully returned as a JSON document: {Response}.", context.Response);
using (var buffer = new MemoryStream())
using (var writer = new JsonTextWriter(new StreamWriter(buffer)))
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, context.Response, new JsonSerializerOptions
{
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Response);
writer.Flush();
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
if (!string.IsNullOrEmpty(context.Response.Error))
if (!string.IsNullOrEmpty(context.Response.Error))
{
// When client authentication is made using basic authentication, the authorization server MUST return
// a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm.
// A similar error MAY be returned even when basic authentication is not used and MUST also be returned
// when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme.
// To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors
// and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials
// were specified in the request form instead of the HTTP headers, as allowed by the specification.
var scheme = context.Response.Error switch
{
// When client authentication is made using basic authentication, the authorization server MUST return
// a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm.
// A similar error MAY be returned even when basic authentication is not used and MUST also be returned
// when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme.
// To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors
// and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials
// were specified in the request form instead of the HTTP headers, as allowed by the specification.
var scheme = context.Response.Error switch
{
Errors.InvalidClient => Schemes.Basic,
Errors.InvalidToken => Schemes.Bearer,
_ => null
};
Errors.InvalidClient => Schemes.Basic,
Errors.InvalidToken => Schemes.Bearer,
_ => null
};
if (!string.IsNullOrEmpty(scheme))
if (!string.IsNullOrEmpty(scheme))
{
if (context.Issuer == null)
{
if (context.Issuer == null)
{
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
request.HttpContext.Response.StatusCode = 401;
request.HttpContext.Response.StatusCode = 401;
request.HttpContext.Response.Headers[HeaderNames.WWWAuthenticate] = new StringBuilder()
.Append(scheme)
.Append(' ')
.Append(Parameters.Realm)
.Append("=\"")
.Append(context.Issuer.AbsoluteUri)
.Append('"')
.ToString();
}
request.HttpContext.Response.Headers[HeaderNames.WWWAuthenticate] = new StringBuilder()
.Append(scheme)
.Append(' ')
.Append(Parameters.Realm)
.Append("=\"")
.Append(context.Issuer.AbsoluteUri)
.Append('"')
.ToString();
}
else
{
request.HttpContext.Response.StatusCode = 400;
}
else
{
request.HttpContext.Response.StatusCode = 400;
}
}
request.HttpContext.Response.ContentLength = buffer.Length;
request.HttpContext.Response.ContentType = "application/json;charset=UTF-8";
request.HttpContext.Response.ContentLength = stream.Length;
request.HttpContext.Response.ContentType = "application/json;charset=UTF-8";
buffer.Seek(offset: 0, loc: SeekOrigin.Begin);
await buffer.CopyToAsync(request.HttpContext.Response.Body, 4096, request.HttpContext.RequestAborted);
}
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
await stream.CopyToAsync(request.HttpContext.Response.Body, 4096, request.HttpContext.RequestAborted);
context.HandleRequest();
}

17
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs

@ -11,9 +11,9 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using Properties = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Properties;
@ -175,7 +175,7 @@ namespace OpenIddict.Server.DataProtection
static ImmutableArray<string> GetArrayProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ?
JArray.Parse(value).Values<string>().ToImmutableArray() : ImmutableArray.Create<string>();
JsonSerializer.Deserialize<ImmutableArray<string>>(value) : ImmutableArray.Create<string>();
static DateTimeOffset? GetDateProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ? (DateTimeOffset?)
@ -362,17 +362,20 @@ namespace OpenIddict.Server.DataProtection
}
}
static void SetArrayProperty(IDictionary<string, string> properties, string name, IEnumerable<string> values)
static void SetArrayProperty(IDictionary<string, string> properties, string name, ImmutableArray<string> values)
{
var array = new JArray(values);
if (array.Count == 0)
if (values.IsDefaultOrEmpty)
{
properties.Remove(name);
}
else
{
properties[name] = array.ToString(Formatting.None);
properties[name] = JsonSerializer.Serialize(values, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
}
}
}

1
src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj

@ -18,7 +18,6 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="$(ExtensionsVersion)" />
<PackageReference Include="Microsoft.Extensions.WebEncoders" Version="$(ExtensionsVersion)" />
<PackageReference Include="Microsoft.Owin.Security" Version="$(OwinVersion)" />
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" />
</ItemGroup>
</Project>

6
src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs

@ -17,6 +17,12 @@ namespace OpenIddict.Server.Owin
public const string LogoutRequest = "openiddict-logout-request:";
}
public static class JsonWebTokenTypes
{
public const string AuthorizationRequest = "oi_auth_req";
public const string LogoutRequest = "oi_lgt_req";
}
public static class Properties
{
public const string Error = ".error";

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

@ -5,27 +5,29 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants;
using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters;
using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes;
namespace OpenIddict.Server.Owin
{
@ -109,8 +111,8 @@ namespace OpenIddict.Server.Owin
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
var payload = await _cache.GetAsync(Cache.AuthorizationRequest + context.Request.RequestId);
if (payload == null)
var token = await _cache.GetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId);
if (token == null || !context.Options.JsonWebTokenHandler.CanReadToken(token))
{
context.Logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
@ -123,16 +125,45 @@ namespace OpenIddict.Server.Owin
}
// Restore the authorization request parameters from the serialized payload.
using var reader = new BsonDataReader(new MemoryStream(payload));
foreach (var parameter in JObject.Load(reader))
var parameters = new TokenValidationParameters
{
IssuerSigningKeys = context.Options.SigningCredentials.Select(credentials => credentials.Key),
TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
ValidateLifetime = false,
ValidAudience = context.Issuer.AbsoluteUri,
ValidIssuer = context.Issuer.AbsoluteUri,
ValidTypes = new[] { JsonWebTokenTypes.AuthorizationRequest }
};
var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
if (!result.IsValid)
{
context.Logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: Errors.InvalidRequest,
description: "The specified 'request_id' parameter is invalid.");
return;
}
using var document = JsonDocument.Parse(
Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload));
if (document.RootElement.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException("The authorization request payload is malformed.");
}
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Key))
if (context.Request.HasParameter(parameter.Name))
{
continue;
}
context.Request.SetParameter(parameter.Key, parameter.Value);
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -210,19 +241,32 @@ namespace OpenIddict.Server.Owin
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Store the serialized authorization request parameters in the distributed cache.
var stream = new MemoryStream();
using (var writer = new BsonDataWriter(stream))
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(new SecurityTokenDescriptor
{
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Request);
}
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AuthorizationRequest
},
Audience = context.Issuer.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
Issuer = context.Issuer.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity()
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: new Dictionary<string, object>
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AuthorizationRequest
});
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
await _cache.SetAsync(Cache.AuthorizationRequest + context.Request.RequestId,
stream.ToArray(), _options.CurrentValue.AuthorizationEndpointCachingPolicy);
await _cache.SetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId,
token, _options.CurrentValue.AuthorizationEndpointCachingPolicy);
// Create a new GET authorization request containing only the request_id parameter.
var address = WebUtilities.AddQueryString(

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

@ -5,26 +5,27 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants;
using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters;
using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes;
namespace OpenIddict.Server.Owin
{
@ -108,8 +109,8 @@ namespace OpenIddict.Server.Owin
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
var payload = await _cache.GetAsync(Cache.LogoutRequest + context.Request.RequestId);
if (payload == null)
var token = await _cache.GetStringAsync(Cache.LogoutRequest + context.Request.RequestId);
if (token == null || !context.Options.JsonWebTokenHandler.CanReadToken(token))
{
context.Logger.LogError("The logout request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
@ -121,17 +122,46 @@ namespace OpenIddict.Server.Owin
return;
}
// Restore the logout request parameters from the serialized payload.
using var reader = new BsonDataReader(new MemoryStream(payload));
foreach (var parameter in JObject.Load(reader))
// Restore the authorization request parameters from the serialized payload.
var parameters = new TokenValidationParameters
{
IssuerSigningKeys = context.Options.SigningCredentials.Select(credentials => credentials.Key),
TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
ValidateLifetime = false,
ValidAudience = context.Issuer.AbsoluteUri,
ValidIssuer = context.Issuer.AbsoluteUri,
ValidTypes = new[] { JsonWebTokenTypes.LogoutRequest }
};
var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
if (!result.IsValid)
{
context.Logger.LogError("The logout request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: Errors.InvalidRequest,
description: "The specified 'request_id' parameter is invalid.");
return;
}
using var document = JsonDocument.Parse(
Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload));
if (document.RootElement.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException("The logout request payload is malformed.");
}
foreach (var parameter in document.RootElement.EnumerateObject())
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Key))
if (context.Request.HasParameter(parameter.Name))
{
continue;
}
context.Request.SetParameter(parameter.Key, parameter.Value);
context.Request.SetParameter(parameter.Name, parameter.Value.Clone());
}
}
}
@ -209,19 +239,32 @@ namespace OpenIddict.Server.Owin
context.Request.RequestId = Base64UrlEncoder.Encode(data);
// Store the serialized logout request parameters in the distributed cache.
var stream = new MemoryStream();
using (var writer = new BsonDataWriter(stream))
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(new SecurityTokenDescriptor
{
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Request);
}
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.LogoutRequest
},
Audience = context.Issuer.AbsoluteUri,
Claims = context.Request.GetParameters().ToDictionary(
parameter => parameter.Key,
parameter => parameter.Value.Value),
Issuer = context.Issuer.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = new ClaimsIdentity()
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: new Dictionary<string, object>
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.LogoutRequest
});
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached payloads.
await _cache.SetAsync(Cache.LogoutRequest + context.Request.RequestId,
stream.ToArray(), _options.CurrentValue.LogoutEndpointCachingPolicy);
await _cache.SetStringAsync(Cache.LogoutRequest + context.Request.RequestId,
token, _options.CurrentValue.AuthorizationEndpointCachingPolicy);
// Create a new GET logout request containing only the request_id parameter.
var address = WebUtilities.AddQueryString(

39
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -10,12 +10,13 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Newtonsoft.Json;
using OpenIddict.Abstractions;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
@ -837,29 +838,27 @@ namespace OpenIddict.Server.Owin
context.Logger.LogInformation("The response was successfully returned as a JSON document: {Response}.", context.Response);
using (var buffer = new MemoryStream())
using (var writer = new JsonTextWriter(new StreamWriter(buffer)))
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, context.Response, new JsonSerializerOptions
{
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Response);
writer.Flush();
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
if (!string.IsNullOrEmpty(context.Response.Error))
{
// Note: when using basic authentication, returning an invalid_client error MUST result in
// an unauthorized response but returning a 401 status code would invoke the previously
// registered authentication middleware and potentially replace it by a 302 response.
// To work around this OWIN/Katana limitation, a 400 response code is always returned.
response.StatusCode = 400;
}
if (!string.IsNullOrEmpty(context.Response.Error))
{
// Note: when using basic authentication, returning an invalid_client error MUST result in
// an unauthorized response but returning a 401 status code would invoke the previously
// registered authentication middleware and potentially replace it by a 302 response.
// To work around this OWIN/Katana limitation, a 400 response code is always returned.
response.StatusCode = 400;
}
response.ContentLength = buffer.Length;
response.ContentType = "application/json;charset=UTF-8";
response.ContentLength = stream.Length;
response.ContentType = "application/json;charset=UTF-8";
buffer.Seek(offset: 0, loc: SeekOrigin.Begin);
await buffer.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled);
}
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled);
context.HandleRequest();
}

4
src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs

@ -8,7 +8,7 @@ using System;
using System.Collections.Generic;
using System.Security.Claims;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using OpenIddict.Abstractions;
namespace OpenIddict.Server
@ -80,7 +80,7 @@ namespace OpenIddict.Server
/// Note: this value should only be populated if the "address"
/// scope was requested and accepted by the resource owner.
/// </summary>
public JObject Address { get; set; }
public JsonElement Address { get; set; }
/// <summary>
/// Gets or sets the values used for the "aud" claim.

1
src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs

@ -16,7 +16,6 @@ using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{

74
src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs

@ -5,18 +5,18 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
@ -1098,12 +1098,13 @@ namespace OpenIddict.Server
return;
}
var keys = new JArray();
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
writer.WriteStartArray();
foreach (var key in notification.Keys)
{
var item = new JObject();
// Ensure a key type has been provided.
// See https://tools.ietf.org/html/rfc7517#section-4.1
if (string.IsNullOrEmpty(key.Kty))
@ -1114,49 +1115,60 @@ namespace OpenIddict.Server
continue;
}
// Create a dictionary associating the
// JsonWebKey components with their values.
var parameters = new Dictionary<string, string>
{
[JsonWebKeyParameterNames.Kid] = key.Kid,
[JsonWebKeyParameterNames.Use] = key.Use,
[JsonWebKeyParameterNames.Kty] = key.Kty,
[JsonWebKeyParameterNames.Alg] = key.Alg,
[JsonWebKeyParameterNames.Crv] = key.Crv,
[JsonWebKeyParameterNames.E] = key.E,
[JsonWebKeyParameterNames.N] = key.N,
[JsonWebKeyParameterNames.X] = key.X,
[JsonWebKeyParameterNames.Y] = key.Y,
[JsonWebKeyParameterNames.X5t] = key.X5t,
[JsonWebKeyParameterNames.X5u] = key.X5u
};
writer.WriteStartObject();
foreach (var parameter in parameters)
if (!string.IsNullOrEmpty(key.Kid)) writer.WriteString(JsonWebKeyParameterNames.Kid, key.Kid);
if (!string.IsNullOrEmpty(key.Use)) writer.WriteString(JsonWebKeyParameterNames.Use, key.Use);
if (!string.IsNullOrEmpty(key.Kty)) writer.WriteString(JsonWebKeyParameterNames.Kty, key.Kty);
if (!string.IsNullOrEmpty(key.Alg)) writer.WriteString(JsonWebKeyParameterNames.Alg, key.Alg);
if (!string.IsNullOrEmpty(key.Crv)) writer.WriteString(JsonWebKeyParameterNames.Crv, key.Crv);
if (!string.IsNullOrEmpty(key.E)) writer.WriteString(JsonWebKeyParameterNames.E, key.E);
if (!string.IsNullOrEmpty(key.N)) writer.WriteString(JsonWebKeyParameterNames.N, key.N);
if (!string.IsNullOrEmpty(key.X)) writer.WriteString(JsonWebKeyParameterNames.X, key.X);
if (!string.IsNullOrEmpty(key.Y)) writer.WriteString(JsonWebKeyParameterNames.Y, key.Y);
if (!string.IsNullOrEmpty(key.X5t)) writer.WriteString(JsonWebKeyParameterNames.X5t, key.X5t);
if (!string.IsNullOrEmpty(key.X5u)) writer.WriteString(JsonWebKeyParameterNames.X5u, key.X5u);
if (key.KeyOps.Count != 0)
{
if (!string.IsNullOrEmpty(parameter.Value))
writer.WritePropertyName(JsonWebKeyParameterNames.KeyOps);
writer.WriteStartArray();
for (var index = 0; index < key.KeyOps.Count; index++)
{
item.Add(parameter.Key, parameter.Value);
writer.WriteStringValue(key.KeyOps[index]);
}
}
if (key.KeyOps.Count != 0)
{
item.Add(JsonWebKeyParameterNames.KeyOps, new JArray(key.KeyOps));
writer.WriteEndArray();
}
if (key.X5c.Count != 0)
{
item.Add(JsonWebKeyParameterNames.X5c, new JArray(key.X5c));
writer.WritePropertyName(JsonWebKeyParameterNames.X5c);
writer.WriteStartArray();
for (var index = 0; index < key.X5c.Count; index++)
{
writer.WriteStringValue(key.X5c[index]);
}
writer.WriteEndArray();
}
keys.Add(item);
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
using var document = JsonDocument.Parse(stream);
// Note: AddParameter() is used here to ensure the mandatory "keys" node
// is returned to the caller, even if the key set doesn't expose any key.
// See https://tools.ietf.org/html/rfc7517#section-5 for more information.
var response = new OpenIddictResponse();
response.AddParameter(Parameters.Keys, keys);
response.AddParameter(Parameters.Keys, document.RootElement.Clone());
context.Response = response;
}

17
src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs

@ -10,17 +10,17 @@ using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{
@ -306,7 +306,7 @@ namespace OpenIddict.Server
break;
default:
response[Claims.Audience] = new JArray(notification.Audiences);
response[Claims.Audience] = notification.Audiences.ToArray();
break;
}
@ -1120,7 +1120,12 @@ namespace OpenIddict.Server
// When multiple claims share the same type, retrieve the underlying
// JSON values and add everything to a new unique JSON array.
_ => new JArray(claims.Select(claim => ConvertToParameter(claim).Value))
_ => JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(
claims.Select(claim => ConvertToParameter(claim).Value), new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
}))
};
}
@ -1132,8 +1137,8 @@ namespace OpenIddict.Server
ClaimValueTypes.Integer32 => int.Parse(claim.Value, CultureInfo.InvariantCulture),
ClaimValueTypes.Integer64 => long.Parse(claim.Value, CultureInfo.InvariantCulture),
JsonClaimValueTypes.Json => JToken.Parse(claim.Value),
JsonClaimValueTypes.JsonArray => JToken.Parse(claim.Value),
JsonClaimValueTypes.Json => JsonSerializer.Deserialize<JsonElement>(claim.Value),
JsonClaimValueTypes.JsonArray => JsonSerializer.Deserialize<JsonElement>(claim.Value),
_ => new OpenIddictParameter(claim.Value)
};

2
src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

@ -6,7 +6,6 @@
using System;
using System.Collections.Immutable;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -15,7 +14,6 @@ using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{

2
src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs

@ -6,7 +6,6 @@
using System;
using System.Collections.Immutable;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -15,7 +14,6 @@ using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{

5
src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs

@ -7,16 +7,13 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{
@ -281,7 +278,7 @@ namespace OpenIddict.Server
break;
default:
response[Claims.Audience] = new JArray(notification.Audiences);
response[Claims.Audience] = notification.Audiences.ToArray();
break;
}

144
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -428,13 +428,41 @@ namespace OpenIddict.Server
// Attach the principal extracted from the token to the parent event context.
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
// Store the token type as a special private claim.
context.Principal.SetClaim(Claims.Private.TokenUsage, ((JsonWebToken) result.SecurityToken).Typ switch
{
JsonWebTokenTypes.AccessToken => TokenUsages.AccessToken,
JsonWebTokenTypes.IdentityToken => TokenUsages.IdToken,
JsonWebTokenTypes.Private.AuthorizationCode => TokenUsages.AuthorizationCode,
JsonWebTokenTypes.Private.DeviceCode => TokenUsages.DeviceCode,
JsonWebTokenTypes.Private.RefreshToken => TokenUsages.RefreshToken,
JsonWebTokenTypes.Private.UserCode => TokenUsages.UserCode,
_ => throw new InvalidOperationException("The token type is not supported.")
});
context.Logger.LogTrace("The token '{Token}' was successfully validated and the following claims " +
"could be extracted: {Claims}.", context.Token, context.Principal.Claims);
async ValueTask<TokenValidationResult> ValidateTokenAsync(string token, string type)
{
var parameters = context.Options.TokenValidationParameters.Clone();
parameters.PropertyBag = new Dictionary<string, object> { [Claims.Private.TokenUsage] = type };
parameters.ValidTypes = new[]
{
type switch
{
TokenUsages.AccessToken => JsonWebTokenTypes.AccessToken,
TokenUsages.IdToken => JsonWebTokenTypes.IdentityToken,
TokenUsages.AuthorizationCode => JsonWebTokenTypes.Private.AuthorizationCode,
TokenUsages.DeviceCode => JsonWebTokenTypes.Private.DeviceCode,
TokenUsages.RefreshToken => JsonWebTokenTypes.Private.RefreshToken,
TokenUsages.UserCode => JsonWebTokenTypes.Private.UserCode,
_ => throw new InvalidOperationException("The token type is not supported.")
}
};
parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
parameters.IssuerSigningKeys = type switch
@ -2685,18 +2713,32 @@ namespace OpenIddict.Server
return;
}
context.Response.AccessToken = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.AccessToken },
EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey),
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AccessToken
},
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.AccessTokenPrincipal.Identity
});
var credentials = context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey);
if (credentials != null)
{
token = context.Options.JsonWebTokenHandler.EncryptToken(
token, credentials, new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.AccessToken
});
}
context.Response.AccessToken = token;
context.Logger.LogTrace("The access token '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.AccessTokenPrincipal.GetClaim(Claims.JwtId),
@ -2840,22 +2882,32 @@ namespace OpenIddict.Server
return;
}
context.Response.Code = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.AuthorizationCode },
EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey),
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.AuthorizationCode
},
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.AuthorizationCodePrincipal.Identity
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.AuthorizationCode
});
context.Response.Code = token;
context.Logger.LogTrace("The authorization code '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.AuthorizationCodePrincipal.GetClaim(Claims.JwtId),
context.Response.Code, context.AuthorizationCodePrincipal.Claims);
context.AuthorizationCodePrincipal.GetClaim(Claims.JwtId), token,
context.AuthorizationCodePrincipal.Claims);
}
}
@ -2994,22 +3046,32 @@ namespace OpenIddict.Server
return;
}
context.Response.DeviceCode = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.DeviceCode },
EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey),
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.DeviceCode
},
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.DeviceCodePrincipal.Identity
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.DeviceCode
});
context.Response.DeviceCode = token;
context.Logger.LogTrace("The device code '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.DeviceCodePrincipal.GetClaim(Claims.JwtId),
context.Response.DeviceCode, context.DeviceCodePrincipal.Claims);
context.DeviceCodePrincipal.GetClaim(Claims.JwtId), token,
context.DeviceCodePrincipal.Claims);
}
}
@ -3249,21 +3311,32 @@ namespace OpenIddict.Server
return;
}
context.Response.RefreshToken = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.RefreshToken },
EncryptingCredentials = context.Options.EncryptionCredentials[0],
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.RefreshToken
},
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.RefreshTokenPrincipal.Identity
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.RefreshToken
});
context.Response.RefreshToken = token;
context.Logger.LogTrace("The refresh token '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.RefreshTokenPrincipal.GetClaim(Claims.JwtId),
context.Response.RefreshToken, context.RefreshTokenPrincipal.Claims);
context.RefreshTokenPrincipal.GetClaim(Claims.JwtId), token,
context.RefreshTokenPrincipal.Claims);
}
}
@ -3442,22 +3515,32 @@ namespace OpenIddict.Server
return;
}
context.Response.UserCode = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
var token = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.UserCode },
EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey),
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.UserCode
},
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) context.UserCodePrincipal.Identity
});
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
context.Options.EncryptionCredentials.First(),
new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.Private.UserCode
});
context.Response.UserCode = token;
context.Logger.LogTrace("The user code '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.UserCodePrincipal.GetClaim(Claims.JwtId),
context.Response.UserCode, context.UserCodePrincipal.Claims);
context.UserCodePrincipal.GetClaim(Claims.JwtId), token,
context.UserCodePrincipal.Claims);
}
}
@ -3743,7 +3826,10 @@ namespace OpenIddict.Server
context.Response.IdToken = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.IdToken },
AdditionalHeaderClaims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.IdentityToken
},
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.First(credentials =>
credentials.Key is AsymmetricSecurityKey),

29
src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs

@ -29,15 +29,7 @@ namespace OpenIddict.Server
throw new ArgumentException("The subject associated with a descriptor cannot be null.", nameof(descriptor));
}
if (descriptor.Claims == null)
{
throw new InvalidOperationException("The claims collection cannot be null or empty.");
}
if (!descriptor.Claims.TryGetValue(Claims.Private.TokenUsage, out var type) || string.IsNullOrEmpty((string) type))
{
throw new InvalidOperationException("The token usage cannot be null or empty.");
}
descriptor.Claims ??= new Dictionary<string, object>(StringComparer.Ordinal);
var destinations = new Dictionary<string, string[]>(StringComparer.Ordinal);
foreach (var group in descriptor.Subject.Claims.GroupBy(claim => claim.Type))
@ -81,14 +73,9 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(parameters));
}
if (parameters.PropertyBag == null)
{
throw new InvalidOperationException("The property bag cannot be null.");
}
if (!parameters.PropertyBag.TryGetValue(Claims.Private.TokenUsage, out var type) || string.IsNullOrEmpty((string) type))
if (parameters.ValidTypes == null || !parameters.ValidTypes.Any())
{
throw new InvalidOperationException("The token usage cannot be null or empty.");
throw new InvalidOperationException("The valid token types collection cannot be empty.");
}
if (!CanReadToken(token))
@ -114,16 +101,6 @@ namespace OpenIddict.Server
var assertion = ((JsonWebToken) result.SecurityToken)?.InnerToken ?? (JsonWebToken) result.SecurityToken;
if (!assertion.TryGetPayloadValue(Claims.Private.TokenUsage, out string usage) ||
!string.Equals(usage, (string) type, StringComparison.OrdinalIgnoreCase))
{
return new ValueTask<TokenValidationResult>(new TokenValidationResult
{
Exception = new SecurityTokenException("The token usage associated to the token does not match the expected type."),
IsValid = false
});
}
// Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object).
if (assertion.TryGetPayloadValue(Claims.Private.ClaimDestinations, out IDictionary<string, string[]> definitions))
{

1
src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj

@ -23,7 +23,6 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" />
</ItemGroup>
</Project>

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

@ -9,13 +9,14 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlerFilters;
@ -262,40 +263,38 @@ namespace OpenIddict.Validation.AspNetCore
context.Logger.LogInformation("The response was successfully returned as a JSON document: {Response}.", context.Response);
using (var buffer = new MemoryStream())
using (var writer = new JsonTextWriter(new StreamWriter(buffer)))
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, context.Response, new JsonSerializerOptions
{
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Response);
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
writer.Flush();
if (!string.IsNullOrEmpty(context.Response.Error))
if (!string.IsNullOrEmpty(context.Response.Error))
{
if (context.Issuer == null)
{
if (context.Issuer == null)
{
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
request.HttpContext.Response.StatusCode = 401;
request.HttpContext.Response.Headers[HeaderNames.WWWAuthenticate] = new StringBuilder()
.Append(Schemes.Bearer)
.Append(' ')
.Append(Parameters.Realm)
.Append("=\"")
.Append(context.Issuer.AbsoluteUri)
.Append('"')
.ToString();
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
request.HttpContext.Response.ContentLength = buffer.Length;
request.HttpContext.Response.ContentType = "application/json;charset=UTF-8";
request.HttpContext.Response.StatusCode = 401;
buffer.Seek(offset: 0, loc: SeekOrigin.Begin);
await buffer.CopyToAsync(request.HttpContext.Response.Body, 4096, request.HttpContext.RequestAborted);
request.HttpContext.Response.Headers[HeaderNames.WWWAuthenticate] = new StringBuilder()
.Append(Schemes.Bearer)
.Append(' ')
.Append(Parameters.Realm)
.Append("=\"")
.Append(context.Issuer.AbsoluteUri)
.Append('"')
.ToString();
}
request.HttpContext.Response.ContentLength = stream.Length;
request.HttpContext.Response.ContentType = "application/json;charset=UTF-8";
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
await stream.CopyToAsync(request.HttpContext.Response.Body, 4096, request.HttpContext.RequestAborted);
context.HandleRequest();
}
}

4
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs

@ -11,7 +11,7 @@ using System.Globalization;
using System.IO;
using System.Security.Claims;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using Properties = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Properties;
@ -173,7 +173,7 @@ namespace OpenIddict.Validation.DataProtection
static ImmutableArray<string> GetArrayProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ?
JArray.Parse(value).Values<string>().ToImmutableArray() : ImmutableArray.Create<string>();
JsonSerializer.Deserialize<ImmutableArray<string>>(value) : ImmutableArray.Create<string>();
static DateTimeOffset? GetDateProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ? (DateTimeOffset?)

1
src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj

@ -18,7 +18,6 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="$(ExtensionsVersion)" />
<PackageReference Include="Microsoft.Extensions.WebEncoders" Version="$(ExtensionsVersion)" />
<PackageReference Include="Microsoft.Owin.Security" Version="$(OwinVersion)" />
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" />
</ItemGroup>
</Project>

53
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs

@ -9,10 +9,11 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using OpenIddict.Abstractions;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
@ -261,40 +262,38 @@ namespace OpenIddict.Validation.Owin
context.Logger.LogInformation("The response was successfully returned as a JSON document: {Response}.", context.Response);
using (var buffer = new MemoryStream())
using (var writer = new JsonTextWriter(new StreamWriter(buffer)))
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, context.Response, new JsonSerializerOptions
{
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, context.Response);
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false
});
writer.Flush();
if (!string.IsNullOrEmpty(context.Response.Error))
if (!string.IsNullOrEmpty(context.Response.Error))
{
if (context.Issuer == null)
{
if (context.Issuer == null)
{
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
request.Context.Response.StatusCode = 401;
request.Context.Response.Headers["WWW-Authenticate"] = new StringBuilder()
.Append(Schemes.Bearer)
.Append(' ')
.Append(Parameters.Realm)
.Append("=\"")
.Append(context.Issuer.AbsoluteUri)
.Append('"')
.ToString();
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
request.Context.Response.ContentLength = buffer.Length;
request.Context.Response.ContentType = "application/json;charset=UTF-8";
request.Context.Response.StatusCode = 401;
buffer.Seek(offset: 0, loc: SeekOrigin.Begin);
await buffer.CopyToAsync(request.Context.Response.Body, 4096, request.CallCancelled);
request.Context.Response.Headers["WWW-Authenticate"] = new StringBuilder()
.Append(Schemes.Bearer)
.Append(' ')
.Append(Parameters.Realm)
.Append("=\"")
.Append(context.Issuer.AbsoluteUri)
.Append('"')
.ToString();
}
request.Context.Response.ContentLength = stream.Length;
request.Context.Response.ContentType = "application/json;charset=UTF-8";
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
await stream.CopyToAsync(request.Context.Response.Body, 4096, request.CallCancelled);
context.HandleRequest();
}
}

10
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs

@ -9,15 +9,14 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
@ -106,7 +105,7 @@ namespace OpenIddict.Validation.SystemNetHttp
};
}
async IAsyncEnumerable<SecurityKey> GetSigningKeysAsync(HttpClient client, Uri address)
static async IAsyncEnumerable<SecurityKey> GetSigningKeysAsync(HttpClient client, Uri address)
{
var response = await SendHttpRequestMessageAsync(client, address);
@ -194,10 +193,7 @@ namespace OpenIddict.Validation.SystemNetHttp
}
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new JsonTextReader(new StreamReader(stream));
var serializer = JsonSerializer.CreateDefault();
return serializer.Deserialize<OpenIddictResponse>(reader);
return await JsonSerializer.DeserializeAsync<OpenIddictResponse>(stream);
}
}
}

3
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -206,9 +206,9 @@ namespace OpenIddict.Validation
// Clone the token validation parameters before mutating them.
parameters = parameters.Clone();
parameters.PropertyBag = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.AccessToken };
parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key);
parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
parameters.ValidTypes = new[] { JsonWebTokenTypes.AccessToken };
// If the token cannot be validated, don't return an error to allow another handle to validate it.
var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(context.Token, parameters);
@ -221,6 +221,7 @@ namespace OpenIddict.Validation
// Attach the principal extracted from the token to the parent event context.
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
context.Principal.SetClaim(Claims.Private.TokenUsage, TokenUsages.AccessToken);
context.Logger.LogTrace("The self-contained JWT token '{Token}' was successfully validated and the following " +
"claims could be extracted: {Claims}.", context.Token, context.Principal.Claims);

20
src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs

@ -24,16 +24,6 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(parameters));
}
if (parameters.PropertyBag == null)
{
throw new InvalidOperationException("The property bag cannot be null.");
}
if (!parameters.PropertyBag.TryGetValue(Claims.Private.TokenUsage, out var type) || string.IsNullOrEmpty((string) type))
{
throw new InvalidOperationException("The token usage cannot be null or empty.");
}
if (!CanReadToken(token))
{
return new ValueTask<TokenValidationResult>(new TokenValidationResult
@ -57,16 +47,6 @@ namespace OpenIddict.Validation
var assertion = ((JsonWebToken) result.SecurityToken)?.InnerToken ?? (JsonWebToken) result.SecurityToken;
if (!assertion.TryGetPayloadValue(Claims.Private.TokenUsage, out string usage) ||
!string.Equals(usage, (string) type, StringComparison.OrdinalIgnoreCase))
{
return new ValueTask<TokenValidationResult>(new TokenValidationResult
{
Exception = new SecurityTokenException("The token usage associated to the token does not match the expected type."),
IsValid = false
});
}
// Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object).
if (assertion.TryGetPayloadValue(Claims.Private.ClaimDestinations, out IDictionary<string, string[]> definitions))
{

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

@ -5,11 +5,10 @@
*/
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Text.Json;
using Xunit;
namespace OpenIddict.Abstractions.Tests.Primitives
@ -51,7 +50,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
}
[Fact]
public void ReadJson_ThrowsAnExceptionForNullReader()
public void Read_ThrowsAnExceptionForNullType()
{
// Arrange
var converter = new OpenIddictConverter();
@ -59,38 +58,24 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() =>
{
converter.ReadJson(reader: null, type: null, value: null, serializer: null);
});
Assert.Equal("reader", exception.ParamName);
}
[Fact]
public void ReadJson_ThrowsAnExceptionForNullType()
{
// Arrange
var converter = new OpenIddictConverter();
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() =>
{
converter.ReadJson(reader: new JsonTextReader(TextReader.Null), type: null, value: null, serializer: null);
var reader = new Utf8JsonReader();
converter.Read(ref reader, type: null, options: null);
});
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void ReadJson_ThrowsAnExceptionForUnexpectedJsonToken()
public void Read_ThrowsAnExceptionForUnexpectedJsonToken()
{
// Arrange
var converter = new OpenIddictConverter();
using var reader = new JsonTextReader(new StringReader("[0,1,2,3]"));
// Act and assert
var exception = Assert.Throws<JsonSerializationException>(() =>
var exception = Assert.Throws<JsonException>(() =>
{
converter.ReadJson(reader: reader, type: typeof(OpenIddictRequest), value: null, serializer: null);
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("[0,1,2,3]"));
converter.Read(ref reader, type: typeof(OpenIddictRequest), options: null);
});
Assert.Equal("An error occurred while reading the JSON payload.", exception.Message);
@ -106,66 +91,50 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[InlineData(typeof(OpenIddictParameter?[]))]
[InlineData(typeof(object))]
[InlineData(typeof(long))]
public void ReadJson_ThrowsAnExceptionForUnsupportedType(Type type)
public void Read_ThrowsAnExceptionForUnsupportedType(Type type)
{
// Arrange
var converter = new OpenIddictConverter();
using var reader = new JsonTextReader(new StringReader(@"{""name"":""value""}"));
// Act and assert
var exception = Assert.Throws<ArgumentException>(() =>
{
converter.ReadJson(reader, type, value: null, serializer: null);
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""name"":""value""}"));
converter.Read(ref reader, type, options: null);
});
Assert.StartsWith("The specified type is not supported.", exception.Message);
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void ReadJson_PopulatesExistingInstance()
{
// Arrange
var message = new OpenIddictMessage();
var converter = new OpenIddictConverter();
var reader = new JsonTextReader(new StringReader(@"{""name"":""value""}"));
// Act
var result = converter.ReadJson(reader: reader, type: typeof(OpenIddictMessage), value: message, serializer: null);
// Assert
Assert.Same(message, result);
Assert.Equal("value", message.GetParameter("name"));
}
[Theory]
[InlineData(typeof(OpenIddictMessage))]
[InlineData(typeof(OpenIddictRequest))]
[InlineData(typeof(OpenIddictResponse))]
public void ReadJson_ReturnsRequestedType(Type type)
public void Read_ReturnsRequestedType(Type type)
{
// Arrange
var converter = new OpenIddictConverter();
var reader = new JsonTextReader(new StringReader(@"{""name"":""value""}"));
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""name"":""value""}"));
// Act
var result = (OpenIddictMessage)converter.ReadJson(reader, type, value: null, serializer: null);
var result = converter.Read(ref reader, type, options: null);
// Assert
Assert.IsType(type, result);
Assert.Equal("value", result.GetParameter("name"));
Assert.Equal("value", (string) result.GetParameter("name"));
}
[Fact]
public void ReadJson_PreservesNullParameters()
public void Read_PreservesNullParameters()
{
// Arrange
var converter = new OpenIddictConverter();
var reader = new JsonTextReader(
new StringReader(@"{""string"":null,""bool"":null,""long"":null,""array"":null,""object"":null}"));
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(
@"{""string"":null,""bool"":null,""long"":null,""array"":null,""object"":null}"));
// Act
var result = (OpenIddictMessage)converter.ReadJson(reader, typeof(OpenIddictMessage), value: null, serializer: null);
var result = converter.Read(ref reader, typeof(OpenIddictMessage), options: null);
// Assert
Assert.Equal(5, result.GetParameters().Count());
@ -174,35 +143,35 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.NotNull(result.GetParameter("long"));
Assert.NotNull(result.GetParameter("array"));
Assert.NotNull(result.GetParameter("object"));
Assert.Null((string)result.GetParameter("string"));
Assert.Null((bool?)result.GetParameter("bool"));
Assert.Null((long?)result.GetParameter("long"));
Assert.Null((JArray)result.GetParameter("array"));
Assert.Null((JObject)result.GetParameter("object"));
Assert.Null((string) result.GetParameter("string"));
Assert.Null((bool?) result.GetParameter("bool"));
Assert.Null((long?) result.GetParameter("long"));
Assert.Null((JsonElement?) result.GetParameter("array"));
Assert.Null((JsonElement?) result.GetParameter("object"));
}
[Fact]
public void ReadJson_PreservesEmptyParameters()
public void Read_PreservesEmptyParameters()
{
// Arrange
var converter = new OpenIddictConverter();
var reader = new JsonTextReader(new StringReader(@"{""string"":"""",""array"":[],""object"":{}}"));
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""string"":"""",""array"":[],""object"":{}}"));
// Act
var result = (OpenIddictMessage)converter.ReadJson(reader, typeof(OpenIddictMessage), value: null, serializer: null);
var result = converter.Read(ref reader, typeof(OpenIddictMessage), options: null);
// Assert
Assert.Equal(3, result.GetParameters().Count());
Assert.NotNull(result.GetParameter("string"));
Assert.NotNull(result.GetParameter("array"));
Assert.NotNull(result.GetParameter("object"));
Assert.Empty((string)result.GetParameter("string"));
Assert.Empty((JArray)result.GetParameter("array"));
Assert.Empty((JObject)result.GetParameter("object"));
Assert.Empty((string) result.GetParameter("string"));
Assert.NotNull((JsonElement?) result.GetParameter("array"));
Assert.NotNull((JsonElement?) result.GetParameter("object"));
}
[Fact]
public void WriteJson_ThrowsAnExceptionForNullWriter()
public void Write_ThrowsAnExceptionForNullWriter()
{
// Arrange
var converter = new OpenIddictConverter();
@ -210,14 +179,14 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() =>
{
converter.WriteJson(writer: null, value: null, serializer: null);
converter.Write(writer: null, value: null, options: null);
});
Assert.Equal("writer", exception.ParamName);
}
[Fact]
public void WriteJson_ThrowsAnExceptionForNullValue()
public void Write_ThrowsAnExceptionForNullValue()
{
// Arrange
var converter = new OpenIddictConverter();
@ -225,98 +194,99 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() =>
{
converter.WriteJson(writer: new JsonTextWriter(TextWriter.Null), value: null, serializer: null);
});
Assert.Equal("value", exception.ParamName);
}
[Fact]
public void WriteJson_ThrowsAnExceptionForInvalidValue()
{
// Arrange
var converter = new OpenIddictConverter();
// Act and assert
var exception = Assert.Throws<ArgumentException>(() =>
{
converter.WriteJson(writer: new JsonTextWriter(TextWriter.Null), value: new object(), serializer: null);
converter.Write(writer: new Utf8JsonWriter(Stream.Null), value: null, options: null);
});
Assert.StartsWith("The specified object is not supported.", exception.Message);
Assert.Equal("value", exception.ParamName);
}
[Fact]
public void WriteJson_WritesEmptyPayloadForEmptyMessages()
public void Write_WritesEmptyPayloadForEmptyMessages()
{
// Arrange
var message = new OpenIddictMessage();
var converter = new OpenIddictConverter();
var writer = new StringWriter(CultureInfo.InvariantCulture);
using var stream = new MemoryStream();
using var reader = new StreamReader(stream);
using var writer = new Utf8JsonWriter(stream);
// Act
converter.WriteJson(writer: new JsonTextWriter(writer), value: message, serializer: null);
converter.Write(writer, value: message, options: null);
Assert.Equal("{}", writer.ToString());
// Assert
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
Assert.Equal("{}", reader.ReadToEnd());
}
[Fact]
public void WriteJson_PreservesNullParameters()
public void Write_PreservesNullParameters()
{
// Arrange
var converter = new OpenIddictConverter();
var writer = new StringWriter(CultureInfo.InvariantCulture);
using var stream = new MemoryStream();
using var reader = new StreamReader(stream);
using var writer = new Utf8JsonWriter(stream);
var message = new OpenIddictMessage();
message.AddParameter("string", new OpenIddictParameter((string)null));
message.AddParameter("bool", new OpenIddictParameter((bool?)null));
message.AddParameter("long", new OpenIddictParameter((long?)null));
message.AddParameter("array", new OpenIddictParameter((JArray)null));
message.AddParameter("object", new OpenIddictParameter((JObject)null));
message.AddParameter("string", new OpenIddictParameter((string) null));
message.AddParameter("bool", new OpenIddictParameter((bool?) null));
message.AddParameter("long", new OpenIddictParameter((long?) null));
message.AddParameter("array", new OpenIddictParameter((JsonElement?) null));
message.AddParameter("object", new OpenIddictParameter((JsonElement?) null));
// Act
converter.WriteJson(writer: new JsonTextWriter(writer), value: message, serializer: null);
converter.Write(writer, value: message, options: null);
// Assert
Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""array"":null,""object"":null}", writer.ToString());
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""array"":null,""object"":null}", reader.ReadToEnd());
}
[Fact]
public void WriteJson_PreservesEmptyParameters()
public void Write_PreservesEmptyParameters()
{
// Arrange
var converter = new OpenIddictConverter();
var writer = new StringWriter(CultureInfo.InvariantCulture);
using var stream = new MemoryStream();
using var reader = new StreamReader(stream);
using var writer = new Utf8JsonWriter(stream);
var message = new OpenIddictMessage();
message.AddParameter("string", new OpenIddictParameter(string.Empty));
message.AddParameter("array", new OpenIddictParameter(new JArray()));
message.AddParameter("object", new OpenIddictParameter(new JObject()));
message.AddParameter("array", new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
message.AddParameter("object", new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("{}")));
// Act
converter.WriteJson(writer: new JsonTextWriter(writer), value: message, serializer: null);
converter.Write(writer, value: message, options: null);
// Assert
Assert.Equal(@"{""string"":"""",""array"":[],""object"":{}}", writer.ToString());
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
Assert.Equal(@"{""string"":"""",""array"":[],""object"":{}}", reader.ReadToEnd());
}
[Fact]
public void WriteJson_WritesExpectedPayload()
public void Write_WritesExpectedPayload()
{
// Arrange
var converter = new OpenIddictConverter();
var writer = new StringWriter(CultureInfo.InvariantCulture);
using var stream = new MemoryStream();
using var reader = new StreamReader(stream);
using var writer = new Utf8JsonWriter(stream);
var message = new OpenIddictMessage();
message.AddParameter("string", "value");
message.AddParameter("array", new JArray("value"));
message.AddParameter("array", new[] { "value" });
// Act
converter.WriteJson(writer: new JsonTextWriter(writer), value: message, serializer: null);
converter.Write(writer, value: message, options: null);
// Assert
Assert.Equal(@"{""string"":""value"",""array"":[""value""]}", writer.ToString());
writer.Flush();
stream.Seek(0L, SeekOrigin.Begin);
Assert.Equal(@"{""string"":""value"",""array"":[""value""]}", reader.ReadToEnd());
}
}
}

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

@ -7,8 +7,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using Xunit;
namespace OpenIddict.Abstractions.Tests.Primitives
@ -151,15 +151,16 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Act
message.AddParameter("string", string.Empty);
message.AddParameter("array", new JArray());
message.AddParameter("object", new JObject());
message.AddParameter("value", new JValue(string.Empty));
message.AddParameter("array", JsonSerializer.Deserialize<JsonElement>("[]"));
message.AddParameter("object", JsonSerializer.Deserialize<JsonElement>("{}"));
message.AddParameter("value", JsonSerializer.Deserialize<JsonElement>(
@"{""property"":""""}").GetProperty("property").GetString());
// Assert
Assert.Empty((string) message.GetParameter("string"));
Assert.Equal(new JArray(), (JArray) message.GetParameter("array"));
Assert.Equal(new JObject(), (JObject) message.GetParameter("object"));
Assert.Equal(new JValue(string.Empty), (JValue) message.GetParameter("value"));
Assert.NotNull((JsonElement?) message.GetParameter("array"));
Assert.NotNull((JsonElement?) message.GetParameter("object"));
Assert.NotNull((JsonElement?) message.GetParameter("value"));
}
[Theory]
@ -349,9 +350,10 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Act
message.SetParameter("string", string.Empty);
message.SetParameter("array", new JArray());
message.SetParameter("object", new JObject());
message.SetParameter("value", new JValue(string.Empty));
message.SetParameter("array", JsonSerializer.Deserialize<JsonElement>("[]"));
message.SetParameter("object", JsonSerializer.Deserialize<JsonElement>("{}"));
message.SetParameter("value", JsonSerializer.Deserialize<JsonElement>(
@"{""property"":""""}").GetProperty("property").GetString());
// Assert
Assert.Empty(message.GetParameters());
@ -366,7 +368,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var message = new OpenIddictMessage();
// Act
var exception = Assert.Throws<ArgumentException>(() => message.TryGetParameter(name, out OpenIddictParameter parameter));
var exception = Assert.Throws<ArgumentException>(() => message.TryGetParameter(name, out var parameter));
// Assert
Assert.Equal("name", exception.ParamName);
@ -377,31 +379,22 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void TryGetParameter_ReturnsTrueAndExpectedParameter()
{
// Arrange
var name = "paramName";
var val = "paramValue";
var message = new OpenIddictMessage();
message.SetParameter(name, val);
// Act
var success = message.TryGetParameter(name, out OpenIddictParameter parameter);
message.SetParameter("parameter", 42);
// Assert
Assert.True(success);
Assert.Equal(val, (string)parameter.Value);
// Act and assert
Assert.True(message.TryGetParameter("parameter", out var parameter));
Assert.Equal(42, (long) parameter.Value);
}
[Fact]
public void TryGetParameter_ReturnsFalse()
public void TryGetParameter_ReturnsFalseForUnsetParameter()
{
// Arrange
var name = "paramName";
var message = new OpenIddictMessage();
// Act
var success = message.TryGetParameter(name, out OpenIddictParameter parameter);
// Assert
Assert.False(success);
// Act and assert
Assert.False(message.TryGetParameter("parameter", out OpenIddictParameter parameter));
Assert.Null(parameter.Value);
}
@ -409,7 +402,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void ToString_ReturnsJsonRepresentation()
{
// Arrange
var message = JsonConvert.DeserializeObject<OpenIddictMessage>(@"{
var message = JsonSerializer.Deserialize<OpenIddictMessage>(@"{
""redirect_uris"": [
""https://client.example.org/callback"",
""https://client.example.org/callback2""
@ -421,8 +414,14 @@ namespace OpenIddict.Abstractions.Tests.Primitives
""example_extension_parameter"": ""example_value""
}");
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
// Act and assert
Assert.Equal(JsonConvert.SerializeObject(message, Formatting.Indented), message.ToString());
Assert.Equal(JsonSerializer.Serialize(message, options), message.ToString());
}
[Theory]
@ -443,8 +442,9 @@ namespace OpenIddict.Abstractions.Tests.Primitives
message.AddParameter(parameter, "secret value");
// Act and assert
var element = JsonSerializer.Deserialize<JsonElement>(message.ToString());
Assert.DoesNotContain("secret value", message.ToString());
Assert.Equal("[removed for security reasons]", JObject.Parse(message.ToString())[parameter]);
Assert.Equal("[removed for security reasons]", element.GetProperty(parameter).GetString());
}
}
}

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

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using Xunit;
namespace OpenIddict.Abstractions.Tests.Primitives
@ -24,17 +24,6 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.True(parameter.Equals(new OpenIddictParameter()));
}
[Fact]
public void Equals_ReturnsTrueWhenReferencesAreIdentical()
{
// Arrange
var value = new JObject();
var parameter = new OpenIddictParameter(value);
// Act and assert
Assert.True(parameter.Equals(new OpenIddictParameter(value)));
}
[Fact]
public void Equals_ReturnsFalseWhenCurrentValueIsNull()
{
@ -65,8 +54,11 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.False(new OpenIddictParameter("42").Equals(new OpenIddictParameter(42)));
Assert.False(new OpenIddictParameter(42).Equals(new OpenIddictParameter("42")));
Assert.False(new OpenIddictParameter(new JObject()).Equals(new OpenIddictParameter(new JArray())));
Assert.False(new OpenIddictParameter(new JArray()).Equals(new OpenIddictParameter(new JObject())));
Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("{}"))
.Equals(new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]"))));
Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]"))
.Equals(new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("{}"))));
}
[Fact]
@ -84,48 +76,33 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void Equals_UsesDeepEqualsForJsonArrays()
{
// Arrange
var parameter = new OpenIddictParameter(new JArray(new[] { 0, 1, 2, 3 }));
var parameter = new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[0,1,2,3]"));
// Act and assert
Assert.True(parameter.Equals(new JArray(new[] { 0, 1, 2, 3 })));
Assert.False(parameter.Equals(new JArray()));
Assert.False(parameter.Equals(new JArray(new[] { 0, 1, 2 })));
Assert.False(parameter.Equals(new JArray(new[] { 3, 2, 1, 0 })));
Assert.True(parameter.Equals(JsonSerializer.Deserialize<JsonElement>("[0,1,2,3]")));
Assert.False(parameter.Equals(JsonSerializer.Deserialize<JsonElement>("[]")));
Assert.False(parameter.Equals(JsonSerializer.Deserialize<JsonElement>("[0,1,2]")));
Assert.False(parameter.Equals(JsonSerializer.Deserialize<JsonElement>("[3,2,1,0]")));
}
[Fact]
public void Equals_UsesDeepEqualsForJsonObjects()
{
// Arrange
var parameter = new OpenIddictParameter(new JObject
{
["field"] = new JArray(new[] { 0, 1, 2, 3 })
});
var parameter = new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>(@"{""field"":[0,1,2,3]}"));
// Act and assert
Assert.True(parameter.Equals(new JObject
{
["field"] = new JArray(new[] { 0, 1, 2, 3 })
}));
Assert.False(parameter.Equals(new JObject()));
Assert.False(parameter.Equals(new JObject
{
["field"] = "value"
}));
Assert.False(parameter.Equals(new JObject
{
["field"] = new JArray(new[] { 0, 1, 2 })
}));
Assert.True(parameter.Equals(JsonSerializer.Deserialize<JsonElement>(@"{""field"":[0,1,2,3]}")));
Assert.False(parameter.Equals(JsonSerializer.Deserialize<JsonElement>(@"{}")));
Assert.False(parameter.Equals(JsonSerializer.Deserialize<JsonElement>(@"{""field"":""value""}")));
Assert.False(parameter.Equals(JsonSerializer.Deserialize<JsonElement>(@"{""field"":[0,1,2]}")));
}
[Fact]
public void Equals_ComparesUnderlyingValuesForJsonValues()
{
// Arrange
var value = new JValue(42);
var value = JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field");
var parameter = new OpenIddictParameter(value);
// Act and assert
@ -140,7 +117,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var parameter = new OpenIddictParameter(42);
// Act and assert
Assert.False(parameter.Equals(new OpenIddictParameter(new JValue((long?) null))));
Assert.False(parameter.Equals(new OpenIddictParameter((JsonElement?) null)));
}
[Fact]
@ -150,8 +127,10 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var parameter = new OpenIddictParameter(42);
// Act and assert
Assert.True(parameter.Equals(new OpenIddictParameter(new JValue(42))));
Assert.False(parameter.Equals(new OpenIddictParameter(new JValue(100))));
Assert.True(parameter.Equals(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field"))));
Assert.False(parameter.Equals(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":100}").GetProperty("field"))));
}
[Fact]
@ -190,7 +169,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
{
// Arrange
var value = "Fabrikam";
var parameter = new OpenIddictParameter(new JValue(value));
var parameter = new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}").GetProperty("field"));
// Act and assert
Assert.Equal(value.GetHashCode(), parameter.GetHashCode());
@ -269,11 +248,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsNullForOutOfRangeJsonArrayIndex()
{
// Arrange
var parameter = new OpenIddictParameter(new JArray
{
"Fabrikam",
"Contoso"
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam"",""Contoso""]"));
// Act and assert
Assert.Null(parameter.GetParameter(2));
@ -283,7 +259,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsNullForNonexistentItem()
{
// Arrange
var parameter = new OpenIddictParameter(new JObject());
var parameter = new OpenIddictParameter(new JsonElement());
// Act and assert
Assert.Null(parameter.GetParameter("parameter"));
@ -293,11 +269,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsNullForJsonArrays()
{
// Arrange
var parameter = new OpenIddictParameter(new JArray
{
"Fabrikam",
"Contoso"
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam"",""Contoso""]"));
// Act and assert
Assert.Null(parameter.GetParameter("Fabrikam"));
@ -307,10 +280,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsNullForJsonObjects()
{
// Arrange
var parameter = new OpenIddictParameter(new JObject
{
["parameter"] = new JValue("value")
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"));
// Act and assert
Assert.Null(parameter.GetParameter(0));
@ -320,10 +291,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsNullForNullJsonObjects()
{
// Arrange
var parameter = new OpenIddictParameter(new JObject
{
["property"] = null
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":null}"));
// Act and assert
Assert.Null(parameter.GetParameter(0));
@ -334,11 +303,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsExpectedNodeForArray()
{
// Arrange
var parameter = new OpenIddictParameter(new[]
{
"Fabrikam",
"Contoso"
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam"",""Contoso""]"));
// Act and assert
Assert.Equal("Fabrikam", (string) parameter.GetParameter(0));
@ -348,10 +314,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsExpectedParameterForJsonObject()
{
// Arrange
var parameter = new OpenIddictParameter(new JObject
{
["parameter"] = new JValue("value")
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"));
// Act and assert
Assert.Equal("value", (string) parameter.GetParameter("parameter"));
@ -361,11 +325,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameter_ReturnsExpectedNodeForJsonArray()
{
// Arrange
var parameter = new OpenIddictParameter(new JArray
{
"Fabrikam",
"Contoso"
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam"",""Contoso""]"));
// Act and assert
Assert.Equal("Fabrikam", (string) parameter.GetParameter(0));
@ -402,7 +363,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameters_ReturnsEmptyEnumerationForJsonValues()
{
// Arrange
var parameter = new OpenIddictParameter(new JValue(42));
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field"));
// Act and assert
Assert.Empty(parameter.GetParameters());
@ -412,13 +374,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void GetParameters_ReturnsNullKeysForJsonArrays()
{
// Arrange
var parameters = new[]
{
"Fabrikam",
"Contoso"
};
var parameter = new OpenIddictParameter(new JArray(parameters));
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam"",""Contoso""]"));
// Act and assert
Assert.All(from element in parameter.GetParameters()
@ -435,7 +392,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
"Contoso"
};
var parameter = new OpenIddictParameter(new JArray(parameters));
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam"",""Contoso""]"));
// Act and assert
Assert.Equal(parameters, from element in parameter.GetParameters()
@ -451,7 +409,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
["parameter"] = "value"
};
var parameter = new OpenIddictParameter(JObject.FromObject(parameters));
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"));
// Act and assert
Assert.Equal(parameters, parameter.GetParameters().ToDictionary(pair => pair.Key, pair => (string) pair.Value));
@ -465,9 +424,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((long?) null)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((string) null)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((string[]) null)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((JArray) null)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((JObject) null)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((JValue) null)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((JsonElement?) null)));
}
[Fact]
@ -476,9 +433,13 @@ namespace OpenIddict.Abstractions.Tests.Primitives
// Arrange, act and assert
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(string.Empty)));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new string[0])));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new JArray())));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new JObject())));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new JValue(string.Empty))));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>("[]"))));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>("{}"))));
Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""""}").GetProperty("field"))));
}
[Fact]
@ -491,9 +452,13 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((long?) 42)));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter("Fabrikam")));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new[] { "Fabrikam" })));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new JArray("Fabrikam"))));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new JObject { ["property"] = "value" })));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new JValue("Fabrikam"))));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam""]"))));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}"))));
Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}").GetProperty("field"))));
}
[Fact]
@ -534,10 +499,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void ToString_ReturnsJsonRepresentation()
{
// Arrange
var parameter = new OpenIddictParameter(new JObject
{
["parameter"] = new JValue("value")
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"));
// Act and assert
Assert.Equal(@"{""parameter"":""value""}", parameter.ToString());
@ -547,7 +510,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void ToString_ReturnsEmptyStringForNullJsonValues()
{
// Arrange
var parameter = new OpenIddictParameter(new JValue((object) null));
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":null}").GetProperty("field"));
// Act and assert
Assert.Empty(parameter.ToString());
@ -557,7 +521,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void ToString_ReturnsUnderlyingJsonValue()
{
// Arrange
var parameter = new OpenIddictParameter(new JValue("Fabrikam"));
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}").GetProperty("field"));
// Act and assert
Assert.Equal("Fabrikam", parameter.ToString());
@ -572,7 +537,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var parameter = new OpenIddictParameter();
// Act
var exception = Assert.Throws<ArgumentException>(() => parameter.TryGetParameter(name, out OpenIddictParameter val));
var exception = Assert.Throws<ArgumentException>(() => parameter.TryGetParameter(name, out var value));
// Assert
Assert.Equal("name", exception.ParamName);
@ -583,34 +548,23 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void TryGetParameter_ReturnsTrueAndExpectedParameter()
{
// Arrange
var name = "paramName";
var val = new JValue("paramValue");
var parameter = new OpenIddictParameter(new JObject
{
[name] = val
});
var parameter = new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}"));
// Act
var success = parameter.TryGetParameter(name, out OpenIddictParameter expectedParameter);
// Assert
Assert.True(success);
Assert.Equal(val, expectedParameter.Value);
// Act and assert
Assert.True(parameter.TryGetParameter("parameter", out var value));
Assert.Equal("value", (string) value);
}
[Fact]
public void TryGetParameter_ReturnsFalse()
{
// Arrange
var name = "paramName";
var parameter = new OpenIddictParameter();
// Act
var success = parameter.TryGetParameter(name, out OpenIddictParameter val);
// Assert
Assert.False(success);
Assert.Null(val.Value);
// Act and assert
Assert.False(parameter.TryGetParameter("parameter", out var value));
Assert.Null(value.Value);
}
[Fact]
@ -655,8 +609,8 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void BoolConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.False((bool) new OpenIddictParameter(new JArray()));
Assert.Null((bool?) new OpenIddictParameter(new JArray()));
Assert.False((bool) new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
Assert.Null((bool?) new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
}
[Fact]
@ -678,180 +632,86 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void BoolConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.True((bool) new OpenIddictParameter(new JValue(true)));
Assert.True((bool?) new OpenIddictParameter(new JValue(true)));
Assert.True((bool) new OpenIddictParameter(new JValue("true")));
Assert.True((bool?) new OpenIddictParameter(new JValue("true")));
Assert.False((bool) new OpenIddictParameter(new JValue(false)));
Assert.False((bool?) new OpenIddictParameter(new JValue(false)));
Assert.False((bool) new OpenIddictParameter(new JValue("false")));
Assert.False((bool?) new OpenIddictParameter(new JValue("false")));
}
[Fact]
public void JArrayConverter_CanCreateParameterFromJArrayValue()
{
// Arrange
var array = new JArray("Fabrikam", "Contoso");
// Act
var parameter = new OpenIddictParameter(array);
// Assert
Assert.Same(array, parameter.Value);
}
[Fact]
public void JArrayConverter_ReturnsDefaultValueForNullValues()
{
// Arrange, act and assert
Assert.Null((JArray) new OpenIddictParameter());
Assert.Null((JArray) (OpenIddictParameter?) null);
}
[Fact]
public void JArrayConverter_ReturnsDefaultValueForUnsupportedPrimitiveValues()
{
// Arrange, act and assert
Assert.Null((JArray) new OpenIddictParameter("Fabrikam"));
}
Assert.True((bool) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":true}").GetProperty("field")));
Assert.True((bool?) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":true}").GetProperty("field")));
Assert.True((bool) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""true""}").GetProperty("field")));
Assert.True((bool?) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""true""}").GetProperty("field")));
[Fact]
public void JArrayConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.Null((JArray) new OpenIddictParameter(new JObject()));
}
[Fact]
public void JArrayConverter_ReturnsDefaultValueForUnsupportedSerializedJson()
{
// Arrange, act and assert
Assert.Null((JArray) new OpenIddictParameter(@"{""Property"":""value""}"));
Assert.Null((JArray) new OpenIddictParameter("["));
}
[Fact]
public void JArrayConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.Equal(new JArray("Contoso", "Fabrikam"), (JArray) new OpenIddictParameter(new JArray("Contoso", "Fabrikam")));
Assert.False((bool) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":false}").GetProperty("field")));
Assert.False((bool?) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":false}").GetProperty("field")));
Assert.False((bool) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""false""}").GetProperty("field")));
Assert.False((bool?) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""false""}").GetProperty("field")));
}
[Fact]
public void JArrayConverter_CanConvertFromSerializedJson()
public void JsonElementConverter_ReturnsDefaultValueForNullValues()
{
// Arrange, act and assert
Assert.Equal(new JArray("Contoso", "Fabrikam"), (JArray) new OpenIddictParameter(@"[""Contoso"",""Fabrikam""]"));
Assert.Null((JsonElement?) new OpenIddictParameter());
Assert.Null((JsonElement?) (OpenIddictParameter?) null);
}
[Fact]
public void JArrayConverter_CanConvertFromArrays()
public void JsonElementConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.Equal(new JArray("Contoso", "Fabrikam"), (JArray) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" }));
Assert.Null((JsonElement?) new OpenIddictParameter(new JsonElement()));
}
[Fact]
public void JObjectConverter_CanCreateParameterFromJObjectValue()
public void JsonElementConverter_CanConvertFromJsonValues()
{
// Arrange
var value = JObject.FromObject(new { Property = "value" });
// Act
var parameter = new OpenIddictParameter(value);
// Arrange and act
var array = (JsonElement) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"));
var dictionary = (JsonElement) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""Property"":""value""}"));
// Assert
Assert.Same(value, parameter.Value);
}
[Fact]
public void JObjectConverter_ReturnsDefaultValueForNullValues()
{
// Arrange, act and assert
Assert.Null((JObject) new OpenIddictParameter());
Assert.Null((JObject) (OpenIddictParameter?) null);
}
[Fact]
public void JObjectConverter_ReturnsDefaultValueForUnsupportedPrimitiveValues()
{
// Arrange, act and assert
Assert.Null((JObject) new OpenIddictParameter("Fabrikam"));
}
[Fact]
public void JObjectConverter_ReturnsDefaultValueForUnsupportedArrays()
{
// Arrange, act and assert
Assert.Null((JObject) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }));
}
Assert.Equal("Contoso", array[0].GetString());
Assert.Equal("Fabrikam", array[1].GetString());
Assert.Equal("value", dictionary.GetProperty("Property").GetString());
[Fact]
public void JObjectConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.Null((JObject) new OpenIddictParameter(new JArray()));
}
Assert.True(((JsonElement) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":true}").GetProperty("field"))).GetBoolean());
[Fact]
public void JObjectConverter_ReturnsDefaultValueForUnsupportedSerializedJson()
{
// Arrange, act and assert
Assert.Null((JObject) new OpenIddictParameter(@"[""Fabrikam"",""Contoso""]"));
Assert.Null((JObject) new OpenIddictParameter("{"));
}
Assert.Equal(42, ((JsonElement) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field"))).GetInt64());
[Fact]
public void JObjectConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.Equal(JObject.FromObject(new { Property = "value" }), (JObject) new OpenIddictParameter(JObject.FromObject(new { Property = "value" })));
Assert.Equal("Fabrikam", ((JsonElement) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}").GetProperty("field"))).GetString());
}
[Fact]
public void JObjectConverter_CanConvertFromSerializedJson()
public void JsonElementConverter_CanConvertFromSerializedJson()
{
// Arrange, act and assert
Assert.Equal(JObject.FromObject(new { Property = "value" }), (JObject) new OpenIddictParameter(@"{""Property"":""value""}"));
}
[Fact]
public void JValueConverter_CanCreateParameterFromJValueValue()
{
// Arrange
var value = new JValue("Fabrikam");
// Act
var parameter = new OpenIddictParameter(value);
// Arrange and act
var array = (JsonElement) new OpenIddictParameter(@"[""Contoso"",""Fabrikam""]");
var dictionary = (JsonElement) new OpenIddictParameter(@"{""Property"":""value""}");
// Assert
Assert.Same(value, parameter.Value);
}
[Fact]
public void JValueConverter_ReturnsDefaultValueForNullValues()
{
// Arrange, act and assert
Assert.Null((JValue) new OpenIddictParameter());
Assert.Null((JValue) (OpenIddictParameter?) null);
Assert.Equal("Contoso", array[0].GetString());
Assert.Equal("Fabrikam", array[1].GetString());
Assert.Equal("value", dictionary.GetProperty("Property").GetString());
}
[Fact]
public void JValueConverter_ReturnsDefaultValueForUnsupportedJsonValues()
public void JsonElementConverter_CanConvertFromArrays()
{
// Arrange, act and assert
Assert.Null((JValue) new OpenIddictParameter(new JArray()));
}
// Arrange and act
var array = (JsonElement) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" });
[Fact]
public void JValueConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.Equal(new JValue(true), (JValue) new OpenIddictParameter(new JValue(true)));
Assert.Equal(new JValue(42), (JValue) new OpenIddictParameter(new JValue(42)));
Assert.Equal(new JValue("Fabrikam"), (JValue) new OpenIddictParameter(new JValue("Fabrikam")));
// Assert
Assert.Equal("Contoso", array[0].GetString());
Assert.Equal("Fabrikam", array[1].GetString());
}
[Fact]
@ -882,16 +742,16 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void LongConverter_ReturnsDefaultValueForUnsupportedArrays()
{
// Arrange, act and assert
Assert.Equal(0, (long) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }));
Assert.Null((long?) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }));
Assert.Equal(0, (long) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" }));
Assert.Null((long?) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" }));
}
[Fact]
public void LongConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.Equal(0, (long) new OpenIddictParameter(new JArray()));
Assert.Null((long?) new OpenIddictParameter(new JArray()));
Assert.Equal(0, (long) new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
Assert.Null((long?) new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>("[]")));
}
[Fact]
@ -908,18 +768,10 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void LongConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.Equal(42, (long) new OpenIddictParameter(new JValue(42)));
Assert.Equal(42, (long?) new OpenIddictParameter(new JValue(42)));
Assert.Equal(42, (long) new OpenIddictParameter(new JValue(42)));
Assert.Equal(42, (long?) new OpenIddictParameter(new JValue(42)));
Assert.Equal(42, (long) new OpenIddictParameter(new JValue(42f)));
Assert.Equal(42, (long?) new OpenIddictParameter(new JValue(42f)));
Assert.Equal(42, (long) new OpenIddictParameter(new JValue(42f)));
Assert.Equal(42, (long?) new OpenIddictParameter(new JValue(42f)));
Assert.Equal(42, (long) new OpenIddictParameter(new JValue(42m)));
Assert.Equal(42, (long?) new OpenIddictParameter(new JValue(42m)));
Assert.Equal(42, (long) new OpenIddictParameter(new JValue(42m)));
Assert.Equal(42, (long?) new OpenIddictParameter(new JValue(42m)));
Assert.Equal(42, (long) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field")));
Assert.Equal(42, (long?) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field")));
}
[Fact]
@ -941,14 +793,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void StringConverter_ReturnsDefaultValueForArrays()
{
// Arrange, act and assert
Assert.Null((string) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }));
}
[Fact]
public void StringConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.Null((string) new OpenIddictParameter(new JArray()));
Assert.Null((string) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" }));
}
[Fact]
@ -964,9 +809,12 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void StringConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.Equal("Fabrikam", (string) new OpenIddictParameter(new JValue("Fabrikam")));
Assert.Equal("False", (string) new OpenIddictParameter(new JValue(false)));
Assert.Equal("42", (string) new OpenIddictParameter(new JValue(42)));
Assert.Equal("Fabrikam", (string) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}").GetProperty("field")));
Assert.Equal("false", (string) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":false}").GetProperty("field")));
Assert.Equal("42", (string) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field")));
}
[Fact]
@ -1009,18 +857,23 @@ namespace OpenIddict.Abstractions.Tests.Primitives
public void StringArrayConverter_ReturnsDefaultValueForUnsupportedJsonValues()
{
// Arrange, act and assert
Assert.Null((string[]) new OpenIddictParameter(new JObject()));
Assert.Null((string[]) new OpenIddictParameter(new JsonElement()));
}
[Fact]
public void StringArrayConverter_CanConvertFromJsonValues()
{
// Arrange, act and assert
Assert.Equal(new[] { "Fabrikam" }, (string[]) new OpenIddictParameter(new JValue("Fabrikam")));
Assert.Equal(new[] { "False" }, (string[]) new OpenIddictParameter(new JValue(false)));
Assert.Equal(new[] { "42" }, (string[]) new OpenIddictParameter(new JValue(42)));
Assert.Equal(new[] { "Fabrikam" }, (string[]) new OpenIddictParameter(new JArray("Fabrikam")));
Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]) new OpenIddictParameter(new JArray(new[] { "Fabrikam", "Contoso" })));
Assert.Equal(new[] { "Fabrikam" }, (string[]) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":""Fabrikam""}").GetProperty("field")));
Assert.Equal(new[] { "False" }, (string[]) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":false}").GetProperty("field")));
Assert.Equal(new[] { "42" }, (string[]) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"{""field"":42}").GetProperty("field")));
Assert.Equal(new[] { "Fabrikam" }, (string[]) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Fabrikam""]")));
Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]) new OpenIddictParameter(
JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]")));
}
}
}

6
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs

@ -5,7 +5,7 @@
*/
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using Xunit;
namespace OpenIddict.Abstractions.Tests.Primitives
@ -48,7 +48,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
{
/* property: */ nameof(OpenIddictRequest.Claims),
/* name: */ OpenIddictConstants.Parameters.Claims,
/* value: */ new OpenIddictParameter(new JObject { ["userinfo"] = new JObject() })
/* value: */ new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>(@"{""userinfo"": {}}"))
};
yield return new object[]
@ -209,7 +209,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
{
/* property: */ nameof(OpenIddictRequest.Registration),
/* name: */ OpenIddictConstants.Parameters.Registration,
/* value: */ new OpenIddictParameter(new JObject { ["policy_uri"] = "http://www.fabrikam.com/policy" })
/* value: */ new OpenIddictParameter(JsonSerializer.Deserialize<JsonElement>(@"{""policy_uri"": ""http://www.fabrikam.com/policy""}"))
};
yield return new object[]

Loading…
Cancel
Save