Browse Source

Add Stripe to the list of supported providers

pull/1686/head
Kévin Chalet 3 years ago
parent
commit
4afc6a8b57
  1. 137
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  2. 31
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
  3. 10
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  4. 8
      src/OpenIddict.Client/OpenIddictClientHandlers.Authentication.cs
  5. 13
      src/OpenIddict.Client/OpenIddictClientHandlers.Session.cs

137
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
}
}
/// <summary>
/// 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.
/// </summary>
public sealed class PopulateUserinfoTokenPrincipalFromTokenResponse : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireTokenRequest>()
.UseSingletonHandler<PopulateUserinfoTokenPrincipalFromTokenResponse>()
.SetOrder(ValidateUserinfoToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
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;
}
}
/// <summary>
/// Contains the logic responsible for overriding the address of
/// the authorization endpoint for the providers that require it.
/// </summary>
public sealed class OverrideAuthorizationEndpoint : IOpenIddictClientHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.UseSingletonHandler<OverrideAuthorizationEndpoint>()
.SetOrder(AttachChallengeParameters.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
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;
}
}
/// <summary>
/// Contains the logic responsible for overriding response mode for providers that require it.
/// </summary>

31
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml

@ -326,6 +326,37 @@
Description="The site specified in userinfo requests (by default, 'stackoverflow')" />
</Provider>
<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
██ ▄▄▄ █▄▄ ▄▄██ ▄▄▀█▄ ▄██ ▄▄ ██ ▄▄▄████ ▄▄▀██ ▄▄▄ ██ ▀██ ██ ▀██ ██ ▄▄▄██ ▄▄▀█▄▄ ▄▄██
██▄▄▄▀▀███ ████ ▀▀▄██ ███ ▀▀ ██ ▄▄▄████ █████ ███ ██ █ █ ██ █ █ ██ ▄▄▄██ ██████ ████
██ ▀▀▀ ███ ████ ██ █▀ ▀██ █████ ▀▀▀████ ▀▀▄██ ▀▀▀ ██ ██▄ ██ ██▄ ██ ▀▀▀██ ▀▀▄███ ████
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-->
<Provider Name="StripeConnect" DisplayName="Stripe Connect" Documentation="https://stripe.com/docs/connect/oauth-reference">
<Environment Issuer="https://connect.stripe.com/">
<!--
Note: Stripe doesn't provide a userinfo endpoint and returns
the user information via custom token response parameters.
-->
<Configuration AuthorizationEndpoint="https://connect.stripe.com/oauth/authorize"
TokenEndpoint="https://connect.stripe.com/oauth/token" />
<!--
Note: while Stripe supports both "read_write" and "read_only" as valid scopes
(and automatically defaults to "read_only" when no scope is explicitly set),
it seems that new applications are only allowed to use "read_write". As such,
"read_write" is automatically added if no scope is explicitly configured.
-->
<Scope Name="read_write" Default="true" Required="false" />
</Environment>
<Setting PropertyName="AccountType" ParameterName="type" Type="String" Required="true" DefaultValue="standard"
Description="The type of the Stripe account (by default, 'standard', but can also be set to 'express')" />
</Provider>
<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█▄▄ ▄▄██ ▄▄▀█ ▄▄▀██ █▀▄█▄▄ ▄▄██

10
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -823,6 +823,11 @@ public static partial class OpenIddictClientEvents
/// </summary>
public string? ProviderName { get; set; }
/// <summary>
/// Gets or sets the URI of the authorization endpoint, if applicable.
/// </summary>
public Uri? AuthorizationEndpoint { get; set; }
/// <summary>
/// Gets the additional parameters returned to the caller.
/// </summary>
@ -987,6 +992,11 @@ public static partial class OpenIddictClientEvents
/// </summary>
public string? ProviderName { get; set; }
/// <summary>
/// Gets or sets the URI of the end session endpoint, if applicable.
/// </summary>
public Uri? EndSessionEndpoint { get; set; }
/// <summary>
/// Gets or sets the client identifier that will be used for the sign-out demand.
/// </summary>

8
src/OpenIddict.Client/OpenIddictClientHandlers.Authentication.cs

@ -119,6 +119,8 @@ public static partial class OpenIddictClientHandlers
var notification = new ApplyAuthorizationRequestContext(context.Transaction)
{
// Note: the endpoint URI is automatically set by a specialized handler if it's not set here.
AuthorizationEndpoint = context.AuthorizationEndpoint?.AbsoluteUri!,
Nonce = context.Nonce,
RedirectUri = context.RedirectUri
};
@ -170,6 +172,12 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context));
}
// Don't overwrite the endpoint URI if it was already set.
if (!string.IsNullOrEmpty(context.AuthorizationEndpoint))
{
return default;
}
// Ensure the authorization endpoint is present and is a valid absolute URI.
if (context.Configuration.AuthorizationEndpoint is not { IsAbsoluteUri: true } ||
!context.Configuration.AuthorizationEndpoint.IsWellFormedOriginalString())

13
src/OpenIddict.Client/OpenIddictClientHandlers.Session.cs

@ -110,7 +110,12 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context));
}
var notification = new ApplyLogoutRequestContext(context.Transaction);
var notification = new ApplyLogoutRequestContext(context.Transaction)
{
// Note: the endpoint URI is automatically set by a specialized handler if it's not set here.
EndSessionEndpoint = context.EndSessionEndpoint?.AbsoluteUri!,
};
await _dispatcher.DispatchAsync(notification);
if (notification.IsRequestHandled)
@ -158,6 +163,12 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context));
}
// Don't overwrite the endpoint URI if it was already set.
if (!string.IsNullOrEmpty(context.EndSessionEndpoint))
{
return default;
}
// Ensure the end session endpoint is present and is a valid absolute URI.
if (context.Configuration.EndSessionEndpoint is not { IsAbsoluteUri: true } ||
!context.Configuration.EndSessionEndpoint.IsWellFormedOriginalString())

Loading…
Cancel
Save