From 7660d7e735d248f3e731aca550c2973e85cdfebc Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 24 Jan 2024 09:28:42 +0000 Subject: [PATCH] feat(LinkedIn Node): Add support for Community Management API (#7451) --- ...ommunityManagementOAuth2Api.credentials.ts | 54 +++++++++++++++++++ .../LinkedInOAuth2Api.credentials.ts | 9 +++- .../nodes/LinkedIn/GenericFunctions.ts | 13 ++++- .../nodes/LinkedIn/LinkedIn.node.ts | 54 ++++++++++++++++--- packages/nodes-base/package.json | 1 + 5 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 packages/nodes-base/credentials/LinkedInCommunityManagementOAuth2Api.credentials.ts diff --git a/packages/nodes-base/credentials/LinkedInCommunityManagementOAuth2Api.credentials.ts b/packages/nodes-base/credentials/LinkedInCommunityManagementOAuth2Api.credentials.ts new file mode 100644 index 0000000000000..1c64bcfe1a733 --- /dev/null +++ b/packages/nodes-base/credentials/LinkedInCommunityManagementOAuth2Api.credentials.ts @@ -0,0 +1,54 @@ +import type { ICredentialType, INodeProperties } from 'n8n-workflow'; + +const scopes = ['w_member_social', 'w_organization_social', 'r_basicprofile']; + +export class LinkedInCommunityManagementOAuth2Api implements ICredentialType { + name = 'linkedInCommunityManagementOAuth2Api'; + + extends = ['oAuth2Api']; + + displayName = 'LinkedIn Community Management OAuth2 API'; + + documentationUrl = 'linkedIn'; + + properties: INodeProperties[] = [ + { + displayName: 'Grant Type', + name: 'grantType', + type: 'hidden', + default: 'authorizationCode', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden', + default: 'https://www.linkedin.com/oauth/v2/authorization', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden', + default: 'https://www.linkedin.com/oauth/v2/accessToken', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden', + default: scopes.join(' '), + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden', + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden', + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/credentials/LinkedInOAuth2Api.credentials.ts b/packages/nodes-base/credentials/LinkedInOAuth2Api.credentials.ts index 065115a69822b..6f186fabd82a0 100644 --- a/packages/nodes-base/credentials/LinkedInOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/LinkedInOAuth2Api.credentials.ts @@ -42,7 +42,7 @@ export class LinkedInOAuth2Api implements ICredentialType { name: 'scope', type: 'hidden', default: - '=w_member_social{{$self["organizationSupport"] === true ? ",w_organization_social":",r_liteprofile,r_emailaddress"}}', + '=w_member_social{{$self["organizationSupport"] === true ? ",w_organization_social": $self["legacy"] === true ? ",r_liteprofile,r_emailaddress" : ",profile,email,openid"}}', description: 'Standard scopes for posting on behalf of a user or organization. See this resource .', }, @@ -58,5 +58,12 @@ export class LinkedInOAuth2Api implements ICredentialType { type: 'hidden', default: 'body', }, + { + displayName: 'Legacy', + name: 'legacy', + type: 'boolean', + default: true, + description: 'Whether to use the legacy API', + }, ]; } diff --git a/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts b/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts index 093356a14ba1f..160780d18deb5 100644 --- a/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts +++ b/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts @@ -25,6 +25,14 @@ export async function linkedInApiRequest( binary?: boolean, _headers?: object, ): Promise { + const authenticationMethod = this.getNodeParameter('authentication', 0); + const credentialType = + authenticationMethod === 'standard' + ? 'linkedInOAuth2Api' + : 'linkedInCommunityManagementOAuth2Api'; + + const baseUrl = 'https://api.linkedin.com'; + let options: OptionsWithUrl = { headers: { Accept: 'application/json', @@ -33,9 +41,10 @@ export async function linkedInApiRequest( }, method, body, - url: binary ? endpoint : `https://api.linkedin.com/rest${endpoint}`, + url: binary ? endpoint : `${baseUrl}${endpoint.includes('v2') ? '' : '/rest'}${endpoint}`, json: true, }; + options = Object.assign({}, options, { resolveWithFullResponse: true, }); @@ -51,7 +60,7 @@ export async function linkedInApiRequest( try { return resolveHeaderData( - await this.helpers.requestOAuth2.call(this, 'linkedInOAuth2Api', options, { + await this.helpers.requestOAuth2.call(this, credentialType, options, { tokenType: 'Bearer', }), ); diff --git a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts index 4f95de5c7da71..c341efe2d599b 100644 --- a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts +++ b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts @@ -28,9 +28,39 @@ export class LinkedIn implements INodeType { { name: 'linkedInOAuth2Api', required: true, + displayOptions: { + show: { + authentication: ['standard'], + }, + }, + }, + { + name: 'linkedInCommunityManagementOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: ['communityManagement'], + }, + }, }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Standard', + value: 'standard', + }, + { + name: 'Community Management', + value: 'communityManagement', + }, + ], + default: 'standard', + }, { displayName: 'Resource', name: 'resource', @@ -55,12 +85,24 @@ export class LinkedIn implements INodeType { // Get Person URN which has to be used with other LinkedIn API Requests // https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin async getPersonUrn(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; - const person = await linkedInApiRequest.call(this, 'GET', '/me', {}); - returnData.push({ - name: `${person.localizedFirstName} ${person.localizedLastName}`, - value: person.id, - }); + const authentication = this.getNodeParameter('authentication', 0); + let endpoint = '/me'; + if (authentication === 'standard') { + const { legacy } = await this.getCredentials('linkedInOAuth2Api'); + if (!legacy) { + endpoint = '/v2/userinfo'; + } + } + const person = await linkedInApiRequest.call(this, 'GET', endpoint, {}); + const firstName = person.localizedFirstName ?? person.given_name; + const lastName = person.localizedLastName ?? person.family_name; + const name = `${firstName} ${lastName}`; + const returnData: INodePropertyOptions[] = [ + { + name, + value: person.id ?? person.sub, + }, + ]; return returnData; }, }, diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 63ecf0f7359b2..34732e3cd4b90 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -195,6 +195,7 @@ "dist/credentials/LinearOAuth2Api.credentials.js", "dist/credentials/LineNotifyOAuth2Api.credentials.js", "dist/credentials/LingvaNexApi.credentials.js", + "dist/credentials/LinkedInCommunityManagementOAuth2Api.credentials.js", "dist/credentials/LinkedInOAuth2Api.credentials.js", "dist/credentials/LoneScaleApi.credentials.js", "dist/credentials/Magento2Api.credentials.js",