diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs index 98f77aab..2bddbb43 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs @@ -7,6 +7,7 @@ using System.Web; using System.Web.Mvc; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; +using OpenIddict.Abstractions; using OpenIddict.Client.Owin; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; @@ -54,7 +55,7 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers { // Note: the OWIN host requires appending the #string suffix to indicate // that the "identity_provider" property is a public string parameter. - properties.Dictionary["identity_provider#string"] = "github"; + properties.Dictionary[Parameters.IdentityProvider + PropertyTypes.String] = "github"; } // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. @@ -112,19 +113,16 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers var claims = new List(result.Identity.Claims .Select(claim => claim switch { - // Map the standard "sub" claim to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier, - // which is the default claim type used by ASP.NET and is required by the antiforgery components. - { Type: Claims.Subject } + // Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is + // the default claim type used by ASP.NET and is required by the antiforgery components. + { Type: Claims.Subject } or + { Type: "id", Issuer: "https://github.com/" or "https://twitter.com/" } => new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer), - // Map the standard "name" claim to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name. + // Map the standard "name" claim to ClaimTypes.Name. { Type: Claims.Name } => new Claim(ClaimTypes.Name, claim.Value, claim.ValueType, claim.Issuer), - // Applications can map non-standard claims issued by specific issuers to a standard equivalent. - { Type: "id", Issuer: "https://github.com/" or "https://twitter.com/" } - => new Claim(Claims.Subject, claim.Value, claim.ValueType, claim.Issuer), - _ => claim }) .Where(claim => claim switch @@ -141,27 +139,31 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers // The antiforgery components require both the nameidentifier and identityprovider claims // so the latter is manually added using the issuer identity resolved from the remote server. - claims.Add(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", claims[0].Issuer)); + claims.Add(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", + result.Identity.GetClaim(Claims.AuthorizationServer))); var identity = new ClaimsIdentity(claims, authenticationType: CookieAuthenticationDefaults.AuthenticationType, nameType: ClaimTypes.Name, roleType: ClaimTypes.Role); - var properties = new AuthenticationProperties - { - RedirectUri = result.Properties.RedirectUri - }; + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Dictionary + .Where(item => item switch + { + // Preserve the redirect URL. + { Key: ".redirect" } => true, - // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. - properties.Dictionary[Tokens.BackchannelAccessToken] = GetProperty(result.Properties, Tokens.BackchannelAccessToken); - properties.Dictionary[Tokens.RefreshToken] = GetProperty(result.Properties, Tokens.RefreshToken); + // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + { Key: Tokens.BackchannelAccessToken or Tokens.RefreshToken } => true, + + // Don't add the other properties to the external cookie. + _ => false + }) + .ToDictionary(pair => pair.Key, pair => pair.Value)); context.Authentication.SignIn(properties, identity); return Redirect(properties.RedirectUri); - - static string GetProperty(AuthenticationProperties properties, string name) - => properties.Dictionary.TryGetValue(name, out var value) ? value : string.Empty; } [AcceptVerbs("GET", "POST"), Route("~/logout")] diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs index 499a1e61..dcec5e66 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs @@ -7,7 +7,9 @@ using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity; using Microsoft.Owin.Security; +using OpenIddict.Abstractions; using OpenIddict.Client.Owin; +using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; namespace OpenIddict.Sandbox.AspNet.Server.Controllers @@ -64,18 +66,22 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers var claims = new List(result.Identity.Claims .Select(claim => claim switch { - // Note: when using external authentication providers with ASP.NET Core Identity, - // the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim - // - which is not configurable in Identity - MUST be used to store the user identifier. + // Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is + // the default claim type used by ASP.NET and is required by the antiforgery components. + { Type: Claims.Subject } or { Type: "id", Issuer: "https://github.com/" } => new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer), + // Map the standard "name" claim to ClaimTypes.Name. + { Type: Claims.Name } + => new Claim(ClaimTypes.Name, claim.Value, claim.ValueType, claim.Issuer), + _ => claim }) .Where(claim => claim switch { - // Preserve the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim. - { Type: ClaimTypes.NameIdentifier } => true, + // Preserve the nameidentifier and name claims. + { Type: ClaimTypes.NameIdentifier or ClaimTypes.Name } => true, // Applications that use multiple client registrations can filter claims based on the issuer. { Type: "bio", Issuer: "https://github.com/" } => true, @@ -86,27 +92,31 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers // The antiforgery components require both the nameidentifier and identityprovider claims // so the latter is manually added using the issuer identity resolved from the remote server. - claims.Add(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", claims[0].Issuer)); + claims.Add(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", + result.Identity.GetClaim(Claims.AuthorizationServer))); var identity = new ClaimsIdentity(claims, authenticationType: DefaultAuthenticationTypes.ExternalCookie, nameType: ClaimTypes.Name, roleType: ClaimTypes.Role); - var properties = new AuthenticationProperties - { - RedirectUri = result.Properties.RedirectUri - }; + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Dictionary + .Where(item => item switch + { + // Preserve the redirect URL. + { Key: ".redirect" } => true, - // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. - properties.Dictionary[Tokens.BackchannelAccessToken] = GetProperty(result.Properties, Tokens.BackchannelAccessToken); - properties.Dictionary[Tokens.RefreshToken] = GetProperty(result.Properties, Tokens.RefreshToken); + // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + { Key: Tokens.BackchannelAccessToken or Tokens.RefreshToken } => true, + + // Don't add the other properties to the external cookie. + _ => false + }) + .ToDictionary(pair => pair.Key, pair => pair.Value)); context.Authentication.SignIn(properties, identity); return Redirect(properties.RedirectUri); - - static string GetProperty(AuthenticationProperties properties, string name) - => properties.Dictionary.TryGetValue(name, out var value) ? value : string.Empty; } } } diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs index dec74133..5dc22ad5 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs @@ -146,13 +146,11 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers identity.AddClaim(new Claim(Claims.Subject, identity.FindFirstValue(ClaimTypes.NameIdentifier))); identity.AddClaim(new Claim(Claims.Name, identity.FindFirstValue(ClaimTypes.Name))); - var principal = new ClaimsPrincipal(identity); - // Note: in this sample, the granted scopes match the requested scope // but you may want to allow the user to uncheck specific scopes. // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); + identity.SetScopes(request.GetScopes()); + identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); // Automatically create a permanent authorization to avoid requiring explicit consent // for future authorization or token requests containing the same scopes. @@ -160,21 +158,17 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers if (authorization == null) { authorization = await _authorizationManager.CreateAsync( - principal: principal, + principal: new ClaimsPrincipal(identity), subject : user.Id, client : await _applicationManager.GetIdAsync(application), type : AuthorizationTypes.Permanent, - scopes : principal.GetScopes()); + scopes : identity.GetScopes()); } - principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(GetDestinations); - context.Authentication.SignIn(new AuthenticationProperties(), (ClaimsIdentity) principal.Identity); + context.Authentication.SignIn(new AuthenticationProperties(), identity); return new EmptyResult(); @@ -267,13 +261,11 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers identity.AddClaim(new Claim(Claims.Subject, identity.FindFirstValue(ClaimTypes.NameIdentifier))); identity.AddClaim(new Claim(Claims.Name, identity.FindFirstValue(ClaimTypes.Name))); - var principal = new ClaimsPrincipal(identity); - // Note: in this sample, the granted scopes match the requested scope // but you may want to allow the user to uncheck specific scopes. // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); + identity.SetScopes(request.GetScopes()); + identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); // Automatically create a permanent authorization to avoid requiring explicit consent // for future authorization or token requests containing the same scopes. @@ -281,22 +273,18 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers if (authorization == null) { authorization = await _authorizationManager.CreateAsync( - principal: principal, + principal: new ClaimsPrincipal(identity), subject : user.Id, client : await _applicationManager.GetIdAsync(application), type : AuthorizationTypes.Permanent, - scopes : principal.GetScopes()); + scopes : identity.GetScopes()); } - principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(GetDestinations); // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. - context.Authentication.SignIn(new AuthenticationProperties(), (ClaimsIdentity) principal.Identity); + context.Authentication.SignIn(new AuthenticationProperties(), identity); return new EmptyResult(); } @@ -351,11 +339,11 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { - // Retrieve the claims principal stored in the authorization code/device code/refresh token. - var principal = new ClaimsPrincipal((await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType)).Identity); + // Retrieve the claims identity stored in the authorization code/device code/refresh token. + var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); // Retrieve the user profile corresponding to the authorization code/refresh token. - var user = await context.GetUserManager().FindByIdAsync(principal.GetClaim(Claims.Subject)); + var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetClaim(Claims.Subject)); if (user == null) { context.Authentication.Challenge( @@ -383,16 +371,8 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers return new EmptyResult(); } - var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType); - identity.AddClaims((await context.Get().CreateUserIdentityAsync(user)).Claims); - - identity.AddClaim(new Claim(Claims.Subject, identity.FindFirstValue(ClaimTypes.NameIdentifier))); - identity.AddClaim(new Claim(Claims.Name, identity.FindFirstValue(ClaimTypes.Name))); - - foreach (var claim in identity.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + var identity = new ClaimsIdentity(result.Identity.Claims, OpenIddictServerOwinDefaults.AuthenticationType); + identity.SetDestinations(GetDestinations); // Ask OpenIddict to issue the appropriate access/identity tokens. context.Authentication.SignIn(new AuthenticationProperties(), identity); @@ -403,7 +383,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers throw new InvalidOperationException("The specified grant type is not supported."); } - private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) + private static IEnumerable GetDestinations(Claim claim) { // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies @@ -414,7 +394,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers case Claims.Name: yield return Destinations.AccessToken; - if (principal.HasScope(Scopes.Profile)) + if (claim.Subject.HasScope(Scopes.Profile)) yield return Destinations.IdentityToken; yield break; @@ -422,7 +402,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers case Claims.Email: yield return Destinations.AccessToken; - if (principal.HasScope(Scopes.Email)) + if (claim.Subject.HasScope(Scopes.Email)) yield return Destinations.IdentityToken; yield break; @@ -430,7 +410,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers case Claims.Role: yield return Destinations.AccessToken; - if (principal.HasScope(Scopes.Roles)) + if (claim.Subject.HasScope(Scopes.Roles)) yield return Destinations.IdentityToken; yield break; diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs index b0304c53..1996f904 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs @@ -45,7 +45,7 @@ public class AuthenticationController : Controller // the user is directly redirected to GitHub (in this case, no login page is shown). if (provider is "local-github") { - properties.Parameters["identity_provider"] = "github"; + properties.Parameters[Parameters.IdentityProvider] = "github"; } // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. @@ -123,13 +123,11 @@ public class AuthenticationController : Controller nameType: Claims.Name, roleType: Claims.Role); - var properties = new AuthenticationProperties - { - RedirectUri = result.Properties.RedirectUri - }; + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Items); // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. - // To make cookies less heavy, tokens that are not used can be filtered out before creating the cookie. + // To make cookies less heavy, tokens that are not used are filtered out before creating the cookie. properties.StoreTokens(result.Properties.GetTokens().Where(token => token switch { // Preserve the access and refresh tokens returned in the token response, if available. diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs index 6676211e..39af7d66 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs @@ -57,8 +57,8 @@ public class AuthenticationController : Controller .Select(claim => claim switch { // Note: when using external authentication providers with ASP.NET Core Identity, - // the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim - // - which is not configurable in Identity - MUST be used to store the user identifier. + // the ClaimTypes.NameIdentifier claim - which is not configurable in Identity - + // MUST be used to store the user identifier. { Type: "id", Issuer: "https://github.com/" } => new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer), @@ -66,7 +66,7 @@ public class AuthenticationController : Controller }) .Where(claim => claim switch { - // Preserve the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim. + // Preserve the ClaimTypes.NameIdentifier claim. { Type: ClaimTypes.NameIdentifier } => true, // Applications that use multiple client registrations can filter claims based on the issuer. @@ -84,17 +84,11 @@ public class AuthenticationController : Controller nameType: ClaimTypes.NameIdentifier, roleType: ClaimTypes.Role); - var properties = new AuthenticationProperties - { - RedirectUri = result.Properties.RedirectUri - }; - - // Store the identity of the external provider in the authentication properties to allow - // ASP.NET Core Identity to resolve it when returning the external login confirmation form. - properties.Items["LoginProvider"] = claims[0].Issuer; + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Items); // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. - // To make cookies less heavy, tokens that are not used can be filtered out before creating the cookie. + // To make cookies less heavy, tokens that are not used are filtered out before creating the cookie. properties.StoreTokens(result.Properties.GetTokens().Where(token => token switch { // Preserve the access and refresh tokens returned in the token response, if available. diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs index e217f0c0..466b215b 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs @@ -112,20 +112,16 @@ public class AuthorizationController : Controller })); } - var properties = new AuthenticationProperties(new Dictionary - { - // Note: when only one client is registered in the client options, - // setting the issuer property is not required and can be omitted. - [OpenIddictClientAspNetCoreConstants.Properties.Issuer] = issuer - }) - { - // Once the callback is handled, redirect the user agent to the ASP.NET Identity - // page responsible for showing the external login confirmation form if necessary. - RedirectUri = Url.Action("ExternalLoginCallback", "Account", new + var properties = _signInManager.ConfigureExternalAuthenticationProperties( + provider: issuer, + redirectUrl: Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = Request.PathBase + Request.Path + QueryString.Create(parameters) - }) - }; + })); + + // Note: when only one client is registered in the client options, + // setting the issuer property is not required and can be omitted. + properties.SetString(OpenIddictClientAspNetCoreConstants.Properties.Issuer, issuer); // Ask the OpenIddict client middleware to redirect the user agent to the identity provider. return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme); @@ -196,11 +192,7 @@ public class AuthorizationController : Controller } principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + principal.SetDestinations(GetDestinations); return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); @@ -286,11 +278,7 @@ public class AuthorizationController : Controller } principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + principal.SetDestinations(GetDestinations); // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); @@ -362,11 +350,7 @@ public class AuthorizationController : Controller // For that, simply restrict the list of scopes before calling SetScopes. principal.SetScopes(result.Principal.GetScopes()); principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + principal.SetDestinations(GetDestinations); var properties = new AuthenticationProperties { @@ -470,11 +454,7 @@ public class AuthorizationController : Controller // For that, simply restrict the list of scopes before calling SetScopes. principal.SetScopes(request.GetScopes()); principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + principal.SetDestinations(GetDestinations); // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); @@ -488,7 +468,8 @@ public class AuthorizationController : Controller // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: - // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); + // + // var user = await _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(principal); if (user is null) { @@ -513,10 +494,7 @@ public class AuthorizationController : Controller })); } - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + principal.SetDestinations(GetDestinations); // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); @@ -526,7 +504,7 @@ public class AuthorizationController : Controller } #endregion - private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) + private IEnumerable GetDestinations(Claim claim) { // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies @@ -537,7 +515,7 @@ public class AuthorizationController : Controller case Claims.Name: yield return Destinations.AccessToken; - if (principal.HasScope(Scopes.Profile)) + if (claim.Subject.HasScope(Scopes.Profile)) yield return Destinations.IdentityToken; yield break; @@ -545,7 +523,7 @@ public class AuthorizationController : Controller case Claims.Email: yield return Destinations.AccessToken; - if (principal.HasScope(Scopes.Email)) + if (claim.Subject.HasScope(Scopes.Email)) yield return Destinations.IdentityToken; yield break; @@ -553,7 +531,7 @@ public class AuthorizationController : Controller case Claims.Role: yield return Destinations.AccessToken; - if (principal.HasScope(Scopes.Roles)) + if (claim.Subject.HasScope(Scopes.Roles)) yield return Destinations.IdentityToken; yield break; diff --git a/src/OpenIddict.Abstractions/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs index 917f84f2..65ffb77e 100644 --- a/src/OpenIddict.Abstractions/OpenIddictConstants.cs +++ b/src/OpenIddict.Abstractions/OpenIddictConstants.cs @@ -127,6 +127,7 @@ public static class OpenIddictConstants public const string DeviceCodeLifetime = "oi_dvc_lft"; public const string ExpirationDate = "oi_exp_dt"; public const string GrantType = "oi_grt_typ"; + public const string HostProperties = "oi_hst_props"; public const string IdentityTokenLifetime = "oi_idt_lft"; public const string Issuer = "oi_iss"; public const string Nonce = "oi_nce"; diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx index 100d8cab..cb4780ed 100644 --- a/src/OpenIddict.Abstractions/OpenIddictResources.resx +++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx @@ -773,7 +773,7 @@ To register the validation services, use 'services.AddOpenIddict().AddValidation The claim type cannot be null or empty. - The claim value cannot be null or empty. + The claim value is not a supported JSON node. The audience cannot be null or empty. diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs index d5cdd0e0..a627c7fc 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs @@ -587,7 +587,7 @@ public static class OpenIddictExtensions return claim; } - if (destinations.Any(destination => string.IsNullOrEmpty(destination))) + if (destinations.Any(string.IsNullOrEmpty)) { throw new ArgumentException(SR.GetResourceString(SR.ID0182), nameof(destinations)); } @@ -630,6 +630,43 @@ public static class OpenIddictExtensions public static Claim SetDestinations(this Claim claim, params string[]? destinations) => claim.SetDestinations(destinations?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Gets the destinations associated with all the claims of the given identity. + /// + /// The identity. + /// The destinations, returned as a flattened dictionary. + public static ImmutableDictionary GetDestinations(this ClaimsIdentity identity) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + + foreach (var group in identity.Claims.GroupBy(claim => claim.Type)) + { + var claims = group.ToList(); + + var destinations = new HashSet(claims[0].GetDestinations(), StringComparer.OrdinalIgnoreCase); + if (destinations.Count != 0) + { + // Ensure the other claims of the same type use the same exact destinations. + for (var index = 0; index < claims.Count; index++) + { + if (!destinations.SetEquals(claims[index].GetDestinations())) + { + throw new InvalidOperationException(SR.FormatID0183(group.Key)); + } + } + + builder.Add(group.Key, destinations.ToArray()); + } + } + + return builder.ToImmutable(); + } + /// /// Gets the destinations associated with all the claims of the given principal. /// @@ -667,6 +704,35 @@ public static class OpenIddictExtensions return builder.ToImmutable(); } + /// + /// Sets the destinations associated with all the claims of the given identity. + /// + /// The identity. + /// The destinations, as a flattened dictionary. + /// The identity. + public static ClaimsIdentity SetDestinations(this ClaimsIdentity identity, ImmutableDictionary destinations) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (destinations is null) + { + throw new ArgumentNullException(nameof(destinations)); + } + + foreach (var destination in destinations) + { + foreach (var claim in identity.Claims.Where(claim => claim.Type == destination.Key)) + { + claim.SetDestinations(destination.Value); + } + } + + return identity; + } + /// /// Sets the destinations associated with all the claims of the given principal. /// @@ -696,6 +762,58 @@ public static class OpenIddictExtensions return principal; } + /// + /// Sets the destinations associated with all the claims of the given identity. + /// + /// The identity. + /// The destinations selector delegate. + /// The identity. + public static ClaimsIdentity SetDestinations(this ClaimsIdentity identity, Func> selector) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + foreach (var claim in identity.Claims) + { + claim.SetDestinations(selector(claim)); + } + + return identity; + } + + /// + /// Sets the destinations associated with all the claims of the given principal. + /// + /// The principal. + /// The destinations selector delegate. + /// The principal. + public static ClaimsPrincipal SetDestinations(this ClaimsPrincipal principal, Func> selector) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + foreach (var claim in principal.Claims) + { + claim.SetDestinations(selector(claim)); + } + + return principal; + } + /// /// Clones an identity by filtering its claims and the claims of its actor, recursively. /// @@ -773,71 +891,99 @@ public static class OpenIddictExtensions /// The type associated with the claim. /// The value associated with the claim. public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string 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, string 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, string value, string issuer) { if (identity is null) { throw new ArgumentNullException(nameof(identity)); } - if (string.IsNullOrEmpty(type)) + if (value is null) { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + throw new ArgumentNullException(nameof(value)); } - if (string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - identity.AddClaim(new Claim(type, value)); + identity.AddClaim(new Claim(type, value, ClaimValueTypes.String, issuer, issuer, identity)); return identity; } /// - /// Adds a claim to a given identity and specify one or more destinations. + /// Adds a claim to a given principal. /// - /// The identity. + /// The principal. /// The type associated with the claim. /// The value associated with the claim. - /// The destinations associated with the claim. - public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value, ImmutableArray destinations) + /// The issuer associated with the claim. + public static ClaimsPrincipal AddClaim(this ClaimsPrincipal principal, string type, string value, string issuer) { - if (identity is null) + if (principal is null) { - throw new ArgumentNullException(nameof(identity)); + throw new ArgumentNullException(nameof(principal)); } - if (string.IsNullOrEmpty(type)) + if (principal.Identity is not ClaimsIdentity identity) { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); } - if (string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - identity.AddClaim(new Claim(type, value).SetDestinations(destinations)); - return identity; + identity.AddClaim(type, value, issuer); + return principal; } /// - /// Adds a claim to a given identity and specify one or more destinations. + /// Adds a claim to a given identity. /// /// The identity. /// The type associated with the claim. /// The value associated with the claim. - /// The destinations associated with the claim. - public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value, params string[]? destinations) - => identity.AddClaim(type, value, destinations?.ToImmutableArray() ?? ImmutableArray.Create()); + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, JsonElement value) + => identity.AddClaim(type, value, ClaimsIdentity.DefaultIssuer); /// - /// Gets the claim value corresponding to the given type. + /// 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, JsonElement value) + => principal.AddClaim(type, value, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds a claim to a given identity. /// /// The identity. /// The type associated with the claim. - /// The claim value. - public static string? GetClaim(this ClaimsIdentity identity, string type) + /// The value associated with the claim. + /// The issuer associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, JsonElement value, string issuer) { if (identity is null) { @@ -849,62 +995,143 @@ public static class OpenIddictExtensions throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - return identity.FindFirst(type)?.Value; + if (value.ValueKind is JsonValueKind.Array) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + } + + identity.AddClaim(new Claim( + type : type, + value : value.ToString()!, + valueType : GetClaimValueType(value), + issuer : issuer, + originalIssuer: issuer, + subject : identity)); + + return identity; } /// - /// Gets the claim value corresponding to the given type. + /// Adds a claim to a given principal. /// /// The principal. /// The type associated with the claim. - /// The claim value. - public static string? GetClaim(this ClaimsPrincipal principal, string type) + /// The value associated with the claim. + /// The issuer associated with the claim. + public static ClaimsPrincipal AddClaim(this ClaimsPrincipal principal, string type, JsonElement 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)); } - return principal.FindFirst(type)?.Value; + identity.AddClaim(type, value, issuer); + + return principal; } /// - /// Gets the claim values corresponding to the given type. + /// Adds a claim to a given identity. /// /// The identity. - /// The type associated with the claims. - /// The claim values. - public static ImmutableArray GetClaims(this ClaimsIdentity identity, string type) + /// The type associated with the claim. + /// The value associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, + string type, IDictionary 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, IDictionary 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, + IDictionary value, string issuer) { if (identity is null) { throw new ArgumentNullException(nameof(identity)); } + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + if (string.IsNullOrEmpty(type)) { throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - return identity.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); + + writer.WriteStartObject(); + + foreach (var property in value) + { + writer.WritePropertyName(property.Key); + writer.WriteStringValue(property.Value); + } + + writer.WriteEndObject(); + writer.Flush(); + + identity.AddClaim(new Claim( + type : type, + value : Encoding.UTF8.GetString(stream.ToArray()), + valueType : "JSON", + issuer : issuer, + originalIssuer: issuer, + subject : identity)); + + return identity; } /// - /// Determines whether the claims identity contains at least one claim of the specified type. + /// Adds a claim to a given principal. /// - /// The claims identity. - /// The claim type. - /// if the identity contains at least one claim of the specified type. - public static bool HasClaim(this ClaimsIdentity identity, string type) + /// 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, + IDictionary value, string issuer) { - if (identity is null) + if (principal is null) { - throw new ArgumentNullException(nameof(identity)); + 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)) @@ -912,20 +1139,41 @@ public static class OpenIddictExtensions throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - return identity.FindAll(type).Any(); + identity.AddClaim(type, value, issuer); + + return principal; } /// - /// Gets the claim values corresponding to the given type. + /// Adds claims to a given identity. + /// + /// The identity. + /// The type associated with the claims. + /// The values associated with the claims. + public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, ImmutableArray values) + => identity.AddClaims(type, values, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds claims to a given principal. /// /// The principal. /// The type associated with the claims. - /// The claim values. - public static ImmutableArray GetClaims(this ClaimsPrincipal principal, string type) + /// The values associated with the claims. + public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) + => principal.AddClaims(type, values, ClaimsIdentity.DefaultIssuer); + + /// + /// Adds claims to a given identity. + /// + /// The identity. + /// The type associated with the claims. + /// The values associated with the claims. + /// The issuer associated with the claims. + public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, ImmutableArray values, string issuer) { - if (principal is null) + if (identity is null) { - throw new ArgumentNullException(nameof(principal)); + throw new ArgumentNullException(nameof(identity)); } if (string.IsNullOrEmpty(type)) @@ -933,16 +1181,472 @@ public static class OpenIddictExtensions throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - return principal.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + var set = new HashSet(StringComparer.Ordinal); + + for (var index = 0; index < values.Length; index++) + { + var item = values[index]; + if (set.Add(item)) + { + identity.AddClaim(new Claim( + type : type, + value : item, + valueType : ClaimValueTypes.String, + issuer : issuer, + originalIssuer: issuer, + subject : identity)); + } + } + + return identity; } /// - /// Determines whether the claims principal contains at least one claim of the specified type. + /// Adds claims to a given principal. /// - /// The claims principal. - /// The claim type. - /// if the principal contains at least one claim of the specified type. - public static bool HasClaim(this ClaimsPrincipal principal, string type) + /// The principal. + /// The type associated with the claims. + /// The values associated with the claims. + /// The issuer associated with the claims. + public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, + string type, ImmutableArray values, 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, values, issuer); + + return principal; + } + + /// + /// Adds claims to a given identity. + /// + /// The identity. + /// The type associated with the claims. + /// The value associated with the claims. + public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, JsonElement 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. + public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, JsonElement 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. + public static ClaimsIdentity AddClaims(this ClaimsIdentity identity, string type, JsonElement 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.ValueKind is not JsonValueKind.Array) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + } + + var set = new HashSet(StringComparer.Ordinal); + + foreach (var element in value.EnumerateArray()) + { + var item = element.GetString()!; + if (set.Add(item)) + { + identity.AddClaim(new Claim( + type : type, + value : item, + valueType : GetClaimValueType(element), + 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. + public static ClaimsPrincipal AddClaims(this ClaimsPrincipal principal, string type, JsonElement 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.ValueKind is not JsonValueKind.Array) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + } + + identity.AddClaims(type, value, issuer); + + return principal; + } + + /// + /// Gets the claim value corresponding to the given type. + /// + /// The identity. + /// The type associated with the claim. + /// The claim value. + public static string? GetClaim(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + return identity.FindFirst(type)?.Value; + } + + /// + /// Gets the claim value corresponding to the given type. + /// + /// The principal. + /// The type associated with the claim. + /// The claim value. + public static string? GetClaim(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + return principal.FindFirst(type)?.Value; + } + + /// + /// Gets the claim values corresponding to the given type. + /// + /// The claims identity. + /// The type associated with the claims. + /// The claim values. + public static ImmutableArray GetClaims(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + return identity.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + } + + /// + /// Gets the claim values corresponding to the given type. + /// + /// The claims principal. + /// The type associated with the claims. + /// The claim values. + public static ImmutableArray GetClaims(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + return principal.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + } + + /// + /// Determines whether the claims identity contains at least one claim of the specified type. + /// + /// The claims identity. + /// The claim type. + /// if the identity contains at least one claim of the specified type. + public static bool HasClaim(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + return identity.FindAll(type).Any(); + } + + /// + /// Determines whether the claims principal contains at least one claim of the specified type. + /// + /// The claims principal. + /// The claim type. + /// if the principal contains at least one claim of the specified type. + public static bool HasClaim(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + return principal.FindAll(type).Any(); + } + + /// + /// Removes all the claims corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The claims identity. + public static ClaimsIdentity RemoveClaims(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + foreach (var claim in identity.FindAll(type).ToList()) + { + identity.RemoveClaim(claim); + } + + return identity; + } + + /// + /// Removes all the claims corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The claims identity. + public static ClaimsPrincipal RemoveClaims(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } + + foreach (var identity in principal.Identities) + { + foreach (var claim in identity.FindAll(type).ToList()) + { + identity.RemoveClaim(claim); + } + } + + 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, string? 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, string? 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, string? 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 (!string.IsNullOrEmpty(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, string? 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 (!string.IsNullOrEmpty(value)) + { + principal.AddClaim(type, value, issuer); + } + + 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, JsonElement 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, JsonElement 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, JsonElement 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 (!IsEmptyJsonElement(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, JsonElement value, string issuer) { if (principal is null) { @@ -954,16 +1658,48 @@ public static class OpenIddictExtensions throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - return principal.FindAll(type).Any(); + principal.RemoveClaims(type); + + if (!IsEmptyJsonElement(value)) + { + principal.AddClaim(type, value, issuer); + } + + return principal; } /// - /// Removes all the claims corresponding to the given type. + /// Sets the claim value corresponding to the given type. /// /// The identity. - /// The type associated with the claims. + /// The type associated with the claim. + /// The value associated with the claim. /// The claims identity. - public static ClaimsIdentity RemoveClaims(this ClaimsIdentity identity, string type) + public static ClaimsIdentity SetClaim(this ClaimsIdentity identity, + string type, IDictionary? 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, IDictionary? 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, + IDictionary? value, string issuer) { if (identity is null) { @@ -975,21 +1711,26 @@ public static class OpenIddictExtensions throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - foreach (var claim in identity.FindAll(type).ToList()) + identity.RemoveClaims(type); + + if (value is { Count: > 0 }) { - identity.RemoveClaim(claim); + identity.AddClaim(type, value, issuer); } return identity; } /// - /// Removes all the claims corresponding to the given type. + /// Sets the claim value corresponding to the given type. /// /// The principal. - /// The type associated with the claims. + /// The type associated with the claim. + /// The value associated with the claim. + /// The issuer associated with the claim. /// The claims identity. - public static ClaimsPrincipal RemoveClaims(this ClaimsPrincipal principal, string type) + public static ClaimsPrincipal SetClaim(this ClaimsPrincipal principal, string type, + IDictionary? value, string issuer) { if (principal is null) { @@ -1001,25 +1742,46 @@ public static class OpenIddictExtensions throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); } - foreach (var identity in principal.Identities) + principal.RemoveClaims(type); + + if (value is { Count: > 0 }) { - foreach (var claim in identity.FindAll(type).ToList()) - { - identity.RemoveClaim(claim); - } + principal.AddClaim(type, value, issuer); } return principal; } /// - /// Sets the claim value corresponding to the given type. + /// Sets the claim values corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The values associated with the claims. + /// The claims identity. + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, ImmutableArray values) + => identity.SetClaims(type, values, ClaimsIdentity.DefaultIssuer); + + /// + /// Sets the claim values corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The values associated with the claims. + /// The claims identity. + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) + => principal.SetClaims(type, values, ClaimsIdentity.DefaultIssuer); + + /// + /// Sets the claim values corresponding to the given type. /// /// The identity. /// The type associated with the claims. - /// The claim value. + /// The values associated with the claims. + /// The issuer associated with the claims. /// The claims identity. - public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, string? value) + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, + string type, ImmutableArray values, string issuer) { if (identity is null) { @@ -1033,33 +1795,30 @@ public static class OpenIddictExtensions identity.RemoveClaims(type); - if (!string.IsNullOrEmpty(value)) + foreach (var value in values.Distinct(StringComparer.Ordinal)) { - identity.AddClaim(type, value); + identity.AddClaim(type, value, issuer); } return identity; } /// - /// Sets the claim value corresponding to the given type. + /// Sets the claim values corresponding to the given type. /// /// The principal. /// The type associated with the claims. - /// The claim value. + /// The values associated with the claims. + /// The issuer associated with the claims. /// The claims identity. - public static ClaimsPrincipal SetClaim(this ClaimsPrincipal principal, string type, string? value) + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, + string type, ImmutableArray values, 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)); @@ -1067,9 +1826,9 @@ public static class OpenIddictExtensions principal.RemoveClaims(type); - if (!string.IsNullOrEmpty(value)) + foreach (var value in values.Distinct(StringComparer.Ordinal)) { - identity.AddClaim(type, value); + principal.AddClaim(type, value, issuer); } return principal; @@ -1080,9 +1839,30 @@ public static class OpenIddictExtensions /// /// The identity. /// The type associated with the claims. - /// The claim values. + /// The JSON array from which claim values are extracted. /// The claims identity. - public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, ImmutableArray values) + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, JsonElement 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. + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, JsonElement 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. + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, JsonElement value, string issuer) { if (identity is null) { @@ -1096,33 +1876,29 @@ public static class OpenIddictExtensions identity.RemoveClaims(type); - foreach (var value in values.Distinct(StringComparer.Ordinal)) + if (!IsEmptyJsonElement(value)) { - identity.AddClaim(type, value); + identity.AddClaims(type, value, issuer); } return identity; } /// - /// Sets the claim values corresponding to the given type. + /// Sets the claim value corresponding to the given type. /// /// The principal. /// The type associated with the claims. - /// The claim values. + /// The JSON array from which claim values are extracted. + /// The issuer associated with the claims. /// The claims identity. - public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, JsonElement 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)); @@ -1130,14 +1906,40 @@ public static class OpenIddictExtensions principal.RemoveClaims(type); - foreach (var value in values.Distinct(StringComparer.Ordinal)) + if (!IsEmptyJsonElement(value)) { - identity.AddClaim(type, value); + principal.AddClaims(type, value, issuer); } return principal; } + /// + /// Gets the creation date stored in the claims identity. + /// + /// The claims identity. + /// The creation date or if the claim cannot be found. + public static DateTimeOffset? GetCreationDate(this ClaimsIdentity identity) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + var claim = identity.FindFirst(Claims.Private.CreationDate); + if (claim is null) + { + return null; + } + + if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) + { + return null; + } + + return value; + } + /// /// Gets the creation date stored in the claims principal. /// @@ -1164,6 +1966,32 @@ public static class OpenIddictExtensions return value; } + /// + /// Gets the expiration date stored in the claims identity. + /// + /// The claims identity. + /// The expiration date or if the claim cannot be found. + public static DateTimeOffset? GetExpirationDate(this ClaimsIdentity identity) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + var claim = identity.FindFirst(Claims.Private.ExpirationDate); + if (claim is null) + { + return null; + } + + if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) + { + return null; + } + + return value; + } + /// /// Gets the expiration date stored in the claims principal. /// @@ -1190,6 +2018,14 @@ public static class OpenIddictExtensions return value; } + /// + /// Gets the audiences list stored in the claims identity. + /// + /// The claims identity. + /// The audiences list or an empty set if the claims cannot be found. + public static ImmutableArray GetAudiences(this ClaimsIdentity identity) + => identity.GetClaims(Claims.Private.Audience); + /// /// Gets the audiences list stored in the claims principal. /// @@ -1198,6 +2034,14 @@ public static class OpenIddictExtensions public static ImmutableArray GetAudiences(this ClaimsPrincipal principal) => principal.GetClaims(Claims.Private.Audience); + /// + /// Gets the presenters list stored in the claims identity. + /// + /// The claims identity. + /// The presenters list or an empty set if the claims cannot be found. + public static ImmutableArray GetPresenters(this ClaimsIdentity identity) + => identity.GetClaims(Claims.Private.Presenter); + /// /// Gets the presenters list stored in the claims principal. /// @@ -1206,6 +2050,14 @@ public static class OpenIddictExtensions public static ImmutableArray GetPresenters(this ClaimsPrincipal principal) => principal.GetClaims(Claims.Private.Presenter); + /// + /// Gets the resources list stored in the claims identity. + /// + /// The claims identity. + /// The resources list or an empty set if the claims cannot be found. + public static ImmutableArray GetResources(this ClaimsIdentity identity) + => identity.GetClaims(Claims.Private.Resource); + /// /// Gets the resources list stored in the claims principal. /// @@ -1214,6 +2066,14 @@ public static class OpenIddictExtensions public static ImmutableArray GetResources(this ClaimsPrincipal principal) => principal.GetClaims(Claims.Private.Resource); + /// + /// Gets the scopes list stored in the claims identity. + /// + /// The claims identity. + /// The scopes list or an empty set if the claim cannot be found. + public static ImmutableArray GetScopes(this ClaimsIdentity identity) + => identity.GetClaims(Claims.Private.Scope); + /// /// Gets the scopes list stored in the claims principal. /// @@ -1222,6 +2082,14 @@ public static class OpenIddictExtensions public static ImmutableArray GetScopes(this ClaimsPrincipal principal) => principal.GetClaims(Claims.Private.Scope); + /// + /// Gets the access token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The access token lifetime or if the claim cannot be found. + public static TimeSpan? GetAccessTokenLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.AccessTokenLifetime); + /// /// Gets the access token lifetime associated with the claims principal. /// @@ -1230,6 +2098,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetAccessTokenLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.AccessTokenLifetime); + /// + /// Gets the authorization code lifetime associated with the claims identity. + /// + /// The claims identity. + /// The authorization code lifetime or if the claim cannot be found. + public static TimeSpan? GetAuthorizationCodeLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.AuthorizationCodeLifetime); + /// /// Gets the authorization code lifetime associated with the claims principal. /// @@ -1238,6 +2114,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetAuthorizationCodeLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.AuthorizationCodeLifetime); + /// + /// Gets the device code lifetime associated with the claims identity. + /// + /// The claims identity. + /// The device code lifetime or if the claim cannot be found. + public static TimeSpan? GetDeviceCodeLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.DeviceCodeLifetime); + /// /// Gets the device code lifetime associated with the claims principal. /// @@ -1246,6 +2130,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetDeviceCodeLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.DeviceCodeLifetime); + /// + /// Gets the identity token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The identity token lifetime or if the claim cannot be found. + public static TimeSpan? GetIdentityTokenLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.IdentityTokenLifetime); + /// /// Gets the identity token lifetime associated with the claims principal. /// @@ -1254,6 +2146,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetIdentityTokenLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.IdentityTokenLifetime); + /// + /// Gets the refresh token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The refresh token lifetime or if the claim cannot be found. + public static TimeSpan? GetRefreshTokenLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.RefreshTokenLifetime); + /// /// Gets the refresh token lifetime associated with the claims principal. /// @@ -1262,6 +2162,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetRefreshTokenLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.RefreshTokenLifetime); + /// + /// Gets the state token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The state token lifetime or if the claim cannot be found. + public static TimeSpan? GetStateTokenLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.StateTokenLifetime); + /// /// Gets the state token lifetime associated with the claims principal. /// @@ -1270,6 +2178,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetStateTokenLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.StateTokenLifetime); + /// + /// Gets the user code lifetime associated with the claims identity. + /// + /// The claims identity. + /// The user code lifetime or if the claim cannot be found. + public static TimeSpan? GetUserCodeLifetime(this ClaimsIdentity identity) + => GetLifetime(identity, Claims.Private.UserCodeLifetime); + /// /// Gets the user code lifetime associated with the claims principal. /// @@ -1278,6 +2194,14 @@ public static class OpenIddictExtensions public static TimeSpan? GetUserCodeLifetime(this ClaimsPrincipal principal) => GetLifetime(principal, Claims.Private.UserCodeLifetime); + /// + /// Gets the internal authorization identifier associated with the claims identity. + /// + /// The claims identity. + /// The unique identifier or if the claim cannot be found. + public static string? GetAuthorizationId(this ClaimsIdentity identity) + => identity.GetClaim(Claims.Private.AuthorizationId); + /// /// Gets the internal authorization identifier associated with the claims principal. /// @@ -1286,6 +2210,14 @@ public static class OpenIddictExtensions public static string? GetAuthorizationId(this ClaimsPrincipal principal) => principal.GetClaim(Claims.Private.AuthorizationId); + /// + /// Gets the internal token identifier associated with the claims identity. + /// + /// The claims identity. + /// The unique identifier or if the claim cannot be found. + public static string? GetTokenId(this ClaimsIdentity identity) + => identity.GetClaim(Claims.Private.TokenId); + /// /// Gets the internal token identifier associated with the claims principal. /// @@ -1294,6 +2226,14 @@ public static class OpenIddictExtensions public static string? GetTokenId(this ClaimsPrincipal principal) => principal.GetClaim(Claims.Private.TokenId); + /// + /// Gets the token type associated with the claims identity. + /// + /// The claims identity. + /// The token type or if the claim cannot be found. + public static string? GetTokenType(this ClaimsIdentity identity) + => identity.GetClaim(Claims.Private.TokenType); + /// /// Gets the token type associated with the claims principal. /// @@ -1302,25 +2242,67 @@ public static class OpenIddictExtensions public static string? GetTokenType(this ClaimsPrincipal principal) => principal.GetClaim(Claims.Private.TokenType); + /// + /// Determines whether the claims identity contains the given audience. + /// + /// The claims identity. + /// The audience. + /// if the identity contains the given audience. + public static bool HasAudience(this ClaimsIdentity identity, string audience) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(audience)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0186), nameof(audience)); + } + + return identity.HasClaim(Claims.Private.Audience, audience); + } + /// /// Determines whether the claims principal contains the given audience. /// - /// The claims principal. - /// The audience. - /// if the principal contains the given audience. - public static bool HasAudience(this ClaimsPrincipal principal, string audience) + /// The claims principal. + /// The audience. + /// if the principal contains the given audience. + public static bool HasAudience(this ClaimsPrincipal principal, string audience) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + if (string.IsNullOrEmpty(audience)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0186), nameof(audience)); + } + + return principal.HasClaim(Claims.Private.Audience, audience); + } + + /// + /// Determines whether the claims identity contains the given presenter. + /// + /// The claims identity. + /// The presenter. + /// if the identity contains the given presenter. + public static bool HasPresenter(this ClaimsIdentity identity, string presenter) { - if (principal is null) + if (identity is null) { - throw new ArgumentNullException(nameof(principal)); + throw new ArgumentNullException(nameof(identity)); } - if (string.IsNullOrEmpty(audience)) + if (string.IsNullOrEmpty(presenter)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0186), nameof(audience)); + throw new ArgumentException(SR.GetResourceString(SR.ID0187), nameof(presenter)); } - return principal.HasClaim(Claims.Private.Audience, audience); + return identity.HasClaim(Claims.Private.Presenter, presenter); } /// @@ -1344,6 +2326,27 @@ public static class OpenIddictExtensions return principal.HasClaim(Claims.Private.Presenter, presenter); } + /// + /// Determines whether the claims identity contains the given resource. + /// + /// The claims identity. + /// The resource. + /// if the identity contains the given resource. + public static bool HasResource(this ClaimsIdentity identity, string resource) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(resource)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } + + return identity.HasClaim(Claims.Private.Resource, resource); + } + /// /// Determines whether the claims principal contains the given resource. /// @@ -1365,6 +2368,27 @@ public static class OpenIddictExtensions return principal.HasClaim(Claims.Private.Resource, resource); } + /// + /// Determines whether the claims identity contains the given scope. + /// + /// The claims identity. + /// The scope. + /// if the identity contains the given scope. + public static bool HasScope(this ClaimsIdentity identity, string scope) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(scope)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0180), nameof(scope)); + } + + return identity.HasClaim(Claims.Private.Scope, scope); + } + /// /// Determines whether the claims principal contains the given scope. /// @@ -1386,6 +2410,27 @@ public static class OpenIddictExtensions return principal.HasClaim(Claims.Private.Scope, scope); } + /// + /// Determines whether the token type associated with the claims identity matches the specified type. + /// + /// The claims identity. + /// The token type. + /// if the token type matches the specified type. + public static bool HasTokenType(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0188), nameof(type)); + } + + return string.Equals(identity.GetTokenType(), type, StringComparison.OrdinalIgnoreCase); + } + /// /// Determines whether the token type associated with the claims principal matches the specified type. /// @@ -1407,6 +2452,15 @@ public static class OpenIddictExtensions return string.Equals(principal.GetTokenType(), type, StringComparison.OrdinalIgnoreCase); } + /// + /// Sets the creation date in the claims identity. + /// + /// The claims identity. + /// The creation date + /// The claims identity. + public static ClaimsIdentity SetCreationDate(this ClaimsIdentity identity, DateTimeOffset? date) + => identity.SetClaim(Claims.Private.CreationDate, date?.ToString("r", CultureInfo.InvariantCulture)); + /// /// Sets the creation date in the claims principal. /// @@ -1416,6 +2470,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetCreationDate(this ClaimsPrincipal principal, DateTimeOffset? date) => principal.SetClaim(Claims.Private.CreationDate, date?.ToString("r", CultureInfo.InvariantCulture)); + /// + /// Sets the expiration date in the claims identity. + /// + /// The claims identity. + /// The expiration date + /// The claims identity. + public static ClaimsIdentity SetExpirationDate(this ClaimsIdentity identity, DateTimeOffset? date) + => identity.SetClaim(Claims.Private.ExpirationDate, date?.ToString("r", CultureInfo.InvariantCulture)); + /// /// Sets the expiration date in the claims principal. /// @@ -1425,6 +2488,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetExpirationDate(this ClaimsPrincipal principal, DateTimeOffset? date) => principal.SetClaim(Claims.Private.ExpirationDate, date?.ToString("r", CultureInfo.InvariantCulture)); + /// + /// Sets the audiences list in the claims identity. + /// Note: this method automatically excludes duplicate audiences. + /// + /// The claims identity. + /// The audiences to store. + /// The claims identity. + public static ClaimsIdentity SetAudiences(this ClaimsIdentity identity, ImmutableArray audiences) + => identity.SetClaims(Claims.Private.Audience, audiences); + /// /// Sets the audiences list in the claims principal. /// Note: this method automatically excludes duplicate audiences. @@ -1435,6 +2508,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, ImmutableArray audiences) => principal.SetClaims(Claims.Private.Audience, audiences); + /// + /// Sets the audiences list in the claims identity. + /// Note: this method automatically excludes duplicate audiences. + /// + /// The claims identity. + /// The audiences to store. + /// The claims identity. + public static ClaimsIdentity SetAudiences(this ClaimsIdentity identity, IEnumerable? audiences) + => identity.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the audiences list in the claims principal. /// Note: this method automatically excludes duplicate audiences. @@ -1445,6 +2528,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, IEnumerable? audiences) => principal.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the audiences list in the claims identity. + /// Note: this method automatically excludes duplicate audiences. + /// + /// The claims identity. + /// The audiences to store. + /// The claims identity. + public static ClaimsIdentity SetAudiences(this ClaimsIdentity identity, params string[]? audiences) + => identity.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the audiences list in the claims principal. /// Note: this method automatically excludes duplicate audiences. @@ -1455,6 +2548,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, params string[]? audiences) => principal.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the presenters list in the claims identity. + /// Note: this method automatically excludes duplicate presenters. + /// + /// The claims identity. + /// The presenters to store. + /// The claims identity. + public static ClaimsIdentity SetPresenters(this ClaimsIdentity identity, ImmutableArray presenters) + => identity.SetClaims(Claims.Private.Presenter, presenters); + /// /// Sets the presenters list in the claims principal. /// Note: this method automatically excludes duplicate presenters. @@ -1465,6 +2568,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, ImmutableArray presenters) => principal.SetClaims(Claims.Private.Presenter, presenters); + /// + /// Sets the presenters list in the claims identity. + /// Note: this method automatically excludes duplicate presenters. + /// + /// The claims identity. + /// The presenters to store. + /// The claims identity. + public static ClaimsIdentity SetPresenters(this ClaimsIdentity identity, IEnumerable? presenters) + => identity.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the presenters list in the claims principal. /// Note: this method automatically excludes duplicate presenters. @@ -1475,6 +2588,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, IEnumerable? presenters) => principal.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the presenters list in the claims identity. + /// Note: this method automatically excludes duplicate presenters. + /// + /// The claims identity. + /// The presenters to store. + /// The claims identity. + public static ClaimsIdentity SetPresenters(this ClaimsIdentity identity, params string[]? presenters) + => identity.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the presenters list in the claims principal. /// Note: this method automatically excludes duplicate presenters. @@ -1485,6 +2608,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, params string[]? presenters) => principal.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the resources list in the claims identity. + /// Note: this method automatically excludes duplicate resources. + /// + /// The claims identity. + /// The resources to store. + /// The claims identity. + public static ClaimsIdentity SetResources(this ClaimsIdentity identity, ImmutableArray resources) + => identity.SetClaims(Claims.Private.Resource, resources); + /// /// Sets the resources list in the claims principal. /// Note: this method automatically excludes duplicate resources. @@ -1495,6 +2628,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, ImmutableArray resources) => principal.SetClaims(Claims.Private.Resource, resources); + /// + /// Sets the resources list in the claims identity. + /// Note: this method automatically excludes duplicate resources. + /// + /// The claims identity. + /// The resources to store. + /// The claims identity. + public static ClaimsIdentity SetResources(this ClaimsIdentity identity, IEnumerable? resources) + => identity.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the resources list in the claims principal. /// Note: this method automatically excludes duplicate resources. @@ -1505,6 +2648,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, IEnumerable? resources) => principal.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the resources list in the claims identity. + /// Note: this method automatically excludes duplicate resources. + /// + /// The claims identity. + /// The resources to store. + /// The claims identity. + public static ClaimsIdentity SetResources(this ClaimsIdentity identity, params string[]? resources) + => identity.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the resources list in the claims principal. /// Note: this method automatically excludes duplicate resources. @@ -1515,6 +2668,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, params string[]? resources) => principal.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the scopes list in the claims identity. + /// Note: this method automatically excludes duplicate scopes. + /// + /// The claims identity. + /// The scopes to store. + /// The claims identity. + public static ClaimsIdentity SetScopes(this ClaimsIdentity identity, ImmutableArray scopes) + => identity.SetClaims(Claims.Private.Scope, scopes); + /// /// Sets the scopes list in the claims principal. /// Note: this method automatically excludes duplicate scopes. @@ -1525,6 +2688,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, ImmutableArray scopes) => principal.SetClaims(Claims.Private.Scope, scopes); + /// + /// Sets the scopes list in the claims identity. + /// Note: this method automatically excludes duplicate scopes. + /// + /// The claims identity. + /// The scopes to store. + /// The claims identity. + public static ClaimsIdentity SetScopes(this ClaimsIdentity identity, IEnumerable? scopes) + => identity.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the scopes list in the claims principal. /// Note: this method automatically excludes duplicate scopes. @@ -1535,6 +2708,16 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, IEnumerable? scopes) => principal.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the scopes list in the claims identity. + /// Note: this method automatically excludes duplicate scopes. + /// + /// The claims identity. + /// The scopes to store. + /// The claims identity. + public static ClaimsIdentity SetScopes(this ClaimsIdentity identity, params string[]? scopes) + => identity.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); + /// /// Sets the scopes list in the claims principal. /// Note: this method automatically excludes duplicate scopes. @@ -1545,6 +2728,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, params string[]? scopes) => principal.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); + /// + /// Sets the access token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The access token lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetAccessTokenLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.AccessTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the access token lifetime associated with the claims principal. /// @@ -1554,6 +2746,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetAccessTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the authorization code lifetime associated with the claims identity. + /// + /// The claims identity. + /// The authorization code lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetAuthorizationCodeLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the authorization code lifetime associated with the claims principal. /// @@ -1563,6 +2764,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetAuthorizationCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the device code lifetime associated with the claims identity. + /// + /// The claims identity. + /// The device code lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetDeviceCodeLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the device code lifetime associated with the claims principal. /// @@ -1572,6 +2782,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetDeviceCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the identity token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The identity token lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetIdentityTokenLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the identity token lifetime associated with the claims principal. /// @@ -1581,6 +2800,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetIdentityTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the refresh token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The refresh token lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetRefreshTokenLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the refresh token lifetime associated with the claims principal. /// @@ -1590,6 +2818,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetRefreshTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the state token lifetime associated with the claims identity. + /// + /// The claims identity. + /// The state token lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetStateTokenLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.StateTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the state token lifetime associated with the claims principal. /// @@ -1599,6 +2836,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetStateTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.StateTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the user code lifetime associated with the claims identity. + /// + /// The claims identity. + /// The user code lifetime to store. + /// The claims identity. + public static ClaimsIdentity SetUserCodeLifetime(this ClaimsIdentity identity, TimeSpan? lifetime) + => identity.SetClaim(Claims.Private.UserCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// /// Sets the user code lifetime associated with the claims principal. /// @@ -1608,6 +2854,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetUserCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) => principal.SetClaim(Claims.Private.UserCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + /// + /// Sets the internal authorization identifier associated with the claims identity. + /// + /// The claims identity. + /// The unique identifier to store. + /// The claims identity. + public static ClaimsIdentity SetAuthorizationId(this ClaimsIdentity identity, string? identifier) + => identity.SetClaim(Claims.Private.AuthorizationId, identifier); + /// /// Sets the internal authorization identifier associated with the claims principal. /// @@ -1617,6 +2872,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetAuthorizationId(this ClaimsPrincipal principal, string? identifier) => principal.SetClaim(Claims.Private.AuthorizationId, identifier); + /// + /// Sets the internal token identifier associated with the claims identity. + /// + /// The claims identity. + /// The unique identifier to store. + /// The claims identity. + public static ClaimsIdentity SetTokenId(this ClaimsIdentity identity, string? identifier) + => identity.SetClaim(Claims.Private.TokenId, identifier); + /// /// Sets the internal token identifier associated with the claims principal. /// @@ -1626,6 +2890,15 @@ public static class OpenIddictExtensions public static ClaimsPrincipal SetTokenId(this ClaimsPrincipal principal, string? identifier) => principal.SetClaim(Claims.Private.TokenId, identifier); + /// + /// Sets the token type associated with the claims identity. + /// + /// The claims identity. + /// The token type to store. + /// The claims identity. + public static ClaimsIdentity SetTokenType(this ClaimsIdentity identity, string? type) + => identity.SetClaim(Claims.Private.TokenType, type); + /// /// Sets the token type associated with the claims principal. /// @@ -1752,6 +3025,43 @@ public static class OpenIddictExtensions return false; } + private static string GetClaimValueType(JsonElement element) => element.ValueKind switch + { + JsonValueKind.String => ClaimValueTypes.String, + JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean, + + JsonValueKind.Number when element.TryGetInt32(out _) => ClaimValueTypes.Integer32, + JsonValueKind.Number when element.TryGetInt64(out _) => ClaimValueTypes.Integer64, + JsonValueKind.Number when element.TryGetUInt32(out _) => ClaimValueTypes.UInteger32, + JsonValueKind.Number when element.TryGetUInt64(out _) => ClaimValueTypes.UInteger64, + JsonValueKind.Number when element.TryGetDouble(out _) => ClaimValueTypes.Double, + + JsonValueKind.Null or JsonValueKind.Undefined => "JSON_NULL", + JsonValueKind.Array => "JSON_ARRAY", + JsonValueKind.Object or _ => "JSON" + }; + + private static TimeSpan? GetLifetime(ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); + } + + var value = identity.GetClaim(type); + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result)) + { + return TimeSpan.FromSeconds(result); + } + + return null; + } + private static TimeSpan? GetLifetime(ClaimsPrincipal principal, string type) { if (principal is null) @@ -1772,4 +3082,24 @@ public static class OpenIddictExtensions return null; } + + private static bool IsEmptyJsonElement(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.String: + return string.IsNullOrEmpty(element.GetString()); + + case JsonValueKind.Array: + return element.GetArrayLength() is 0; + + case JsonValueKind.Object: + using (var enumerator = element.EnumerateObject()) + { + return !enumerator.MoveNext(); + } + + default: return false; + } + } } diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs index 3a1ab85f..bd7fc2ca 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs @@ -6,6 +6,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; +using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; @@ -157,15 +158,18 @@ public class OpenIddictClientAspNetCoreHandler : AuthenticationHandler? tokens = null; @@ -338,6 +342,29 @@ public class OpenIddictClientAspNetCoreHandler : AuthenticationHandler(comparer: StringComparer.Ordinal); + using var document = JsonDocument.Parse(value); + + foreach (var property in document.RootElement.EnumerateObject()) + { + dictionary[property.Name] = property.Value.GetString(); + } + + return new AuthenticationProperties(dictionary); + } + } + + return new AuthenticationProperties(); + } } } diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs index 6320c475..a88d741d 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs @@ -412,6 +412,8 @@ public static partial class OpenIddictClientAspNetCoreHandlers throw new ArgumentNullException(nameof(context)); } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); if (properties is null) { @@ -436,6 +438,12 @@ public static partial class OpenIddictClientAspNetCoreHandlers context.TargetLinkUri = properties.RedirectUri; } + // Preserve the host properties in the principal. + if (properties.Items.Count is not 0) + { + context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); + } + foreach (var parameter in properties.Parameters) { context.Parameters[parameter.Key] = parameter.Value switch diff --git a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConstants.cs b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConstants.cs index 66784ba6..6898b3b4 100644 --- a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConstants.cs +++ b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConstants.cs @@ -13,6 +13,7 @@ public static class OpenIddictClientDataProtectionConstants public const string Audiences = ".audiences"; public const string CodeVerifier = ".code_verifier"; public const string Expires = ".expires"; + public const string HostProperties = ".host_properties"; public const string InternalTokenId = ".internal_token_id"; public const string Issued = ".issued"; public const string Nonce = ".nonce"; diff --git a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs index 688131a4..ee20518e 100644 --- a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs +++ b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs @@ -29,10 +29,12 @@ public class OpenIddictClientDataProtectionFormatter : IOpenIddictClientDataProt // can be reused, well-known properties are manually mapped to their claims equivalents. return principal - .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) - .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) - .SetResources(GetArrayProperty(properties, Properties.Resources)) - .SetScopes(GetArrayProperty(properties, Properties.Scopes)) + .SetClaims(Claims.Private.Audience, GetJsonProperty(properties, Properties.Audiences)) + .SetClaims(Claims.Private.Presenter, GetJsonProperty(properties, Properties.Presenters)) + .SetClaims(Claims.Private.Resource, GetJsonProperty(properties, Properties.Resources)) + .SetClaims(Claims.Private.Scope, GetJsonProperty(properties, Properties.Scopes)) + + .SetClaim(Claims.Private.HostProperties, GetJsonProperty(properties, Properties.HostProperties)) .SetClaim(Claims.Private.CodeVerifier, GetProperty(properties, Properties.CodeVerifier)) .SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued)) @@ -158,28 +160,15 @@ public class OpenIddictClientDataProtectionFormatter : IOpenIddictClientDataProt static string? GetProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? value : null; - static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + static JsonElement GetJsonProperty(IReadOnlyDictionary properties, string name) { if (properties.TryGetValue(name, out var value)) { using var document = JsonDocument.Parse(value); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) - { - var item = element.GetString(); - if (string.IsNullOrEmpty(item)) - { - continue; - } - - builder.Add(item); - } - - return builder.ToImmutable(); + return document.RootElement.Clone(); } - return ImmutableArray.Create(); + return default; } } @@ -209,6 +198,7 @@ public class OpenIddictClientDataProtectionFormatter : IOpenIddictClientDataProt SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId()); SetProperty(properties, Properties.CodeVerifier, principal.GetClaim(Claims.Private.CodeVerifier)); + SetProperty(properties, Properties.HostProperties, principal.GetClaim(Claims.Private.HostProperties)); SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce)); SetProperty(properties, Properties.OriginalRedirectUri, principal.GetClaim(Claims.Private.RedirectUri)); diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs index f6732d51..f372c5f4 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs @@ -5,6 +5,7 @@ */ using System.Security.Claims; +using System.Text.Json; using Microsoft.IdentityModel.Tokens; using Microsoft.Owin.Security.Infrastructure; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; @@ -173,15 +174,18 @@ public class OpenIddictClientOwinHandler : AuthenticationHandler(comparer: StringComparer.Ordinal); + using var document = JsonDocument.Parse(value); + + foreach (var property in document.RootElement.EnumerateObject()) + { + dictionary[property.Name] = property.Value.GetString(); + } + + return new AuthenticationProperties(dictionary); + } + } + + return new AuthenticationProperties(); + } } } diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs index a6eb1e3b..d5f4a0a3 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs @@ -432,6 +432,8 @@ public static partial class OpenIddictClientOwinHandlers throw new ArgumentNullException(nameof(context)); } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); if (properties is null) { @@ -456,6 +458,12 @@ public static partial class OpenIddictClientOwinHandlers context.TargetLinkUri = properties.RedirectUri; } + // Preserve the host properties in the principal. + if (properties.Dictionary.Count is not 0) + { + context.Principal.SetClaim(Claims.Private.HostProperties, properties.Dictionary); + } + // Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed // dictionary that allows flowing parameters while preserving their original types. To allow // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs index c69cf73c..a44eda6d 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Security.Claims; using System.Text.Json; -using Microsoft.IdentityModel.JsonWebTokens; namespace OpenIddict.Client; @@ -159,28 +158,11 @@ public static partial class OpenIddictClientHandlers // Top-level claims represented as arrays are split and mapped to multiple CLR claims // to match the logic implemented by IdentityModel for JWT token deserialization. case { ValueKind: JsonValueKind.Array } value: - foreach (var item in value.EnumerateArray()) - { - identity.AddClaim(new Claim( - type : parameter.Key, - value : item.ToString()!, - valueType : GetClaimValueType(item), - issuer : issuer, - originalIssuer: issuer, - subject : identity)); - } + identity.AddClaims(parameter.Key, value, issuer); break; - // Note: JsonElement.ToString() returns string.Empty for JsonValueKind.Null and - // JsonValueKind.Undefined, which, unlike null strings, is a valid claim value. case { ValueKind: _ } value: - identity.AddClaim(new Claim( - type : parameter.Key, - value : value.ToString()!, - valueType : GetClaimValueType(value), - issuer : issuer, - originalIssuer: issuer, - subject : identity)); + identity.AddClaim(parameter.Key, value, issuer); break; } } @@ -188,22 +170,6 @@ public static partial class OpenIddictClientHandlers context.Principal = new ClaimsPrincipal(identity); return default; - - static string GetClaimValueType(JsonElement element) => element.ValueKind switch - { - JsonValueKind.String => ClaimValueTypes.String, - JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean, - - JsonValueKind.Number when element.TryGetInt32(out _) => ClaimValueTypes.Integer32, - JsonValueKind.Number when element.TryGetInt64(out _) => ClaimValueTypes.Integer64, - JsonValueKind.Number when element.TryGetUInt32(out _) => ClaimValueTypes.UInteger32, - JsonValueKind.Number when element.TryGetUInt64(out _) => ClaimValueTypes.UInteger64, - JsonValueKind.Number when element.TryGetDouble(out _) => ClaimValueTypes.Double, - - JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull, - JsonValueKind.Array => JsonClaimValueTypes.JsonArray, - JsonValueKind.Object or _ => JsonClaimValueTypes.Json - }; } } } diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index 85264ee1..3a3a6202 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -10,6 +10,8 @@ using System.Diagnostics; using System.Security.Claims; using System.Security.Cryptography; using System.Text; +using System.Text.Json; +using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; #if !SUPPORTS_TIME_CONSTANT_COMPARISONS diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs index 60502667..93653b2c 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs @@ -6,6 +6,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; +using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; @@ -181,11 +182,10 @@ public class OpenIddictServerAspNetCoreHandler : AuthenticationHandler? tokens = null; @@ -290,6 +290,29 @@ public class OpenIddictServerAspNetCoreHandler : AuthenticationHandler(comparer: StringComparer.Ordinal); + using var document = JsonDocument.Parse(value); + + foreach (var property in document.RootElement.EnumerateObject()) + { + dictionary[property.Name] = property.Value.GetString(); + } + + return new AuthenticationProperties(dictionary); + } + } + + return new AuthenticationProperties(); + } } /// diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index e5d2e89f..6a818b7d 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; +using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -316,7 +317,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers } /// - /// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET + /// Contains the logic responsible for resolving the additional challenge parameters stored in the ASP.NET /// Core authentication properties specified by the application that triggered the sign-in operation. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// @@ -396,12 +397,20 @@ public static partial class OpenIddictServerAspNetCoreHandlers throw new ArgumentNullException(nameof(context)); } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); if (properties is null) { return default; } + // Preserve the host properties in the principal. + if (properties.Items.Count is not 0) + { + context.Principal.SetClaim(Claims.Private.HostProperties, properties.Items); + } + foreach (var parameter in properties.Parameters) { context.Parameters[parameter.Key] = parameter.Value switch diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs index 30dfbea4..7a208bf4 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs @@ -17,6 +17,7 @@ public static class OpenIddictServerDataProtectionConstants public const string CodeChallengeMethod = ".code_challenge_method"; public const string DeviceCodeId = ".device_code_id"; public const string DeviceCodeLifetime = ".device_code_lifetime"; + public const string HostProperties = ".host_properties"; public const string Expires = ".expires"; public const string IdentityTokenLifetime = ".identity_token_lifetime"; public const string InternalAuthorizationId = ".internal_authorization_id"; diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs index 8fbfe657..96ae957a 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs @@ -29,10 +29,12 @@ public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProt // can be reused, well-known properties are manually mapped to their claims equivalents. return principal - .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) - .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) - .SetResources(GetArrayProperty(properties, Properties.Resources)) - .SetScopes(GetArrayProperty(properties, Properties.Scopes)) + .SetClaims(Claims.Private.Audience, GetJsonProperty(properties, Properties.Audiences)) + .SetClaims(Claims.Private.Presenter, GetJsonProperty(properties, Properties.Presenters)) + .SetClaims(Claims.Private.Resource, GetJsonProperty(properties, Properties.Resources)) + .SetClaims(Claims.Private.Scope, GetJsonProperty(properties, Properties.Scopes)) + + .SetClaim(Claims.Private.HostProperties, GetJsonProperty(properties, Properties.HostProperties)) .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) @@ -166,28 +168,15 @@ public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProt static string? GetProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? value : null; - static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + static JsonElement GetJsonProperty(IReadOnlyDictionary properties, string name) { if (properties.TryGetValue(name, out var value)) { using var document = JsonDocument.Parse(value); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) - { - var item = element.GetString(); - if (string.IsNullOrEmpty(item)) - { - continue; - } - - builder.Add(item); - } - - return builder.ToImmutable(); + return document.RootElement.Clone(); } - return ImmutableArray.Create(); + return default; } } @@ -221,6 +210,7 @@ public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProt SetProperty(properties, Properties.CodeChallenge, principal.GetClaim(Claims.Private.CodeChallenge)); SetProperty(properties, Properties.CodeChallengeMethod, principal.GetClaim(Claims.Private.CodeChallengeMethod)); + SetProperty(properties, Properties.HostProperties, principal.GetClaim(Claims.Private.HostProperties)); SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId()); SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId()); diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs index b1328849..7375862b 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs @@ -5,6 +5,7 @@ */ using System.Security.Claims; +using System.Text.Json; using Microsoft.Owin.Security.Infrastructure; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties; @@ -187,11 +188,10 @@ public class OpenIddictServerOwinHandler : AuthenticationHandler(comparer: StringComparer.Ordinal); + using var document = JsonDocument.Parse(value); + + foreach (var property in document.RootElement.EnumerateObject()) + { + dictionary[property.Name] = property.Value.GetString(); + } + + return new AuthenticationProperties(dictionary); + } + } + + return new AuthenticationProperties(); + } } /// diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index cfb5b46e..0410ccbd 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Globalization; +using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -317,7 +318,7 @@ public static partial class OpenIddictServerOwinHandlers } /// - /// Contains the logic responsible for resolving the additional sign-in parameters stored in the + /// Contains the logic responsible for resolving the additional challenge parameters stored in the /// OWIN authentication properties specified by the application that triggered the sign-in operation. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// @@ -416,12 +417,20 @@ public static partial class OpenIddictServerOwinHandlers throw new ArgumentNullException(nameof(context)); } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); if (properties is null) { return default; } + // Preserve the host properties in the principal. + if (properties.Dictionary.Count is not 0) + { + context.Principal.SetClaim(Claims.Private.HostProperties, properties.Dictionary); + } + // Note: unlike ASP.NET Core, Owin's AuthenticationProperties doesn't offer a strongly-typed // dictionary that allows flowing parameters while preserving their original types. To allow // returning custom parameters, the OWIN host allows using AuthenticationProperties.Dictionary diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs index e20e3f06..9d55277a 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs @@ -18,6 +18,7 @@ public static class OpenIddictValidationDataProtectionConstants public const string DeviceCodeId = ".device_code_id"; public const string DeviceCodeLifetime = ".device_code_lifetime"; public const string Expires = ".expires"; + public const string HostProperties = ".host_properties"; public const string IdentityTokenLifetime = ".identity_token_lifetime"; public const string InternalAuthorizationId = ".internal_authorization_id"; public const string InternalTokenId = ".internal_token_id"; diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs index c9fae7b4..2e369b6a 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs @@ -4,7 +4,6 @@ * the license and the contributors participating to this project. */ -using System.Collections.Immutable; using System.Security.Claims; using System.Text.Json; using Properties = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Properties; @@ -27,10 +26,12 @@ public class OpenIddictValidationDataProtectionFormatter : IOpenIddictValidation // can be reused, well-known properties are manually mapped to their claims equivalents. return principal - .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) - .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) - .SetResources(GetArrayProperty(properties, Properties.Resources)) - .SetScopes(GetArrayProperty(properties, Properties.Scopes)) + .SetClaims(Claims.Private.Audience, GetJsonProperty(properties, Properties.Audiences)) + .SetClaims(Claims.Private.Presenter, GetJsonProperty(properties, Properties.Presenters)) + .SetClaims(Claims.Private.Resource, GetJsonProperty(properties, Properties.Resources)) + .SetClaims(Claims.Private.Scope, GetJsonProperty(properties, Properties.Scopes)) + + .SetClaim(Claims.Private.HostProperties, GetJsonProperty(properties, Properties.HostProperties)) .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) @@ -164,28 +165,15 @@ public class OpenIddictValidationDataProtectionFormatter : IOpenIddictValidation static string? GetProperty(IReadOnlyDictionary properties, string name) => properties.TryGetValue(name, out var value) ? value : null; - static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + static JsonElement GetJsonProperty(IReadOnlyDictionary properties, string name) { if (properties.TryGetValue(name, out var value)) { using var document = JsonDocument.Parse(value); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) - { - var item = element.GetString(); - if (string.IsNullOrEmpty(item)) - { - continue; - } - - builder.Add(item); - } - - return builder.ToImmutable(); + return document.RootElement.Clone(); } - return ImmutableArray.Create(); + return default; } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs index 44cf0792..ce51a2f5 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Security.Claims; using System.Text.Json; -using Microsoft.IdentityModel.JsonWebTokens; namespace OpenIddict.Validation; @@ -411,28 +410,11 @@ public static partial class OpenIddictValidationHandlers // Top-level claims represented as arrays are split and mapped to multiple CLR claims // to match the logic implemented by IdentityModel for JWT token deserialization. case { ValueKind: JsonValueKind.Array } value: - foreach (var item in value.EnumerateArray()) - { - identity.AddClaim(new Claim( - type : parameter.Key, - value : item.ToString()!, - valueType : GetClaimValueType(item), - issuer : issuer, - originalIssuer: issuer, - subject : identity)); - } + identity.AddClaims(parameter.Key, value, issuer); break; - // Note: JsonElement.ToString() returns string.Empty for JsonValueKind.Null and - // JsonValueKind.Undefined, which, unlike null strings, is a valid claim value. case { ValueKind: _ } value: - identity.AddClaim(new Claim( - type : parameter.Key, - value : value.ToString()!, - valueType : GetClaimValueType(value), - issuer : issuer, - originalIssuer: issuer, - subject : identity)); + identity.AddClaim(parameter.Key, value, issuer); break; } } @@ -440,22 +422,6 @@ public static partial class OpenIddictValidationHandlers context.Principal = new ClaimsPrincipal(identity); return default; - - static string GetClaimValueType(JsonElement element) => element.ValueKind switch - { - JsonValueKind.String => ClaimValueTypes.String, - JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean, - - JsonValueKind.Number when element.TryGetInt32(out _) => ClaimValueTypes.Integer32, - JsonValueKind.Number when element.TryGetInt64(out _) => ClaimValueTypes.Integer64, - JsonValueKind.Number when element.TryGetUInt32(out _) => ClaimValueTypes.UInteger32, - JsonValueKind.Number when element.TryGetUInt64(out _) => ClaimValueTypes.UInteger64, - JsonValueKind.Number when element.TryGetDouble(out _) => ClaimValueTypes.Double, - - JsonValueKind.Null or JsonValueKind.Undefined => JsonClaimValueTypes.JsonNull, - JsonValueKind.Array => JsonClaimValueTypes.JsonArray, - JsonValueKind.Object or _ => JsonClaimValueTypes.Json - }; } } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs index 0ca8b065..11b8a721 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.Globalization; using System.Security.Claims; +using System.Text.Json; using Xunit; namespace OpenIddict.Abstractions.Tests.Primitives; @@ -1071,6 +1072,18 @@ public class OpenIddictExtensionsTests Assert.Equal(destination, claim.Properties[Properties.Destinations]); } + [Fact] + public void ClaimsIdentity_GetDestinations_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(identity.GetDestinations); + + Assert.Equal("identity", exception.ParamName); + } + [Fact] public void ClaimsPrincipal_GetDestinations_ThrowsAnExceptionForNullPrincipal() { @@ -1083,6 +1096,40 @@ public class OpenIddictExtensionsTests Assert.Equal("principal", exception.ParamName); } + [Fact] + public void ClaimsIdentity_GetDestinations_ReturnsExpectedDestinations() + { + // Arrange + var claims = new[] + { + new Claim(Claims.Name, "Bob le Bricoleur") + { + Properties = + { + [Properties.Destinations] = @"[""access_token"",""id_token""]" + } + }, + new Claim(Claims.Email, "bob@bricoleur.com") + { + Properties = + { + [Properties.Destinations] = @"[""id_token""]" + } + }, + new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") + }; + + var identity = new ClaimsIdentity(claims); + + // Act + var destinations = identity.GetDestinations(); + + // Assert + Assert.Equal(2, destinations.Count); + Assert.Equal(new[] { Destinations.AccessToken, Destinations.IdentityToken }, destinations[Claims.Name]); + Assert.Equal(new[] { Destinations.IdentityToken }, destinations[Claims.Email]); + } + [Fact] public void ClaimsPrincipal_GetDestinations_ReturnsExpectedDestinations() { @@ -1118,7 +1165,19 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsPrincipal_SetDestinations_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_SetDestinationsWithDictionary_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetDestinations(destinations: null!)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetDestinationsWithDictionary_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; @@ -1130,7 +1189,20 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsPrincipal_SetDestinations_ThrowsAnExceptionForNullDestinations() + public void ClaimsIdentity_SetDestinationsWithDictionary_ThrowsAnExceptionForNullDestinations() + { + // Arrange + var identity = new ClaimsIdentity(); + var destinations = (ImmutableDictionary) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetDestinations(destinations)); + + Assert.Equal("destinations", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetDestinationsWithDictionary_ThrowsAnExceptionForNullDestinations() { // Arrange var principal = new ClaimsPrincipal(new ClaimsIdentity()); @@ -1143,7 +1215,34 @@ public class OpenIddictExtensionsTests } [Fact] - public void ClaimsPrincipal_SetDestinations_SetsAppropriateDestinations() + public void ClaimsIdentity_SetDestinationsWithDictionary_SetsAppropriateDestinations() + { + // Arrange + var claims = new[] + { + new Claim(Claims.Name, "Bob le Bricoleur"), + new Claim(Claims.Email, "bob@bricoleur.com"), + new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") + }; + + var identity = new ClaimsIdentity(claims); + + var destinations = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + destinations.Add(Claims.Name, new[] { Destinations.AccessToken, Destinations.IdentityToken }); + destinations.Add(Claims.Email, new[] { Destinations.IdentityToken }); + destinations.Add(Claims.Nonce, Array.Empty()); + + // Act + identity.SetDestinations(destinations.ToImmutable()); + + // Assert + Assert.Equal(@"[""access_token"",""id_token""]", identity.FindFirst(Claims.Name)!.Properties[Properties.Destinations]); + Assert.Equal(@"[""id_token""]", identity.FindFirst(Claims.Email)!.Properties[Properties.Destinations]); + Assert.DoesNotContain(Properties.Destinations, identity.FindFirst(Claims.Nonce)!.Properties); + } + + [Fact] + public void ClaimsPrincipal_SetDestinationsWithDictionary_SetsAppropriateDestinations() { // Arrange var claims = new[] @@ -1169,1055 +1268,1031 @@ public class OpenIddictExtensionsTests Assert.DoesNotContain(Properties.Destinations, principal.FindFirst(Claims.Nonce)!.Properties); } - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void SetDestinations_IEnumerable_SetsAppropriateDestinations(string[] destinations, string destination) + [Fact] + public void ClaimsIdentity_SetDestinationsWithDelegate_ThrowsAnExceptionForNullIdentity() { // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + var identity = (ClaimsIdentity) null!; - // Act - claim.SetDestinations((IEnumerable) destinations); + // Act and assert + var exception = Assert.Throws(() => identity.SetDestinations(selector: null!)); - // Assert - Assert.Equal(destination, claim.Properties[Properties.Destinations]); + Assert.Equal("identity", exception.ParamName); } - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void SetDestinations_ImmutableArray_SetsAppropriateDestinations(string[] destinations, string destination) + [Fact] + public void ClaimsPrincipal_SetDestinationsWithDelegate_ThrowsAnExceptionForNullPrincipal() { // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + var principal = (ClaimsPrincipal) null!; - // Act - claim.SetDestinations(ImmutableArray.Create(destinations)); + // Act and assert + var exception = Assert.Throws(() => principal.SetDestinations(selector: null!)); - // Assert - Assert.Equal(destination, claim.Properties[Properties.Destinations]); + Assert.Equal("principal", exception.ParamName); } [Fact] - public void ClaimsIdentity_Clone_ReturnsDifferentInstanceWithFilteredClaims() + public void ClaimsIdentity_SetDestinationsWithDelegate_ThrowsAnExceptionForNullSelector() { // Arrange var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.ClientId, "B56BF6CE-8D8C-4290-A0E7-A4F8EE0A9FC4")); + var selector = (Func>) null!; - // Act - var clone = identity.Clone(claim => claim.Type == Claims.Name); - clone.AddClaim(new Claim("clone_claim", "value")); + // Act and assert + var exception = Assert.Throws(() => identity.SetDestinations(selector)); - // Assert - Assert.NotSame(identity, clone); - Assert.Null(identity.FindFirst("clone_claim")); - Assert.NotNull(clone.FindFirst(Claims.Name)); - Assert.Null(clone.FindFirst(Claims.ClientId)); + Assert.Equal("selector", exception.ParamName); } [Fact] - public void ClaimsIdentity_Clone_ExcludesUnwantedClaims() + public void ClaimsPrincipal_SetDestinationsWithDelegate_ThrowsAnExceptionForNullSelector() { // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + var selector = (Func>) null!; - // Act - var clone = identity.Clone(claim => claim.Type == Claims.Name); + // Act and assert + var exception = Assert.Throws(() => principal.SetDestinations(selector)); - // Assert - Assert.Single(clone.Claims); - Assert.Null(clone.FindFirst(Claims.Subject)); - Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); + Assert.Equal("selector", exception.ParamName); } [Fact] - public void ClaimsIdentity_Clone_ExcludesUnwantedClaimsFromActor() + public void ClaimsIdentity_SetDestinationsWithDelegate_SetsAppropriateDestinations() { // Arrange - var identity = new ClaimsIdentity + var claims = new[] { - Actor = new ClaimsIdentity() + new Claim(Claims.Name, "Bob le Bricoleur"), + new Claim(Claims.Email, "bob@bricoleur.com"), + new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") }; - identity.Actor.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.Actor.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); + + var identity = new ClaimsIdentity(claims); // Act - var clone = identity.Clone(claim => claim.Type == Claims.Name); + identity.SetDestinations(claim => claim.Type switch + { + Claims.Name => new[] { Destinations.AccessToken, Destinations.IdentityToken }, + Claims.Email => new[] { Destinations.IdentityToken }, + + _ => Array.Empty() + }); // Assert - Assert.Single(clone.Actor!.Claims); - Assert.Null(clone.Actor.FindFirst(Claims.Subject)); - Assert.Equal("Bob le Bricoleur", clone.Actor.FindFirst(Claims.Name)!.Value); + Assert.Equal(@"[""access_token"",""id_token""]", identity.FindFirst(Claims.Name)!.Properties[Properties.Destinations]); + Assert.Equal(@"[""id_token""]", identity.FindFirst(Claims.Email)!.Properties[Properties.Destinations]); + Assert.DoesNotContain(Properties.Destinations, identity.FindFirst(Claims.Nonce)!.Properties); } [Fact] - public void ClaimsPrincipal_Clone_ExcludesUnwantedClaimsFromIdentities() + public void ClaimsPrincipal_SetDestinationsWithDelegate_SetsAppropriateDestinations() { // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); + var claims = new[] + { + new Claim(Claims.Name, "Bob le Bricoleur"), + new Claim(Claims.Email, "bob@bricoleur.com"), + new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") + }; - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims)); // Act - var clone = principal.Clone(claim => claim.Type == Claims.Name); + principal.SetDestinations(claim => claim.Type switch + { + Claims.Name => new[] { Destinations.AccessToken, Destinations.IdentityToken }, + Claims.Email => new[] { Destinations.IdentityToken }, + + _ => Array.Empty() + }); // Assert - Assert.Single(clone.Claims); - Assert.Null(clone.FindFirst(Claims.Subject)); - Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); + Assert.Equal(@"[""access_token"",""id_token""]", principal.FindFirst(Claims.Name)!.Properties[Properties.Destinations]); + Assert.Equal(@"[""id_token""]", principal.FindFirst(Claims.Email)!.Properties[Properties.Destinations]); + Assert.DoesNotContain(Properties.Destinations, principal.FindFirst(Claims.Nonce)!.Properties); } [Fact] - public void AddClaim_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_Clone_ThrowsAnExceptionForNullIdentity() { // Arrange var identity = (ClaimsIdentity) null!; // Act and assert - var exception = Assert.Throws(() => - { - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - }); + var exception = Assert.Throws(() => identity.Clone(claim => true)); Assert.Equal("identity", exception.ParamName); } [Fact] - public void AddClaim_SetsAppropriateClaim() + public void ClaimsPrincipal_Clone_ThrowsAnExceptionForNullPrincipal() { // Arrange - var identity = new ClaimsIdentity(); + var principal = (ClaimsPrincipal) null!; - // Act - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + // Act and assert + var exception = Assert.Throws(() => principal.Clone(claim => true)); - // Assert - Assert.Equal("Bob le Bricoleur", identity.FindFirst(Claims.Name)!.Value); + Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void AddClaim_ImmutableArray_SetsAppropriateDestinations(string[] destinations, string destination) + [Fact] + public void ClaimsIdentity_Clone_ReturnsIdenticalIdentity() { // Arrange var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); // Act - identity.AddClaim(Claims.Name, "Bob le Bricoleur", ImmutableArray.Create(destinations)); - - var claim = identity.FindFirst(Claims.Name)!; + var copy = identity.Clone(claim => true); // Assert - Assert.Equal("Bob le Bricoleur", claim.Value); - Assert.Equal(destination, claim.Properties[Properties.Destinations]); + Assert.Equal("value", copy.GetClaim("type")); + Assert.Equal(identity.Claims.Count(), copy.Claims.Count()); } - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void AddClaim_SetsAppropriateDestinations(string[] destinations, string destination) + [Fact] + public void ClaimsPrincipal_Clone_ReturnsIdenticalPrincipal() { // Arrange var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - // Act - identity.AddClaim(Claims.Name, "Bob le Bricoleur", destinations); + var principal = new ClaimsPrincipal(identity); - var claim = identity.FindFirst(Claims.Name)!; + // Act + var copy = principal.Clone(claim => true); // Assert - Assert.Equal("Bob le Bricoleur", claim.Value); - Assert.Equal(destination, claim.Properties[Properties.Destinations]); + Assert.Equal("Bob le Bricoleur", copy.GetClaim(Claims.Name)); + Assert.Equal(principal.Claims.Count(), copy.Claims.Count()); } [Fact] - public void GetClaim_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_Clone_ReturnsDifferentIdentityInstance() { // Arrange - var identity = (ClaimsIdentity) null!; + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); - // Act and assert - var exception = Assert.Throws(() => - { - identity.GetClaim(Claims.Name); - }); + // Act + var copy = identity.Clone(claim => true); + copy.AddClaim("clone_type", "value"); - Assert.Equal("identity", exception.ParamName); + // Assert + Assert.NotSame(identity, copy); + Assert.Null(identity.FindFirst("clone_type")); } [Fact] - public void GetClaim_ReturnsNullForMissingClaims() + public void ClaimsPrincipal_Clone_ReturnsDifferentPrincipalInstance() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(); - - // Act and assert - Assert.Null(identity.GetClaim(Claims.Name)); - Assert.Null(principal.GetClaim(Claims.Name)); - } + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - [Fact] - public void GetClaim_ReturnsAppropriateResult() - { - // Arrange - var identity = new ClaimsIdentity(); var principal = new ClaimsPrincipal(identity); - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + // Act + var copy = principal.Clone(claim => true); + copy.SetClaim("clone_claim", "value"); - // Act and assert - Assert.Equal("Bob le Bricoleur", identity.GetClaim(Claims.Name)); - Assert.Equal("Bob le Bricoleur", principal.GetClaim(Claims.Name)); + // Assert + Assert.NotSame(principal, copy); + Assert.Null(principal.FindFirst("clone_claim")); } [Fact] - public void ClaimsIdentity_Clone_ThrowsAnExceptionForNullIdentity() + public void ClaimsIdentity_Clone_ReturnsDifferentInstanceWithFilteredClaims() { // Arrange - var identity = (ClaimsIdentity) null!; - - // Act and assert - var exception = Assert.Throws(() => identity.Clone(claim => true)); + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.ClientId, "B56BF6CE-8D8C-4290-A0E7-A4F8EE0A9FC4")); - Assert.Equal("identity", exception.ParamName); + // Act + var clone = identity.Clone(claim => claim.Type == Claims.Name); + clone.AddClaim(new Claim("clone_claim", "value")); + + // Assert + Assert.NotSame(identity, clone); + Assert.Null(identity.FindFirst("clone_claim")); + Assert.NotNull(clone.FindFirst(Claims.Name)); + Assert.Null(clone.FindFirst(Claims.ClientId)); } [Fact] - public void ClaimsIdentity_Clone_ReturnsIdenticalIdentity() + public void ClaimsPrincipal_Clone_ReturnsDifferentInstanceWithFilteredClaims() { // Arrange var identity = new ClaimsIdentity(); - identity.AddClaim("type", "value"); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.ClientId, "B56BF6CE-8D8C-4290-A0E7-A4F8EE0A9FC4")); + var principal = new ClaimsPrincipal(identity); // Act - var copy = identity.Clone(claim => true); + var clone = principal.Clone(claim => claim.Type == Claims.Name); + ((ClaimsIdentity) clone.Identity!).AddClaim(new Claim("clone_claim", "value")); // Assert - Assert.Equal("value", copy.GetClaim("type")); - Assert.Equal(identity.Claims.Count(), copy.Claims.Count()); + Assert.NotSame(identity, clone); + Assert.Null(identity.FindFirst("clone_claim")); + Assert.NotNull(clone.FindFirst(Claims.Name)); + Assert.Null(clone.FindFirst(Claims.ClientId)); } [Fact] - public void ClaimsPrincipal_Clone_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_Clone_ExcludesUnwantedClaims() { // Arrange - var principal = (ClaimsPrincipal) null!; + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); - // Act and assert - var exception = Assert.Throws(() => principal.Clone(claim => true)); + // Act + var clone = identity.Clone(claim => claim.Type == Claims.Name); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Single(clone.Claims); + Assert.Null(clone.FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); } [Fact] - public void ClaimsPrincipal_Clone_ReturnsIdenticalPrincipal() + public void ClaimsPrincipal_Clone_ExcludesUnwantedClaims() { // Arrange var identity = new ClaimsIdentity(); identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - + identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); var principal = new ClaimsPrincipal(identity); // Act - var copy = principal.Clone(claim => true); + var clone = principal.Clone(claim => claim.Type == Claims.Name); // Assert - Assert.Equal("Bob le Bricoleur", copy.GetClaim(Claims.Name)); - Assert.Equal(principal.Claims.Count(), copy.Claims.Count()); + Assert.Single(clone.Claims); + Assert.Null(clone.FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); } [Fact] - public void ClaimsIdentity_Clone_ReturnsDifferentIdentityInstance() + public void ClaimsIdentity_Clone_ExcludesUnwantedClaimsFromActor() { // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim("type", "value"); + var identity = new ClaimsIdentity + { + Actor = new ClaimsIdentity() + }; + identity.Actor.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.Actor.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); // Act - var copy = identity.Clone(claim => true); - copy.AddClaim("clone_type", "value"); + var clone = identity.Clone(claim => claim.Type == Claims.Name); // Assert - Assert.NotSame(identity, copy); - Assert.Null(identity.FindFirst("clone_type")); + Assert.Single(clone.Actor!.Claims); + Assert.Null(clone.Actor.FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", clone.Actor.FindFirst(Claims.Name)!.Value); } [Fact] - public void ClaimsPrincipal_Clone_ReturnsDifferentPrincipalInstance() + public void ClaimsPrincipal_Clone_ExcludesUnwantedClaimsFromActor() { // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - + var identity = new ClaimsIdentity + { + Actor = new ClaimsIdentity() + }; + identity.Actor.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.Actor.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); var principal = new ClaimsPrincipal(identity); // Act - var copy = principal.Clone(claim => true); - copy.SetClaim("clone_claim", "value"); + var clone = principal.Clone(claim => claim.Type == Claims.Name); // Assert - Assert.NotSame(principal, copy); - Assert.Null(principal.FindFirst("clone_claim")); + Assert.Single(((ClaimsIdentity) clone.Identity!).Actor!.Claims); + Assert.Null(((ClaimsIdentity) clone.Identity!).FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", ((ClaimsIdentity) clone.Identity!).Actor!.FindFirst(Claims.Name)!.Value); } [Fact] - public void GetClaim_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_AddClaimWithString_ThrowsAnExceptionForNullIdentity() { // Arrange - var principal = (ClaimsPrincipal) null!; + var identity = (ClaimsIdentity) null!; // Act and assert - var exception = Assert.Throws(() => principal.GetClaim("type")); + var exception = Assert.Throws(() => identity.AddClaim(Claims.Name, "Bob le Bricoleur")); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void GetClaim_ReturnsNullForMissingClaim() + public void ClaimsPrincipal_AddClaimWithString_ThrowsAnExceptionForNullPrincipal() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = (ClaimsPrincipal) null!; // Act and assert - Assert.Null(principal.GetClaim("type")); + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, "Bob le Bricoleur")); + + Assert.Equal("principal", exception.ParamName); } [Fact] - public void GetClaim_IsCaseInsensitive() + public void ClaimsPrincipal_AddClaimWithString_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim("type", "value"); + var principal = new ClaimsPrincipal(); // Act and assert - Assert.Equal("value", principal.GetClaim("TYPE")); + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, "Bob le Bricoleur")); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); } - [Fact] - public void GetCreationDate_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_AddClaimWithString_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange - var principal = (ClaimsPrincipal) null!; + var identity = new ClaimsIdentity(); // Act and assert - var exception = Assert.Throws(() => principal.GetCreationDate()); + var exception = Assert.Throws(() => identity.AddClaim(type, "value")); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Fact] - public void GetCreationDate_ReturnsNullIfNoClaim() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_AddClaimWithString_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - Assert.Null(principal.GetCreationDate()); + var exception = Assert.Throws(() => principal.AddClaim(type, "value")); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } [Fact] - public void GetCreationDate_ReturnsCreationDate() + public void ClaimsIdentity_AddClaimWithString_AddsExpectedClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.CreationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); // Act - var date = principal.GetCreationDate(); + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); // Assert - Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + Assert.Equal("Bob le Bricoleur", identity.FindFirst(Claims.Name)!.Value); } [Fact] - public void GetExpirationDate_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithString_AddsExpectedClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.GetExpirationDate()); + // Act + principal.AddClaim(Claims.Name, "Bob le Bricoleur"); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal("Bob le Bricoleur", principal.FindFirst(Claims.Name)!.Value); } [Fact] - public void GetExpirationDate_ReturnsNullIfNoClaim() + public void ClaimsIdentity_AddClaimWithDictionary_ThrowsAnExceptionForNullIdentity() { // Arrange - var principal = new ClaimsPrincipal(); + var identity = (ClaimsIdentity) null!; // Act and assert - Assert.Null(principal.GetExpirationDate()); + var exception = Assert.Throws(() => identity.AddClaim(Claims.Name, new Dictionary())); + + Assert.Equal("identity", exception.ParamName); } [Fact] - public void GetExpirationDate_ReturnsExpirationDate() + public void ClaimsPrincipal_AddClaimWithDictionary_ThrowsAnExceptionForNullPrincipal() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.ExpirationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + var principal = (ClaimsPrincipal) null!; - // Act - var date = principal.GetExpirationDate(); + // Act and assert + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, new Dictionary())); - // Assert - Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + Assert.Equal("principal", exception.ParamName); } [Fact] - public void GetAudiences_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithDictionary_ThrowsAnExceptionForNullIdentity() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(); // Act and assert - var exception = Assert.Throws(() => principal.GetAudiences()); + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, new Dictionary())); Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); } [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void GetAudiences_ReturnsExpectedAudiences(string[] audience, string[] audiences) + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_AddClaimWithDictionary_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); // Act and assert - Assert.Equal(audiences, principal.GetAudiences()); + var exception = Assert.Throws(() => identity.AddClaim(type, new Dictionary())); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Fact] - public void GetPresenters_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_AddClaimWithDictionary_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.GetPresenters()); + var exception = Assert.Throws(() => principal.AddClaim(type, new Dictionary())); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void GetPresenters_ReturnsExpectedPresenters(string[] presenter, string[] presenters) + [Fact] + public void ClaimsIdentity_AddClaimWithDictionary_AddsExpectedClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + // Act + identity.AddClaim("type", new Dictionary + { + ["parameter"] = "value" + }); - // Act and assert - Assert.Equal(presenters, principal.GetPresenters()); + // Assert + Assert.Equal(@"{""parameter"":""value""}", identity.FindFirst("type")!.Value); } [Fact] - public void GetResources_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithDictionary_AddsExpectedClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.GetResources()); + // Act + principal.AddClaim("type", new Dictionary + { + ["parameter"] = "value" + }); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); } - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void GetResources_ReturnsExpectedResources(string[] resource, string[] resources) + [Fact] + public void ClaimsIdentity_AddClaimWithJsonElement_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + var identity = (ClaimsIdentity) null!; // Act and assert - Assert.Equal(resources, principal.GetResources()); + var exception = Assert.Throws(() => identity.AddClaim(Claims.Name, default(JsonElement))); + + Assert.Equal("identity", exception.ParamName); } [Fact] - public void GetScopes_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithJsonElement_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.GetScopes()); + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, default(JsonElement))); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void ClaimsPrincipal_GetScopes_ReturnsExpectedScopes(string[] scope, string[] scopes) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); - - // Act and assert - Assert.Equal(scopes, principal.GetScopes()); - } - [Fact] - public void GetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithJsonElement_ThrowsAnExceptionForNullIdentity() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(); // Act and assert - var exception = Assert.Throws(() => principal.GetAccessTokenLifetime()); + var exception = Assert.Throws(() => principal.AddClaim(Claims.Name, default(JsonElement))); Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); } [Theory] [InlineData(null)] - [InlineData("62")] - public void GetAccessTokenLifetime_ReturnsExpectedResult(string lifetime) + [InlineData("")] + public void ClaimsIdentity_AddClaimWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime); // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetAccessTokenLifetime()); + var exception = Assert.Throws(() => identity.AddClaim(type, default(JsonElement))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Fact] - public void GetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_AddClaimWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.GetAuthorizationCodeLifetime()); + var exception = Assert.Throws(() => principal.AddClaim(type, default(JsonElement))); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetAuthorizationCodeLifetime_ReturnsExpectedResult(string lifetime) + [Fact] + public void ClaimsIdentity_AddClaimWithJsonElement_ThrowsAnExceptionForIncompatibleJsonElement() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime); // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetAuthorizationCodeLifetime()); + var exception = Assert.Throws(() => identity.AddClaim("type", + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"))); + + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0185), exception.Message); } [Fact] - public void GetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithJsonElement_ThrowsAnExceptionForIncompatibleJsonElement() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.GetDeviceCodeLifetime()); + var exception = Assert.Throws(() => principal.AddClaim("type", + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"))); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0185), exception.Message); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetDeviceCodeLifetime_ReturnsExpectedResult(string lifetime) + [Fact] + public void ClaimsIdentity_AddClaimWithJsonElement_AddsExpectedClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime); + // Act + identity.AddClaim("type", JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetDeviceCodeLifetime()); + // Assert + Assert.Equal(@"{""parameter"":""value""}", identity.FindFirst("type")!.Value); } [Fact] - public void GetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimWithJsonElement_AddsExpectedClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.GetIdentityTokenLifetime()); + // Act + principal.AddClaim("type", JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetIdentityTokenLifetime_ReturnsExpectedResult(string lifetime) + [Fact] + public void ClaimsIdentity_AddClaimsWithArray_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime); + var identity = (ClaimsIdentity) null!; // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetIdentityTokenLifetime()); + var exception = Assert.Throws(() => identity.AddClaims("type", ImmutableArray.Create())); + + Assert.Equal("identity", exception.ParamName); } [Fact] - public void GetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithArray_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.GetRefreshTokenLifetime()); + var exception = Assert.Throws(() => principal.AddClaims("type", ImmutableArray.Create())); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetRefreshTokenLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime); - - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetRefreshTokenLifetime()); - } - [Fact] - public void GetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithArray_ThrowsAnExceptionForNullIdentity() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(); // Act and assert - var exception = Assert.Throws(() => principal.GetUserCodeLifetime()); + var exception = Assert.Throws(() => principal.AddClaims("type", ImmutableArray.Create("value1", "value2"))); Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); } [Theory] [InlineData(null)] - [InlineData("62")] - public void GetUserCodeLifetime_ReturnsExpectedResult(string lifetime) + [InlineData("")] + public void ClaimsIdentity_AddClaimsWithArray_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaim(Claims.Private.UserCodeLifetime, lifetime); // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetUserCodeLifetime()); + var exception = Assert.Throws(() => identity.AddClaims(type, ImmutableArray.Create())); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Fact] - public void GetAuthorizationId_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_AddClaimsWithArray_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(principal.GetAuthorizationId); + var exception = Assert.Throws(() => principal.AddClaims(type, ImmutableArray.Create())); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void GetAuthorizationId_ReturnsExpectedResult(string identifier) + [Fact] + public void ClaimsIdentity_AddClaimsWithArray_AddsExpectedClaims() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.AuthorizationId, identifier); + // Act + identity.AddClaims("type", ImmutableArray.Create("value1", "value2"), "issuer"); - // Act and assert - Assert.Equal(identifier, principal.GetAuthorizationId()); + // 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 GetTokenId_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithArray_AddsExpectedClaims() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(principal.GetTokenId); + // Act + principal.AddClaims("type", ImmutableArray.Create("value1", "value2"), "issuer"); - Assert.Equal("principal", exception.ParamName); + // Assert + var claims = principal.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); } - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void GetTokenId_ReturnsExpectedResult(string identifier) + [Fact] + public void ClaimsIdentity_AddClaimsWithArray_IsCaseInsensitive() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.TokenId, identifier); + // Act + identity.AddClaims("TYPE", ImmutableArray.Create("value1", "value2")); - // Act and assert - Assert.Equal(identifier, principal.GetTokenId()); + // Assert + Assert.Equal(ImmutableArray.Create("value1", "value2"), identity.GetClaims("type")); } [Fact] - public void GetTokenType_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithArray_IsCaseInsensitive() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(principal.GetTokenType); + // Act + principal.AddClaims("TYPE", ImmutableArray.Create("value1", "value2")); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal(ImmutableArray.Create("value1", "value2"), principal.GetClaims("type")); } - [Theory] - [InlineData(null)] - [InlineData("access_token")] - public void GetTokenType_ReturnsExpectedResult(string type) + [Fact] + public void ClaimsIdentity_AddClaimsWithJsonElement_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetTokenType(type); + var identity = (ClaimsIdentity) null!; // Act and assert - Assert.Equal(type, principal.GetTokenType()); + var exception = Assert.Throws(() => identity.AddClaims("type", default(JsonElement))); + + Assert.Equal("identity", exception.ParamName); } [Fact] - public void HasAudience_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithJsonElement_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.HasAudience("Fabrikam")); + var exception = Assert.Throws(() => principal.AddClaims("type", default(JsonElement))); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasAudience_ThrowsAnExceptionForNullOrEmptyAudience(string audience) + [Fact] + public void ClaimsPrincipal_AddClaimsWithJsonElement_ThrowsAnExceptionForNullIdentity() { // Arrange var principal = new ClaimsPrincipal(); // Act and assert - var exception = Assert.Throws(() => principal.HasAudience(audience)); + var exception = Assert.Throws(() => principal.AddClaims("type", default(JsonElement))); - Assert.Equal("audience", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0186), exception.Message); + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); } [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "contoso" }, false)] - [InlineData(new[] { "contoso", "fabrikam" }, true)] - [InlineData(new[] { "fabrikam" }, true)] - [InlineData(new[] { "fabrikam", "contoso" }, true)] - [InlineData(new[] { "CONTOSO" }, false)] - [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] - public void HasAudience_ReturnsExpectedResult(string[] audience, bool result) + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_AddClaimsWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); - - // Act and assert - Assert.Equal(result, principal.HasAudience("fabrikam")); - } - - [Fact] - public void HasPresenter_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.HasPresenter("Fabrikam")); + var exception = Assert.Throws(() => identity.AddClaims(type, default(JsonElement))); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } [Theory] [InlineData(null)] [InlineData("")] - public void HasPresenter_ThrowsAnExceptionForNullOrEmptyPresenter(string presenter) + public void ClaimsPrincipal_AddClaimsWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange - var principal = new ClaimsPrincipal(); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.HasPresenter(presenter)); + var exception = Assert.Throws(() => principal.AddClaims(type, default(JsonElement))); - Assert.Equal("presenter", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0187), exception.Message); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); } - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "contoso" }, false)] - [InlineData(new[] { "contoso", "fabrikam" }, true)] - [InlineData(new[] { "fabrikam" }, true)] - [InlineData(new[] { "fabrikam", "contoso" }, true)] - [InlineData(new[] { "CONTOSO" }, false)] - [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] - public void HasPresenter_ReturnsExpectedResult(string[] presenter, bool result) + [Fact] + public void ClaimsIdentity_AddClaimsWithJsonElement_ThrowsAnExceptionForIncompatibleJsonElement() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); // Act and assert - Assert.Equal(result, principal.HasPresenter("fabrikam")); + var exception = Assert.Throws(() => identity.AddClaims("type", + JsonSerializer.Deserialize(@"{""parameter"":""value""}"))); + + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0185), exception.Message); } [Fact] - public void HasResource_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_AddClaimsWithJsonElement_ThrowsAnExceptionForIncompatibleJsonElement() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert - var exception = Assert.Throws(() => principal.HasResource("Fabrikam")); + var exception = Assert.Throws(() => principal.AddClaims("type", + JsonSerializer.Deserialize(@"{""parameter"":""value""}"))); - Assert.Equal("principal", exception.ParamName); + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0185), exception.Message); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasResource_ThrowsAnExceptionForNullOrEmptyResource(string resource) + [Fact] + public void ClaimsIdentity_AddClaimsWithJsonElement_AddsExpectedClaims() { // Arrange - var principal = new ClaimsPrincipal(); + var identity = new ClaimsIdentity(); - // Act and assert - var exception = Assert.Throws(() => principal.HasResource(resource)); + // Act + identity.AddClaims("type", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"), "issuer"); - Assert.Equal("resource", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0062), exception.Message); + // 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); } - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "contoso" }, false)] - [InlineData(new[] { "contoso", "fabrikam" }, true)] - [InlineData(new[] { "fabrikam" }, true)] - [InlineData(new[] { "fabrikam", "contoso" }, true)] - [InlineData(new[] { "CONTOSO" }, false)] - [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] - public void HasResource_ReturnsExpectedResult(string[] resource, bool result) + [Fact] + public void ClaimsPrincipal_AddClaimsWithJsonElement_AddsExpectedClaims() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + // Act + principal.AddClaims("type", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]"), "issuer"); - // Act and assert - Assert.Equal(result, principal.HasResource("fabrikam")); + // 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 ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_AddClaimsWithJsonElement_IsCaseInsensitive() { // Arrange - var principal = (ClaimsPrincipal) null!; + var identity = new ClaimsIdentity(); - // Act and assert - var exception = Assert.Throws(() => principal.HasScope(Scopes.OpenId)); + // Act + identity.AddClaims("TYPE", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal(ImmutableArray.Create("Fabrikam", "Contoso"), identity.GetClaims("type")); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) + [Fact] + public void ClaimsPrincipal_AddClaimsWithJsonElement_IsCaseInsensitive() { // Arrange - var principal = new ClaimsPrincipal(); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.HasScope(scope)); + // Act + principal.AddClaims("TYPE", JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - Assert.Equal("scope", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); + // Assert + Assert.Equal(ImmutableArray.Create("Fabrikam", "Contoso"), principal.GetClaims("type")); } - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "profile" }, false)] - [InlineData(new[] { "profile", "openid" }, true)] - [InlineData(new[] { "openid" }, true)] - [InlineData(new[] { "openid", "profile" }, true)] - [InlineData(new[] { "PROFILE" }, false)] - [InlineData(new[] { "PROFILE", "OPENID" }, false)] - [InlineData(new[] { "OPENID" }, false)] - [InlineData(new[] { "OPENID", "PROFILE" }, false)] - public void ClaimsPrincipal_HasScope_ReturnsExpectedResult(string[] scope, bool result) + [Fact] + public void ClaimsIdentity_GetClaim_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + var identity = (ClaimsIdentity) null!; // Act and assert - Assert.Equal(result, principal.HasScope(Scopes.OpenId)); + var exception = Assert.Throws(() => + { + identity.GetClaim(Claims.Name); + }); + + Assert.Equal("identity", exception.ParamName); } [Fact] - public void HasTokenType_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_GetClaim_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.HasTokenType(TokenTypeHints.AccessToken)); + var exception = Assert.Throws(() => + { + principal.GetClaim(Claims.Name); + }); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasTokenType_ThrowsAnExceptionForNullOrEmptyTokenType(string type) + [Fact] + public void ClaimsIdentity_GetClaim_ReturnsNullForMissingClaims() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act and assert - var exception = Assert.Throws(() => principal.HasTokenType(type)); + Assert.Null(identity.GetClaim(Claims.Name)); + } - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0188), exception.Message); + [Fact] + public void ClaimsPrincipal_GetClaim_ReturnsNullForMissingClaims() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + Assert.Null(principal.GetClaim(Claims.Name)); } [Fact] - public void HasTokenType_ReturnsExpectedResult() + public void ClaimsIdentity_GetClaim_ReturnsAppropriateResult() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - principal.SetTokenType(TokenTypeHints.AccessToken); + // 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.True(principal.HasTokenType(TokenTypeHints.AccessToken)); - Assert.False(principal.HasTokenType(TokenTypeHints.RefreshToken)); + Assert.Equal("Bob le Bricoleur", principal.GetClaim(Claims.Name)); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void AddClaim_ThrowsAnExceptionForNullOrEmptyType(string type) + [Fact] + public void ClaimsIdentity_GetClaim_IsCaseInsensitive() { // Arrange var identity = new ClaimsIdentity(); + identity.SetClaim("type", "value"); // Act and assert - var exception = Assert.Throws(() => identity.AddClaim(type, "value")); + Assert.Equal("value", identity.GetClaim("TYPE")); + } - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + [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 AddClaim_AddsExpectedClaim() + public void ClaimsIdentity_GetClaims_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); + var identity = (ClaimsIdentity) null!; - // Act - identity.AddClaim("type", "value"); + // Act and assert + var exception = Assert.Throws(() => identity.GetClaims("type")); - // Assert - Assert.Equal("value", identity.GetClaim("type")); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void GetClaims_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_GetClaims_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; @@ -2231,10 +2306,25 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + public void ClaimsIdentity_GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) { // Arrange - var principal = new ClaimsPrincipal(); + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.GetClaims(type)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert var exception = Assert.Throws(() => principal.GetClaims(type)); @@ -2244,7 +2334,20 @@ public class OpenIddictExtensionsTests } [Fact] - public void GetClaims_ReturnsExpectedResult() + public void ClaimsIdentity_GetClaims_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId)); + identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile)); + + // Act and assert + Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, identity.GetClaims(Claims.Scope)); + } + + [Fact] + public void ClaimsPrincipal_GetClaims_ReturnsExpectedResult() { // Arrange var identity = new ClaimsIdentity(); @@ -2259,7 +2362,19 @@ public class OpenIddictExtensionsTests } [Fact] - public void HasClaim_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_HasClaim_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.HasClaim("type")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_HasClaim_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; @@ -2273,10 +2388,25 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void HasClaim_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + public void ClaimsIdentity_HasClaim_ThrowsAnExceptionForNullOrEmptyClaimType(string type) { // Arrange - var principal = new ClaimsPrincipal(); + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.HasClaim(type)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasClaim_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert var exception = Assert.Throws(() => principal.HasClaim(type)); @@ -2286,7 +2416,22 @@ public class OpenIddictExtensionsTests } [Fact] - public void HasClaim_ReturnsExpectedResult() + public void ClaimsIdentity_HasClaim_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId)); + identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile)); + + // Act and assert + Assert.True(identity.HasClaim(Claims.Name)); + Assert.True(identity.HasClaim(Claims.Scope)); + Assert.False(identity.HasClaim(Claims.Nickname)); + } + + [Fact] + public void ClaimsPrincipal_HasClaim_ReturnsExpectedResult() { // Arrange var identity = new ClaimsIdentity(); @@ -2303,7 +2448,19 @@ public class OpenIddictExtensionsTests } [Fact] - public void RemoveClaims_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_RemoveClaims_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.RemoveClaims("type")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_RemoveClaims_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; @@ -2317,10 +2474,25 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void RemoveClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + public void ClaimsIdentity_RemoveClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) { // Arrange - var principal = new ClaimsPrincipal(); + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.RemoveClaims(type)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_RemoveClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert var exception = Assert.Throws(() => principal.RemoveClaims(type)); @@ -2330,12 +2502,24 @@ public class OpenIddictExtensionsTests } [Fact] - public void RemoveClaims_RemoveClaims() + public void ClaimsIdentity_RemoveClaims_RemoveClaims() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.SetClaim("type", "value"); + + // Act + identity.RemoveClaims("type"); + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_RemoveClaims_RemoveClaims() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); principal.SetClaim("type", "value"); // Act @@ -2346,7 +2530,19 @@ public class OpenIddictExtensionsTests } [Fact] - public void SetClaim_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_SetClaimWithString_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaim("type", "value")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithString_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; @@ -2358,7 +2554,7 @@ public class OpenIddictExtensionsTests } [Fact] - public void SetClaim_ThrowsAnExceptionForNullIdentity() + public void ClaimsPrincipal_SetClaimWithString_ThrowsAnExceptionForNullIdentity() { // Arrange var principal = new ClaimsPrincipal(); @@ -2373,11 +2569,25 @@ public class OpenIddictExtensionsTests [Theory] [InlineData(null)] [InlineData("")] - public void SetClaim_ThrowsAnExceptionForNullOrEmptyType(string type) + public void ClaimsIdentity_SetClaimWithString_ThrowsAnExceptionForNullOrEmptyType(string type) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaim(type, "value")); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetClaimWithString_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act and assert var exception = Assert.Throws(() => principal.SetClaim(type, "value")); @@ -2387,525 +2597,3227 @@ public class OpenIddictExtensionsTests } [Fact] - public void SetClaim_AddsExpectedClaim() + public void ClaimsIdentity_SetClaimWithString_AddsExpectedClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value1"); + + // Act + identity.SetClaim("type", "value2", "issuer"); + + // Assert + var claim = Assert.Single(identity.FindAll("type")); + Assert.Equal("value2", claim.Value); + Assert.Equal(ClaimValueTypes.String, claim.ValueType); + Assert.Equal("issuer", claim.Issuer); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithString_AddsExpectedClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value1"); + + // Act + principal.SetClaim("type", "value2", "issuer"); + + // Assert + var claim = Assert.Single(principal.FindAll("type")); + Assert.Equal("value2", claim.Value); + Assert.Equal(ClaimValueTypes.String, claim.ValueType); + Assert.Equal("issuer", claim.Issuer); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithString_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaim("TYPE", "value"); + + // Assert + Assert.Equal("value", identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithString_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaim("TYPE", "value"); + + // Assert + Assert.Equal("value", principal.GetClaim("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithString_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaim("type", string.Empty); + + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithString_RemovesEmptyClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaim("type", string.Empty); + + // Assert + Assert.Null(principal.GetClaim("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithDictionary_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaim("type", new Dictionary())); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithDictionary_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim("type", new Dictionary())); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithDictionary_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim("type", new Dictionary + { + ["parameter"] = "value" + })); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_SetClaimWithDictionary_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaim(type, new Dictionary())); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetClaimWithDictionary_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim(type, new Dictionary())); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithDictionary_AddsExpectedClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value1"); + + // Act + identity.SetClaim("type", new Dictionary + { + ["parameter"] = "value" + }, "issuer"); + + // Assert + 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_SetClaimWithDictionary_AddsExpectedClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value1"); + + // Act + principal.SetClaim("type", new Dictionary + { + ["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_SetClaimWithDictionary_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaim("TYPE", new Dictionary + { + ["parameter"] = "value" + }); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", identity.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithDictionary_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaim("TYPE", new Dictionary + { + ["parameter"] = "value" + }); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithDictionary_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaim("type", new Dictionary()); + + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithDictionary_RemovesEmptyClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaim("type", new Dictionary()); + + // Assert + Assert.Null(principal.GetClaim("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonElement_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaim("type", default(JsonElement))); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonElement_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim("type", default(JsonElement))); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonElement_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim("type", + JsonSerializer.Deserialize(@"{""parameter"":""value""}"))); + + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_SetClaimWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.SetClaim(type, default(JsonElement))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetClaimWithJsonElement_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim(type, default(JsonElement))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonElement_AddsExpectedClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value1"); + + // Act + identity.SetClaim("type", JsonSerializer.Deserialize(@"{""parameter"":""value""}"), "issuer"); + + // Assert + 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_SetClaimWithJsonElement_AddsExpectedClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value1"); + + // Act + principal.SetClaim("type", JsonSerializer.Deserialize(@"{""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_SetClaimWithJsonElement_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetClaim("TYPE", JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", identity.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonElement_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaim("TYPE", JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + + // Assert + Assert.Equal(@"{""parameter"":""value""}", principal.FindFirst("type")!.Value); + } + + [Fact] + public void ClaimsIdentity_SetClaimWithJsonElement_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaim("type", JsonSerializer.Deserialize("{}")); + + // Assert + Assert.Null(identity.GetClaim("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimWithJsonElement_RemovesEmptyClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaim("type", JsonSerializer.Deserialize("{}")); + + // 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", ImmutableArray.Create())); + + 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", ImmutableArray.Create())); + + 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", ImmutableArray.Create("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, ImmutableArray.Create())); + + 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, ImmutableArray.Create())); + + 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", ImmutableArray.Create("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", ImmutableArray.Create("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", ImmutableArray.Create("value1", "value2")); + + // Assert + Assert.Equal(ImmutableArray.Create("value1", "value2"), identity.GetClaims("type")); + } + + [Fact] + public void ClaimsPrincipal_SetClaimsWithArray_IsCaseInsensitive() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetClaims("TYPE", ImmutableArray.Create("value1", "value2")); + + // Assert + Assert.Equal(ImmutableArray.Create("value1", "value2"), principal.GetClaims("type")); + } + + [Fact] + public void ClaimsIdentity_SetClaimsWithArray_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); + + // Act + identity.SetClaims("type", ImmutableArray.Create()); + + // 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", ImmutableArray.Create()); + + // 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(ImmutableArray.Create("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(ImmutableArray.Create("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_SetClaimsWithJsonElement_RemovesEmptyClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim("type", "value"); + + // Act + principal.SetClaims("type", JsonSerializer.Deserialize("[]")); + + // Assert + Assert.Empty(principal.GetClaims("type")); + } + + [Fact] + public void ClaimsIdentity_GetCreationDate_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetCreationDate()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetCreationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetCreationDate()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetCreationDate_ReturnsNullIfNoClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetCreationDate()); + } + + [Fact] + public void ClaimsPrincipal_GetCreationDate_ReturnsNullIfNoClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetCreationDate()); + } + + [Fact] + public void ClaimsIdentity_GetCreationDate_ReturnsCreationDate() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.CreationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + + // Act + var date = identity.GetCreationDate(); + + // Assert + Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + } + + [Fact] + public void ClaimsPrincipal_GetCreationDate_ReturnsCreationDate() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.CreationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + + // Act + var date = principal.GetCreationDate(); + + // Assert + Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + } + + [Fact] + public void ClaimsIdentity_GetExpirationDate_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetExpirationDate()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetExpirationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetExpirationDate()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetExpirationDate_ReturnsNullIfNoClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetExpirationDate()); + } + + [Fact] + public void ClaimsPrincipal_GetExpirationDate_ReturnsNullIfNoClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetExpirationDate()); + } + + [Fact] + public void ClaimsIdentity_GetExpirationDate_ReturnsExpirationDate() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.ExpirationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + + // Act + var date = identity.GetExpirationDate(); + + // Assert + Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + } + + [Fact] + public void ClaimsPrincipal_GetExpirationDate_ReturnsExpirationDate() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.ExpirationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + + // Act + var date = principal.GetExpirationDate(); + + // Assert + Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + } + + [Fact] + public void ClaimsIdentity_GetAudiences_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetAudiences()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetAudiences_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetAudiences()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsIdentity_GetAudiences_ReturnsExpectedAudiences(string[] audience, string[] audiences) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + + // Act and assert + Assert.Equal(audiences, identity.GetAudiences()); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsPrincipal_GetAudiences_ReturnsExpectedAudiences(string[] audience, string[] audiences) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + + // Act and assert + Assert.Equal(audiences, principal.GetAudiences()); + } + + [Fact] + public void ClaimsIdentity_GetPresenters_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetPresenters()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetPresenters_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetPresenters()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsIdentity_GetPresenters_ReturnsExpectedPresenters(string[] presenter, string[] presenters) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + + // Act and assert + Assert.Equal(presenters, identity.GetPresenters()); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsPrincipal_GetPresenters_ReturnsExpectedPresenters(string[] presenter, string[] presenters) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + + // Act and assert + Assert.Equal(presenters, principal.GetPresenters()); + } + + [Fact] + public void ClaimsIdentity_GetResources_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetResources()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetResources_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetResources()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsIdentity_GetResources_ReturnsExpectedResources(string[] resource, string[] resources) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + + // Act and assert + Assert.Equal(resources, identity.GetResources()); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsPrincipal_GetResources_ReturnsExpectedResources(string[] resource, string[] resources) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + + // Act and assert + Assert.Equal(resources, principal.GetResources()); + } + + [Fact] + public void ClaimsIdentity_GetScopes_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetScopes()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetScopes_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetScopes()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void ClaimsIdentity_GetScopes_ReturnsExpectedScopes(string[] scope, string[] scopes) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + + // Act and assert + Assert.Equal(scopes, identity.GetScopes()); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void ClaimsPrincipal_GetScopes_ReturnsExpectedScopes(string[] scope, string[] scopes) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + + // Act and assert + Assert.Equal(scopes, principal.GetScopes()); + } + + [Fact] + public void ClaimsIdentity_GetAccessTokenLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetAccessTokenLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetAccessTokenLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetAccessTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetAccessTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetAccessTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetAccessTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetAccessTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.AccessTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetAccessTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetAccessTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.AccessTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetAccessTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetAuthorizationCodeLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetAuthorizationCodeLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetAuthorizationCodeLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetAuthorizationCodeLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetAuthorizationCodeLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetAuthorizationCodeLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetAuthorizationCodeLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetAuthorizationCodeLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.AuthorizationCodeLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetAuthorizationCodeLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetAuthorizationCodeLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetAuthorizationCodeLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetDeviceCodeLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetDeviceCodeLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetDeviceCodeLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetDeviceCodeLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetDeviceCodeLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetDeviceCodeLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetDeviceCodeLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetDeviceCodeLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.DeviceCodeLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetDeviceCodeLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetDeviceCodeLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.DeviceCodeLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetDeviceCodeLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetIdentityTokenLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetIdentityTokenLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetIdentityTokenLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetIdentityTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetIdentityTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetIdentityTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetIdentityTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetIdentityTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.IdentityTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetIdentityTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetIdentityTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.IdentityTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetIdentityTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetRefreshTokenLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetRefreshTokenLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetRefreshTokenLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetRefreshTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetRefreshTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetRefreshTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetRefreshTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetRefreshTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.RefreshTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetRefreshTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetRefreshTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.RefreshTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetRefreshTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetStateTokenLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetStateTokenLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetStateTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetStateTokenLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetStateTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetStateTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetStateTokenLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetStateTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetStateTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.StateTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetStateTokenLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetStateTokenLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.StateTokenLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetStateTokenLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetUserCodeLifetime_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.GetUserCodeLifetime()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetUserCodeLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetUserCodeLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetUserCodeLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetUserCodeLifetime_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsIdentity(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetUserCodeLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetUserCodeLifetime_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.UserCodeLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), identity.GetUserCodeLifetime()); + } + + [Fact] + public void ClaimsPrincipal_GetUserCodeLifetime_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.UserCodeLifetime, "2520"); + + // Act and assert + Assert.Equal(TimeSpan.FromMinutes(42), principal.GetUserCodeLifetime()); + } + + [Fact] + public void ClaimsIdentity_GetAuthorizationId_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(identity.GetAuthorizationId); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetAuthorizationId_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(principal.GetAuthorizationId); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_GetAuthorizationId_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + Assert.Null(identity.GetAuthorizationId()); + } + + [Fact] + public void ClaimsPrincipal_GetAuthorizationId_ReturnsNullForMissingClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + Assert.Null(principal.GetAuthorizationId()); + } + + [Fact] + public void ClaimsIdentity_GetAuthorizationId_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaim(Claims.Private.AuthorizationId, "42"); + + // Act and assert + Assert.Equal("42", identity.GetAuthorizationId()); + } + + [Fact] + public void ClaimsPrincipal_GetAuthorizationId_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaim(Claims.Private.AuthorizationId, "42"); + + // Act and assert + Assert.Equal("42", principal.GetAuthorizationId()); + } + + [Fact] + public void ClaimsIdentity_HasAudience_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.HasAudience("Fabrikam")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_HasAudience_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.HasAudience("Fabrikam")); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_HasAudience_ThrowsAnExceptionForNullOrEmptyAudience(string audience) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.HasAudience(audience)); + + Assert.Equal("audience", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0186), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasAudience_ThrowsAnExceptionForNullOrEmptyAudience(string audience) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.HasAudience(audience)); + + Assert.Equal("audience", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0186), exception.Message); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void ClaimsIdentity_HasAudience_ReturnsExpectedResult(string[] audience, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, identity.HasAudience("fabrikam")); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void ClaimsPrincipal_HasAudience_ReturnsExpectedResult(string[] audience, bool result) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, principal.HasAudience("fabrikam")); + } + + [Fact] + public void ClaimsIdentity_HasPresenter_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.HasPresenter("Fabrikam")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_HasPresenter_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.HasPresenter("Fabrikam")); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_HasPresenter_ThrowsAnExceptionForNullOrEmptyPresenter(string presenter) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.HasPresenter(presenter)); + + Assert.Equal("presenter", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0187), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasPresenter_ThrowsAnExceptionForNullOrEmptyPresenter(string presenter) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.HasPresenter(presenter)); + + Assert.Equal("presenter", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0187), exception.Message); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void ClaimsIdentity_HasPresenter_ReturnsExpectedResult(string[] presenter, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, identity.HasPresenter("fabrikam")); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void ClaimsPrincipal_HasPresenter_ReturnsExpectedResult(string[] presenter, bool result) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, principal.HasPresenter("fabrikam")); + } + + [Fact] + public void ClaimsIdentity_HasResource_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.HasResource("Fabrikam")); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_HasResource_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.HasResource("Fabrikam")); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_HasResource_ThrowsAnExceptionForNullOrEmptyResource(string resource) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.HasResource(resource)); + + Assert.Equal("resource", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0062), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasResource_ThrowsAnExceptionForNullOrEmptyResource(string resource) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.HasResource(resource)); + + Assert.Equal("resource", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0062), exception.Message); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void ClaimsIdentity_HasResource_ReturnsExpectedResult(string[] resource, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, identity.HasResource("fabrikam")); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void ClaimsPrincipal_HasResource_ReturnsExpectedResult(string[] resource, bool result) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, principal.HasResource("fabrikam")); + } + + [Fact] + public void ClaimsIdentity_HasScope_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.HasScope(Scopes.Profile)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.HasScope(Scopes.Profile)); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.HasScope(scope)); + + Assert.Equal("scope", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.HasScope(scope)); + + Assert.Equal("scope", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "profile" }, false)] + [InlineData(new[] { "profile", "openid" }, true)] + [InlineData(new[] { "openid" }, true)] + [InlineData(new[] { "openid", "profile" }, true)] + [InlineData(new[] { "PROFILE" }, false)] + [InlineData(new[] { "PROFILE", "OPENID" }, false)] + [InlineData(new[] { "OPENID" }, false)] + [InlineData(new[] { "OPENID", "PROFILE" }, false)] + public void ClaimsIdentity_HasScope_ReturnsExpectedResult(string[] scope, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, identity.HasScope(Scopes.OpenId)); + } + + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "profile" }, false)] + [InlineData(new[] { "profile", "openid" }, true)] + [InlineData(new[] { "openid" }, true)] + [InlineData(new[] { "openid", "profile" }, true)] + [InlineData(new[] { "PROFILE" }, false)] + [InlineData(new[] { "PROFILE", "OPENID" }, false)] + [InlineData(new[] { "OPENID" }, false)] + [InlineData(new[] { "OPENID", "PROFILE" }, false)] + public void ClaimsPrincipal_HasScope_ReturnsExpectedResult(string[] scope, bool result) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + + // Act and assert + Assert.Equal(result, principal.HasScope(Scopes.OpenId)); + } + + [Fact] + public void ClaimsIdentity_HasTokenType_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.HasTokenType(TokenTypeHints.AccessToken)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_HasTokenType_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.HasTokenType(TokenTypeHints.AccessToken)); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsIdentity_HasTokenType_ThrowsAnExceptionForNullOrEmptyTokenType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act and assert + var exception = Assert.Throws(() => identity.HasTokenType(type)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0188), exception.Message); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasTokenType_ThrowsAnExceptionForNullOrEmptyTokenType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act and assert + var exception = Assert.Throws(() => principal.HasTokenType(type)); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0188), exception.Message); + } + + [Fact] + public void ClaimsIdentity_HasTokenType_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.SetTokenType(TokenTypeHints.AccessToken); + + // Act and assert + Assert.True(identity.HasTokenType(TokenTypeHints.AccessToken)); + Assert.False(identity.HasTokenType(TokenTypeHints.RefreshToken)); + } + + [Fact] + public void ClaimsPrincipal_HasTokenType_ReturnsExpectedResult() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetTokenType(TokenTypeHints.AccessToken); + + // Act and assert + Assert.True(principal.HasTokenType(TokenTypeHints.AccessToken)); + Assert.False(principal.HasTokenType(TokenTypeHints.RefreshToken)); + } + + [Fact] + public void ClaimsIdentity_SetCreationDate_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetCreationDate(date: null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetCreationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetCreationDate(date: null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetCreationDate_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetCreationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + + // Assert + Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", identity.GetClaim(Claims.Private.CreationDate)); + } + + [Fact] + public void ClaimsPrincipal_SetCreationDate_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetCreationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + + // Assert + Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.CreationDate)); + } + + [Fact] + public void ClaimsIdentity_SetExpirationDate_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetExpirationDate(date: null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetExpirationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetExpirationDate(date: null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetExpirationDate_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetExpirationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + + // Assert + Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", identity.GetClaim(Claims.Private.ExpirationDate)); + } + + [Fact] + public void ClaimsPrincipal_SetExpirationDate_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetExpirationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + + // Assert + Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.ExpirationDate)); + } + + [Fact] + public void ClaimsIdentity_SetAudiences_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetAudiences()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetAudiences_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetAudiences()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsIdentity_SetAudiences_AddsAudiences(string[] audiences, string[] audience) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetAudiences(audiences); + + // Assert + Assert.Equal(audience, identity.GetClaims(Claims.Private.Audience)); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsPrincipal_SetAudiences_AddsAudiences(string[] audiences, string[] audience) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetAudiences(audiences); + + // Assert + Assert.Equal(audience, principal.GetClaims(Claims.Private.Audience)); + } + + [Fact] + public void ClaimsIdentity_SetPresenters_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetPresenters()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetPresenters_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetPresenters()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsIdentity_SetPresenters_AddsPresenters(string[] presenters, string[] presenter) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetPresenters(presenters); + + // Assert + Assert.Equal(presenter, identity.GetClaims(Claims.Private.Presenter)); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsPrincipal_SetPresenters_AddsPresenters(string[] presenters, string[] presenter) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetPresenters(presenters); + + // Assert + Assert.Equal(presenter, principal.GetClaims(Claims.Private.Presenter)); + } + + [Fact] + public void ClaimsIdentity_SetResources_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetResources()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetResources_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetResources()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsIdentity_SetResources_AddsResources(string[] resources, string[] resource) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetResources(resources); + + // Assert + Assert.Equal(resource, identity.GetClaims(Claims.Private.Resource)); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void ClaimsPrincipal_SetResources_AddsResources(string[] resources, string[] resource) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetResources(resources); + + // Assert + Assert.Equal(resource, principal.GetClaims(Claims.Private.Resource)); + } + + [Fact] + public void ClaimsIdentity_SetScopes_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetScopes()); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetScopes_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetScopes()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void ClaimsIdentity_SetScopes_AddsScopes(string[] scopes, string[] scope) + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetScopes(scopes); + + // Assert + Assert.Equal(scope, identity.GetClaims(Claims.Private.Scope)); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void ClaimsPrincipal_SetScopes_AddsScopes(string[] scopes, string[] scope) + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetScopes(scopes); + + // Assert + Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + } + + [Fact] + public void ClaimsIdentity_SetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetAccessTokenLifetime(null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetAccessTokenLifetime(null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetAccessTokenLifetime_RemovesClaimForNullValue() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(Claims.Private.AccessTokenLifetime, "2520"); + + // Act + identity.SetAccessTokenLifetime(null); + + // Assert + Assert.Empty(identity.Claims); + } + + [Fact] + public void ClaimsPrincipal_SetAccessTokenLifetime_RemovesClaimForNullValue() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.AccessTokenLifetime, "2520"); + + // Act + principal.SetAccessTokenLifetime(null); + + // Assert + Assert.Empty(principal.Claims); + } + + [Fact] + public void ClaimsIdentity_SetAccessTokenLifetime_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", identity.GetClaim(Claims.Private.AccessTokenLifetime)); + } + + [Fact] + public void ClaimsPrincipal_SetAccessTokenLifetime_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.AccessTokenLifetime)); + } + + [Fact] + public void ClaimsIdentity_SetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetAuthorizationCodeLifetime(null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetAuthorizationCodeLifetime(null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetAuthorizationCodeLifetime_RemovesClaimForNullValue() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(Claims.Private.AuthorizationCodeLifetime, "2520"); + + // Act + identity.SetAuthorizationCodeLifetime(null); + + // Assert + Assert.Empty(identity.Claims); + } + + [Fact] + public void ClaimsPrincipal_SetAuthorizationCodeLifetime_RemovesClaimForNullValue() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.AuthorizationCodeLifetime, "2520"); + + // Act + principal.SetAuthorizationCodeLifetime(null); + + // Assert + Assert.Empty(principal.Claims); + } + + [Fact] + public void ClaimsIdentity_SetAuthorizationCodeLifetime_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", identity.GetClaim(Claims.Private.AuthorizationCodeLifetime)); + } + + [Fact] + public void ClaimsPrincipal_SetAuthorizationCodeLifetime_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); + } + + [Fact] + public void ClaimsIdentity_SetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetDeviceCodeLifetime(null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetDeviceCodeLifetime(null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetDeviceCodeLifetime_RemovesClaimForNullValue() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(Claims.Private.DeviceCodeLifetime, "2520"); + + // Act + identity.SetDeviceCodeLifetime(null); + + // Assert + Assert.Empty(identity.Claims); + } + + [Fact] + public void ClaimsPrincipal_SetDeviceCodeLifetime_RemovesClaimForNullValue() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.DeviceCodeLifetime, "2520"); + + // Act + principal.SetDeviceCodeLifetime(null); + + // Assert + Assert.Empty(principal.Claims); + } + + [Fact] + public void ClaimsIdentity_SetDeviceCodeLifetime_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + + // Act + identity.SetDeviceCodeLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", identity.GetClaim(Claims.Private.DeviceCodeLifetime)); + } + + [Fact] + public void ClaimsPrincipal_SetDeviceCodeLifetime_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetDeviceCodeLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.DeviceCodeLifetime)); + } + + [Fact] + public void ClaimsIdentity_SetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetIdentityTokenLifetime(null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.SetIdentityTokenLifetime(null)); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsIdentity_SetIdentityTokenLifetime_RemovesClaimForNullValue() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(Claims.Private.IdentityTokenLifetime, "2520"); + + // Act + identity.SetIdentityTokenLifetime(null); + + // Assert + Assert.Empty(identity.Claims); + } + + [Fact] + public void ClaimsPrincipal_SetIdentityTokenLifetime_RemovesClaimForNullValue() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.IdentityTokenLifetime, "2520"); + + // Act + principal.SetIdentityTokenLifetime(null); + + // Assert + Assert.Empty(principal.Claims); + } + + [Fact] + public void ClaimsIdentity_SetIdentityTokenLifetime_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetClaim("type", "value"); + identity.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); // Assert - Assert.Equal("value", principal.GetClaim("type")); + Assert.Equal("2520", identity.GetClaim(Claims.Private.IdentityTokenLifetime)); } [Fact] - public void SetClaim_IsCaseInsensitive() + public void ClaimsPrincipal_SetIdentityTokenLifetime_AddsClaim() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); // Act - principal.SetClaim("TYPE", "value"); + principal.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); // Assert - Assert.Equal("value", principal.GetClaim("type")); + Assert.Equal("2520", principal.GetClaim(Claims.Private.IdentityTokenLifetime)); } [Fact] - public void SetClaim_RemovesEmptyClaim() + public void ClaimsIdentity_SetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var identity = (ClaimsIdentity) null!; - // Act - principal.SetClaim("type", string.Empty); + // Act and assert + var exception = Assert.Throws(() => identity.SetRefreshTokenLifetime(null)); - // Assert - Assert.Null(principal.GetClaim("type")); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void SetCreationDate_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetCreationDate(date: null)); + var exception = Assert.Throws(() => principal.SetRefreshTokenLifetime(null)); Assert.Equal("principal", exception.ParamName); } [Fact] - public void SetCreationDate_AddsClaim() + public void ClaimsIdentity_SetRefreshTokenLifetime_RemovesClaimForNullValue() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Private.RefreshTokenLifetime, "2520"); // Act - principal.SetCreationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + identity.SetRefreshTokenLifetime(null); // Assert - Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.CreationDate)); + Assert.Empty(identity.Claims); } [Fact] - public void SetExpirationDate_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetRefreshTokenLifetime_RemovesClaimForNullValue() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.RefreshTokenLifetime, "2520"); - // Act and assert - var exception = Assert.Throws(() => principal.SetExpirationDate(date: null)); + // Act + principal.SetRefreshTokenLifetime(null); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Empty(principal.Claims); } [Fact] - public void SetExpirationDate_AddsClaim() + public void ClaimsIdentity_SetRefreshTokenLifetime_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetExpirationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + identity.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); // Assert - Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.ExpirationDate)); + Assert.Equal("2520", identity.GetClaim(Claims.Private.RefreshTokenLifetime)); } [Fact] - public void SetAudiences_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetRefreshTokenLifetime_AddsClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.SetAudiences()); + // Act + principal.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.RefreshTokenLifetime)); } - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void SetAudiences_AddsAudiences(string[] audiences, string[] audience) + [Fact] + public void ClaimsIdentity_SetStateTokenLifetime_ThrowsAnExceptionForNullPrincipal() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var identity = (ClaimsIdentity) null!; - // Act - principal.SetAudiences(audiences); + // Act and assert + var exception = Assert.Throws(() => identity.SetStateTokenLifetime(null)); - // Assert - Assert.Equal(audience, principal.GetClaims(Claims.Private.Audience)); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void SetPresenters_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetStateTokenLifetime_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetPresenters()); + var exception = Assert.Throws(() => principal.SetStateTokenLifetime(null)); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void SetPresenters_AddsPresenters(string[] presenters, string[] presenter) + [Fact] + public void ClaimsIdentity_SetStateTokenLifetime_RemovesClaimForNullValue() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Private.StateTokenLifetime, "2520"); // Act - principal.SetPresenters(presenters); + identity.SetStateTokenLifetime(null); // Assert - Assert.Equal(presenter, principal.GetClaims(Claims.Private.Presenter)); + Assert.Empty(identity.Claims); } [Fact] - public void SetResources_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetStateTokenLifetime_RemovesClaimForNullValue() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.StateTokenLifetime, "2520"); - // Act and assert - var exception = Assert.Throws(() => principal.SetResources()); + // Act + principal.SetStateTokenLifetime(null); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Empty(principal.Claims); } - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void SetResources_AddsResources(string[] resources, string[] resource) + [Fact] + public void ClaimsIdentity_SetStateTokenLifetime_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetResources(resources); + identity.SetStateTokenLifetime(TimeSpan.FromMinutes(42)); // Assert - Assert.Equal(resource, principal.GetClaims(Claims.Private.Resource)); + Assert.Equal("2520", identity.GetClaim(Claims.Private.StateTokenLifetime)); + } + + [Fact] + public void ClaimsPrincipal_SetStateTokenLifetime_AddsClaim() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + + // Act + principal.SetStateTokenLifetime(TimeSpan.FromMinutes(42)); + + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.StateTokenLifetime)); } [Fact] - public void SetScopes_ThrowsAnExceptionForNullPrincipal() + public void ClaimsIdentity_SetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var identity = (ClaimsIdentity) null!; + + // Act and assert + var exception = Assert.Throws(() => identity.SetUserCodeLifetime(null)); + + Assert.Equal("identity", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetScopes()); + var exception = Assert.Throws(() => principal.SetUserCodeLifetime(null)); Assert.Equal("principal", exception.ParamName); } - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void SetScopes_AddsScopes(string[] scopes, string[] scope) + [Fact] + public void ClaimsIdentity_SetUserCodeLifetime_RemovesClaimForNullValue() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Private.UserCodeLifetime, "2520"); // Act - principal.SetScopes(scopes); + identity.SetUserCodeLifetime(null); // Assert - Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + Assert.Empty(identity.Claims); } - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void SetScopes_IEnumerable_AddsScopes(string[] scopes, string[] scope) + [Fact] + public void ClaimsPrincipal_SetUserCodeLifetime_RemovesClaimForNullValue() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.UserCodeLifetime, "2520"); // Act - principal.SetScopes((IEnumerable) scopes); + principal.SetUserCodeLifetime(null); // Assert - Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + Assert.Empty(principal.Claims); } - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void SetScopes_ImmutableArray_AddsScopes(string[] scopes, string[] scope) + [Fact] + public void ClaimsIdentity_SetUserCodeLifetime_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetScopes(ImmutableArray.Create(scopes)); + identity.SetUserCodeLifetime(TimeSpan.FromMinutes(42)); // Assert - Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + Assert.Equal("2520", identity.GetClaim(Claims.Private.UserCodeLifetime)); } [Fact] - public void SetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetUserCodeLifetime_AddsClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.SetAccessTokenLifetime(null)); + // Act + principal.SetUserCodeLifetime(TimeSpan.FromMinutes(42)); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal("2520", principal.GetClaim(Claims.Private.UserCodeLifetime)); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetAccessTokenLifetime_AddsLifetime(string lifetime) + [Fact] + public void ClaimsIdentity_SetAuthorizationId_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var identity = (ClaimsIdentity) null!; - // Act - principal.SetAccessTokenLifetime(ParseLifeTime(lifetime)); + // Act and assert + var exception = Assert.Throws(() => identity.SetAuthorizationId(null)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime)); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void SetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetAuthorizationId_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetAuthorizationCodeLifetime(null)); + var exception = Assert.Throws(() => principal.SetAuthorizationId(null)); Assert.Equal("principal", exception.ParamName); } [Theory] [InlineData(null)] - [InlineData("62")] - public void SetAuthorizationCodeLifetime_AddsLifetime(string lifetime) + [InlineData("")] + public void ClaimsIdentity_SetAuthorizationId_RemovesClaimForNullOrEmptyValue(string value) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Private.AuthorizationId, "2520"); // Act - principal.SetAuthorizationCodeLifetime(ParseLifeTime(lifetime)); + identity.SetAuthorizationId(value); // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); + Assert.Empty(identity.Claims); } - [Fact] - public void SetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetAuthorizationId_RemovesClaimForNullOrEmptyValue(string value) { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.AuthorizationId, "2520"); - // Act and assert - var exception = Assert.Throws(() => principal.SetDeviceCodeLifetime(null)); + // Act + principal.SetAuthorizationId(value); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Empty(principal.Claims); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetDeviceCodeLifetime_AddsLifetime(string lifetime) + [Fact] + public void ClaimsIdentity_SetAuthorizationId_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetDeviceCodeLifetime(ParseLifeTime(lifetime)); + identity.SetAuthorizationId("42"); // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime)); + Assert.Equal("42", identity.GetClaim(Claims.Private.AuthorizationId)); } [Fact] - public void SetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetAuthorizationId_AddsClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.SetIdentityTokenLifetime(null)); + // Act + principal.SetAuthorizationId("42"); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal("42", principal.GetClaim(Claims.Private.AuthorizationId)); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetIdentityTokenLifetime_AddsLifetime(string lifetime) + [Fact] + public void ClaimsIdentity_SetTokenId_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var identity = (ClaimsIdentity) null!; - // Act - principal.SetIdentityTokenLifetime(ParseLifeTime(lifetime)); + // Act and assert + var exception = Assert.Throws(() => identity.SetTokenId(null)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime)); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void SetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetTokenId_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetRefreshTokenLifetime(null)); + var exception = Assert.Throws(() => principal.SetTokenId(null)); Assert.Equal("principal", exception.ParamName); } [Theory] [InlineData(null)] - [InlineData("62")] - public void SetRefreshTokenLifetime_AddsLifetime(string lifetime) + [InlineData("")] + public void ClaimsIdentity_SetTokenId_RemovesClaimForNullOrEmptyValue(string value) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Private.TokenId, "2520"); // Act - principal.SetRefreshTokenLifetime(ParseLifeTime(lifetime)); + identity.SetTokenId(value); // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime)); + Assert.Empty(identity.Claims); } - [Fact] - public void SetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetTokenId_RemovesClaimForNullOrEmptyValue(string value) { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.TokenId, "2520"); - // Act and assert - var exception = Assert.Throws(() => principal.SetUserCodeLifetime(null)); + // Act + principal.SetTokenId(value); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Empty(principal.Claims); } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetUserCodeLifetime_AddsLifetime(string lifetime) + [Fact] + public void ClaimsIdentity_SetTokenId_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetUserCodeLifetime(ParseLifeTime(lifetime)); + identity.SetTokenId("42"); // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.UserCodeLifetime)); + Assert.Equal("42", identity.GetClaim(Claims.Private.TokenId)); } [Fact] - public void SetAuthorizationId_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetTokenId_AddsClaim() { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act and assert - var exception = Assert.Throws(() => principal.SetAuthorizationId(null)); + // Act + principal.SetTokenId("42"); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Equal("42", principal.GetClaim(Claims.Private.TokenId)); } - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void SetAuthorizationId_AddsScopes(string identifier) + [Fact] + public void ClaimsIdentity_SetTokenType_ThrowsAnExceptionForNullIdentity() { // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var identity = (ClaimsIdentity) null!; - // Act - principal.SetAuthorizationId(identifier); + // Act and assert + var exception = Assert.Throws(() => identity.SetTokenType(null)); - // Assert - Assert.Equal(identifier, principal.GetClaim(Claims.Private.AuthorizationId)); + Assert.Equal("identity", exception.ParamName); } [Fact] - public void SetTokenId_ThrowsAnExceptionForNullPrincipal() + public void ClaimsPrincipal_SetTokenType_ThrowsAnExceptionForNullPrincipal() { // Arrange var principal = (ClaimsPrincipal) null!; // Act and assert - var exception = Assert.Throws(() => principal.SetTokenId(null)); + var exception = Assert.Throws(() => principal.SetTokenType(null)); Assert.Equal("principal", exception.ParamName); } [Theory] [InlineData(null)] - [InlineData("identifier")] - public void SetTokenId_AddsScopes(string identifier) + [InlineData("")] + public void ClaimsIdentity_SetTokenType_RemovesClaimForNullOrEmptyValue(string value) { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + identity.AddClaim(Claims.Private.TokenType, "2520"); // Act - principal.SetTokenId(identifier); + identity.SetTokenType(value); // Assert - Assert.Equal(identifier, principal.GetClaim(Claims.Private.TokenId)); + Assert.Empty(identity.Claims); } - [Fact] - public void SetTokenType_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_SetTokenType_RemovesClaimForNullOrEmptyValue(string value) { // Arrange - var principal = (ClaimsPrincipal) null!; + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.AddClaim(Claims.Private.TokenType, "2520"); - // Act and assert - var exception = Assert.Throws(() => principal.SetTokenType(null)); + // Act + principal.SetTokenType(value); - Assert.Equal("principal", exception.ParamName); + // Assert + Assert.Empty(principal.Claims); } - [Theory] - [InlineData(null)] - [InlineData("access_token")] - public void SetTokenType_AddsType(string type) + [Fact] + public void ClaimsIdentity_SetTokenType_AddsClaim() { // Arrange var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); // Act - principal.SetTokenType(type); + identity.SetTokenType(TokenTypeHints.AccessToken); // Assert - Assert.Equal(type, principal.GetClaim(Claims.Private.TokenType)); + Assert.Equal(TokenTypeHints.AccessToken, identity.GetClaim(Claims.Private.TokenType)); } - private TimeSpan? ParseLifeTime(string lifetime) + [Fact] + public void ClaimsPrincipal_SetTokenType_AddsClaim() { - var lifeT = lifetime is not null - ? (TimeSpan?) TimeSpan.FromSeconds(double.Parse(lifetime, NumberStyles.Number, CultureInfo.InvariantCulture)) - : null; + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); - return lifeT; + // Act + principal.SetTokenType(TokenTypeHints.AccessToken); + + // Assert + Assert.Equal(TokenTypeHints.AccessToken, principal.GetClaim(Claims.Private.TokenType)); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs index 9d0345fc..99069604 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs @@ -862,8 +862,7 @@ public abstract partial class OpenIddictServerIntegrationTests .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") - .SetClaim(Claims.Private.CodeChallengeMethod, null); + .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); return default; });