diff --git a/eng/Versions.props b/eng/Versions.props index 12070c3f..8d861cc5 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -30,12 +30,11 @@ 1.0.0 + 1.0.0 1.8.5 4.4.0 6.3.0 2019.1.3 - 12.0.2 - 1.0.2 5.6.0 1.5.0 4.0.0 @@ -43,6 +42,7 @@ 4.7.63 5.2.2 4.0.0 + 4.6.0 4.5.3 diff --git a/samples/Mvc.Server/Controllers/UserinfoController.cs b/samples/Mvc.Server/Controllers/UserinfoController.cs index c6ef839b..acdbe83b 100644 --- a/samples/Mvc.Server/Controllers/UserinfoController.cs +++ b/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 diff --git a/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj b/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj index 6ea13d67..97b0faeb 100644 --- a/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj +++ b/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj @@ -12,13 +12,14 @@ - + + diff --git a/src/OpenIddict.Abstractions/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs index c9e6af1a..2331e716 100644 --- a/src/OpenIddict.Abstractions/OpenIddictConstants.cs +++ b/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"; diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs index cafbb874..9963caf6 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs +++ b/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 { /// /// Represents a JSON.NET converter able to convert OpenIddict primitives. /// - public class OpenIddictConverter : JsonConverter + public class OpenIddictConverter : JsonConverter { /// /// Determines whether the specified type is supported by this converter. @@ -36,62 +36,50 @@ namespace OpenIddict.Abstractions /// /// The JSON reader. /// The type of the deserialized instance. - /// The existing , if applicable. - /// The JSON serializer. + /// The JSON serializer options. /// The deserialized instance. - 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(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; } /// @@ -99,8 +87,8 @@ namespace OpenIddict.Abstractions /// /// The JSON writer. /// The instance. - /// The JSON serializer. - public override void WriteJson([NotNull] JsonWriter writer, [NotNull] object value, [CanBeNull] JsonSerializer serializer) + /// The JSON serializer options. + 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(); } } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs index ce3e2bec..3f751541 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs +++ b/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(); } - return JArray.Parse(destinations).Values().Distinct(StringComparer.OrdinalIgnoreCase).ToImmutableArray(); + return JsonSerializer.Deserialize>(destinations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToImmutableArray(); } /// @@ -565,7 +567,8 @@ namespace OpenIddict.Abstractions return false; } - return JArray.Parse(destinations).Values().Contains(destination, StringComparer.OrdinalIgnoreCase); + return JsonSerializer.Deserialize>(destinations) + .Contains(destination, StringComparer.OrdinalIgnoreCase); } /// @@ -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; } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs index 3badeb5a..ab969867 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs +++ b/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. /// /// The message parameters. - public OpenIddictMessage([NotNull] IEnumerable> parameters) + public OpenIddictMessage([NotNull] IEnumerable> parameters) { if (parameters == null) { @@ -316,12 +316,12 @@ namespace OpenIddict.Abstractions /// The indented JSON representation corresponding to this message. 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()); } } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs index bd1e8361..9ad8661e 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs +++ b/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 { /// - /// 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. /// public readonly struct OpenIddictParameter : IEquatable @@ -39,7 +40,14 @@ namespace OpenIddict.Abstractions /// parameter using the specified value. /// /// The parameter value. - public OpenIddictParameter(JToken value) => Value = value; + public OpenIddictParameter(JsonElement value) => Value = value; + + /// + /// Initializes a new OpenID Connect + /// parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(JsonElement? value) => Value = value; /// /// Initializes a new OpenID Connect @@ -96,33 +104,119 @@ namespace OpenIddict.Abstractions /// /// The other object to which to compare this instance. /// true if the two instances are equal, false otherwise. - 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; + } + } + } /// /// Determines whether the current @@ -137,9 +231,69 @@ namespace OpenIddict.Abstractions /// Returns the hash code of the current instance. /// /// The hash code for the current instance. - // 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; + } + } + } /// /// 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(null, child); + case JsonValueKind.Array: + foreach (var value in element.EnumerateArray()) + { + yield return new KeyValuePair(null, value); + } - continue; - } + break; + + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + yield return new KeyValuePair(property.Name, property.Value); + } - yield return new KeyValuePair(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 /// /// The parameter to convert. /// The converted value. - public static explicit operator bool(OpenIddictParameter? parameter) => Convert(parameter); + public static explicit operator bool(OpenIddictParameter? parameter) + => ((bool?) parameter).GetValueOrDefault(); /// /// Converts an instance to a nullable boolean. /// /// The parameter to convert. /// The converted value. - public static explicit operator bool?(OpenIddictParameter? parameter) => Convert(parameter); + public static explicit operator bool?(OpenIddictParameter? parameter) => parameter?.Value switch + { + // When the parameter is a null value, return null. + null => null, - /// - /// Converts an instance to a . - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator JArray(OpenIddictParameter? parameter) => Convert(parameter); + // When the parameter is a boolean value, return it as-is. + bool value => value, - /// - /// Converts an instance to a . - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator JObject(OpenIddictParameter? parameter) => Convert(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 + }; /// - /// Converts an instance to a . + /// Converts an instance to a . /// /// The parameter to convert. /// The converted value. - public static explicit operator JToken(OpenIddictParameter? parameter) => Convert(parameter); + public static explicit operator JsonElement(OpenIddictParameter? parameter) + => ((JsonElement?) parameter).GetValueOrDefault(); /// - /// Converts an instance to a . + /// Converts an instance to a nullale . /// /// The parameter to convert. /// The converted value. - public static explicit operator JValue(OpenIddictParameter? parameter) => Convert(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(value); } + catch (JsonException) { return null; } + } + } /// /// Converts an instance to a long integer. /// /// The parameter to convert. /// The converted value. - public static explicit operator long(OpenIddictParameter? parameter) => Convert(parameter); + public static explicit operator long(OpenIddictParameter? parameter) + => ((long?) parameter).GetValueOrDefault(); /// /// Converts an instance to a nullable long integer. /// /// The parameter to convert. /// The converted value. - public static explicit operator long?(OpenIddictParameter? parameter) => Convert(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 + }; /// /// Converts an instance to a string. /// /// The parameter to convert. /// The converted value. - public static explicit operator string(OpenIddictParameter? parameter) => Convert(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 + }; /// /// Converts an instance to an array of strings. /// /// The parameter to convert. /// The converted value. - public static explicit operator string[](OpenIddictParameter? parameter) => Convert(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; + } + } /// /// Converts a boolean to an instance. @@ -390,11 +713,11 @@ namespace OpenIddict.Abstractions public static implicit operator OpenIddictParameter(bool? value) => new OpenIddictParameter(value); /// - /// Converts a to an instance. + /// Converts a to an instance. /// /// The value to convert /// An instance. - public static implicit operator OpenIddictParameter(JToken value) => new OpenIddictParameter(value); + public static implicit operator OpenIddictParameter(JsonElement value) => new OpenIddictParameter(value); /// /// Converts a long integer to an instance. @@ -425,78 +748,36 @@ namespace OpenIddict.Abstractions public static implicit operator OpenIddictParameter(string[] value) => new OpenIddictParameter(value); /// - /// Converts the parameter to the specified generic type. + /// Determines whether an OpenID Connect parameter is null or empty. /// - /// The type the parameter will be converted to. - /// The instance. - /// The converted parameter. - private static T Convert(OpenIddictParameter? parameter) + /// The OpenID Connect parameter. + /// true if the parameter is null or empty, false otherwise. + 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(), - - JValue value when typeof(T) == typeof(string[]) => (T) (object) new string[] - { - value.ToObject() - }, + _ => false + }; - JToken token => token.ToObject(), - - var value when typeof(T) == typeof(string[]) => (T) (object) new string[] - { - new JValue(value).ToObject() - }, - - _ => new JValue(parameter?.Value).ToObject() - }; - } - - // 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. } - - /// - /// Determines whether an OpenID Connect parameter is null or empty. - /// - /// The OpenID Connect parameter. - /// true if the parameter is null or empty, false otherwise. - 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 - }; } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs index a953ce6a..f2e2cd2a 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs +++ b/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. /// /// The request parameters. - public OpenIddictRequest([NotNull] IEnumerable> parameters) + public OpenIddictRequest([NotNull] IEnumerable> parameters) : base(parameters) { } /// @@ -105,9 +105,9 @@ namespace OpenIddict.Abstractions /// /// Gets or sets the "claims" parameter. /// - 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 /// /// Gets or sets the "registration" parameter. /// - public JObject Registration + public JsonElement Registration { - get => (JObject) GetParameter(OpenIddictConstants.Parameters.Registration); + get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Registration); set => SetParameter(OpenIddictConstants.Parameters.Registration, value); } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs index d31f5b37..50f79c3e 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs +++ b/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. /// /// The response parameters. - public OpenIddictResponse([NotNull] IEnumerable> parameters) + public OpenIddictResponse([NotNull] IEnumerable> parameters) : base(parameters) { } /// diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs index 2859f277..1289e4e1 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the application. /// - ValueTask GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken); + ValueTask> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken); /// /// Retrieves the callback addresses associated with an application. @@ -341,7 +341,8 @@ namespace OpenIddict.Abstractions /// The additional properties associated with the application. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken); + ValueTask SetPropertiesAsync([NotNull] TApplication application, + [CanBeNull] ImmutableDictionary properties, CancellationToken cancellationToken); /// /// Sets the callback addresses associated with an application. diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs index eecb897c..93ddf219 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the authorization. /// - ValueTask GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); + ValueTask> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); /// /// Retrieves the scopes associated with an authorization. @@ -285,7 +285,8 @@ namespace OpenIddict.Abstractions /// The additional properties associated with the authorization. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken); + ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, + [CanBeNull] ImmutableDictionary properties, CancellationToken cancellationToken); /// /// Sets the scopes associated with an authorization. diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs index aee87eff..6876dd77 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs +++ b/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 that can be used to monitor the asynchronous operation, whose /// result returns all the additional properties associated with the scope. /// - ValueTask GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken); + ValueTask> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken); /// /// Retrieves the resources associated with a scope. @@ -245,7 +245,8 @@ namespace OpenIddict.Abstractions /// The additional properties associated with the scope. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken); + ValueTask SetPropertiesAsync([NotNull] TScope scope, + [CanBeNull] ImmutableDictionary properties, CancellationToken cancellationToken); /// /// Sets the resources associated with a scope. diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs index 6b79c69f..5c0c2f93 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the token. /// - ValueTask GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken); + ValueTask> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken); /// /// Retrieves the reference identifier associated with a token. @@ -371,7 +371,8 @@ namespace OpenIddict.Abstractions /// The additional properties associated with the token. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken); + ValueTask SetPropertiesAsync([NotNull] TToken token, + [CanBeNull] ImmutableDictionary properties, CancellationToken cancellationToken); /// /// Sets the reference identifier associated with a token. diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index dc9c2a39..dfe26f3e 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/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>(application.Permissions); }); return new ValueTask>(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>(application.PostLogoutRedirectUris); }); return new ValueTask>(addresses); @@ -541,7 +537,7 @@ namespace OpenIddict.EntityFramework /// A that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the application. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) { if (application == null) { @@ -550,7 +546,7 @@ namespace OpenIddict.EntityFramework if (string.IsNullOrEmpty(application.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(application.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -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>(application.RedirectUris); }); return new ValueTask>(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>(application.Requirements); }); return new ValueTask>(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 /// The additional properties associated with the application. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index ea3019b5..970642dd 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the authorization. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) { if (authorization == null) { @@ -497,7 +497,7 @@ namespace OpenIddict.EntityFramework if (string.IsNullOrEmpty(authorization.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(authorization.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -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>(authorization.Scopes); }); return new ValueTask>(scopes); @@ -820,21 +818,26 @@ namespace OpenIddict.EntityFramework /// /// A that can be used to monitor the asynchronous operation. /// - public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs index 00b01b95..ae0cc855 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the scope. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) { if (scope == null) { @@ -366,7 +366,7 @@ namespace OpenIddict.EntityFramework if (string.IsNullOrEmpty(scope.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(scope.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -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>(scope.Resources); }); return new ValueTask>(resources); @@ -556,21 +554,26 @@ namespace OpenIddict.EntityFramework /// The additional properties associated with the scope. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 914fb14f..907d488b 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the token. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) { if (token == null) { @@ -584,7 +584,7 @@ namespace OpenIddict.EntityFramework if (string.IsNullOrEmpty(token.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(token.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -987,21 +987,26 @@ namespace OpenIddict.EntityFramework /// The additional properties associated with the token. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index fc2cdbdc..45ee2718 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/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>(application.Permissions); }); return new ValueTask>(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>(application.PostLogoutRedirectUris); }); return new ValueTask>(addresses); @@ -588,7 +584,7 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the application. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) { if (application == null) { @@ -597,7 +593,7 @@ namespace OpenIddict.EntityFrameworkCore if (string.IsNullOrEmpty(application.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(application.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -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>(application.RedirectUris); }); return new ValueTask>(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>(application.Requirements); }); return new ValueTask>(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 /// The additional properties associated with the application. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 118e65ec..0887f2e4 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the authorization. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) { if (authorization == null) { @@ -551,7 +551,7 @@ namespace OpenIddict.EntityFrameworkCore if (string.IsNullOrEmpty(authorization.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(authorization.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -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>(authorization.Scopes); }); return new ValueTask>(scopes); @@ -888,21 +886,26 @@ namespace OpenIddict.EntityFrameworkCore /// The additional properties associated with the authorization. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs index 54205bf6..00bf2e56 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the scope. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) { if (scope == null) { @@ -381,7 +381,7 @@ namespace OpenIddict.EntityFrameworkCore if (string.IsNullOrEmpty(scope.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(scope.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -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>(scope.Resources); }); return new ValueTask>(resources); @@ -571,21 +569,26 @@ namespace OpenIddict.EntityFrameworkCore /// The additional properties associated with the scope. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 6c357d28..6475338c 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the token. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) { if (token == null) { @@ -630,7 +631,7 @@ namespace OpenIddict.EntityFrameworkCore if (string.IsNullOrEmpty(token.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } // 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>(token.Properties); }); - return new ValueTask((JObject) properties.DeepClone()); + return new ValueTask>(properties); } /// @@ -1068,21 +1069,26 @@ namespace OpenIddict.EntityFrameworkCore /// /// A that can be used to monitor the asynchronous operation. /// - public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictApplicationStore.cs index 000a2648..0a7c8ddf 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictApplicationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the application. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) { if (application == null) { @@ -457,10 +455,10 @@ namespace OpenIddict.MongoDb if (application.Properties == null) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(JObject.FromObject(application.Properties.ToDictionary())); + return new ValueTask>(application.Properties.ToDictionary().ToImmutableDictionary()); } /// @@ -763,21 +761,22 @@ namespace OpenIddict.MongoDb /// The additional properties associated with the application. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs index 381a3e52..16e7a51f 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the authorization. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) { if (authorization == null) { @@ -491,10 +489,10 @@ namespace OpenIddict.MongoDb if (authorization.Properties == null) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(JObject.FromObject(authorization.Properties.ToDictionary())); + return new ValueTask>(authorization.Properties.ToDictionary().ToImmutableDictionary()); } /// @@ -766,21 +764,22 @@ namespace OpenIddict.MongoDb /// The additional properties associated with the authorization. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictScopeStore.cs index 3c67375e..ced982bd 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictScopeStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the scope. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) { if (scope == null) { @@ -358,10 +358,10 @@ namespace OpenIddict.MongoDb if (scope.Properties == null) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(JObject.FromObject(scope.Properties.ToDictionary())); + return new ValueTask>(scope.Properties.ToDictionary().ToImmutableDictionary()); } /// @@ -541,21 +541,22 @@ namespace OpenIddict.MongoDb /// The additional properties associated with the scope. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs index d0aaef0d..2394b474 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the token. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) { if (token == null) { @@ -556,10 +554,10 @@ namespace OpenIddict.MongoDb if (token.Properties == null) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(JObject.FromObject(token.Properties.ToDictionary())); + return new ValueTask>(token.Properties.ToDictionary().ToImmutableDictionary()); } /// @@ -865,21 +863,22 @@ namespace OpenIddict.MongoDb /// The additional properties associated with the token. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs index e202d34a..eb808ce2 100644 --- a/src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs +++ b/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>(application.Permissions); }); return new ValueTask>(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>(application.PostLogoutRedirectUris); }); return new ValueTask>(addresses); @@ -528,7 +524,7 @@ namespace OpenIddict.NHibernate /// A that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the application. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken) { if (application == null) { @@ -537,10 +533,21 @@ namespace OpenIddict.NHibernate if (string.IsNullOrEmpty(application.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(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>(application.Properties); + }); + + return new ValueTask>(properties); } /// @@ -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>(application.RedirectUris); }); return new ValueTask>(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>(application.Requirements); }); return new ValueTask>(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 /// The additional properties associated with the application. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TApplication application, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs index 023ffcca..c25c0ac7 100644 --- a/src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the authorization. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) { if (authorization == null) { @@ -515,10 +515,21 @@ namespace OpenIddict.NHibernate if (string.IsNullOrEmpty(authorization.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(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>(authorization.Properties); + }); + + return new ValueTask>(properties); } /// @@ -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>(authorization.Scopes); }); return new ValueTask>(scopes); @@ -763,21 +772,26 @@ namespace OpenIddict.NHibernate /// The additional properties associated with the authorization. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TAuthorization authorization, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs index 1c0b3b39..f325a07f 100644 --- a/src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the scope. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken) { if (scope == null) { @@ -399,10 +399,21 @@ namespace OpenIddict.NHibernate if (string.IsNullOrEmpty(scope.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(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>(scope.Properties); + }); + + return new ValueTask>(properties); } /// @@ -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>(scope.Resources); }); return new ValueTask>(resources); @@ -594,21 +603,26 @@ namespace OpenIddict.NHibernate /// The additional properties associated with the scope. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs index 610e822d..a5c859c0 100644 --- a/src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs +++ b/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 that can be used to monitor the asynchronous operation, /// whose result returns all the additional properties associated with the token. /// - public virtual ValueTask GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) + public virtual ValueTask> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken) { if (token == null) { @@ -635,10 +635,21 @@ namespace OpenIddict.NHibernate if (string.IsNullOrEmpty(token.Properties)) { - return new ValueTask(new JObject()); + return new ValueTask>(ImmutableDictionary.Create()); } - return new ValueTask(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>(token.Properties); + }); + + return new ValueTask>(properties); } /// @@ -951,21 +962,26 @@ namespace OpenIddict.NHibernate /// The additional properties associated with the token. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken) + public virtual ValueTask SetPropertiesAsync([NotNull] TToken token, + [CanBeNull] ImmutableDictionary 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; } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj b/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj index 8cb53981..bc26e35a 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj +++ b/src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj @@ -25,7 +25,6 @@ - diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs index a9b55b23..846ecbc1 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs +++ b/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"; diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs index 4e71c24b..142355e3 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs +++ b/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(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 + { + [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( diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs index a139eaa7..29d2f084 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs +++ b/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(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 + { + [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( diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index 2cb3e9a1..df7663aa 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/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(); } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs index 837f35e0..984e4589 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs +++ b/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 GetArrayProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? - JArray.Parse(value).Values().ToImmutableArray() : ImmutableArray.Create(); + JsonSerializer.Deserialize>(value) : ImmutableArray.Create(); static DateTimeOffset? GetDateProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? (DateTimeOffset?) @@ -362,17 +362,20 @@ namespace OpenIddict.Server.DataProtection } } - static void SetArrayProperty(IDictionary properties, string name, IEnumerable values) + static void SetArrayProperty(IDictionary properties, string name, ImmutableArray 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 + }); } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj b/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj index cc31469d..afc864d2 100644 --- a/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj +++ b/src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj @@ -18,7 +18,6 @@ - diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs index e1b18c0a..1d4e6639 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs +++ b/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"; diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs index 17eaa4de..58fe2381 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs +++ b/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(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 + { + [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( diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs index f02f01c9..c6202106 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs +++ b/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(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 + { + [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( diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index 511d48ff..29cae2b6 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/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(); } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs index 67a8622f..14ebe2a3 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs +++ b/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. /// - public JObject Address { get; set; } + public JsonElement Address { get; set; } /// /// Gets or sets the values used for the "aud" claim. diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs index c8e043e5..9ea6b02c 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs +++ b/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 { diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs index 1f578fbe..6d56ceb6 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs +++ b/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 - { - [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; } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs index 2d2aa55d..b97b9a09 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs +++ b/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(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(claim.Value), + JsonClaimValueTypes.JsonArray => JsonSerializer.Deserialize(claim.Value), _ => new OpenIddictParameter(claim.Value) }; diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs index f07a8a53..ce0e4e1f 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs +++ b/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 { diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs index 9ad11f78..3c691a57 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs +++ b/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 { diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs index 23745267..b4b3f8d7 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs +++ b/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; } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 95f41642..50809be8 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/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 ValidateTokenAsync(string token, string type) { var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.PropertyBag = new Dictionary { [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 { [Claims.Private.TokenUsage] = TokenUsages.AccessToken }, - EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault( - credentials => credentials.Key is SymmetricSecurityKey), + AdditionalHeaderClaims = new Dictionary(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(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 { [Claims.Private.TokenUsage] = TokenUsages.AuthorizationCode }, - EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault( - credentials => credentials.Key is SymmetricSecurityKey), + AdditionalHeaderClaims = new Dictionary(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(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 { [Claims.Private.TokenUsage] = TokenUsages.DeviceCode }, - EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault( - credentials => credentials.Key is SymmetricSecurityKey), + AdditionalHeaderClaims = new Dictionary(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(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 { [Claims.Private.TokenUsage] = TokenUsages.RefreshToken }, - EncryptingCredentials = context.Options.EncryptionCredentials[0], + AdditionalHeaderClaims = new Dictionary(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(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 { [Claims.Private.TokenUsage] = TokenUsages.UserCode }, - EncryptingCredentials = context.Options.EncryptionCredentials.FirstOrDefault( - credentials => credentials.Key is SymmetricSecurityKey), + AdditionalHeaderClaims = new Dictionary(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(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 { [Claims.Private.TokenUsage] = TokenUsages.IdToken }, + AdditionalHeaderClaims = new Dictionary(StringComparer.Ordinal) + { + [JwtHeaderParameterNames.Typ] = JsonWebTokenTypes.IdentityToken + }, Issuer = context.Issuer?.AbsoluteUri, SigningCredentials = context.Options.SigningCredentials.First(credentials => credentials.Key is AsymmetricSecurityKey), diff --git a/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs b/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs index 1b9c1dd2..7dbeedeb 100644 --- a/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs +++ b/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(StringComparer.Ordinal); var destinations = new Dictionary(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(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 definitions)) { diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj b/src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj index 73659ceb..010d6010 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj @@ -23,7 +23,6 @@ - diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs index fa77e68a..db41af83 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs +++ b/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(); } } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs index db53b44f..49064d39 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs +++ b/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 GetArrayProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? - JArray.Parse(value).Values().ToImmutableArray() : ImmutableArray.Create(); + JsonSerializer.Deserialize>(value) : ImmutableArray.Create(); static DateTimeOffset? GetDateProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? (DateTimeOffset?) diff --git a/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj b/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj index ae551217..cafeb9ad 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj +++ b/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj @@ -18,7 +18,6 @@ - diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index f69d3d8a..186953dd 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/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(); } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index bce643a5..cb4fbca6 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/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 GetSigningKeysAsync(HttpClient client, Uri address) + static async IAsyncEnumerable 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(reader); + return await JsonSerializer.DeserializeAsync(stream); } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs index f145ed1e..628dfa73 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs +++ b/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 { [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); diff --git a/src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs b/src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs index fc797f93..63b512f9 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs +++ b/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(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(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 definitions)) { diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs index 1c5fa082..890142c8 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs +++ b/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(() => { - 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(() => - { - 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(() => + var exception = Assert.Throws(() => { - 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(() => { - 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(() => { - 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(() => { - 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(() => - { - 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("[]"))); + message.AddParameter("object", new OpenIddictParameter(JsonSerializer.Deserialize("{}"))); // 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()); } } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs index 034f9634..11e98dc8 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs +++ b/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("[]")); + message.AddParameter("object", JsonSerializer.Deserialize("{}")); + message.AddParameter("value", JsonSerializer.Deserialize( + @"{""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("[]")); + message.SetParameter("object", JsonSerializer.Deserialize("{}")); + message.SetParameter("value", JsonSerializer.Deserialize( + @"{""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(() => message.TryGetParameter(name, out OpenIddictParameter parameter)); + var exception = Assert.Throws(() => 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(@"{ + var message = JsonSerializer.Deserialize(@"{ ""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(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()); } } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs index 8b133c88..fedd2c1e 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs +++ b/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("{}")) + .Equals(new OpenIddictParameter(JsonSerializer.Deserialize("[]")))); + + Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize("[]")) + .Equals(new OpenIddictParameter(JsonSerializer.Deserialize("{}")))); } [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("[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("[0,1,2,3]"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize("[]"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize("[0,1,2]"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize("[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(@"{""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(@"{""field"":[0,1,2,3]}"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{}"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":""value""}"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":[0,1,2]}"))); } [Fact] public void Equals_ComparesUnderlyingValuesForJsonValues() { // Arrange - var value = new JValue(42); + var value = JsonSerializer.Deserialize(@"{""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(@"{""field"":42}").GetProperty("field")))); + Assert.False(parameter.Equals(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"{""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(@"[""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(@"[""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(@"{""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(@"{""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(@"[""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(@"{""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(@"[""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(@"{""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(@"[""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(@"[""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(@"{""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("[]")))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize("{}")))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"[""Fabrikam""]")))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}")))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"{""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(@"{""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(@"{""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(() => parameter.TryGetParameter(name, out OpenIddictParameter val)); + var exception = Assert.Throws(() => 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(@"{""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("[]"))); + Assert.Null((bool?) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); } [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(@"{""field"":true}").GetProperty("field"))); + Assert.True((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))); + Assert.True((bool) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""true""}").GetProperty("field"))); + Assert.True((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"{""field"":false}").GetProperty("field"))); + Assert.False((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.False((bool) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""false""}").GetProperty("field"))); + Assert.False((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"[""Contoso"",""Fabrikam""]")); + var dictionary = (JsonElement) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"{""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(@"{""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(@"{""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("[]"))); + Assert.Null((long?) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); } [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(@"{""field"":42}").GetProperty("field"))); + Assert.Equal(42, (long?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"{""field"":""Fabrikam""}").GetProperty("field"))); + Assert.Equal("false", (string) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.Equal("42", (string) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""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(@"{""field"":""Fabrikam""}").GetProperty("field"))); + Assert.Equal(new[] { "False" }, (string[]) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.Equal(new[] { "42" }, (string[]) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); + Assert.Equal(new[] { "Fabrikam" }, (string[]) new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam""]"))); + Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]) new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"))); } } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs index 5d06a9ef..33e9cde0 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs +++ b/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(@"{""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(@"{""policy_uri"": ""http://www.fabrikam.com/policy""}")) }; yield return new object[]