/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using JetBrains.Annotations;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Abstractions
{
///
/// Provides extension methods to make
/// and easier to work with.
///
public static class OpenIddictExtensions
{
///
/// Gets all the parameters associated with the specified message as a flattened collection:
/// array parameters are automatically converted to multiple parameters and parameters that
/// can't be converted to string instances are ignored and excluded from the returned collection.
/// This extension is primarily intended to be used by components that need to represent
/// an OpenID Connect message as a query string or as a list of key/value pairs in a HTTP form.
///
/// The instance.
/// The parameters, as a flattened collection.
public static ImmutableList> GetFlattenedParameters([NotNull] this OpenIddictMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var parameters = ImmutableList.CreateBuilder>();
foreach (var parameter in message.GetParameters())
{
var values = (string[]) parameter.Value;
if (values == null)
{
continue;
}
foreach (var value in values)
{
parameters.Add(new KeyValuePair(parameter.Key, value));
}
}
return parameters.ToImmutable();
}
///
/// Extracts the authentication context class values from an .
///
/// The instance.
public static ImmutableHashSet GetAcrValues([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.AcrValues))
{
return ImmutableHashSet.Create(StringComparer.Ordinal);
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, GetValues(request.AcrValues, Separators.Space));
}
///
/// Extracts the scopes from an .
///
/// The instance.
public static ImmutableHashSet GetScopes([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.Scope))
{
return ImmutableHashSet.Create(StringComparer.Ordinal);
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, GetValues(request.Scope, Separators.Space));
}
///
/// Determines whether the requested authentication context class values contain the specified item.
///
/// The instance.
/// The component to look for in the parameter.
public static bool HasAcrValue([NotNull] this OpenIddictRequest request, [NotNull] string value)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("The value cannot be null or empty.", nameof(value));
}
if (string.IsNullOrEmpty(request.AcrValues))
{
return false;
}
return HasValue(request.AcrValues, value, Separators.Space);
}
///
/// Determines whether the requested prompt contains the specified value.
///
/// The instance.
/// The component to look for in the parameter.
public static bool HasPrompt([NotNull] this OpenIddictRequest request, [NotNull] string prompt)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(prompt))
{
throw new ArgumentException("The prompt cannot be null or empty.", nameof(prompt));
}
if (string.IsNullOrEmpty(request.Prompt))
{
return false;
}
return HasValue(request.Prompt, prompt, Separators.Space);
}
///
/// Determines whether the requested response type contains the specified value.
///
/// The instance.
/// The component to look for in the parameter.
public static bool HasResponseType([NotNull] this OpenIddictRequest request, [NotNull] string type)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The response type cannot be null or empty.", nameof(type));
}
if (string.IsNullOrEmpty(request.ResponseType))
{
return false;
}
return HasValue(request.ResponseType, type, Separators.Space);
}
///
/// Determines whether the requested scope contains the specified value.
///
/// The instance.
/// The component to look for in the parameter.
public static bool HasScope([NotNull] this OpenIddictRequest request, [NotNull] string scope)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(scope))
{
throw new ArgumentException("The scope cannot be null or empty.", nameof(scope));
}
if (string.IsNullOrEmpty(request.Scope))
{
return false;
}
return HasValue(request.Scope, scope, Separators.Space);
}
///
/// Determines whether the "response_type" parameter corresponds to the "none" response type.
/// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none for more information.
///
/// The instance.
/// true if the request is a response_type=none request, false otherwise.
public static bool IsNoneFlow([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.ResponseType))
{
return false;
}
var segment = Trim(new StringSegment(request.ResponseType), Separators.Space);
if (segment.Length == 0)
{
return false;
}
return segment.Equals(ResponseTypes.None, StringComparison.Ordinal);
}
///
/// Determines whether the "response_type" parameter corresponds to the authorization code flow.
/// See http://tools.ietf.org/html/rfc6749#section-4.1.1 for more information.
///
/// The instance.
/// true if the request is a code flow request, false otherwise.
public static bool IsAuthorizationCodeFlow([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.ResponseType))
{
return false;
}
var segment = Trim(new StringSegment(request.ResponseType), Separators.Space);
if (segment.Length == 0)
{
return false;
}
return segment.Equals(ResponseTypes.Code, StringComparison.Ordinal);
}
///
/// Determines whether the "response_type" parameter corresponds to the implicit flow.
/// See http://tools.ietf.org/html/rfc6749#section-4.2.1 and
/// http://openid.net/specs/openid-connect-core-1_0.html for more information
///
/// The instance.
/// true if the request is an implicit flow request, false otherwise.
public static bool IsImplicitFlow([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.ResponseType))
{
return false;
}
var flags = /* none: */ 0x00;
foreach (var element in new StringTokenizer(request.ResponseType, Separators.Space))
{
var segment = Trim(element, Separators.Space);
if (segment.Length == 0)
{
continue;
}
if (segment.Equals(ResponseTypes.IdToken, StringComparison.Ordinal))
{
flags |= /* id_token: */ 0x01;
continue;
}
// Note: though the OIDC core specs does not include the OAuth 2.0-inherited response_type=token,
// it is considered as a valid response_type for the implicit flow for backward compatibility.
else if (segment.Equals(ResponseTypes.Token, StringComparison.Ordinal))
{
flags |= /* token */ 0x02;
continue;
}
// Always return false if the response_type item
// is not a valid component for the implicit flow.
return false;
}
// Return true if the response_type parameter contains "id_token" or "token".
return (flags & /* id_token: */ 0x01) == 0x01 || (flags & /* token: */ 0x02) == 0x02;
}
///
/// Determines whether the "response_type" parameter corresponds to the hybrid flow.
/// See http://tools.ietf.org/html/rfc6749#section-4.2.1 and
/// http://openid.net/specs/openid-connect-core-1_0.html for more information.
///
/// The instance.
/// true if the request is an hybrid flow request, false otherwise.
public static bool IsHybridFlow([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.ResponseType))
{
return false;
}
var flags = /* none */ 0x00;
foreach (var element in new StringTokenizer(request.ResponseType, Separators.Space))
{
var segment = Trim(element, Separators.Space);
if (segment.Length == 0)
{
continue;
}
if (segment.Equals(ResponseTypes.Code, StringComparison.Ordinal))
{
flags |= /* code: */ 0x01;
continue;
}
else if (segment.Equals(ResponseTypes.IdToken, StringComparison.Ordinal))
{
flags |= /* id_token: */ 0x02;
continue;
}
else if (segment.Equals(ResponseTypes.Token, StringComparison.Ordinal))
{
flags |= /* token: */ 0x04;
continue;
}
// Always return false if the response_type item
// is not a valid component for the hybrid flow.
return false;
}
// Return false if the response_type parameter doesn't contain "code".
if ((flags & /* code: */ 0x01) != 0x01)
{
return false;
}
// Return true if the response_type parameter contains "id_token" or "token".
return (flags & /* id_token: */ 0x02) == 0x02 || (flags & /* token: */ 0x04) == 0x04;
}
///
/// Determines whether the "response_mode" parameter corresponds to the fragment response mode.
/// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html for more information.
///
/// The instance.
///
/// true if the request specified the fragment response mode or if
/// it's the default value for the requested flow, false otherwise.
///
public static bool IsFragmentResponseMode([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.Equals(request.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal))
{
return true;
}
// Don't guess the response_mode value
// if an explicit value has been provided.
if (!string.IsNullOrEmpty(request.ResponseMode))
{
return false;
}
// Both the implicit and the hybrid flows
// use response_mode=fragment by default.
return request.IsImplicitFlow() || request.IsHybridFlow();
}
///
/// Determines whether the "response_mode" parameter corresponds to the query response mode.
/// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html for more information.
///
/// The instance.
///
/// true if the request specified the query response mode or if
/// it's the default value for the requested flow, false otherwise.
///
public static bool IsQueryResponseMode([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.Equals(request.ResponseMode, ResponseModes.Query, StringComparison.Ordinal))
{
return true;
}
// Don't guess the response_mode value
// if an explicit value has been provided.
if (!string.IsNullOrEmpty(request.ResponseMode))
{
return false;
}
// Code flow and "response_type=none" use response_mode=query by default.
return request.IsAuthorizationCodeFlow() || request.IsNoneFlow();
}
///
/// Determines whether the "response_mode" parameter corresponds to the form post response mode.
/// See http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html for more information.
///
/// The instance.
///
/// true if the request specified the form post response mode or if
/// it's the default value for the requested flow, false otherwise.
///
public static bool IsFormPostResponseMode([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
return string.Equals(request.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal);
}
///
/// Determines whether the "grant_type" parameter corresponds to the authorization code grant.
/// See http://tools.ietf.org/html/rfc6749#section-4.1.3 for more information.
///
/// The instance.
/// true if the request is a code grant request, false otherwise.
public static bool IsAuthorizationCodeGrantType([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
return string.Equals(request.GrantType, GrantTypes.AuthorizationCode, StringComparison.Ordinal);
}
///
/// Determines whether the "grant_type" parameter corresponds to the client credentials grant.
/// See http://tools.ietf.org/html/rfc6749#section-4.4.2 for more information.
///
/// The instance.
/// true if the request is a client credentials grant request, false otherwise.
public static bool IsClientCredentialsGrantType([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
return string.Equals(request.GrantType, GrantTypes.ClientCredentials, StringComparison.Ordinal);
}
///
/// Determines whether the "grant_type" parameter corresponds to the password grant.
/// See http://tools.ietf.org/html/rfc6749#section-4.3.2 for more information.
///
/// The instance.
/// true if the request is a password grant request, false otherwise.
public static bool IsPasswordGrantType([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
return string.Equals(request.GrantType, GrantTypes.Password, StringComparison.Ordinal);
}
///
/// Determines whether the "grant_type" parameter corresponds to the refresh token grant.
/// See http://tools.ietf.org/html/rfc6749#section-6 for more information.
///
/// The instance.
/// true if the request is a refresh token grant request, false otherwise.
public static bool IsRefreshTokenGrantType([NotNull] this OpenIddictRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
return string.Equals(request.GrantType, GrantTypes.RefreshToken, StringComparison.Ordinal);
}
///
/// Gets the destinations associated with a claim.
///
/// The instance.
/// The destinations associated with the claim.
public static ImmutableHashSet GetDestinations([NotNull] this Claim claim)
{
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
claim.Properties.TryGetValue(Properties.Destinations, out string destinations);
if (string.IsNullOrEmpty(destinations))
{
return ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase);
}
return ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, JArray.Parse(destinations).Values());
}
///
/// Determines whether the given claim contains the required destination.
///
/// The instance.
/// The required destination.
public static bool HasDestination([NotNull] this Claim claim, [NotNull] string destination)
{
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (string.IsNullOrEmpty(destination))
{
throw new ArgumentException("The destination cannot be null or empty.", nameof(destination));
}
return GetDestinations(claim).Contains(destination, StringComparer.OrdinalIgnoreCase);
}
///
/// Adds specific destinations to a claim.
///
/// The instance.
/// The destinations.
public static Claim SetDestinations([NotNull] this Claim claim, IEnumerable destinations)
{
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (destinations == null || !destinations.Any())
{
claim.Properties.Remove(Properties.Destinations);
return claim;
}
if (destinations.Any(destination => string.IsNullOrEmpty(destination)))
{
throw new ArgumentException("Destinations cannot be null or empty.", nameof(destinations));
}
claim.Properties[Properties.Destinations] =
new JArray(destinations.Distinct(StringComparer.OrdinalIgnoreCase)).ToString(Formatting.None);
return claim;
}
///
/// Adds specific destinations to a claim.
///
/// The instance.
/// The destinations.
public static Claim SetDestinations([NotNull] this Claim claim, params string[] destinations)
// Note: guarding the destinations parameter against null values
// is not necessary as AsEnumerable() doesn't throw on null values.
=> claim.SetDestinations(destinations.AsEnumerable());
///
/// Clones an identity by filtering its claims and the claims of its actor, recursively.
///
/// The instance to filter.
///
/// The delegate filtering the claims: return true
/// to accept the claim, false to remove it.
///
public static ClaimsIdentity Clone(
[NotNull] this ClaimsIdentity identity,
[NotNull] Func filter)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
var clone = identity.Clone();
// Note: make sure to call ToList() to avoid modifying
// the initial collection iterated by ClaimsIdentity.Claims.
foreach (var claim in clone.Claims.ToList())
{
if (!filter(claim))
{
clone.RemoveClaim(claim);
}
}
if (clone.Actor != null)
{
clone.Actor = clone.Actor.Clone(filter);
}
return clone;
}
///
/// Clones a principal by filtering its identities.
///
/// The instance to filter.
///
/// The delegate filtering the claims: return true
/// to accept the claim, false to remove it.
///
public static ClaimsPrincipal Clone(
[NotNull] this ClaimsPrincipal principal,
[NotNull] Func filter)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
var clone = new ClaimsPrincipal();
foreach (var identity in principal.Identities)
{
clone.AddIdentity(identity.Clone(filter));
}
return clone;
}
///
/// Adds a claim to a given identity.
///
/// The identity.
/// The type associated with the claim.
/// The value associated with the claim.
public static ClaimsIdentity AddClaim(
[NotNull] this ClaimsIdentity identity,
[NotNull] string type, [NotNull] string value)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("The claim value cannot be null or empty.", nameof(value));
}
identity.AddClaim(new Claim(type, value));
return identity;
}
///
/// Adds a claim to a given identity and specify one or more destinations.
///
/// The identity.
/// The type associated with the claim.
/// The value associated with the claim.
/// The destinations associated with the claim.
public static ClaimsIdentity AddClaim(
[NotNull] this ClaimsIdentity identity,
[NotNull] string type, [NotNull] string value,
[NotNull] IEnumerable destinations)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("The claim value cannot be null or empty.", nameof(value));
}
if (destinations == null)
{
throw new ArgumentNullException(nameof(destinations));
}
identity.AddClaim(new Claim(type, value).SetDestinations(destinations));
return identity;
}
///
/// Adds a claim to a given identity and specify one or more destinations.
///
/// The identity.
/// The type associated with the claim.
/// The value associated with the claim.
/// The destinations associated with the claim.
public static ClaimsIdentity AddClaim(
[NotNull] this ClaimsIdentity identity,
[NotNull] string type, [NotNull] string value,
[NotNull] params string[] destinations)
// Note: guarding the destinations parameter against null values
// is not necessary as AsEnumerable() doesn't throw on null values.
=> identity.AddClaim(type, value, destinations.AsEnumerable());
///
/// Gets the claim value corresponding to the given type.
///
/// The identity.
/// The type associated with the claim.
/// The claim value.
public static string GetClaim([NotNull] this ClaimsIdentity identity, [NotNull] string type)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", 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([NotNull] this ClaimsPrincipal principal, [NotNull] string type)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
return principal.FindFirst(type)?.Value;
}
///
/// Gets the claim values corresponding to the given type.
///
/// The identity.
/// The type associated with the claims.
/// The claim values.
public static ImmutableHashSet GetClaims([NotNull] this ClaimsIdentity identity, [NotNull] string type)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, identity.FindAll(type).Select(claim => claim.Value));
}
///
/// Gets the claim values corresponding to the given type.
///
/// The principal.
/// The type associated with the claims.
/// The claim values.
public static ImmutableHashSet GetClaims([NotNull] this ClaimsPrincipal principal, [NotNull] string type)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.FindAll(type).Select(claim => claim.Value));
}
///
/// Removes all the claims corresponding to the given type.
///
/// The identity.
/// The type associated with the claims.
/// The claims identity.
public static ClaimsIdentity RemoveClaims([NotNull] this ClaimsIdentity identity, [NotNull] string type)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", 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([NotNull] this ClaimsPrincipal principal, [NotNull] string type)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", 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 claims.
/// The claim value.
/// The claims identity.
public static ClaimsIdentity SetClaims(
[NotNull] this ClaimsIdentity identity,
[NotNull] string type, [CanBeNull] string value)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
identity.RemoveClaims(type);
if (!string.IsNullOrEmpty(value))
{
identity.AddClaim(type, value);
}
return identity;
}
///
/// Sets the claim value corresponding to the given type.
///
/// The principal.
/// The type associated with the claims.
/// The claim value.
/// The claims identity.
public static ClaimsPrincipal SetClaim(
[NotNull] this ClaimsPrincipal principal,
[NotNull] string type, [CanBeNull] string value)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
principal.RemoveClaims(type);
if (!string.IsNullOrEmpty(value))
{
((ClaimsIdentity) principal.Identity).AddClaim(type, value);
}
return principal;
}
///
/// Sets the claim values corresponding to the given type.
///
/// The identity.
/// The type associated with the claims.
/// The claim values.
/// The claims identity.
public static ClaimsIdentity SetClaims([NotNull] this ClaimsIdentity identity,
[NotNull] string type, [NotNull] IEnumerable values)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
identity.RemoveClaims(type);
foreach (var value in values)
{
identity.AddClaim(type, value);
}
return identity;
}
///
/// Sets the claim values corresponding to the given type.
///
/// The principal.
/// The type associated with the claims.
/// The claim values.
/// The claims identity.
public static ClaimsPrincipal SetClaims([NotNull] this ClaimsPrincipal principal,
[NotNull] string type, [NotNull] IEnumerable values)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The claim type cannot be null or empty.", nameof(type));
}
principal.RemoveClaims(type);
foreach (var value in values)
{
((ClaimsIdentity) principal.Identity).AddClaim(type, value);
}
return principal;
}
///
/// Gets the creation date stored in the claims principal.
///
/// The claims principal.
/// The creation date or null if the claim cannot be found.
public static DateTimeOffset? GetCreationDate([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var claim = principal.FindFirst(Claims.IssuedAt);
if (claim == null)
{
return null;
}
if (!long.TryParse(claim.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
return null;
}
return DateTimeOffset.FromUnixTimeSeconds(value);
}
///
/// Gets the expiration date stored in the claims principal.
///
/// The claims principal.
/// The expiration date or null if the claim cannot be found.
public static DateTimeOffset? GetExpirationDate([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var claim = principal.FindFirst(Claims.ExpiresAt);
if (claim == null)
{
return null;
}
if (!long.TryParse(claim.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
return null;
}
return DateTimeOffset.FromUnixTimeSeconds(value);
}
///
/// Gets the audiences list stored in the claims principal.
///
/// The claims principal.
/// The audiences list or an empty set if the claims cannot be found.
public static ImmutableHashSet GetAudiences([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.Audience));
}
///
/// Gets the presenters list stored in the claims principal.
///
/// The claims principal.
/// The presenters list or an empty set if the claims cannot be found.
public static ImmutableHashSet GetPresenters([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.AuthorizedParty));
}
///
/// Gets the resources list stored in the claims principal.
///
/// The claims principal.
/// The resources list or an empty set if the claims cannot be found.
public static ImmutableHashSet GetResources([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, principal.GetClaims(Claims.Private.Resource));
}
///
/// Gets the scopes list stored in the claims principal.
///
/// The claims principal.
/// The scopes list or an empty set if the claim cannot be found.
public static ImmutableHashSet GetScopes([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
// Note: scopes are deliberately formatted as a single space-separated
// string to respect the usual representation of the standard scope claim.
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
var value = principal.GetClaim(Claims.Scope);
if (string.IsNullOrEmpty(value))
{
return ImmutableHashSet.Create(StringComparer.Ordinal);
}
return ImmutableHashSet.CreateRange(StringComparer.Ordinal, GetValues(value, Separators.Space));
}
///
/// Gets the access token lifetime associated with the claims principal.
///
/// The claims principal.
/// The access token lifetime or null if the claim cannot be found.
public static TimeSpan? GetAccessTokenLifetime([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var value = principal.GetClaim(Claims.Private.AccessTokenLifetime);
if (string.IsNullOrEmpty(value))
{
return null;
}
if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result))
{
return TimeSpan.FromSeconds(result);
}
return null;
}
///
/// Gets the authorization code lifetime associated with the claims principal.
///
/// The claims principal.
/// The authorization code lifetime or null if the claim cannot be found.
public static TimeSpan? GetAuthorizationCodeLifetime([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var value = principal.GetClaim(Claims.Private.AuthorizationCodeLifetime);
if (string.IsNullOrEmpty(value))
{
return null;
}
if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result))
{
return TimeSpan.FromSeconds(result);
}
return null;
}
///
/// Gets the identity token lifetime associated with the claims principal.
///
/// The claims principal.
/// The identity token lifetime or null if the claim cannot be found.
public static TimeSpan? GetIdentityTokenLifetime([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var value = principal.GetClaim(Claims.Private.IdentityTokenLifetime);
if (string.IsNullOrEmpty(value))
{
return null;
}
if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result))
{
return TimeSpan.FromSeconds(result);
}
return null;
}
///
/// Gets the refresh token lifetime associated with the claims principal.
///
/// The claims principal.
/// The refresh token lifetime or null if the claim cannot be found.
public static TimeSpan? GetRefreshTokenLifetime([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
var value = principal.GetClaim(Claims.Private.RefreshTokenLifetime);
if (string.IsNullOrEmpty(value))
{
return null;
}
if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result))
{
return TimeSpan.FromSeconds(result);
}
return null;
}
///
/// Gets the unique identifier associated with the claims principal.
///
/// The claims principal.
/// The unique identifier or null if the claim cannot be found.
public static string GetTokenId([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.GetClaim(Claims.JwtId);
}
///
/// Gets the token usage associated with the claims principal.
///
/// The claims principal.
/// The token usage or null if the claim cannot be found.
public static string GetTokenUsage([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.GetClaim(Claims.Private.TokenUsage);
}
///
/// Gets a boolean value indicating whether the
/// claims principal corresponds to an access token.
///
/// The claims principal.
/// true if the principal corresponds to an access token.
public static bool IsAccessToken([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.AccessToken, StringComparison.OrdinalIgnoreCase);
}
///
/// Gets a boolean value indicating whether the
/// claims principal corresponds to an access token.
///
/// The claims principal.
/// true if the principal corresponds to an authorization code.
public static bool IsAuthorizationCode([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.AuthorizationCode, StringComparison.OrdinalIgnoreCase);
}
///
/// Gets a boolean value indicating whether the
/// claims principal corresponds to an identity token.
///
/// The claims principal.
/// true if the principal corresponds to an identity token.
public static bool IsIdentityToken([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.IdToken, StringComparison.OrdinalIgnoreCase);
}
///
/// Gets a boolean value indicating whether the
/// claims principal corresponds to a refresh token.
///
/// The claims principal.
/// true if the principal corresponds to a refresh token.
public static bool IsRefreshToken([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.RefreshToken, StringComparison.OrdinalIgnoreCase);
}
///
/// Determines whether the claims principal contains at least one audience.
///
/// The claims principal.
/// true if the principal contains at least one audience.
public static bool HasAudience([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Audience).Any();
}
///
/// Determines whether the claims principal contains the given audience.
///
/// The claims principal.
/// The audience.
/// true if the principal contains the given audience.
public static bool HasAudience([NotNull] this ClaimsPrincipal principal, [NotNull] string audience)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(audience))
{
throw new ArgumentException("The audience cannot be null or empty.", nameof(audience));
}
return principal.GetAudiences().Contains(audience);
}
///
/// Determines whether the claims principal contains at least one presenter.
///
/// The claims principal.
/// true if the principal contains at least one presenter.
public static bool HasPresenter([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.AuthorizedParty).Any();
}
///
/// Determines whether the claims principal contains the given presenter.
///
/// The claims principal.
/// The presenter.
/// true if the principal contains the given presenter.
public static bool HasPresenter([NotNull] this ClaimsPrincipal principal, [NotNull] string presenter)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(presenter))
{
throw new ArgumentException("The presenter cannot be null or empty.", nameof(presenter));
}
return principal.GetPresenters().Contains(presenter);
}
///
/// Determines whether the claims principal contains at least one resource.
///
/// The claims principal.
/// true if the principal contains at least one resource.
public static bool HasResource([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Resource).Any();
}
///
/// Determines whether the claims principal contains the given resource.
///
/// The claims principal.
/// The resource.
/// true if the principal contains the given resource.
public static bool HasResource([NotNull] this ClaimsPrincipal principal, [NotNull] string resource)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
}
return principal.GetResources().Contains(resource);
}
///
/// Determines whether the claims principal contains at least one scope.
///
/// The claims principal.
/// true if the principal contains at least one scope.
public static bool HasScope([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Scope).Any();
}
///
/// Determines whether the claims principal contains the given scope.
///
/// The claims principal.
/// The scope.
/// true if the principal contains the given scope.
public static bool HasScope([NotNull] this ClaimsPrincipal principal, [NotNull] string scope)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (string.IsNullOrEmpty(scope))
{
throw new ArgumentException("The scope cannot be null or empty.", nameof(scope));
}
return principal.GetScopes().Contains(scope);
}
///
/// Sets the creation date in the claims principal.
///
/// The claims principal.
/// The creation date
/// The claims principal.
public static ClaimsPrincipal SetCreationDate([NotNull] this ClaimsPrincipal principal, [CanBeNull] DateTimeOffset? date)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
principal.RemoveClaims(Claims.IssuedAt);
if (date.HasValue)
{
var claim = new Claim(Claims.IssuedAt, date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64);
((ClaimsIdentity) principal.Identity).AddClaim(claim);
}
return principal;
}
///
/// Sets the expiration date in the claims principal.
///
/// The claims principal.
/// The expiration date
/// The claims principal.
public static ClaimsPrincipal SetExpirationDate([NotNull] this ClaimsPrincipal principal, [CanBeNull] DateTimeOffset? date)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
principal.RemoveClaims(Claims.ExpiresAt);
if (date.HasValue)
{
var claim = new Claim(Claims.ExpiresAt, date?.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64);
((ClaimsIdentity) principal.Identity).AddClaim(claim);
}
return principal;
}
///
/// Sets the audiences list in the claims principal.
/// Note: this method automatically excludes duplicate audiences.
///
/// The claims principal.
/// The audiences to store.
/// The claims principal.
public static ClaimsPrincipal SetAudiences(
[NotNull] this ClaimsPrincipal principal,
[CanBeNull] IEnumerable audiences)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaims(Claims.Audience, audiences.Distinct(StringComparer.Ordinal));
}
///
/// Sets the audiences list in the claims principal.
/// Note: this method automatically excludes duplicate audiences.
///
/// The claims principal.
/// The audiences to store.
/// The claims principal.
public static ClaimsPrincipal SetAudiences(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] params string[] audiences)
// Note: guarding the audiences parameter against null values
// is not necessary as AsEnumerable() doesn't throw on null values.
=> principal.SetAudiences(audiences.AsEnumerable());
///
/// Sets the presenters list in the claims principal.
/// Note: this method automatically excludes duplicate presenters.
///
/// The claims principal.
/// The presenters to store.
/// The claims principal.
public static ClaimsPrincipal SetPresenters(
[NotNull] this ClaimsPrincipal principal,
[CanBeNull] IEnumerable presenters)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaims(Claims.AuthorizedParty, presenters.Distinct(StringComparer.Ordinal));
}
///
/// Sets the presenters list in the claims principal.
/// Note: this method automatically excludes duplicate presenters.
///
/// The claims principal.
/// The presenters to store.
/// The claims principal.
public static ClaimsPrincipal SetPresenters(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] params string[] presenters)
// Note: guarding the presenters parameter against null values
// is not necessary as AsEnumerable() doesn't throw on null values.
=> principal.SetPresenters(presenters.AsEnumerable());
///
/// Sets the resources list in the claims principal.
/// Note: this method automatically excludes duplicate resources.
///
/// The claims principal.
/// The resources to store.
/// The claims principal.
public static ClaimsPrincipal SetResources(
[NotNull] this ClaimsPrincipal principal,
[CanBeNull] IEnumerable resources)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaims(Claims.Private.Resource, resources.Distinct(StringComparer.Ordinal));
}
///
/// Sets the resources list in the claims principal.
/// Note: this method automatically excludes duplicate resources.
///
/// The claims principal.
/// The resources to store.
/// The claims principal.
public static ClaimsPrincipal SetResources(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] params string[] resources)
// Note: guarding the resources parameter against null values
// is not necessary as AsEnumerable() doesn't throw on null values.
=> principal.SetResources(resources.AsEnumerable());
///
/// Sets the scopes list in the claims principal.
/// Note: this method automatically excludes duplicate scopes.
///
/// The claims principal.
/// The scopes to store.
/// The claims principal.
public static ClaimsPrincipal SetScopes(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] IEnumerable scopes)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (scopes == null)
{
return principal.RemoveClaims(Claims.Scope);
}
// Note: scopes are deliberately formatted as a single space-separated
// string to respect the usual representation of the standard scope claim.
// See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
return principal.SetClaim(Claims.Scope, string.Join(" ", scopes.Distinct(StringComparer.Ordinal)));
}
///
/// Sets the scopes list in the claims principal.
/// Note: this method automatically excludes duplicate scopes.
///
/// The claims principal.
/// The scopes to store.
/// The claims principal.
public static ClaimsPrincipal SetScopes(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] params string[] scopes)
// Note: guarding the scopes parameter against null values
// is not necessary as AsEnumerable() doesn't throw on null values.
=> principal.SetScopes(scopes.AsEnumerable());
///
/// Sets the access token lifetime associated with the claims principal.
///
/// The claims principal.
/// The access token lifetime to store.
/// The claims principal.
public static ClaimsPrincipal SetAccessTokenLifetime([NotNull] this ClaimsPrincipal principal, TimeSpan? lifetime)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture));
}
///
/// Sets the authorization code lifetime associated with the claims principal.
///
/// The claims principal.
/// The authorization code lifetime to store.
/// The claims principal.
public static ClaimsPrincipal SetAuthorizationCodeLifetime([NotNull] this ClaimsPrincipal principal, TimeSpan? lifetime)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture));
}
///
/// Sets the identity token lifetime associated with the claims principal.
///
/// The claims principal.
/// The identity token lifetime to store.
/// The claims principal.
public static ClaimsPrincipal SetIdentityTokenLifetime([NotNull] this ClaimsPrincipal principal, TimeSpan? lifetime)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture));
}
///
/// Sets the refresh token lifetime associated with the claims principal.
///
/// The claims principal.
/// The refresh token lifetime to store.
/// The claims principal.
public static ClaimsPrincipal SetRefreshTokenLifetime([NotNull] this ClaimsPrincipal principal, TimeSpan? lifetime)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture));
}
///
/// Sets the unique identifier associated with the claims principal.
///
/// The claims principal.
/// The unique identifier to store.
/// The claims principal.
public static ClaimsPrincipal SetTokenId([NotNull] this ClaimsPrincipal principal, string identifier)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.SetClaim(Claims.JwtId, identifier);
}
private static IEnumerable GetValues(string source, char[] separators)
{
Debug.Assert(!string.IsNullOrEmpty(source), "The source string shouldn't be null or empty.");
Debug.Assert(separators?.Length != 0, "The separators collection shouldn't be null or empty.");
foreach (var element in new StringTokenizer(source, separators))
{
var segment = Trim(element, separators);
if (segment.Length == 0)
{
continue;
}
yield return segment.Value;
}
yield break;
}
private static bool HasValue(string source, string value, char[] separators)
{
Debug.Assert(!string.IsNullOrEmpty(source), "The source string shouldn't be null or empty.");
Debug.Assert(!string.IsNullOrEmpty(value), "The value string shouldn't be null or empty.");
Debug.Assert(separators?.Length != 0, "The separators collection shouldn't be null or empty.");
foreach (var element in new StringTokenizer(source, separators))
{
var segment = Trim(element, separators);
if (segment.Length == 0)
{
continue;
}
if (segment.Equals(value, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
private static StringSegment TrimStart(StringSegment segment, char[] separators)
{
Debug.Assert(separators?.Length != 0, "The separators collection shouldn't be null or empty.");
var index = segment.Offset;
while (index < segment.Offset + segment.Length)
{
if (!IsSeparator(segment.Buffer[index], separators))
{
break;
}
index++;
}
return new StringSegment(segment.Buffer, index, segment.Offset + segment.Length - index);
}
private static StringSegment TrimEnd(StringSegment segment, char[] separators)
{
Debug.Assert(separators?.Length != 0, "The separators collection shouldn't be null or empty.");
var index = segment.Offset + segment.Length - 1;
while (index >= segment.Offset)
{
if (!IsSeparator(segment.Buffer[index], separators))
{
break;
}
index--;
}
return new StringSegment(segment.Buffer, segment.Offset, index - segment.Offset + 1);
}
private static StringSegment Trim(StringSegment segment, char[] separators)
{
Debug.Assert(separators?.Length != 0, "The separators collection shouldn't be null or empty.");
return TrimEnd(TrimStart(segment, separators), separators);
}
private static bool IsSeparator(char character, char[] separators)
{
Debug.Assert(separators?.Length != 0, "The separators collection shouldn't be null or empty.");
for (var index = 0; index < separators.Length; index++)
{
if (character == separators[index])
{
return true;
}
}
return false;
}
}
}