Browse Source

Add Yandex and VK ID to the list of supported providers

pull/2249/head
Timoxa 1 year ago
committed by GitHub
parent
commit
03bd575678
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 39
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Revocation.cs
  3. 10
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
  4. 100
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  5. 54
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml

3
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1707,6 +1707,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0459" xml:space="preserve">
<value>A token must be specified when using revocation.</value>
</data>
<data name="ID0467" xml:space="preserve">
<value>The VK ID integration requires sending the device identifier to the token and revocation endpoints. For that, attach a ".device_id" authentication property containing the device identifier returned by the authorization endpoint.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

39
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Revocation.cs

@ -21,6 +21,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/*
* Revocation request preparation:
*/
MapNonStandardRequestParameters.Descriptor,
OverrideHttpMethod.Descriptor,
AttachBearerAccessToken.Descriptor,
@ -30,6 +31,43 @@ public static partial class OpenIddictClientWebIntegrationHandlers
NormalizeContentType.Descriptor
]);
/// <summary>
/// Contains the logic responsible for mapping non-standard request parameters
/// to their standard equivalent for the providers that require it.
/// </summary>
public sealed class MapNonStandardRequestParameters : IOpenIddictClientHandler<PrepareRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<PrepareRevocationRequestContext>()
.UseSingletonHandler<MapNonStandardRequestParameters>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(PrepareRevocationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Weibo, VK ID and Yandex don't support the standard "token" parameter and
// require using the non-standard "access_token" parameter instead.
if (context.Registration.ProviderType is ProviderTypes.Weibo or ProviderTypes.VkId or ProviderTypes.Yandex)
{
context.Request.AccessToken = context.Token;
context.Request.Token = null;
context.Request.TokenTypeHint = null;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for overriding the HTTP method for the providers that require it.
/// </summary>
@ -61,7 +99,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers
request.Method = context.Registration.ProviderType switch
{
ProviderTypes.Zendesk => HttpMethod.Delete,
_ => request.Method

10
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs

@ -236,6 +236,12 @@ public static partial class OpenIddictClientWebIntegrationHandlers
context.Request["f"] = "json";
}
// VK ID requires attaching the "client_id" parameter to userinfo requests.
else if (context.Registration.ProviderType is ProviderTypes.VkId)
{
context.Request.ClientId = context.Registration.ClientId;
}
return default;
}
}
@ -406,8 +412,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
ProviderTypes.ExactOnline => new(context.Response["d"]?["results"]?[0]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334("d/results/0"))),
// Fitbit, Todoist and Zendesk return a nested "user" object.
ProviderTypes.Fitbit or ProviderTypes.Todoist or ProviderTypes.Zendesk
// These providers return a nested "user" object.
ProviderTypes.Fitbit or ProviderTypes.Todoist or ProviderTypes.VkId or ProviderTypes.Zendesk
=> new(context.Response["user"]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334("user"))),

100
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

