From 5b038a483196dffd8ae636cf8b72a57d8713bc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Sat, 15 Oct 2022 15:42:31 +0200 Subject: [PATCH] Add PayPal support --- ...tClientWebIntegrationHandlers.Discovery.cs | 50 ++++++++++++++++- .../OpenIddictClientWebIntegrationHandlers.cs | 56 +++++++++++++++++-- ...penIddictClientWebIntegrationProviders.xml | 13 +++++ 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs index e153c927..58b0cac9 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs @@ -20,7 +20,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers */ AmendIssuer.Descriptor, AmendClientAuthenticationMethods.Descriptor, - AmendCodeChallengeMethods.Descriptor); + AmendCodeChallengeMethods.Descriptor, + AmendEndpoints.Descriptor); /// /// Contains the logic responsible for amending the issuer for the providers that require it. @@ -142,5 +143,52 @@ public static partial class OpenIddictClientWebIntegrationHandlers return default; } } + + /// + /// Contains the logic responsible for amending the endpoint URIs for the providers that require it. + /// + public class AmendEndpoints : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationResponseContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + // While PayPal supports OpenID Connect discovery, the configuration document returned + // by the sandbox environment always contains the production endpoints, which would + // prevent the OpenIddict integration from working properly when using the sandbox mode. + // To work around that, the endpoints are manually overriden when this environment is used. + if (context.Registration.ProviderName is Providers.PayPal) + { + var options = context.Registration.GetPayPalOptions(); + if (options.Environment is PayPal.Environments.Sandbox) + { + context.Configuration.AuthorizationEndpoint = + new Uri("https://www.sandbox.paypal.com/signin/authorize", UriKind.Absolute); + context.Configuration.JwksUri = + new Uri("https://api-m.sandbox.paypal.com/v1/oauth2/certs", UriKind.Absolute); + context.Configuration.TokenEndpoint = + new Uri("https://api-m.sandbox.paypal.com/v1/oauth2/token", UriKind.Absolute); + context.Configuration.UserinfoEndpoint = + new Uri("https://api-m.sandbox.paypal.com/v1/oauth2/token/userinfo", UriKind.Absolute); + } + } + + return default; + } + } } } diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index 57216ce3..48acb1b9 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -22,6 +22,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers HandleNonStandardFrontchannelErrorResponse.Descriptor, AttachNonStandardClientAssertionTokenClaims.Descriptor, AttachTokenRequestNonStandardClientCredentials.Descriptor, + OverrideValidatedBackchannelTokens.Descriptor, AttachAdditionalUserinfoRequestParameters.Descriptor, /* @@ -177,6 +178,47 @@ public static partial class OpenIddictClientWebIntegrationHandlers } } + /// + /// Contains the logic responsible for overriding the set + /// of required tokens for the providers that require it. + /// + public class OverrideValidatedBackchannelTokens : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(EvaluateValidatedBackchannelTokens.Descriptor.Order + 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + (context.ExtractBackchannelIdentityToken, + context.RequireBackchannelIdentityToken, + context.ValidateBackchannelIdentityToken) = context.Registration.ProviderName switch + { + // While PayPal claims the OpenID Connect flavor of the code flow is supported, + // their implementation doesn't return an id_token from the token endpoint. + Providers.PayPal => (false, false, false), + + _ => (context.ExtractBackchannelIdentityToken, + context.RequireBackchannelIdentityToken, + context.ValidateBackchannelIdentityToken) + }; + + return default; + } + } + /// /// Contains the logic responsible for attaching additional parameters /// to the userinfo request for the providers that require it. @@ -204,16 +246,18 @@ public static partial class OpenIddictClientWebIntegrationHandlers Debug.Assert(context.UserinfoRequest is not null, SR.GetResourceString(SR.ID4008)); + // By default, LinkedIn returns all the basic fields except the profile image. + // To retrieve the profile image, a projection parameter must be sent with + // all the parameters that should be returned from the userinfo endpoint. if (context.Registration.ProviderName is Providers.LinkedIn) { var options = context.Registration.GetLinkedInOptions(); - // By default, LinkedIn returns all the basic fields except the profile image. - // To retrieve the profile image, a projection parameter must be sent with - // all the parameters that should be returned from the userinfo endpoint. context.UserinfoRequest["projection"] = string.Concat("(", string.Join(",", options.Fields), ")"); } + // StackOverflow requires sending an application key and a site parameter + // containing the name of the site from which the user profile is retrieved. else if (context.Registration.ProviderName is Providers.StackExchange) { var options = context.Registration.GetStackExchangeOptions(); @@ -222,13 +266,13 @@ public static partial class OpenIddictClientWebIntegrationHandlers context.UserinfoRequest["site"] = options.Site; } + // Twitter limits the number of fields returned by the userinfo endpoint + // but allows returning additional information using special parameters that + // determine what fields will be returned as part of the userinfo response. else if (context.Registration.ProviderName is Providers.Twitter) { var options = context.Registration.GetTwitterOptions(); - // Twitter limits the number of fields returned by the userinfo endpoint - // but allows returning additional information using special parameters that - // determine what fields will be returned as part of the userinfo response. context.UserinfoRequest["expansions"] = string.Join(",", options.Expansions); context.UserinfoRequest["tweet.fields"] = string.Join(",", options.TweetFields); context.UserinfoRequest["user.fields"] = string.Join(",", options.UserFields); diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml index d537f0d3..2364ff9d 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml @@ -76,6 +76,19 @@ Description="The tenant used to identify the Azure AD instance (by default, the common tenant is used)" /> + + + + + + +