From 4afc6a8b5706ff14f6f2273effb934ac4d88c017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Sun, 19 Feb 2023 16:40:33 +0100 Subject: [PATCH] Add Stripe to the list of supported providers --- .../OpenIddictClientWebIntegrationHandlers.cs | 137 ++++++++++++++++++ ...penIddictClientWebIntegrationProviders.xml | 31 ++++ .../OpenIddictClientEvents.cs | 10 ++ ...OpenIddictClientHandlers.Authentication.cs | 8 + .../OpenIddictClientHandlers.Session.cs | 13 +- 5 files changed, 198 insertions(+), 1 deletion(-) diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index a56b73f3..7b47c129 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Security.Claims; +using System.Text.Json; using OpenIddict.Extensions; using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; @@ -26,10 +27,12 @@ public static partial class OpenIddictClientWebIntegrationHandlers AdjustRedirectUriInTokenRequest.Descriptor, OverrideValidatedBackchannelTokens.Descriptor, AttachAdditionalUserinfoRequestParameters.Descriptor, + PopulateUserinfoTokenPrincipalFromTokenResponse.Descriptor, /* * Challenge processing: */ + OverrideAuthorizationEndpoint.Descriptor, OverrideResponseMode.Descriptor, FormatNonStandardScopeParameter.Descriptor, IncludeStateParameterInRedirectUri.Descriptor, @@ -379,6 +382,140 @@ public static partial class OpenIddictClientWebIntegrationHandlers } } + /// + /// Contains the logic responsible for creating a userinfo token principal from the custom + /// parameters returned in the token response for the providers that require it. + /// + public sealed class PopulateUserinfoTokenPrincipalFromTokenResponse : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateUserinfoToken.Descriptor.Order + 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + Debug.Assert(context.TokenResponse is not null, SR.GetResourceString(SR.ID4007)); + + // Don't overwrite the userinfo token principal if one was already set. + if (context.UserinfoTokenPrincipal is not null) + { + return default; + } + + // Some providers don't provide an OAuth 2.0/OpenID Connect userinfo endpoint but + // return the user information using custom/non-standard token response parameters. + // To work around that, this handler is responsible for extracting these parameters + // from the token response and creating a userinfo token principal containing them. + if (context.Registration.ProviderName is Providers.StripeConnect) + { + var identity = new ClaimsIdentity( + context.Registration.TokenValidationParameters.AuthenticationType, + context.Registration.TokenValidationParameters.NameClaimType, + context.Registration.TokenValidationParameters.RoleClaimType); + + var issuer = context.Configuration.Issuer!.AbsoluteUri; + + foreach (var parameter in context.TokenResponse.GetParameters()) + { + switch (context.Registration.ProviderName) + { + // For Stripe, only include "livemode" and the parameters that are prefixed with "stripe_": + case Providers.StripeConnect when + !string.Equals(parameter.Key, "livemode", StringComparison.OrdinalIgnoreCase) && + !parameter.Key.StartsWith("stripe_", StringComparison.OrdinalIgnoreCase): + continue; + } + + // Note: in the typical case, the response parameters should be deserialized from a + // JSON response and thus natively stored as System.Text.Json.JsonElement instances. + // + // In the rare cases where the underlying value wouldn't be a JsonElement instance + // (e.g when custom parameters are manually added to the response), the static + // conversion operator would take care of converting the underlying value to a + // JsonElement instance using the same value type as the original parameter value. + switch ((JsonElement) parameter.Value) + { + // 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: + identity.AddClaims(parameter.Key, value, issuer); + break; + + case { ValueKind: _ } value: + identity.AddClaim(parameter.Key, value, issuer); + break; + } + } + + context.UserinfoTokenPrincipal = new ClaimsPrincipal(identity); + } + + return default; + } + } + + /// + /// Contains the logic responsible for overriding the address of + /// the authorization endpoint for the providers that require it. + /// + public sealed class OverrideAuthorizationEndpoint : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachChallengeParameters.Descriptor.Order - 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + context.AuthorizationEndpoint = context.Registration.ProviderName switch + { + // Stripe uses a different authorization endpoint for express accounts. + // + // The type of account can be defined globally (via the Stripe options) or + // per authentication demand by adding a specific authentication property. + // If the authentication property is present, the global option is ignored. + // + // For more information, see + // https://stripe.com/docs/connect/oauth-reference?locale=en-us#get-authorize. + Providers.StripeConnect when context.Properties.TryGetValue(".stripe_account_type", out string? type) && + string.Equals(type, "express", StringComparison.OrdinalIgnoreCase) + => new Uri("https://connect.stripe.com/express/oauth/authorize", UriKind.Absolute), + + Providers.StripeConnect when context.Registration.GetStripeConnectOptions() is { AccountType: string type } && + string.Equals(type, "express", StringComparison.OrdinalIgnoreCase) + => new Uri("https://connect.stripe.com/express/oauth/authorize", UriKind.Absolute), + + _ => context.AuthorizationEndpoint + }; + + return default; + } + } + /// /// Contains the logic responsible for overriding response mode for providers that require it. /// diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml index d728868c..9fc27b13 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml @@ -326,6 +326,37 @@ Description="The site specified in userinfo requests (by default, 'stackoverflow')" /> + + + + + + + + + + + + + + +