using System.Security.Claims; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; namespace OpenIddict.Extensions; /// /// Exposes common helpers used by the OpenIddict assemblies. /// internal static class OpenIddictHelpers { /// /// Finds the first base type that matches the specified generic type definition. /// /// The type to introspect. /// The generic type definition. /// A instance if the base type was found, otherwise. public static Type? FindGenericBaseType(Type type, Type definition) => FindGenericBaseTypes(type, definition).FirstOrDefault(); /// /// Finds all the base types that matches the specified generic type definition. /// /// The type to introspect. /// The generic type definition. /// A instance if the base type was found, otherwise. public static IEnumerable FindGenericBaseTypes(Type type, Type definition) { if (type is null) { throw new ArgumentNullException(nameof(type)); } if (definition is null) { throw new ArgumentNullException(nameof(definition)); } if (!definition.IsGenericTypeDefinition) { throw new ArgumentException(SR.GetResourceString(SR.ID0263), nameof(definition)); } if (definition.IsInterface) { foreach (var contract in type.GetInterfaces()) { if (!contract.IsGenericType && !contract.IsConstructedGenericType) { continue; } if (contract.GetGenericTypeDefinition() == definition) { yield return contract; } } } else { for (var candidate = type; candidate is not null; candidate = candidate.BaseType) { if (!candidate.IsGenericType && !candidate.IsConstructedGenericType) { continue; } if (candidate.GetGenericTypeDefinition() == definition) { yield return candidate; } } } } /// /// Extracts the parameters from the specified query string. /// /// The query string, which may start with a '?'. /// The parameters extracted from the specified query string. /// is . public static IReadOnlyDictionary ParseQuery(string query) { if (query is null) { throw new ArgumentNullException(nameof(query)); } return query.TrimStart(Separators.QuestionMark[0]) .Split(new[] { Separators.Ampersand[0], Separators.Semicolon[0] }, StringSplitOptions.RemoveEmptyEntries) .Select(parameter => parameter.Split(Separators.EqualsSign, StringSplitOptions.RemoveEmptyEntries)) .Select(parts => ( Key: parts[0] is string key ? Uri.UnescapeDataString(key) : null, Value: parts.Length > 1 && parts[1] is string value ? Uri.UnescapeDataString(value) : null)) .Where(pair => !string.IsNullOrEmpty(pair.Key)) .GroupBy(pair => pair.Key) .ToDictionary(pair => pair.Key!, pair => new StringValues(pair.Select(parts => parts.Value).ToArray())); } /// /// Creates a merged principal based on the specified principals. /// /// The collection of principals to merge. /// The merged principal. public static ClaimsPrincipal CreateMergedPrincipal(params ClaimsPrincipal?[] principals) { // Note: components like the client handler can be used as a pure OAuth 2.0 stack for // delegation scenarios where the identity of the user is not needed. In this case, // since no principal can be resolved from a token or a userinfo response to construct // a user identity, a fake one containing an "unauthenticated" identity (i.e with its // AuthenticationType property deliberately left to null) is used to allow the host // to return a "successful" authentication result for these delegation-only scenarios. if (!principals.Any(principal => principal?.Identity is ClaimsIdentity { IsAuthenticated: true })) { return new ClaimsPrincipal(new ClaimsIdentity()); } // Create a new composite identity containing the claims of all the principals. var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType); foreach (var principal in principals) { // Note: the principal may be null if no value was extracted from the corresponding token. if (principal is null) { continue; } foreach (var claim in principal.Claims) { // If a claim with the same type and the same value already exist, skip it. if (identity.HasClaim(claim.Type, claim.Value)) { continue; } identity.AddClaim(claim); } } return new ClaimsPrincipal(identity); } }