diff --git a/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs b/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs index 4e7aa3371..152af9e3e 100644 --- a/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs +++ b/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs @@ -117,11 +117,11 @@ internal static class UserManagerExtensions if (values.Properties != null) { - RemoveClaims(x => x.Type.StartsWith(SquidexClaimTypes.CustomPrefix, StringComparison.OrdinalIgnoreCase)); + RemoveClaims(x => x.Type.StartsWith(SquidexClaimTypes.Custom, StringComparison.OrdinalIgnoreCase)); foreach (var (name, value) in values.Properties) { - AddClaim($"{SquidexClaimTypes.CustomPrefix}:{name}", value); + AddClaim(SquidexClaimTypes.Custom, $"{name}={value}"); } } diff --git a/backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs b/backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs index e37b9ab61..878a23768 100644 --- a/backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs +++ b/backend/src/Squidex.Shared/Identity/SquidexClaimTypes.cs @@ -15,7 +15,7 @@ namespace Squidex.Shared.Identity public const string ConsentForEmails = "urn:squidex:consent:emails"; - public const string CustomPrefix = "urn:squidex:custom"; + public const string Custom = "urn:squidex:custom"; public const string DisplayName = "urn:squidex:name"; @@ -33,6 +33,6 @@ namespace Squidex.Shared.Identity public const string TotalApps = "urn:squidex:internal:totalApps"; - public const string UIPrefix = "urn:squidex:custom"; + public const string UIProperty = "urn:squidex:ui"; } } diff --git a/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs b/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs index 5788ce040..1b7ba22bb 100644 --- a/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs +++ b/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Claims; +using System.Text.RegularExpressions; using Squidex.Infrastructure.Security; namespace Squidex.Shared.Identity @@ -18,6 +19,9 @@ namespace Squidex.Shared.Identity { private const string ClientPrefix = "client_"; + private static readonly Regex KeyValueAppClaim = new Regex("(?[\\S]+),(?[^=]+)=(?.+)", RegexOptions.Compiled); + private static readonly Regex KeyValueClaim = new Regex("(?[^=]+)=(?.+)", RegexOptions.Compiled); + public static PermissionSet Permissions(this IEnumerable user) { var permissions = user.GetClaims(SquidexClaimTypes.Permissions).Select(x => x.Value); @@ -106,7 +110,7 @@ namespace Squidex.Shared.Identity public static IEnumerable<(string Name, string Value)> GetCustomProperties(this IEnumerable user) { - var prefix = SquidexClaimTypes.CustomPrefix; + var prefix = $"{SquidexClaimTypes.Custom}:"; foreach (var claim in user) { @@ -114,16 +118,25 @@ namespace Squidex.Shared.Identity if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - var name = type[(prefix.Length + 1)..].ToString(); + var name = type[prefix.Length..].ToString(); + + yield return (name.Trim(), claim.Value.Trim()); + } + else if (type.Equals(SquidexClaimTypes.Custom, StringComparison.OrdinalIgnoreCase)) + { + var match = KeyValueClaim.Match(claim.Value); - yield return (name, claim.Value); + if (match.Success) + { + yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim()); + } } } } public static IEnumerable<(string Name, string Value)> GetUIProperties(this IEnumerable user, string app) { - var prefix = $"{SquidexClaimTypes.UIPrefix}:{app}"; + var prefix = $"{SquidexClaimTypes.UIProperty}:{app}:"; foreach (var claim in user) { @@ -131,9 +144,23 @@ namespace Squidex.Shared.Identity if (type.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - var name = type[(prefix.Length + 1)..].ToString(); + var name = type[prefix.Length..].ToString(); + + yield return (name.Trim(), claim.Value.Trim()); + } + else if (type.Equals(SquidexClaimTypes.UIProperty, StringComparison.OrdinalIgnoreCase)) + { + if (!claim.Value.StartsWith(app, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var match = KeyValueAppClaim.Match(claim.Value); - yield return (name, claim.Value); + if (match.Success) + { + yield return (match.Groups["Key"].Value.Trim(), match.Groups["Value"].Value.Trim()); + } } } } diff --git a/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs b/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs new file mode 100644 index 000000000..b849ac04e --- /dev/null +++ b/backend/tests/Squidex.Domain.Users.Tests/SquidexClaimExtensionsTests.cs @@ -0,0 +1,90 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Security.Claims; +using Squidex.Shared.Identity; + +namespace Squidex.Domain.Users; + +public class SquidexClaimExtensionsTests +{ + [Fact] + public void Should_extract_custom_claims_v1() + { + var source = new[] + { + new Claim($"{SquidexClaimTypes.Custom}:key1", "value1"), + new Claim($"{SquidexClaimTypes.Custom}:key2", "value2 "), + }; + + var result = source.GetCustomProperties(); + + Assert.Equal(new[] + { + ("key1", "value1"), + ("key2", "value2") + }, result.ToArray()); + } + + [Fact] + public void Should_extract_custom_claims_v2() + { + var source = new[] + { + new Claim(SquidexClaimTypes.Custom, "key1=value1"), + new Claim(SquidexClaimTypes.Custom, "key2=value2 "), + new Claim(SquidexClaimTypes.Custom, "value "), + }; + + var result = source.GetCustomProperties(); + + Assert.Equal(new[] + { + ("key1", "value1"), + ("key2", "value2") + }, result.ToArray()); + } + + [Fact] + public void Should_extract_ui_claims_v1() + { + var source = new[] + { + new Claim($"{SquidexClaimTypes.UIProperty}:app1:key1", "value1"), + new Claim($"{SquidexClaimTypes.UIProperty}:app1:key2", "value2 "), + new Claim($"{SquidexClaimTypes.UIProperty}:app2:key3", "value3"), + }; + + var result = source.GetUIProperties("app1"); + + Assert.Equal(new[] + { + ("key1", "value1"), + ("key2", "value2") + }, result.ToArray()); + } + + [Fact] + public void Should_extract_ui_claims_v2() + { + var source = new[] + { + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,value"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key1=value1"), + new Claim($"{SquidexClaimTypes.UIProperty}", "app1,key2=value2 "), + new Claim($"{SquidexClaimTypes.UIProperty}", "app2,key3=value3"), + }; + + var result = source.GetUIProperties("app1"); + + Assert.Equal(new[] + { + ("key1", "value1"), + ("key2", "value2") + }, result.ToArray()); + } +}