From 5c2e03bcfc14fd99b86108276e67e4e88d529f50 Mon Sep 17 00:00:00 2001 From: Erwin van der Valk Date: Tue, 4 Feb 2025 15:39:28 +0100 Subject: [PATCH] Refactored the use of ClaimsLite and ClaimsRecord into a single type --- bff/migrations/Directory.Build.props | 3 ++ .../UserSessionDb/UserSessionDb.csproj | 4 +- bff/samples/Apis/Api.DPoP/Api.DPoP.csproj | 2 +- bff/samples/Bff.EF/Duende.BFF.db | Bin 0 -> 40960 bytes bff/samples/Bff/Bff.csproj | 2 +- bff/samples/Directory.Build.props | 19 ++++++++ bff/samples/Hosts.Tests/Hosts.Tests.csproj | 2 +- .../Duende.Bff.Blazor.Client/ClaimRecord.cs | 41 ++++++++++++++++++ .../Duende.Bff.Blazor.Client.csproj | 5 +-- .../Internals/GetUserService.cs | 3 -- .../Internals/PersistentUserService.cs | 2 +- .../BffServerAuthenticationStateProvider.cs | 6 +-- .../User/DefaultUserService.cs | 16 +------ .../AuthenticationTicketExtensions.cs | 2 +- .../Shared/{ClaimLite.cs => ClaimRecord.cs} | 20 ++++++++- ...Extensions.cs => ClaimRecordExtensions.cs} | 13 +++--- ...ncipalLite.cs => ClaimsPrincipalRecord.cs} | 4 +- .../Endpoints/DpopRemoteEndpointTests.cs | 3 +- .../Endpoints/RemoteEndpointTests.cs | 7 +-- .../Duende.Bff.Tests/TestFramework/Records.cs | 9 ---- 20 files changed, 105 insertions(+), 58 deletions(-) create mode 100644 bff/samples/Bff.EF/Duende.BFF.db create mode 100644 bff/src/Duende.Bff.Blazor.Client/ClaimRecord.cs rename bff/src/Duende.Bff/Shared/{ClaimLite.cs => ClaimRecord.cs} (50%) rename bff/src/Duende.Bff/Shared/{ClaimsLiteExtensions.cs => ClaimRecordExtensions.cs} (79%) rename bff/src/Duende.Bff/Shared/{ClaimsPrincipalLite.cs => ClaimsPrincipalRecord.cs} (86%) diff --git a/bff/migrations/Directory.Build.props b/bff/migrations/Directory.Build.props index 90f2c11d2..e2167e09a 100644 --- a/bff/migrations/Directory.Build.props +++ b/bff/migrations/Directory.Build.props @@ -1,4 +1,7 @@ + + net9.0 + diff --git a/bff/migrations/UserSessionDb/UserSessionDb.csproj b/bff/migrations/UserSessionDb/UserSessionDb.csproj index 3f8805859..69f93e7ac 100644 --- a/bff/migrations/UserSessionDb/UserSessionDb.csproj +++ b/bff/migrations/UserSessionDb/UserSessionDb.csproj @@ -1,11 +1,11 @@ - net9.0 + net9.0 - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/bff/samples/Apis/Api.DPoP/Api.DPoP.csproj b/bff/samples/Apis/Api.DPoP/Api.DPoP.csproj index b6b74ea47..af4bfa95e 100644 --- a/bff/samples/Apis/Api.DPoP/Api.DPoP.csproj +++ b/bff/samples/Apis/Api.DPoP/Api.DPoP.csproj @@ -1,7 +1,7 @@ - net9.0 + net9.0 diff --git a/bff/samples/Bff.EF/Duende.BFF.db b/bff/samples/Bff.EF/Duende.BFF.db new file mode 100644 index 0000000000000000000000000000000000000000..96ce588851e3c9cf67369219f1695291b4e16ab8 GIT binary patch literal 40960 zcmeI)&u`jh7{GBG+BmQ#^fLLjzVa5SC_iKp5Np;$8S^?60x6g(IZPG>w_;5o!H(MQ zMy4Hh*kym-dfIXS#ad(p@-xV)={j;Vo_LTcd zET%jWLQ#~Md?w{HESlW76Yj`YHL^W#Gp6kPda;(uDv89OO6u|Y`ue~3{ywi8R=&@w6 zW)*Bplqx@1$HJJ?!VuMpFrw`Y@z5BBGtz^`s-}Az>eYjpmo1#1pLSYJU*=G0p1Dr_ z^2IN1%P+N^QD&ufG<0H=QlnCO)|i{w0zqeIJ=M_I62>^8;m);IsV)1J)im9aoEMAr zD{L9<&Fbg%CJeQBAI2@x17k(g>oV<=h0;E7-$fJFJ}Y4vWZ_hUy6L04EBfZ9dg3=< zoVwn{sa!)io_lfWc3acCE2E6;!uMrMOx1nZl~Za*hcTmlif-SX zh&DZ`*OEdOLyo?@C2Z@dRTGt}Eh>$2Srn_4x?L;C|+4r_dvhNE+%3y;r_hxlgOV&850uFU#6>Qu{`+jZZ} zJsaM~H>28})@#?F6LdJ_6PH^{rU%C{P0#1mtLktN9LM_cu=A=vy!pMKI-cL_zngh5 zJ~=Ei>&Q2q5#~5%Fi;aYOpnr>K#}1Jd5`+N_GQa|?)HOs%c-!kYo+*#{&QYchBwt{ z7B(;a-f-WUc^xM+^Kh^RUJX8|1_f2qGa2=2dxloI*LpqKJU9~jm{U;U1JWXs_~r#B zrgCUwcg|eTZ=RjsWLXP3a$iwWe+L^B1Q0*~0R#|0009ILKmY**5LhmOZ`8O_-8FY} z+ve`JnH8Db{@$bgthuw7$?oOz-)@@bzG(*Qe>L@9kq-(22q1s}0tg_000IagfB*sr zET6#FYP_h+F93E;x#G{{a`{|l;#V(^cFY|!_<_Hg`bUuu3IYfqfB*srAb;Kz3w)6}E1Q0*~ z0R#|0009ILKmdVIfc1YU!7Bm?Ab - net9.0 + net9.0 Bff enable diff --git a/bff/samples/Directory.Build.props b/bff/samples/Directory.Build.props index 90f2c11d2..7e64d18da 100644 --- a/bff/samples/Directory.Build.props +++ b/bff/samples/Directory.Build.props @@ -1,4 +1,23 @@ + + + net9.0 + diff --git a/bff/samples/Hosts.Tests/Hosts.Tests.csproj b/bff/samples/Hosts.Tests/Hosts.Tests.csproj index 153ba681f..dea8c45b4 100644 --- a/bff/samples/Hosts.Tests/Hosts.Tests.csproj +++ b/bff/samples/Hosts.Tests/Hosts.Tests.csproj @@ -1,7 +1,7 @@ - net9.0 + net9.0 enable enable false diff --git a/bff/src/Duende.Bff.Blazor.Client/ClaimRecord.cs b/bff/src/Duende.Bff.Blazor.Client/ClaimRecord.cs new file mode 100644 index 000000000..4ef129550 --- /dev/null +++ b/bff/src/Duende.Bff.Blazor.Client/ClaimRecord.cs @@ -0,0 +1,41 @@ +using System.Text.Json.Serialization; + +namespace Duende.Bff; + +/// +/// Serialization friendly claim. +/// +/// Note, this is a copy of the ClaimRecord class from Duende.Bff, but since we can't create a reference to it, we need to copy it here. +/// We also can't link to it (as we do with the extensions) because we had to make it public. +/// +internal class ClaimRecord() +{ + /// + /// Serialization friendly claim + /// + /// The type + /// The Value + internal ClaimRecord(string type, object value) : this() + { + Type = type; + Value = value; + } + + /// + /// The type + /// + [JsonPropertyName("type")] + public string Type { get; init; } = default!; + + /// + /// The value + /// + [JsonPropertyName("value")] + public object Value { get; init; } = default!; + + /// + /// The value type + /// + [JsonPropertyName("valueType")] + public string? ValueType { get; init; } +} diff --git a/bff/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj b/bff/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj index d575ef270..ec07ac15b 100644 --- a/bff/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj +++ b/bff/src/Duende.Bff.Blazor.Client/Duende.Bff.Blazor.Client.csproj @@ -8,9 +8,8 @@ - - - + + diff --git a/bff/src/Duende.Bff.Blazor.Client/Internals/GetUserService.cs b/bff/src/Duende.Bff.Blazor.Client/Internals/GetUserService.cs index b5629fc15..59b6b06c3 100644 --- a/bff/src/Duende.Bff.Blazor.Client/Internals/GetUserService.cs +++ b/bff/src/Duende.Bff.Blazor.Client/Internals/GetUserService.cs @@ -58,9 +58,6 @@ public async ValueTask GetUserAsync(bool useCache = true) return _cachedUser; } - // TODO - Consider using ClaimLite instead here - record ClaimRecord(string Type, object Value); - internal async Task FetchUser() { try diff --git a/bff/src/Duende.Bff.Blazor.Client/Internals/PersistentUserService.cs b/bff/src/Duende.Bff.Blazor.Client/Internals/PersistentUserService.cs index af2d98ddc..27205cf44 100644 --- a/bff/src/Duende.Bff.Blazor.Client/Internals/PersistentUserService.cs +++ b/bff/src/Duende.Bff.Blazor.Client/Internals/PersistentUserService.cs @@ -17,7 +17,7 @@ internal class PersistentUserService(PersistentComponentState state, ILogger public ClaimsPrincipal GetPersistedUser() { - if (!state.TryTakeFromJson(nameof(ClaimsPrincipalLite), out var lite) || lite is null) + if (!state.TryTakeFromJson(nameof(ClaimsPrincipalRecord), out var lite) || lite is null) { logger.LogDebug("Failed to load persisted user."); return new ClaimsPrincipal(new ClaimsIdentity()); diff --git a/bff/src/Duende.Bff.Blazor/BffServerAuthenticationStateProvider.cs b/bff/src/Duende.Bff.Blazor/BffServerAuthenticationStateProvider.cs index ade0a888c..89760b5e9 100644 --- a/bff/src/Duende.Bff.Blazor/BffServerAuthenticationStateProvider.cs +++ b/bff/src/Duende.Bff.Blazor/BffServerAuthenticationStateProvider.cs @@ -73,14 +73,14 @@ private async Task OnPersistingAsync() var authenticationState = await _authenticationStateTask; var claims = authenticationState.User.Claims - .Select(c => new ClaimLite + .Select(c => new ClaimRecord { Type = c.Type, Value = c.Value?.ToString() ?? string.Empty, ValueType = c.ValueType == ClaimValueTypes.String ? null : c.ValueType }).ToArray(); - var principal = new ClaimsPrincipalLite + var principal = new ClaimsPrincipalRecord { AuthenticationType = authenticationState.User.Identity!.AuthenticationType, NameClaimType = authenticationState.User.Identities.First().NameClaimType, @@ -90,7 +90,7 @@ private async Task OnPersistingAsync() _logger.LogDebug("Persisting Authentication State"); - _state.PersistAsJson(nameof(ClaimsPrincipalLite), principal); + _state.PersistAsJson(nameof(ClaimsPrincipalRecord), principal); } diff --git a/bff/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs b/bff/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs index c15a3957b..a057b662b 100644 --- a/bff/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs +++ b/bff/src/Duende.Bff/EndpointServices/User/DefaultUserService.cs @@ -8,6 +8,7 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Text.Encodings.Web; @@ -134,20 +135,5 @@ protected virtual Task> GetManagementClaimsAsync(HttpCo return Task.FromResult((IEnumerable)claims); } - - /// - /// Serialization-friendly claim - /// - /// - /// - protected record ClaimRecord(string Type, object Value) - { - /// - [JsonPropertyName("type")] - public string Type { get; init; } = Type; - /// - [JsonPropertyName("value")] - public object Value { get; init; } = Value; - } } \ No newline at end of file diff --git a/bff/src/Duende.Bff/Extensions/AuthenticationTicketExtensions.cs b/bff/src/Duende.Bff/Extensions/AuthenticationTicketExtensions.cs index 2464667be..b23cdcfe3 100644 --- a/bff/src/Duende.Bff/Extensions/AuthenticationTicketExtensions.cs +++ b/bff/src/Duende.Bff/Extensions/AuthenticationTicketExtensions.cs @@ -149,7 +149,7 @@ internal class AuthenticationTicketLite /// /// The user /// - public ClaimsPrincipalLite User { get; set; } = default!; + public ClaimsPrincipalRecord User { get; set; } = default!; /// /// The items diff --git a/bff/src/Duende.Bff/Shared/ClaimLite.cs b/bff/src/Duende.Bff/Shared/ClaimRecord.cs similarity index 50% rename from bff/src/Duende.Bff/Shared/ClaimLite.cs rename to bff/src/Duende.Bff/Shared/ClaimRecord.cs index 6a1189c45..426b0ee01 100644 --- a/bff/src/Duende.Bff/Shared/ClaimLite.cs +++ b/bff/src/Duende.Bff/Shared/ClaimRecord.cs @@ -1,25 +1,41 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. +using System.Text.Json.Serialization; + namespace Duende.Bff; /// /// Serialization friendly claim /// -internal class ClaimLite +public class ClaimRecord() { + /// + /// + /// + /// + /// + public ClaimRecord(string type, object value) : this() + { + Type = type; + Value = value; + } + /// /// The type /// + [JsonPropertyName("type")] public string Type { get; init; } = default!; /// /// The value /// - public string Value { get; init; } = default!; + [JsonPropertyName("value")] + public object Value { get; init; } = default!; /// /// The value type /// + [JsonPropertyName("valueType")] public string? ValueType { get; init; } } \ No newline at end of file diff --git a/bff/src/Duende.Bff/Shared/ClaimsLiteExtensions.cs b/bff/src/Duende.Bff/Shared/ClaimRecordExtensions.cs similarity index 79% rename from bff/src/Duende.Bff/Shared/ClaimsLiteExtensions.cs rename to bff/src/Duende.Bff/Shared/ClaimRecordExtensions.cs index da7073af4..dafb25eda 100644 --- a/bff/src/Duende.Bff/Shared/ClaimsLiteExtensions.cs +++ b/bff/src/Duende.Bff/Shared/ClaimRecordExtensions.cs @@ -1,19 +1,18 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System.Linq; using System.Security.Claims; namespace Duende.Bff; -internal static class ClaimsLiteExtensions +internal static class ClaimRecordExtensions { /// /// Converts a ClaimsPrincipalLite to ClaimsPrincipal /// - public static ClaimsPrincipal ToClaimsPrincipal(this ClaimsPrincipalLite principal) + public static ClaimsPrincipal ToClaimsPrincipal(this ClaimsPrincipalRecord principal) { - var claims = principal.Claims.Select(x => new Claim(x.Type, x.Value, x.ValueType ?? ClaimValueTypes.String)) + var claims = principal.Claims.Select(x => new Claim(x.Type, x.Value.ToString() ?? string.Empty, x.ValueType ?? ClaimValueTypes.String)) .ToArray(); var id = new ClaimsIdentity(claims, principal.AuthenticationType, principal.NameClaimType, principal.RoleClaimType); @@ -24,17 +23,17 @@ public static ClaimsPrincipal ToClaimsPrincipal(this ClaimsPrincipalLite princip /// /// Converts a ClaimsPrincipal to ClaimsPrincipalLite /// - public static ClaimsPrincipalLite ToClaimsPrincipalLite(this ClaimsPrincipal principal) + public static ClaimsPrincipalRecord ToClaimsPrincipalLite(this ClaimsPrincipal principal) { var claims = principal.Claims.Select( - x => new ClaimLite + x => new ClaimRecord { Type = x.Type, Value = x.Value, ValueType = x.ValueType == ClaimValueTypes.String ? null : x.ValueType }).ToArray(); - return new ClaimsPrincipalLite + return new ClaimsPrincipalRecord { AuthenticationType = principal.Identity!.AuthenticationType, NameClaimType = principal.Identities.First().NameClaimType, diff --git a/bff/src/Duende.Bff/Shared/ClaimsPrincipalLite.cs b/bff/src/Duende.Bff/Shared/ClaimsPrincipalRecord.cs similarity index 86% rename from bff/src/Duende.Bff/Shared/ClaimsPrincipalLite.cs rename to bff/src/Duende.Bff/Shared/ClaimsPrincipalRecord.cs index cfe2f9c2e..2d6a2b3aa 100644 --- a/bff/src/Duende.Bff/Shared/ClaimsPrincipalLite.cs +++ b/bff/src/Duende.Bff/Shared/ClaimsPrincipalRecord.cs @@ -6,7 +6,7 @@ namespace Duende.Bff; /// /// Serialization friendly ClaimsPrincipal /// -internal class ClaimsPrincipalLite +internal class ClaimsPrincipalRecord { /// /// The authentication type @@ -26,5 +26,5 @@ internal class ClaimsPrincipalLite /// /// The claims /// - public ClaimLite[] Claims { get; init; } = default!; + public ClaimRecord[] Claims { get; init; } = default!; } \ No newline at end of file diff --git a/bff/test/Duende.Bff.Tests/Endpoints/DpopRemoteEndpointTests.cs b/bff/test/Duende.Bff.Tests/Endpoints/DpopRemoteEndpointTests.cs index a4a184da5..b1d73d9dd 100644 --- a/bff/test/Duende.Bff.Tests/Endpoints/DpopRemoteEndpointTests.cs +++ b/bff/test/Duende.Bff.Tests/Endpoints/DpopRemoteEndpointTests.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography; using System.Text.Json; using System.Threading.Tasks; +using Duende.Bff.Tests.TestFramework; using Duende.Bff.Tests.TestHosts; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; @@ -35,7 +36,7 @@ public DpopRemoteEndpointTests(ITestOutputHelper output) : base(output) [Fact] public async Task test_dpop() { - var apiResult = await BffHost.BrowserClient.CallBffHostApi( + ApiResponse apiResult = await BffHost.BrowserClient.CallBffHostApi( url: BffHost.Url("/api_client/test") ); diff --git a/bff/test/Duende.Bff.Tests/Endpoints/RemoteEndpointTests.cs b/bff/test/Duende.Bff.Tests/Endpoints/RemoteEndpointTests.cs index adf7596f6..00cd4e956 100644 --- a/bff/test/Duende.Bff.Tests/Endpoints/RemoteEndpointTests.cs +++ b/bff/test/Duende.Bff.Tests/Endpoints/RemoteEndpointTests.cs @@ -3,14 +3,9 @@ using Duende.Bff.Tests.TestFramework; using Duende.Bff.Tests.TestHosts; -using System; -using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Http.Json; -using System.Threading.Tasks; -using Shouldly; -using Xunit; +using System.Text.Json; using Xunit.Abstractions; namespace Duende.Bff.Tests.Endpoints diff --git a/bff/test/Duende.Bff.Tests/TestFramework/Records.cs b/bff/test/Duende.Bff.Tests/TestFramework/Records.cs index be74df303..80b7e4d14 100644 --- a/bff/test/Duende.Bff.Tests/TestFramework/Records.cs +++ b/bff/test/Duende.Bff.Tests/TestFramework/Records.cs @@ -14,13 +14,4 @@ public record JsonRecord(string Type, JsonElement Value) [JsonPropertyName("value")] public JsonElement Value { get; } = Value; } - - public record ClaimRecord(string Type, string Value) - { - [JsonPropertyName("type")] - public string Type { get; } = Type; - - [JsonPropertyName("value")] - public string Value { get; } = Value; - } }