From 79b1f135db21e2410be1056c55496be5c530bc9f Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Tue, 2 Jan 2024 14:30:05 +0100 Subject: [PATCH] Do not crash when User.Locale and UserInfo.Locale are an object --- .../Models/UserInfo.cs | 2 + .../StringOrObjectAsStringConverter.cs | 37 ++++++++++++++++ src/Auth0.ManagementApi/Models/User.cs | 2 + .../UserInfoDeserializationTests.cs | 19 ++++++++ .../StringOrObjectAsStringConverterTests.cs | 44 +++++++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs create mode 100644 tests/Auth0.Core.UnitTests/StringOrObjectAsStringConverterTests.cs diff --git a/src/Auth0.AuthenticationApi/Models/UserInfo.cs b/src/Auth0.AuthenticationApi/Models/UserInfo.cs index 78ddd3faa..8b56df489 100644 --- a/src/Auth0.AuthenticationApi/Models/UserInfo.cs +++ b/src/Auth0.AuthenticationApi/Models/UserInfo.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using Auth0.Core.Serialization; namespace Auth0.AuthenticationApi.Models { @@ -158,6 +159,7 @@ public class UserInfo /// ISO 3166-1 Alpha-2 country code in uppercase, separated by a dash. /// [JsonProperty("locale")] + [JsonConverter(typeof(StringOrObjectAsStringConverter))] public string Locale { get; set; } /// diff --git a/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs b/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs new file mode 100644 index 000000000..95c8b2841 --- /dev/null +++ b/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs @@ -0,0 +1,37 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Auth0.Core.Serialization +{ + internal class StringOrObjectAsStringConverter : JsonConverter + { + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override bool CanConvert(Type objectType) + { + return true; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var instance = ""; + + if (reader.TokenType == JsonToken.String) + { + instance = reader.Value?.ToString(); + } + else if (reader.TokenType == JsonToken.StartObject) + { + instance = JObject.Load(reader).ToString(); + } + + return instance; + } + } +} \ No newline at end of file diff --git a/src/Auth0.ManagementApi/Models/User.cs b/src/Auth0.ManagementApi/Models/User.cs index 1c64c06b3..51876cbc5 100644 --- a/src/Auth0.ManagementApi/Models/User.cs +++ b/src/Auth0.ManagementApi/Models/User.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Auth0.Core.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -47,6 +48,7 @@ public class User : UserBase /// Returned for the Facebook, Google, and Microsoft social providers. /// [JsonProperty("locale")] + [JsonConverter(typeof(StringOrObjectAsStringConverter))] public string Locale { get; set; } /// diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs index efe86149f..9b0ea3ea8 100644 --- a/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs +++ b/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs @@ -71,6 +71,25 @@ public void Can_read_standard_claims() userInfo.UpdatedAt.Should().Be(new DateTime(2009, 1, 29, 12, 48, 53)); } + [Fact] + public void Can_read_custom_locale_claim() + { + var jsonPayload = @"{ + 'sub': '123456', + 'locale': { + 'country': 'US', + 'language': 'en' + }, + 'updated_at': 1233233333 +}"; + var userInfo = GetUserInfoFromJsonPayload(jsonPayload); + + userInfo.UserId.Should().Be("123456"); + userInfo.Locale.Should().NotBeNull(); + JObject.Parse(userInfo.Locale).GetValue("country")!.Value().Should().Be("US"); + JObject.Parse(userInfo.Locale).GetValue("language")!.Value().Should().Be("en"); + } + [Fact] public void Missing_values_are_null() { diff --git a/tests/Auth0.Core.UnitTests/StringOrObjectAsStringConverterTests.cs b/tests/Auth0.Core.UnitTests/StringOrObjectAsStringConverterTests.cs new file mode 100644 index 000000000..75cd6e501 --- /dev/null +++ b/tests/Auth0.Core.UnitTests/StringOrObjectAsStringConverterTests.cs @@ -0,0 +1,44 @@ +using Auth0.Core.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace Auth0.Core.UnitTests; + +public class StringOrObjectAsStringConverterTests +{ + internal class StringOrObjectAsStringConverterData + { + [JsonProperty("value")] + [JsonConverter(typeof(StringOrObjectAsStringConverter))] + public string Value { get; set; } + } + [Fact] + public void Should_deserialize_string() + { + var content = "{ 'value': 'test' }"; + + var parsed = JsonConvert.DeserializeObject(content); + + Assert.Equal("test", parsed.Value); + } + + [Fact] + public void Should_deserialize_object_as_string() + { + var content = "{ 'value': { 'innerValue': 'test' } }"; + + var parsed = JsonConvert.DeserializeObject(content); + + Assert.Equal("{\n \"innerValue\": \"test\"\n}", parsed.Value); + } + + [Fact] + public void Should_deserialize_when_omitted() + { + var content = "{}"; + + var parsed = JsonConvert.DeserializeObject(content); + + Assert.Null(parsed.Value); + } +}