@ -391,6 +391,25 @@ public static partial class OpenIddictClientWebIntegrationHandlers
}
}
// VK ID uses a non-standard "device_id" parameter in authorization responses.
else if (context.Registration.ProviderType is ProviderTypes.VkId)
{
var identifier = (string?) context.Request["device_id"];
if (string.IsNullOrEmpty(identifier))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2029("device_id"),
uri: SR.FormatID8000(SR.ID2029));
return default;
}
// Store the device identifier as an authentication property
// so it can be resolved later to make refresh token requests.
context.Properties[VkId.Properties.DeviceId] = identifier;
}
// Zoho returns the region of the authenticated user as a non-standard "location" parameter
// that must be used to compute the address of the token and userinfo endpoints.
else if (context.Registration.ProviderType is ProviderTypes.Zoho)
@ -407,7 +426,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
}
// Ensure the specified location corresponds to well-known region.
if (location.ToUpperInvariant() is not ( "AU" or "CA" or "EU" or "IN" or "JP" or "SA" or "US"))
if (location.ToUpperInvariant() is not ("AU" or "CA" or "EU" or "IN" or "JP" or "SA" or "US"))
{
context.Reject(
error: Errors.InvalidRequest,
@ -640,6 +659,23 @@ public static partial class OpenIddictClientWebIntegrationHandlers
context.TokenRequest.UserCode = code;
}
// VK ID requires attaching a non-standard "device_id" parameter to all token requests.
//
// This parameter is either resolved from the authorization response (for the authorization
// code or hybrid grants) or manually provided by the application for other grant types.
else if (context.Registration.ProviderType is ProviderTypes.VkId)
{
context.TokenRequest["device_id"] = context.GrantType switch
{
GrantTypes.AuthorizationCode or GrantTypes.Implicit => context.Request["device_id"],
_ when context.Properties.TryGetValue(VkId.Properties.DeviceId, out string? identifier) &&
!string.IsNullOrEmpty(identifier) => identifier,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0467))
};
}
return default;
}
}
@ -1363,6 +1399,9 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Shopify returns the email address as a custom "associated_user/email" node in token responses:
ProviderTypes.Shopify => (string?) context.TokenResponse?["associated_user"]?["email"],
// Yandex returns the email address as a custom "default_email" node:
ProviderTypes.Yandex => (string?) context.UserInfoResponse?["default_email"],
_ => context.MergedPrincipal.GetClaim(ClaimTypes.Email)
});
@ -1375,8 +1414,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
ProviderTypes.Trakt or ProviderTypes.WordPress
=> (string?) context.UserInfoResponse?["username"],
// Basecamp and Harvest don't return a username so one is created using the "first_name" and "last_name" nodes:
ProviderTypes.Basecamp or ProviderTypes.Harvest
// These providers don't return a username so one is created using the "first_name" and "last_name" nodes:
ProviderTypes.Basecamp or ProviderTypes.Harvest or ProviderTypes.VkId
when context.UserInfoResponse?.HasParameter("first_name") is true &&
context.UserInfoResponse?.HasParameter("last_name") is true
=> $"{(string?) context.UserInfoResponse?["first_name"]} {(string?) context.UserInfoResponse?["last_name"]}",
@ -1423,7 +1462,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
=> $"{(string?) context.UserInfoResponse?["firstName"]} {(string?) context.UserInfoResponse?["lastName"]}",
// These providers return the username as a custom "display_name" node:
ProviderTypes.Spotify or ProviderTypes.StackExchange or ProviderTypes.Zoom
ProviderTypes.Spotify or ProviderTypes.StackExchange or
ProviderTypes.Yandex or ProviderTypes.Zoom
=> (string?) context.UserInfoResponse?["display_name"],
// Strava returns the username as a custom "athlete/username" node in token responses:
@ -1451,7 +1491,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
{
// These providers return the user identifier as a custom "user_id" node:
ProviderTypes.Amazon or ProviderTypes.HubSpot or
ProviderTypes.StackExchange or ProviderTypes.Typeform
ProviderTypes.StackExchange or ProviderTypes.Typeform or
ProviderTypes.VkId
=> (string?) context.UserInfoResponse?["user_id"],
// ArcGIS and Trakt don't return a user identifier and require using the username as the identifier:
@ -1462,16 +1503,16 @@ public static partial class OpenIddictClientWebIntegrationHandlers
ProviderTypes.Atlassian => (string?) context.UserInfoResponse?["account_id"],
// These providers return the user identifier as a custom "id" node:
ProviderTypes.Airtable or ProviderTypes.Basecamp or ProviderTypes.Box or
ProviderTypes.Dailymotion or ProviderTypes.Deezer or ProviderTypes.Discord or
ProviderTypes.Disqus or ProviderTypes.Facebook or ProviderTypes.GitCode or
ProviderTypes.Gitee or ProviderTypes.GitHub or ProviderTypes.Harvest or
ProviderTypes.Kook or ProviderTypes.Kroger or ProviderTypes.Lichess or
ProviderTypes.Mastodon or ProviderTypes.Meetup or ProviderTypes.Nextcloud or
ProviderTypes.Patreon or ProviderTypes.Pipedrive or ProviderTypes.Reddit or
ProviderTypes.Smartsheet or ProviderTypes.Spotify or ProviderTypes.SubscribeStar or
ProviderTypes.Todoist or ProviderTypes.Twitter or ProviderTypes.Weibo or
ProviderTypes.Zoom
ProviderTypes.Airtable or ProviderTypes.Basecamp or ProviderTypes.Box or
ProviderTypes.Dailymotion or ProviderTypes.Deezer or ProviderTypes.Discord or
ProviderTypes.Disqus or ProviderTypes.Facebook or ProviderTypes.Gitee or
ProviderTypes.GitHub or ProviderTypes.Harvest or ProviderTypes.Kook or
ProviderTypes.Kroger or ProviderTypes.Lichess or ProviderTypes.Mastodon or
ProviderTypes.Meetup or ProviderTypes.Nextcloud or ProviderTypes.Patreon or
ProviderTypes.Pipedrive or ProviderTypes.Reddit or ProviderTypes.Smartsheet or
ProviderTypes.Spotify or ProviderTypes.SubscribeStar or ProviderTypes.Todoist or
ProviderTypes.Twitter or ProviderTypes.Weibo or ProviderTypes.Yandex or
ProviderTypes.Zoom
=> (string?) context.UserInfoResponse?["id"],
// Bitbucket returns the user identifier as a custom "uuid" node:
@ -1920,6 +1961,27 @@ public static partial class OpenIddictClientWebIntegrationHandlers
context.Request["language"] = settings.Language;
}
// Yandex allows sending optional "device_id" and "device_name" parameters.
else if (context.Registration.ProviderType is ProviderTypes.Yandex)
{
var settings = context.Registration.GetYandexSettings();
if (!context.Properties.TryGetValue(Yandex.Properties.DeviceId, out string? identifier) ||
string.IsNullOrEmpty(identifier))
{
identifier = settings.DeviceId;
}
if (!context.Properties.TryGetValue(Yandex.Properties.DeviceName, out string? name) ||
string.IsNullOrEmpty(name))
{
name = settings.DeviceName;
}
context.Request["device_id"] = identifier;
context.Request["device_name"] = name;
}
// By default, Zoho doesn't return a refresh token but
// allows sending an "access_type" parameter to retrieve one.
else if (context.Registration.ProviderType is ProviderTypes.Zoho)
@ -2058,14 +2120,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers
context.RevocationRequest.ClientAssertionType = null;
}
// Weibo implements a non-standard client authentication method for its endpoints that
// requires sending the token as "access_token" instead of the standard "token" parameter.
else if (context.Registration.ProviderType is ProviderTypes.Weibo)
{
context.RevocationRequest.AccessToken = context.RevocationRequest.Token;
context.RevocationRequest.Token = null;
}
return default;
}
}

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

