mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Merge pull request #2328 from DuendeSoftware/pg/cherry-pick-changes
Cherry Pick : Fix nullability issues with ClaimRecord and ClaimsPrincipalRecord (#2323) into BFF 4.0.x
This commit is contained in:
commit
34a222d7ba
8 changed files with 113 additions and 18 deletions
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Duende.Bff.Blazor.Client;
|
||||
namespace Duende.Bff.Blazor.Client.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Serialization friendly claim.
|
||||
|
|
@ -28,13 +28,13 @@ internal class ClaimRecord()
|
|||
/// The type
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = default!;
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The value
|
||||
/// </summary>
|
||||
[JsonPropertyName("value")]
|
||||
public object Value { get; init; } = default!;
|
||||
public object Value { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The value type
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Security.Claims;
|
||||
using Duende.Bff.Blazor.Client;
|
||||
|
||||
namespace Duende.Bff.Internal;
|
||||
namespace Duende.Bff.Blazor.Client.Internals;
|
||||
|
||||
internal static class ClaimRecordExtensions
|
||||
{
|
||||
|
|
@ -13,8 +12,13 @@ internal static class ClaimRecordExtensions
|
|||
/// </summary>
|
||||
public static ClaimsPrincipal ToClaimsPrincipal(this ClaimsPrincipalRecord principal)
|
||||
{
|
||||
var claims = principal.Claims.Select(x => new Claim(x.Type, x.Value.ToString() ?? string.Empty, x.ValueType ?? ClaimValueTypes.String))
|
||||
.ToArray();
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
var claims = principal.Claims is null
|
||||
? []
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
: principal.Claims.Select(x => new Claim(x.Type ?? string.Empty, x.Value?.ToString() ?? string.Empty, x.ValueType ?? ClaimValueTypes.String)).ToArray();
|
||||
|
||||
var id = new ClaimsIdentity(claims, principal.AuthenticationType, principal.NameClaimType,
|
||||
principal.RoleClaimType);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.Bff.Blazor.Client;
|
||||
|
||||
namespace Duende.Bff.Internal;
|
||||
namespace Duende.Bff.Blazor.Client.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Serialization friendly ClaimsPrincipal
|
||||
|
|
@ -28,5 +26,5 @@ internal class ClaimsPrincipalRecord
|
|||
/// <summary>
|
||||
/// The claims
|
||||
/// </summary>
|
||||
public ClaimRecord[] Claims { get; init; } = default!;
|
||||
public ClaimRecord[] Claims { get; init; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Claims;
|
||||
using Duende.Bff.Internal;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Duende.Bff.Internal;
|
|||
internal class ClaimRecord()
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="value"></param>
|
||||
|
|
@ -25,13 +25,13 @@ internal class ClaimRecord()
|
|||
/// The type
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; init; } = default!;
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The value
|
||||
/// </summary>
|
||||
[JsonPropertyName("value")]
|
||||
public object Value { get; init; } = default!;
|
||||
public object Value { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The value type
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ internal static class ClaimRecordExtensions
|
|||
/// </summary>
|
||||
public static ClaimsPrincipal ToClaimsPrincipal(this ClaimsPrincipalRecord principal)
|
||||
{
|
||||
var claims = principal.Claims.Select(x => new Claim(x.Type, x.Value.ToString() ?? string.Empty, x.ValueType ?? ClaimValueTypes.String))
|
||||
.ToArray();
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
var claims = principal.Claims is null
|
||||
? []
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
: principal.Claims.Select(x => new Claim(x.Type ?? string.Empty, x.Value?.ToString() ?? string.Empty, x.ValueType ?? ClaimValueTypes.String)).ToArray();
|
||||
|
||||
var id = new ClaimsIdentity(claims, principal.AuthenticationType, principal.NameClaimType,
|
||||
principal.RoleClaimType);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,5 +26,5 @@ internal class ClaimsPrincipalRecord
|
|||
/// <summary>
|
||||
/// The claims
|
||||
/// </summary>
|
||||
public ClaimRecord[] Claims { get; init; } = default!;
|
||||
public ClaimRecord[] Claims { get; init; } = [];
|
||||
}
|
||||
|
|
|
|||
89
bff/test/Bff.Tests/Internal/ClaimsPrincipalRecordTests.cs
Normal file
89
bff/test/Bff.Tests/Internal/ClaimsPrincipalRecordTests.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Duende.Bff.Internal;
|
||||
|
||||
namespace Duende.Bff.Tests.Internal;
|
||||
|
||||
public class ClaimsPrincipalRecordTests
|
||||
{
|
||||
[Fact]
|
||||
public void Can_convert_between_ClaimsPrincipal_and_ClaimsPrincipalRecord()
|
||||
{
|
||||
var original = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim("sub", "123"),
|
||||
new Claim("name", "Alice"),
|
||||
new Claim("role", "admin")
|
||||
}, "TestAuthType", "name", "role"));
|
||||
|
||||
var record = original.ToClaimsPrincipalLite();
|
||||
var reconstructed = record.ToClaimsPrincipal();
|
||||
|
||||
reconstructed.Identity!.AuthenticationType.ShouldBe(original.Identity!.AuthenticationType);
|
||||
reconstructed.Identity!.Name.ShouldBe(original.Identity!.Name);
|
||||
|
||||
var originalClaims = original.Claims.ToDictionary(c => c.Type, c => c.Value);
|
||||
var reconstructedClaims = reconstructed.Claims.ToDictionary(c => c.Type, c => c.Value);
|
||||
|
||||
originalClaims.Count.ShouldBe(reconstructedClaims.Count);
|
||||
foreach (var kvp in originalClaims)
|
||||
{
|
||||
reconstructedClaims.ShouldContainKey(kvp.Key);
|
||||
reconstructedClaims[kvp.Key].ShouldBe(kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Can_convert_default_ClaimsPrincipalRecord()
|
||||
{
|
||||
var original = new ClaimsPrincipalRecord();
|
||||
|
||||
Should.NotThrow(() => original.ToClaimsPrincipal());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Can_convert_ClaimsPrincipalRecord_with_default_ClaimsRecord()
|
||||
{
|
||||
var original = new ClaimsPrincipalRecord
|
||||
{
|
||||
Claims =
|
||||
[
|
||||
new ClaimRecord()
|
||||
]
|
||||
};
|
||||
|
||||
Should.NotThrow(() => original.ToClaimsPrincipal());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToClaimsPrincipal_handles_null_Claims_from_deserialization()
|
||||
{
|
||||
var serializedClaimsPrincipal = """{"Claims": null}""";
|
||||
var record = JsonSerializer.Deserialize<ClaimsPrincipalRecord>(serializedClaimsPrincipal);
|
||||
|
||||
Should.NotThrow(() => record!.ToClaimsPrincipal());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToClaimsPrincipal_handles_null_Type_in_ClaimRecord_from_deserialization()
|
||||
{
|
||||
var serializedClaimsPrincipal = """{"Claims": [{"type": null, "value": "test"}]}""";
|
||||
var record = JsonSerializer.Deserialize<ClaimsPrincipalRecord>(serializedClaimsPrincipal);
|
||||
|
||||
var principal = record!.ToClaimsPrincipal();
|
||||
principal.Claims.Single().Type.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToClaimsPrincipal_handles_null_Value_in_ClaimRecord_from_deserialization()
|
||||
{
|
||||
var serializedClaimsPrincipal = """{"Claims": [{"type": "sub", "value": null}]}""";
|
||||
var record = JsonSerializer.Deserialize<ClaimsPrincipalRecord>(serializedClaimsPrincipal);
|
||||
|
||||
var principal = record!.ToClaimsPrincipal();
|
||||
principal.Claims.Single().Value.ShouldBe(string.Empty);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue