From ff57da7ec0f0d2f9de9e4d7a8389257b1c4dc136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Tue, 6 May 2025 16:44:54 +0200 Subject: [PATCH] Add AddClaim(s)/SetClaim(s) extensions accepting JsonNode instances --- .../Primitives/OpenIddictExtensions.cs | 444 +++++- .../Primitives/OpenIddictExtensionsTests.cs | 1203 ++++++++++++++--- 2 files changed, 1470 insertions(+), 177 deletions(-) diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs index b3f11936..4aec04ee 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs @@ -6,12 +6,14 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.Extensions.Primitives; namespace OpenIddict.Abstractions; @@ -1142,7 +1144,7 @@ public static class OpenIddictExtensions identity.AddClaim(new Claim( type : type, - value : value.ToString()!, + value : value.ToString(), valueType : GetClaimValueType(value), issuer : issuer, originalIssuer: issuer, @@ -1180,6 +1182,105 @@ public static class OpenIddictExtensions return principal; } + /// + /// Adds a claim to a given identity. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, JsonNode value) + => identity.AddClaim(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds a claim to a given principal. + /// + /// The principal. + /// The type associated with the claim. + /// The value associated with the claim. + public static ClaimsPrincipal AddClaim(this ClaimsPrincipal principal, string type, JsonNode value) + => principal.AddClaim(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds a claim to a given identity. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + /// The issuer associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, JsonNode value, string issuer) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value is JsonArray) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + } + + identity.AddClaim(new Claim( + type : type, + value : value switch + { + // If the item can be directly retrieved as a string, store it as-is. + JsonValue instance when instance.TryGetValue(out string? result) => result, + + // Otherwise, serialize it as a JSON string. + JsonNode instance => instance.ToJsonString() + }, + valueType : GetClaimValueType(value), + issuer : issuer, + originalIssuer: issuer, + subject : identity)); + + return identity; + } + + /// + /// Adds a claim to a given principal. + /// + /// The principal. + /// The type associated with the claim. + /// The value associated with the claim. + /// The issuer associated with the claim. + public static ClaimsPrincipal AddClaim(this ClaimsPrincipal principal, string type, JsonNode value, string issuer) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (principal.Identity is not ClaimsIdentity identity) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + identity.AddClaim(type, value, issuer); + + return principal; + } + /// /// Adds a claim to a given identity. /// @@ -1290,6 +1391,7 @@ public static class OpenIddictExtensions /// The identity. /// The type associated with the claims. /// The values associated with the claims. + [OverloadResolutionPriority(0)] public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, ImmutableArray values) => identity.AddClaims(type, values, ClaimsIdentity.DefaultIssuer); @@ -1299,6 +1401,7 @@ public static class OpenIddictExtensions /// The principal. /// The type associated with the claims. /// The values associated with the claims. + [OverloadResolutionPriority(0)] public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) => principal.AddClaims(type, values, ClaimsIdentity.DefaultIssuer); @@ -1309,6 +1412,7 @@ public static class OpenIddictExtensions /// The type associated with the claims. /// The values associated with the claims. /// The issuer associated with the claims. + [OverloadResolutionPriority(0)] public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, ImmutableArray values, string issuer) { if (identity is null) @@ -1348,6 +1452,7 @@ public static class OpenIddictExtensions /// The type associated with the claims. /// The values associated with the claims. /// The issuer associated with the claims. + [OverloadResolutionPriority(0)] public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, ImmutableArray values, string issuer) { @@ -1417,7 +1522,7 @@ public static class OpenIddictExtensions foreach (var element in value.EnumerateArray()) { - var item = element.ToString()!; + var item = element.ToString(); if (set.Add(item)) { identity.AddClaim(new Claim( @@ -1467,6 +1572,112 @@ public static class OpenIddictExtensions return principal; } + /// + /// Adds claims to a given identity. + /// + /// The identity. + /// The type associated with the claims. + /// The value associated with the claims. + [OverloadResolutionPriority(-1)] + public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, JsonArray value) + => identity.AddClaims(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds claims to a given principal. + /// + /// The principal. + /// The type associated with the claims. + /// The value associated with the claims. + [OverloadResolutionPriority(-1)] + public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, JsonArray value) + => principal.AddClaims(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds claims to a given identity. + /// + /// The identity. + /// The type associated with the claims. + /// The value associated with the claims. + /// The issuer associated with the claims. + [OverloadResolutionPriority(-1)] + public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, JsonArray value, string issuer) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + var set = new HashSet(StringComparer.Ordinal); + + foreach (var node in value) + { + var item = node switch + { + // If the item is null, store it as an empty string. + null => string.Empty, + + // If the item can be directly retrieved as a string, store it as-is. + JsonValue instance when instance.TryGetValue(out string? result) => result, + + // Otherwise, serialize it as a JSON string. + JsonNode instance => instance.ToJsonString() + }; + + if (set.Add(item)) + { + identity.AddClaim(new Claim( + type : type, + value : item, + valueType : GetClaimValueType(node), + issuer : issuer, + originalIssuer: issuer, + subject : identity)); + } + } + + return identity; + } + + /// + /// Adds claims to a given principal. + /// + /// The principal. + /// The type associated with the claims. + /// The value associated with the claims. + /// The issuer associated with the claims. + [OverloadResolutionPriority(-1)] + public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, JsonArray value, string issuer) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (principal.Identity is not ClaimsIdentity identity) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + identity.AddClaims(type, value, issuer); + + return principal; + } + /// /// Gets the unique claim value corresponding to the given type. /// @@ -2088,6 +2299,86 @@ public static class OpenIddictExtensions return principal; } + /// + /// Sets the claim value corresponding to the given type. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + /// The claims identity. + public static ClaimsIdentity SetClaim(this ClaimsIdentity identity, string type, JsonNode? value) + => identity.SetClaim(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Sets the claim value corresponding to the given type. + /// + /// The principal. + /// The type associated with the claim. + /// The value associated with the claim. + /// The claims identity. + public static ClaimsPrincipal SetClaim(this ClaimsPrincipal principal, string type, JsonNode? value) + => principal.SetClaim(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Sets the claim value corresponding to the given type. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + /// The issuer associated with the claim. + /// The claims identity. + public static ClaimsIdentity SetClaim(this ClaimsIdentity identity, string type, JsonNode? value, string issuer) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + identity.RemoveClaims(type); + + if (!IsEmptyJsonNode(value)) + { + identity.AddClaim(type, value, issuer); + } + + return identity; + } + + /// + /// Sets the claim value corresponding to the given type. + /// + /// The principal. + /// The type associated with the claim. + /// The value associated with the claim. + /// The issuer associated with the claim. + /// The claims identity. + public static ClaimsPrincipal SetClaim(this ClaimsPrincipal principal, string type, JsonNode? value, string issuer) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + principal.RemoveClaims(type); + + if (!IsEmptyJsonNode(value)) + { + principal.AddClaim(type, value, issuer); + } + + return principal; + } + /// /// Sets the claim value corresponding to the given type. /// @@ -2179,6 +2470,7 @@ public static class OpenIddictExtensions /// The type associated with the claims. /// The values associated with the claims. /// The claims identity. + [OverloadResolutionPriority(0)] public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, ImmutableArray values) => identity.SetClaims(type, values, ClaimsIdentity.DefaultIssuer); @@ -2189,6 +2481,7 @@ public static class OpenIddictExtensions /// The type associated with the claims. /// The values associated with the claims. /// The claims identity. + [OverloadResolutionPriority(0)] public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) => principal.SetClaims(type, values, ClaimsIdentity.DefaultIssuer); @@ -2200,6 +2493,7 @@ public static class OpenIddictExtensions /// The values associated with the claims. /// The issuer associated with the claims. /// The claims identity. + [OverloadResolutionPriority(0)] public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, ImmutableArray values, string issuer) { @@ -2231,6 +2525,7 @@ public static class OpenIddictExtensions /// The values associated with the claims. /// The issuer associated with the claims. /// The claims identity. + [OverloadResolutionPriority(0)] public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, ImmutableArray values, string issuer) { @@ -2334,6 +2629,100 @@ public static class OpenIddictExtensions return principal; } + /// + /// Sets the claim values corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The JSON array from which claim values are extracted. + /// The claims identity. + [OverloadResolutionPriority(-1)] + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, JsonArray value) + => identity.SetClaims(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Sets the claim values corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The JSON array from which claim values are extracted. + /// The claims identity. + [OverloadResolutionPriority(-1)] + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, JsonArray value) + => principal.SetClaims(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Sets the claim values corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The JSON array from which claim values are extracted. + /// The issuer associated with the claims. + /// The claims identity. + [OverloadResolutionPriority(-1)] + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, JsonArray value, string issuer) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + identity.RemoveClaims(type); + + if (value.Count is not 0) + { + identity.AddClaims(type, value, issuer); + } + + return identity; + } + + /// + /// Sets the claim value corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The JSON array from which claim values are extracted. + /// The issuer associated with the claims. + /// The claims identity. + [OverloadResolutionPriority(-1)] + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, JsonArray value, string issuer) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + principal.RemoveClaims(type); + + if (value.Count is not 0) + { + principal.AddClaims(type, value, issuer); + } + + return principal; + } + /// /// Gets the creation date stored in the claims identity. /// @@ -3278,6 +3667,24 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetRefreshTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.RefreshTokenLifetime, (long?) lifetime?.TotalSeconds); + /// + /// Sets the request token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The request token lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetRequestTokenLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.RequestTokenLifetime, (long?) lifetime?.TotalSeconds); + + /// + /// Sets the request token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The request token lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetRequestTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.RequestTokenLifetime, (long?) lifetime?.TotalSeconds); + /// /// Sets the state token lifetime associated with the claims identity. /// @@ -3511,6 +3918,25 @@ public static class OpenIddictExtensions JsonValueKind.Object or _ => "JSON" }; + private static string GetClaimValueType(JsonNode? node) => node switch + { + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.String, + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.Boolean, + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.Integer32, + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.Integer64, + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.UInteger32, + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.UInteger64, + JsonValue value when value.TryGetValue(out _) => ClaimValueTypes.Double, + + null => "JSON_NULL", + JsonArray => "JSON_ARRAY", + JsonObject => "JSON", + + // If the JSON node cannot be mapped to a primitive type, convert it to + // a JsonElement instance and infer the corresponding claim value type. + JsonNode value => GetClaimValueType(value.Deserialize(OpenIddictSerializer.Default.JsonElement)) + }; + private static TimeSpan? GetLifetime(ClaimsIdentity identity, string type) { if (identity is null) @@ -3575,4 +4001,18 @@ public static class OpenIddictExtensions default: return false; } } + + private static bool IsEmptyJsonNode([NotNullWhen(false)] JsonNode? node) => node switch + { + null => true, + + JsonArray value => value.Count is 0, + JsonObject value => value.Count is 0, + + JsonValue value when value.TryGetValue(out string? result) => string.IsNullOrEmpty(result), + + // If the JSON node cannot be mapped to a primitive type, convert it to + // a JsonElement instance and infer the corresponding claim value type. + JsonNode value => IsEmptyJsonElement(value.Deserialize(OpenIddictSerializer.Default.JsonElement)) + }; } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs index 1ca7f98b..dae77881 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Security.Claims; using System.Text.Json; +using System.Text.Json.Nodes; using Xunit; namespace OpenIddict.Abstractions.Tests.Primitives; @@ -2077,6 +2078,151 @@ public class OpenIddictExtensionsTests Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); } + [Fact] + public void ClaimsIdentity_AddClaimWithJsonNode_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.AddClaim(Claims.Name, (JsonNode) null!)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_AddClaimWithJsonNode_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, (JsonNode) null!)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_AddClaimWithJsonNode_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, (JsonNode) null!)); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_AddClaimWithJsonNode_ThrowsAnExceptionForNullOrEmptyType(string? type) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.AddClaim(type!, (JsonNode) null!)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_AddClaimWithJsonNode_ThrowsAnExceptionForNullOrEmptyType(string? type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.AddClaim(type!, (JsonNode) null!)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Fact] + public void ClaimsIdentity_AddClaimWithJsonNode_ThrowsAnExceptionForNullNode() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.AddClaim(Claims.Name, (JsonNode) null!)); + + Assert.Equal("value", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_AddClaimWithJsonNode_ThrowsAnExceptionForNullNode() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, (JsonNode) null!)); + + Assert.Equal("value", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_AddClaimWithJsonNode_ThrowsAnExceptionForIncompatibleJsonNode() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.AddClaim("type", + new JsonArray(["Fabrikam", "Contoso"]))); + + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0185), exception.Message); + } + + [Fact] + public void ClaimsPrincipal_AddClaimWithJsonNode_ThrowsAnExceptionForIncompatibleJsonNode() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.AddClaim("type", + new JsonArray(["Fabrikam", "Contoso"]))); + + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0185), exception.Message); + } + + [Fact] + public void ClaimsIdentity_AddClaimWithJsonNode_AddsExpectedClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.AddClaim("type", new JsonObject { ["parameter"] = "value" }); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", identity.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsPrincipal_AddClaimWithJsonNode_AddsExpectedClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.AddClaim("type", new JsonObject { ["parameter"] = "value" }); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); + } + [Fact] public void ClaimsIdentity_AddClaimsWithArray_ThrowsAnExceptionForNullIdentity() { @@ -2383,165 +2529,333 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsIdentity_GetClaim_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_AddClaimsWithJsonArray_ThrowsAnExceptionForNullIdentity() { // Arrange var identity = (ClaimsIdentity) null!; // Act and assert - var exception = Assert.Throws(() => - { - identity.GetClaim(Claims.Name); - }); + var exception = Assert.Throws(() => identity.AddClaims("type", (JsonArray) null!)); Assert.Equal("identity", exception.ParamName); } [Fact] - public void ClaimsPrincipal_GetClaim_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithJsonArray_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => - { - principal.GetClaim(Claims.Name); - }); + var exception = Assert.Throws(() => principal.AddClaims("type", (JsonArray) null!)); Assert.Equal("principal", exception.ParamName); } [Fact] - public void ClaimsIdentity_GetClaim_ThrowsAnExceptionForDuplicateClaimType() + public void ClaimsPrincipal_AddClaimsWithJsonArray_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + var exception = Assert.Throws(() => principal.AddClaims("type", (JsonArray) null!)); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_AddClaimsWithJsonArray_ThrowsAnExceptionForNullOrEmptyType(string? type) { // Arrange var identity = new ClaimsIdentity(); - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); // Act and assert - var exception = Assert.Throws(() => - { - identity.GetClaim(Claims.Name); - }); + var exception = Assert.Throws(() => identity.AddClaims(type!, (JsonArray) null!)); - Assert.Equal(SR.GetResourceString(SR.ID0423), exception.Message); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Fact] - public void ClaimsPrincipal_GetClaim_ThrowsAnExceptionForDuplicateClaimType() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_AddClaimsWithJsonArray_ThrowsAnExceptionForNullOrEmptyType(string? type) { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.AddClaim(Claims.Name, "Bob le Bricoleur"); - principal.AddClaim(Claims.Name, "Bob le Bricoleur"); // Act and assert - var exception = Assert.Throws(() => - { - principal.GetClaim(Claims.Name); - }); + var exception = Assert.Throws(() => principal.AddClaims(type!, (JsonArray) null!)); - Assert.Equal(SR.GetResourceString(SR.ID0423), exception.Message); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } [Fact] - public void ClaimsIdentity_GetClaim_ReturnsNullForMissingClaims() + public void ClaimsIdentity_AddClaimsWithJsonArray_ThrowsAnExceptionForNullNode() { // Arrange var identity = new ClaimsIdentity(); // Act and assert - Assert.Null(identity.GetClaim(Claims.Name)); + var exception = Assert.Throws(() => identity.AddClaims("type", (JsonArray) null!)); + + Assert.Equal("value", exception.ParamName); } [Fact] - public void ClaimsPrincipal_GetClaim_ReturnsNullForMissingClaims() + public void ClaimsPrincipal_AddClaimsWithJsonArray_ThrowsAnExceptionForNullNode() { // Arrange - var principal = new ClaimsPrincipal(); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - Assert.Null(principal.GetClaim(Claims.Name)); + var exception = Assert.Throws(() => principal.AddClaims("type", (JsonArray) null!)); + + Assert.Equal("value", exception.ParamName); } [Fact] - public void ClaimsIdentity_GetClaim_ReturnsAppropriateResult() + public void ClaimsIdentity_AddClaimsWithJsonArray_AddsExpectedClaims() { // Arrange var identity = new ClaimsIdentity(); - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - // Act and assert - Assert.Equal("Bob le Bricoleur", identity.GetClaim(Claims.Name)); + // Act + identity.AddClaims( + "type", + new JsonArray(["Fabrikam", "Contoso", new JsonObject { ["Foo"] = "Bar" }]), + "issuer"); + + // Assert + var claims = identity.FindAll("type").ToArray(); + Assert.Equal("Fabrikam", claims[0].Value); + Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); + Assert.Equal("issuer", claims[0].Issuer); + Assert.Equal("Contoso", claims[1].Value); + Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); + Assert.Equal("issuer", claims[1].Issuer); + Assert.Equal("{\"Foo\":\"Bar\"}", claims[2].Value); + Assert.Equal("JSON", claims[2].ValueType); + Assert.Equal("issuer", claims[2].Issuer); } [Fact] - public void ClaimsPrincipal_GetClaim_ReturnsAppropriateResult() + public void ClaimsPrincipal_AddClaimsWithJsonArray_AddsExpectedClaims() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.AddClaim(Claims.Name, "Bob le Bricoleur"); - // Act and assert - Assert.Equal("Bob le Bricoleur", principal.GetClaim(Claims.Name)); + // Act + principal.AddClaims( + "type", + new JsonArray(["Fabrikam", "Contoso", new JsonObject { ["Foo"] = "Bar" }]), + "issuer"); + + // Assert + var claims = principal.FindAll("type").ToArray(); + Assert.Equal(3, claims.Length); + Assert.Equal("Fabrikam", claims[0].Value); + Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); + Assert.Equal("issuer", claims[0].Issuer); + Assert.Equal("Contoso", claims[1].Value); + Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); + Assert.Equal("issuer", claims[1].Issuer); + Assert.Equal("{\"Foo\":\"Bar\"}", claims[2].Value); + Assert.Equal("JSON", claims[2].ValueType); + Assert.Equal("issuer", claims[2].Issuer); } [Fact] - public void ClaimsIdentity_GetClaim_IsCaseInsensitive() + public void ClaimsIdentity_AddClaimsWithJsonArray_IsCaseInsensitive() { // Arrange var identity = new ClaimsIdentity(); - identity.SetClaim("type", "value"); - // Act and assert - Assert.Equal("value", identity.GetClaim("TYPE")); + // Act + identity.AddClaims("TYPE", new JsonArray(["Fabrikam", "Contoso"])); + + // Assert + Assert.Equal(["Fabrikam", "Contoso"], identity.GetClaims("type")); } [Fact] - public void ClaimsPrincipal_GetClaim_IsCaseInsensitive() + public void ClaimsPrincipal_AddClaimsWithJsonArray_IsCaseInsensitive() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.SetClaim("type", "value"); - // Act and assert - Assert.Equal("value", principal.GetClaim("TYPE")); + // Act + principal.AddClaims("TYPE", new JsonArray(["Fabrikam", "Contoso"])); + + // Assert + Assert.Equal(["Fabrikam", "Contoso"], principal.GetClaims("type")); } [Fact] - public void ClaimsIdentity_GetClaims_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_GetClaim_ThrowsAnExceptionForNullIdentity() { // Arrange var identity = (ClaimsIdentity) null!; // Act and assert - var exception = Assert.Throws(() => identity.GetClaims("type")); + var exception = Assert.Throws(() => + { + identity.GetClaim(Claims.Name); + }); Assert.Equal("identity", exception.ParamName); } [Fact] - public void ClaimsPrincipal_GetClaims_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_GetClaim_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.GetClaims("type")); + var exception = Assert.Throws(() => + { + principal.GetClaim(Claims.Name); + }); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void ClaimsIdentity_GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string? type) + [Fact] + public void ClaimsIdentity_GetClaim_ThrowsAnExceptionForDuplicateClaimType() { // Arrange var identity = new ClaimsIdentity(); - + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + + // Act and assert + var exception = Assert.Throws(() => + { + identity.GetClaim(Claims.Name); + }); + + Assert.Equal(SR.GetResourceString(SR.ID0423), exception.Message); + } + + [Fact] + public void ClaimsPrincipal_GetClaim_ThrowsAnExceptionForDuplicateClaimType() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Name, "Bob le Bricoleur"); + principal.AddClaim(Claims.Name, "Bob le Bricoleur"); + + // Act and assert + var exception = Assert.Throws(() => + { + principal.GetClaim(Claims.Name); + }); + + Assert.Equal(SR.GetResourceString(SR.ID0423), exception.Message); + } + + [Fact] + public void ClaimsIdentity_GetClaim_ReturnsNullForMissingClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetClaim(Claims.Name)); + } + + [Fact] + public void ClaimsPrincipal_GetClaim_ReturnsNullForMissingClaims() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + Assert.Null(principal.GetClaim(Claims.Name)); + } + + [Fact] + public void ClaimsIdentity_GetClaim_ReturnsAppropriateResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + + // Act and assert + Assert.Equal("Bob le Bricoleur", identity.GetClaim(Claims.Name)); + } + + [Fact] + public void ClaimsPrincipal_GetClaim_ReturnsAppropriateResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Name, "Bob le Bricoleur"); + + // Act and assert + Assert.Equal("Bob le Bricoleur", principal.GetClaim(Claims.Name)); + } + + [Fact] + public void ClaimsIdentity_GetClaim_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim("type", "value"); + + // Act and assert + Assert.Equal("value", identity.GetClaim("TYPE")); + } + + [Fact] + public void ClaimsPrincipal_GetClaim_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim("type", "value"); + + // Act and assert + Assert.Equal("value", principal.GetClaim("TYPE")); + } + + [Fact] + public void ClaimsIdentity_GetClaims_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetClaims("type")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetClaims_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetClaims("type")); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string? type) + { + // Arrange + var identity = new ClaimsIdentity(); + // Act and assert var exception = Assert.Throws(() => identity.GetClaims(type!)); @@ -3644,37 +3958,38 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsIdentity_SetClaimsWithArray_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_SetClaimWithJsonNode_ThrowsAnExceptionForNullIdentity() { // Arrange var identity = (ClaimsIdentity) null!; // Act and assert - var exception = Assert.Throws(() => identity.SetClaims("type", [])); + var exception = Assert.Throws(() => identity.SetClaim("type", (JsonNode) null!)); Assert.Equal("identity", exception.ParamName); } [Fact] - public void ClaimsPrincipal_SetClaimsWithArray_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetClaimWithJsonNode_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetClaims("type", [])); + var exception = Assert.Throws(() => principal.SetClaim("type", (JsonNode) null!)); Assert.Equal("principal", exception.ParamName); } [Fact] - public void ClaimsPrincipal_SetClaimsWithArray_ThrowsAnExceptionForNullIdentity() + public void ClaimsPrincipal_SetClaimWithJsonNode_ThrowsAnExceptionForNullIdentity() { // Arrange var principal = new ClaimsPrincipal(); // Act and assert - var exception = Assert.Throws(() => principal.SetClaims("type", ["value1", "value2"])); + var exception = Assert.Throws(() => principal.SetClaim("type", + new JsonObject { ["parameter"] = "value" })); Assert.Equal("principal", exception.ParamName); Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); @@ -3683,13 +3998,13 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void ClaimsIdentity_SetClaimsWithArray_ThrowsAnExceptionForNullOrEmptyType(string? type) + public void ClaimsIdentity_SetClaimWithJsonNode_ThrowsAnExceptionForNullOrEmptyType(string? type) { // Arrange var identity = new ClaimsIdentity(); // Act and assert - var exception = Assert.Throws(() => identity.SetClaims(type!, [])); + var exception = Assert.Throws(() => identity.SetClaim(type!, (JsonNode) null!)); Assert.Equal("type", exception.ParamName); Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); @@ -3698,144 +4013,568 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void ClaimsPrincipal_SetClaimsWithArray_ThrowsAnExceptionForNullOrEmptyType(string? type) + public void ClaimsPrincipal_SetClaimWithJsonNode_ThrowsAnExceptionForNullOrEmptyType(string? type) { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.SetClaims(type!, [])); + var exception = Assert.Throws(() => principal.SetClaim(type!, (JsonNode) null!)); Assert.Equal("type", exception.ParamName); Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } [Fact] - public void ClaimsIdentity_SetClaimsWithArray_AddsExpectedClaims() + public void ClaimsIdentity_SetClaimWithJsonNode_AddsExpectedClaim() { // Arrange var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value1"); // Act - identity.SetClaims("type", ["value1", "value2"], "issuer"); + identity.SetClaim("type", new JsonObject { ["parameter"] = "value" }, "issuer"); // Assert - var claims = identity.FindAll("type").ToArray(); - Assert.Equal(2, claims.Length); - Assert.Equal("value1", claims[0].Value); - Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); - Assert.Equal("issuer", claims[0].Issuer); - Assert.Equal("value2", claims[1].Value); - Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); - Assert.Equal("issuer", claims[1].Issuer); + var claim = Assert.Single(identity.FindAll("type")); + Assert.Equal(@"{""parameter"":""value""}", claim.Value); + Assert.Equal("JSON", claim.ValueType); + Assert.Equal("issuer", claim.Issuer); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonNode_AddsExpectedClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value1"); + + // Act + principal.SetClaim("type", new JsonObject { ["parameter"] = "value" }, "issuer"); + + // Assert + var claim = Assert.Single(principal.FindAll("type")); + Assert.Equal(@"{""parameter"":""value""}", claim.Value); + Assert.Equal("JSON", claim.ValueType); + Assert.Equal("issuer", claim.Issuer); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonNode_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaim("TYPE", new JsonObject { ["parameter"] = "value" }); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", identity.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonNode_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaim("TYPE", new JsonObject { ["parameter"] = "value" }); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonNode_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaim("type", new JsonObject()); + + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonNode_RemovesEmptyClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaim("type", new JsonObject()); + + // Assert + Assert.Null(principal.GetClaim("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonNode_Undefined() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaim("type", (JsonNode) null!); + + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonNode_Undefined() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaim("type", (JsonNode) null!); + + // Assert + Assert.Null(principal.GetClaim("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonNode_Null() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaim("type", JsonSerializer.Deserialize("null")); + + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonNode_Null() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaim("type", JsonSerializer.Deserialize("null")); + + // Assert + Assert.Null(principal.GetClaim("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithArray_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaims("type", [])); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithArray_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims("type", [])); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithArray_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims("type", ["value1", "value2"])); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_SetClaimsWithArray_ThrowsAnExceptionForNullOrEmptyType(string? type) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaims(type!, [])); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetClaimsWithArray_ThrowsAnExceptionForNullOrEmptyType(string? type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims(type!, [])); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithArray_AddsExpectedClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaims("type", ["value1", "value2"], "issuer"); + + // Assert + var claims = identity.FindAll("type").ToArray(); + Assert.Equal(2, claims.Length); + Assert.Equal("value1", claims[0].Value); + Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); + Assert.Equal("issuer", claims[0].Issuer); + Assert.Equal("value2", claims[1].Value); + Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); + Assert.Equal("issuer", claims[1].Issuer); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithArray_AddsExpectedClaims() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaims("type", ["value1", "value2"], "issuer"); + + // Assert + var claims = principal.FindAll("type").ToArray(); + Assert.Equal("value1", claims[0].Value); + Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); + Assert.Equal("issuer", claims[0].Issuer); + Assert.Equal("value2", claims[1].Value); + Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); + Assert.Equal("issuer", claims[1].Issuer); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithArray_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaims("TYPE", ["value1", "value2"]); + + // Assert + Assert.Equal(["value1", "value2"], identity.GetClaims("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithArray_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaims("TYPE", ["value1", "value2"]); + + // Assert + Assert.Equal(["value1", "value2"], principal.GetClaims("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithArray_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaims("type", []); + + // Assert + Assert.Empty(identity.GetClaims("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithArray_RemovesEmptyClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaims("type", []); + + // Assert + Assert.Empty(principal.GetClaims("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithJsonElement_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaims("type", default(JsonElement))); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithJsonElement_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims("type", default(JsonElement))); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithJsonElement_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims("type", + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"))); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_SetClaimsWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string? type) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaims(type!, default(JsonElement))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetClaimsWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string? type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims(type!, default(JsonElement))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithJsonElement_AddsExpectedClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaims("type", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"), "issuer"); + + // Assert + var claims = identity.FindAll("type").ToArray(); + Assert.Equal("Fabrikam", claims[0].Value); + Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); + Assert.Equal("issuer", claims[0].Issuer); + Assert.Equal("Contoso", claims[1].Value); + Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); + Assert.Equal("issuer", claims[1].Issuer); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithJsonElement_AddsExpectedClaims() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaims("type", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"), "issuer"); + + // Assert + var claims = principal.FindAll("type").ToArray(); + Assert.Equal(2, claims.Length); + Assert.Equal("Fabrikam", claims[0].Value); + Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); + Assert.Equal("issuer", claims[0].Issuer); + Assert.Equal("Contoso", claims[1].Value); + Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); + Assert.Equal("issuer", claims[1].Issuer); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithJsonElement_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaims("TYPE", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + + // Assert + Assert.Equal(["Fabrikam", "Contoso"], identity.GetClaims("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithJsonElement_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaims("TYPE", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + + // Assert + Assert.Equal(["Fabrikam", "Contoso"], principal.GetClaims("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithJsonElement_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaims("type", JsonSerializer.Deserialize("[]")); + + // Assert + Assert.Empty(identity.GetClaims("type")); } [Fact] - public void ClaimsPrincipal_SetClaimsWithArray_AddsExpectedClaims() + public void ClaimsPrincipal_SetClaimsWithJsonElement_RemovesEmptyClaim() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); // Act - principal.SetClaims("type", ["value1", "value2"], "issuer"); + principal.SetClaims("type", JsonSerializer.Deserialize("[]")); // Assert - var claims = principal.FindAll("type").ToArray(); - Assert.Equal("value1", claims[0].Value); - Assert.Equal(ClaimValueTypes.String, claims[0].ValueType); - Assert.Equal("issuer", claims[0].Issuer); - Assert.Equal("value2", claims[1].Value); - Assert.Equal(ClaimValueTypes.String, claims[1].ValueType); - Assert.Equal("issuer", claims[1].Issuer); + Assert.Empty(principal.GetClaims("type")); } [Fact] - public void ClaimsIdentity_SetClaimsWithArray_IsCaseInsensitive() + public void ClaimsIdentity_SetClaimsWithJsonElement_Undefined() { // Arrange var identity = new ClaimsIdentity(); // Act - identity.SetClaims("TYPE", ["value1", "value2"]); + identity.SetClaims("type", default(JsonElement)); // Assert - Assert.Equal(["value1", "value2"], identity.GetClaims("type")); + Assert.Null(identity.GetClaim("type")); } [Fact] - public void ClaimsPrincipal_SetClaimsWithArray_IsCaseInsensitive() + public void ClaimsPrincipal_SetClaimsWithJsonElement_Undefined() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); // Act - principal.SetClaims("TYPE", ["value1", "value2"]); + principal.SetClaims("type", default(JsonElement)); // Assert - Assert.Equal(["value1", "value2"], principal.GetClaims("type")); + Assert.Null(principal.GetClaim("type")); } [Fact] - public void ClaimsIdentity_SetClaimsWithArray_RemovesEmptyClaim() + public void ClaimsIdentity_SetClaimsWithJsonElement_Null() { // Arrange var identity = new ClaimsIdentity(); - identity.AddClaim("type", "value"); // Act - identity.SetClaims("type", []); + identity.SetClaims("type", JsonSerializer.Deserialize("null")); // Assert - Assert.Empty(identity.GetClaims("type")); + Assert.Null(identity.GetClaim("type")); } [Fact] - public void ClaimsPrincipal_SetClaimsWithArray_RemovesEmptyClaim() + public void ClaimsPrincipal_SetClaimsWithJsonElement_Null() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); principal.AddClaim("type", "value"); // Act - principal.SetClaims("type", []); + principal.SetClaims("type", JsonSerializer.Deserialize("null")); // Assert - Assert.Empty(principal.GetClaims("type")); + Assert.Null(principal.GetClaim("type")); } [Fact] - public void ClaimsIdentity_SetClaimsWithJsonElement_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_SetClaimsWithJsonArray_ThrowsAnExceptionForNullIdentity() { // Arrange var identity = (ClaimsIdentity) null!; // Act and assert - var exception = Assert.Throws(() => identity.SetClaims("type", default(JsonElement))); + var exception = Assert.Throws(() => identity.SetClaims("type", (JsonArray) null!)); Assert.Equal("identity", exception.ParamName); } [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetClaimsWithJsonArray_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetClaims("type", default(JsonElement))); + var exception = Assert.Throws(() => principal.SetClaims("type", (JsonArray) null!)); Assert.Equal("principal", exception.ParamName); } [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_ThrowsAnExceptionForNullIdentity() + public void ClaimsPrincipal_SetClaimsWithJsonArray_ThrowsAnExceptionForNullIdentity() { // Arrange var principal = new ClaimsPrincipal(); // Act and assert var exception = Assert.Throws(() => principal.SetClaims("type", - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"))); + new JsonArray("Fabrikam", "Contoso"))); Assert.Equal("principal", exception.ParamName); Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); @@ -3844,13 +4583,13 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void ClaimsIdentity_SetClaimsWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string? type) + public void ClaimsIdentity_SetClaimsWithJsonArray_ThrowsAnExceptionForNullOrEmptyType(string? type) { // Arrange var identity = new ClaimsIdentity(); // Act and assert - var exception = Assert.Throws(() => identity.SetClaims(type!, default(JsonElement))); + var exception = Assert.Throws(() => identity.SetClaims(type!, (JsonArray) null!)); Assert.Equal("type", exception.ParamName); Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); @@ -3859,26 +4598,50 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void ClaimsPrincipal_SetClaimsWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string? type) + public void ClaimsPrincipal_SetClaimsWithJsonArray_ThrowsAnExceptionForNullOrEmptyType(string? type) { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.SetClaims(type!, default(JsonElement))); + var exception = Assert.Throws(() => principal.SetClaims(type!, (JsonArray) null!)); Assert.Equal("type", exception.ParamName); Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } [Fact] - public void ClaimsIdentity_SetClaimsWithJsonElement_AddsExpectedClaims() + public void ClaimsIdentity_SetClaimsWithJsonArray_ThrowsAnExceptionForNullNode() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaims("type", (JsonArray) null!)); + + Assert.Equal("value", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithJsonArray_ThrowsAnExceptionForNullNode() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaims("type", (JsonArray) null!)); + + Assert.Equal("value", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithJsonArray_AddsExpectedClaims() { // Arrange var identity = new ClaimsIdentity(); // Act - identity.SetClaims("type", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"), "issuer"); + identity.SetClaims("type", new JsonArray("Fabrikam", "Contoso"), "issuer"); // Assert var claims = identity.FindAll("type").ToArray(); @@ -3891,13 +4654,13 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_AddsExpectedClaims() + public void ClaimsPrincipal_SetClaimsWithJsonArray_AddsExpectedClaims() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act - principal.SetClaims("type", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"), "issuer"); + principal.SetClaims("type", new JsonArray("Fabrikam", "Contoso"), "issuer"); // Assert var claims = principal.FindAll("type").ToArray(); @@ -3911,113 +4674,59 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsIdentity_SetClaimsWithJsonElement_IsCaseInsensitive() + public void ClaimsIdentity_SetClaimsWithJsonArray_IsCaseInsensitive() { // Arrange var identity = new ClaimsIdentity(); // Act - identity.SetClaims("TYPE", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + identity.SetClaims("TYPE", new JsonArray("Fabrikam", "Contoso")); // Assert Assert.Equal(["Fabrikam", "Contoso"], identity.GetClaims("type")); } [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_IsCaseInsensitive() + public void ClaimsPrincipal_SetClaimsWithJsonArray_IsCaseInsensitive() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act - principal.SetClaims("TYPE", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + principal.SetClaims("TYPE", new JsonArray("Fabrikam", "Contoso")); // Assert Assert.Equal(["Fabrikam", "Contoso"], principal.GetClaims("type")); } [Fact] - public void ClaimsIdentity_SetClaimsWithJsonElement_RemovesEmptyClaim() + public void ClaimsIdentity_SetClaimsWithJsonArray_RemovesEmptyClaim() { // Arrange var identity = new ClaimsIdentity(); identity.AddClaim("type", "value"); // Act - identity.SetClaims("type", JsonSerializer.Deserialize("[]")); + identity.SetClaims("type", new JsonArray()); // Assert Assert.Empty(identity.GetClaims("type")); } [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_RemovesEmptyClaim() + public void ClaimsPrincipal_SetClaimsWithJsonArray_RemovesEmptyClaim() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); principal.AddClaim("type", "value"); // Act - principal.SetClaims("type", JsonSerializer.Deserialize("[]")); + principal.SetClaims("type", new JsonArray()); // Assert Assert.Empty(principal.GetClaims("type")); } - [Fact] - public void ClaimsIdentity_SetClaimsWithJsonElement_Undefined() - { - // Arrange - var identity = new ClaimsIdentity(); - - // Act - identity.SetClaims("type", default(JsonElement)); - - // Assert - Assert.Null(identity.GetClaim("type")); - } - - [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_Undefined() - { - // Arrange - var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.AddClaim("type", "value"); - - // Act - principal.SetClaims("type", default(JsonElement)); - - // Assert - Assert.Null(principal.GetClaim("type")); - } - - [Fact] - public void ClaimsIdentity_SetClaimsWithJsonElement_Null() - { - // Arrange - var identity = new ClaimsIdentity(); - - // Act - identity.SetClaims("type", JsonSerializer.Deserialize("null")); - - // Assert - Assert.Null(identity.GetClaim("type")); - } - - [Fact] - public void ClaimsPrincipal_SetClaimsWithJsonElement_Null() - { - // Arrange - var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.AddClaim("type", "value"); - - // Act - principal.SetClaims("type", JsonSerializer.Deserialize("null")); - - // Assert - Assert.Null(principal.GetClaim("type")); - } - [Fact] public void ClaimsIdentity_GetCreationDate_ThrowsAnExceptionForNullIdentity() { @@ -4716,6 +5425,72 @@ public class OpenIddictExtensionsTests Assert.Equal(TimeSpan.FromMinutes(42), principal.GetRefreshTokenLifetime()); } + [Fact] + public void ClaimsIdentity_GetRequestTokenLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetRequestTokenLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetRequestTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetRequestTokenLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetRequestTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetRequestTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetRequestTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetRequestTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetRequestTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.RequestTokenLifetime, 2520); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetRequestTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetRequestTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.RequestTokenLifetime, 2520); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetRequestTokenLifetime()); + } + [Fact] public void ClaimsIdentity_GetStateTokenLifetime_ThrowsAnExceptionForNullIdentity() { @@ -6106,6 +6881,84 @@ public class OpenIddictExtensionsTests Assert.Equal("2520", principal.GetClaim(Claims.Private.RefreshTokenLifetime)); } + [Fact] + public void ClaimsIdentity_SetRequestTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetRequestTokenLifetime(null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetRequestTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetRequestTokenLifetime(null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetRequestTokenLifetime_RemovesClaimForNullValue() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(Claims.Private.RequestTokenLifetime, 2520); + + // Act + identity.SetRequestTokenLifetime(null); + + // Assert + Assert.Empty(identity.Claims); + } + + [Fact] + public void ClaimsPrincipal_SetRequestTokenLifetime_RemovesClaimForNullValue() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.RequestTokenLifetime, 2520); + + // Act + principal.SetRequestTokenLifetime(null); + + // Assert + Assert.Empty(principal.Claims); + } + + [Fact] + public void ClaimsIdentity_SetRequestTokenLifetime_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetRequestTokenLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", identity.GetClaim(Claims.Private.RequestTokenLifetime)); + } + + [Fact] + public void ClaimsPrincipal_SetRequestTokenLifetime_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetRequestTokenLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.RequestTokenLifetime)); + } + [Fact] public void ClaimsIdentity_SetStateTokenLifetime_ThrowsAnExceptionForNullPrincipal() {