@ -2065,6 +2065,31 @@
</Environment>
</Provider>
<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
██ ███ █ █▀▄████▄ ▄██ ▄▄▀██
███ █ ██ ▄▀██████ ███ ██ ██
███▄▀▄██ ██ ████▀ ▀██ ▀▀ ██
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-->
<Provider Name="VkId" Id="9bf89c19-401b-4076-893e-a4136e719432"
Documentation="https://id.vk.com/about/business/go/docs/en/vkid/latest/oauth-vk">
<Environment Issuer="https://id.vk.com/">
<Configuration AuthorizationEndpoint="https://id.vk.com/authorize"
RevocationEndpoint="https://id.vk.com/oauth2/revoke"
TokenEndpoint="https://id.vk.com/oauth2/auth"
UserInfoEndpoint="https://id.vk.com/oauth2/user_info">
<CodeChallengeMethod Value="S256" />
<GrantType Value="authorization_code" />
<GrantType Value="refresh_token" />
</Configuration>
</Environment>
<Property Name="DeviceId" DictionaryKey=".device_id" />
</Provider>
<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
██ ███ ██ ▄▄▄██ ▄▄▀██ ▄▄▄█▄▀█▀▄██
@ -2193,6 +2218,35 @@
<Provider Name="Yahoo" Id="874d78ec-3d79-4492-ab79-76a7dd7fa0b5"
Documentation="https://developer.yahoo.com/oauth2/guide/openid_connect/">
<Environment Issuer="https://api.login.yahoo.com/" />
</Provider>
<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
██ ███ █ ▄▄▀██ ▀██ ██ ▄▄▀██ ▄▄▄██▄▀█▀▄██
██▄▀▀▀▄█ ▀▀ ██ █ █ ██ ██ ██ ▄▄▄████ ████
████ ███ ██ ██ ██▄ ██ ▀▀ ██ ▀▀▀██▀▄█▄▀██
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-->
<Provider Name="Yandex" Id="313298d4-d210-4541-a348-96ced013dab1" Documentation="https://yandex.ru/dev/id/doc/en/">
<Environment Issuer="https://oauth.yandex.ru/">
<Configuration AuthorizationEndpoint="https://oauth.yandex.ru/authorize"
RevocationEndpoint="https://oauth.yandex.ru/revoke_token"
TokenEndpoint="https://oauth.yandex.ru/token"
UserInfoEndpoint="https://login.yandex.ru/info">
<GrantType Value="authorization_code" />
<GrantType Value="refresh_token" />
</Configuration>
</Environment>
<Property Name="DeviceId" DictionaryKey=".device_id" />
<Property Name="DeviceName" DictionaryKey=".device_name" />
<Setting PropertyName="DeviceId" ParameterName="identifier" Type="String" Required="false"
Description="Gets or sets the optional device identifier that will be attached to authorization requests" />
<Setting PropertyName="DeviceName" ParameterName="name" Type="String" Required="false"
Description="Gets or sets the optional device name that will be attached to authorization requests" />
</Provider>
<!--

Loading…
Cancel
Save