diff --git a/backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs b/backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs index 73c29f326..23a7d1bfd 100644 --- a/backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs +++ b/backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Text.Json; using NodaTime; namespace Squidex.Infrastructure.Json.Objects; @@ -171,6 +172,44 @@ public readonly struct JsonValue : IEquatable return v; } + static JsonValue Parse(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.False: + return False; + case JsonValueKind.True: + return True; + case JsonValueKind.Number: + return Create(element.GetDouble()); + case JsonValueKind.String: + return Create(element.GetString()); + case JsonValueKind.Null: + return Null; + case JsonValueKind.Object: + var obj = Object(); + + foreach (var property in element.EnumerateObject()) + { + obj.Add(property.Name, Parse(property.Value)); + } + + return obj; + case JsonValueKind.Array: + var arr = Array(); + + foreach (var item in element.EnumerateArray()) + { + arr.Add(Parse(item)); + } + + return arr; + default: + ThrowHelper.NotSupportedException(); + return default!; + } + } + switch (value) { case Guid typed: @@ -197,6 +236,10 @@ public readonly struct JsonValue : IEquatable return typed; case JsonObject typed: return typed; + case JsonElement typed: + return Parse(typed); + case JsonDocument typed: + return Parse(typed.RootElement); case IReadOnlyDictionary typed: return Create(typed); } diff --git a/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs b/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs index 1b7ba22bb..90d55831f 100644 --- a/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs +++ b/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs @@ -10,7 +10,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Claims; +using System.Text.Json; using System.Text.RegularExpressions; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Security; namespace Squidex.Shared.Identity @@ -134,10 +136,26 @@ namespace Squidex.Shared.Identity } } - public static IEnumerable<(string Name, string Value)> GetUIProperties(this IEnumerable user, string app) + public static IEnumerable<(string Name, JsonValue Value)> GetUIProperties(this IEnumerable user, string app) { var prefix = $"{SquidexClaimTypes.UIProperty}:{app}:"; + static JsonValue Parse(string value) + { + value = value.Trim(); + + try + { + var root = JsonDocument.Parse(value).RootElement; + + return JsonValue.Create(root); + } + catch + { + return JsonValue.Create(value); + } + } + foreach (var claim in user) { var type = GetType(claim); @@ -146,7 +164,7 @@ namespace Squidex.Shared.Identity { var name = type[prefix.Length..].ToString(); - yield return (name.Trim(), claim.Value.Trim()); + yield return (name.Trim(), Parse(claim.Value)); } else if (type.Equals(SquidexClaimTypes.UIProperty, StringComparison.OrdinalIgnoreCase)) { @@ -159,7 +177,7 @@ namespace Squidex.Shared.Identity if (match.Success) { - yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim()); + yield return (match.Groups["Key"].Value.Trim(), Parse(match.Groups["Value"].Value)); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index e5fdcabda..99dffd7c3 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -21,6 +21,7 @@ using Squidex.Infrastructure.Validation; using Squidex.Shared; using Squidex.Shared.Identity; using Squidex.Web; +using System.Text.Json; #pragma warning disable RECS0033 // Convert 'if' to '||' expression @@ -128,7 +129,7 @@ public sealed class AppDto : Resource foreach (var (key, value) in resources.Context.UserPrincipal.Claims.GetUIProperties(app.Name)) { - result.RoleProperties[key] = JsonValue.Create(value); + result.RoleProperties[key] = value; } if (resources.Includes(PermissionIds.ForApp(PermissionIds.AppContents, app.Name), permissions)) diff --git a/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs b/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs index b849ac04e..882df1c60 100644 --- a/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs +++ b/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Security.Claims; +using Squidex.Infrastructure.Json.Objects; using Squidex.Shared.Identity; namespace Squidex.Domain.Users; @@ -63,8 +64,8 @@ public class SquidexClaimExtensionsTests Assert.Equal(new[] { - ("key1", "value1"), - ("key2", "value2") + ("key1", JsonValue.Create("value1")), + ("key2", JsonValue.Create("value2")) }, result.ToArray()); } @@ -83,8 +84,38 @@ public class SquidexClaimExtensionsTests Assert.Equal(new[] { - ("key1", "value1"), - ("key2", "value2") + ("key1", JsonValue.Create("value1")), + ("key2", JsonValue.Create("value2")) + }, result.ToArray()); + } + + [Fact] + public void Should_extract_and_parse_values() + { + var source = new[] + { + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key1=null"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key2=true"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key3=false"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key4=42"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key5=42.5"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key6=string1"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key7=\"string2\""), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key8=\"string3\" "), + }; + + var result = source.GetUIProperties("app1"); + + Assert.Equal(new[] + { + ("key1", JsonValue.Null), + ("key2", JsonValue.True), + ("key3", JsonValue.False), + ("key4", JsonValue.Create(42)), + ("key5", JsonValue.Create(42.5)), + ("key6", JsonValue.Create("string1")), + ("key7", JsonValue.Create("string2")), + ("key8", JsonValue.Create("string3")), }, result.ToArray()); } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs index fe1a1e8c8..7be55e4fc 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs @@ -5,9 +5,11 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Text.Json; using NodaTime; #pragma warning disable xUnit2004 // Do not use equality check to test for boolean conditions +#pragma warning disable JSON001 // Invalid JSON pattern namespace Squidex.Infrastructure.Json.Objects; @@ -541,4 +543,78 @@ public class JsonObjectTests Assert.False(found); Assert.Equal(default, actual); } + + [Fact] + public void Should_convert_true_json_element() + { + var element = JsonValue.Create(JsonDocument.Parse("true").RootElement); + + Assert.Equal(JsonValue.True, element); + } + + [Fact] + public void Should_convert_false_json_element() + { + var element = JsonValue.Create(JsonDocument.Parse("false").RootElement); + + Assert.Equal(JsonValue.False, element); + } + + [Fact] + public void Should_convert_integer_json_element() + { + var element = JsonValue.Create(JsonDocument.Parse("42").RootElement); + + Assert.Equal(JsonValue.Create(42), element); + } + + [Fact] + public void Should_convert_number_json_element() + { + var element = JsonValue.Create(JsonDocument.Parse("42.5").RootElement); + + Assert.Equal(JsonValue.Create(42.5), element); + } + + [Fact] + public void Should_convert_null_json_element() + { + var element = JsonValue.Create(JsonDocument.Parse("null").RootElement); + + Assert.Equal(JsonValue.Null, element); + } + + [Fact] + public void Should_convert_string_json_element() + { + var element = JsonValue.Create(JsonDocument.Parse("\"string42\"").RootElement); + + Assert.Equal(JsonValue.Create("string42"), element); + } + + [Fact] + public void Should_convert_array_element() + { + var element = JsonValue.Create(JsonDocument.Parse("[1,2,3]").RootElement); + + Assert.Equal( + JsonValue.Array() + .Add(JsonValue.Create(1)) + .Add(JsonValue.Create(2)) + .Add(JsonValue.Create(3)), + element); + } + + [Fact] + public void Should_convert_object_element() + { + var element = JsonValue.Create(JsonDocument.Parse("{ \"key1\": 1, \"key2\": 2, \"key3\": 3 }").RootElement); + + Assert.Equal( + JsonValue.Object() + .Add("key1", JsonValue.Create(1)) + .Add("key2", JsonValue.Create(2)) + .Add("key3", JsonValue.Create(3)), + element); + } }