mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
⚡ Extend googleApi credentials to support user impersonification (#1304)
* ⚡ Extend googleApi credentials to support user impersonification * ⚡ Add service account authentication to Gmail * ⚡ Minor improvements Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
@@ -10,7 +10,7 @@ export class GoogleApi implements ICredentialType {
|
|||||||
documentationUrl = 'google';
|
documentationUrl = 'google';
|
||||||
properties = [
|
properties = [
|
||||||
{
|
{
|
||||||
displayName: 'Email',
|
displayName: 'Service Account Email',
|
||||||
name: 'email',
|
name: 'email',
|
||||||
type: 'string' as NodePropertyTypes,
|
type: 'string' as NodePropertyTypes,
|
||||||
default: '',
|
default: '',
|
||||||
@@ -25,5 +25,25 @@ export class GoogleApi implements ICredentialType {
|
|||||||
default: '',
|
default: '',
|
||||||
description: 'Use the multiline editor. Make sure there are exactly 3 lines.<br />-----BEGIN PRIVATE KEY-----<br />KEY IN A SINGLE LINE<br />-----END PRIVATE KEY-----',
|
description: 'Use the multiline editor. Make sure there are exactly 3 lines.<br />-----BEGIN PRIVATE KEY-----<br />KEY IN A SINGLE LINE<br />-----END PRIVATE KEY-----',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: ' Impersonate a User',
|
||||||
|
name: 'inpersonate',
|
||||||
|
type: 'boolean' as NodePropertyTypes,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'delegatedEmail',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
inpersonate: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The email address of the user for which the application is requesting delegated access.',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
|
|||||||
const signature = jwt.sign(
|
const signature = jwt.sign(
|
||||||
{
|
{
|
||||||
'iss': credentials.email as string,
|
'iss': credentials.email as string,
|
||||||
'sub': credentials.email as string,
|
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||||
'scope': scopes.join(' '),
|
'scope': scopes.join(' '),
|
||||||
'aud': `https://oauth2.googleapis.com/token`,
|
'aud': `https://oauth2.googleapis.com/token`,
|
||||||
'iat': now,
|
'iat': now,
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||||||
|
|
||||||
} else if (error.response.body.error.message) {
|
} else if (error.response.body.error.message) {
|
||||||
errorMessages = error.response.body.error.message;
|
errorMessages = error.response.body.error.message;
|
||||||
|
} else if (error.response.body.error_description) {
|
||||||
|
errorMessages = error.response.body.error_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Google Drive error response [${error.statusCode}]: ${errorMessages}`);
|
throw new Error(`Google Drive error response [${error.statusCode}]: ${errorMessages}`);
|
||||||
@@ -107,7 +109,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
|
|||||||
const signature = jwt.sign(
|
const signature = jwt.sign(
|
||||||
{
|
{
|
||||||
'iss': credentials.email as string,
|
'iss': credentials.email as string,
|
||||||
'sub': credentials.email as string,
|
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||||
'scope': scopes.join(' '),
|
'scope': scopes.join(' '),
|
||||||
'aud': `https://oauth2.googleapis.com/token`,
|
'aud': `https://oauth2.googleapis.com/token`,
|
||||||
'iat': now,
|
'iat': now,
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ import {
|
|||||||
IEmail,
|
IEmail,
|
||||||
} from './Gmail.node';
|
} from './Gmail.node';
|
||||||
|
|
||||||
|
import * as moment from 'moment-timezone';
|
||||||
|
|
||||||
|
import * as jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
const mailComposer = require('nodemailer/lib/mail-composer');
|
const mailComposer = require('nodemailer/lib/mail-composer');
|
||||||
|
|
||||||
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string,
|
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string,
|
||||||
endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
|
||||||
let options: OptionsWithUri = {
|
let options: OptionsWithUri = {
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
@@ -46,8 +51,22 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||||||
delete options.body;
|
delete options.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authenticationMethod === 'serviceAccount') {
|
||||||
|
const credentials = this.getCredentials('googleApi');
|
||||||
|
|
||||||
|
if (credentials === undefined) {
|
||||||
|
throw new Error('No credentials got returned!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { access_token } = await getAccessToken.call(this, credentials as IDataObject);
|
||||||
|
|
||||||
|
options.headers!.Authorization = `Bearer ${access_token}`;
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.request(options);
|
||||||
|
} else {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
|
return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response && error.response.body && error.response.body.error) {
|
if (error.response && error.response.body && error.response.body.error) {
|
||||||
@@ -64,6 +83,8 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||||||
|
|
||||||
} else if (error.response.body.error.message) {
|
} else if (error.response.body.error.message) {
|
||||||
errorMessages = error.response.body.error.message;
|
errorMessages = error.response.body.error.message;
|
||||||
|
} else if (error.response.body.error_description) {
|
||||||
|
errorMessages = error.response.body.error_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Gmail error response [${error.statusCode}]: ${errorMessages}`);
|
throw new Error(`Gmail error response [${error.statusCode}]: ${errorMessages}`);
|
||||||
@@ -190,3 +211,50 @@ export function extractEmail(s: string) {
|
|||||||
const data = s.split('<')[1];
|
const data = s.split('<')[1];
|
||||||
return data.substring(0, data.length - 1);
|
return data.substring(0, data.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise<IDataObject> {
|
||||||
|
//https://developers.google.com/identity/protocols/oauth2/service-account#httprest
|
||||||
|
|
||||||
|
const scopes = [
|
||||||
|
'https://www.googleapis.com/auth/books',
|
||||||
|
];
|
||||||
|
|
||||||
|
const now = moment().unix();
|
||||||
|
|
||||||
|
const signature = jwt.sign(
|
||||||
|
{
|
||||||
|
'iss': credentials.email as string,
|
||||||
|
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||||
|
'scope': scopes.join(' '),
|
||||||
|
'aud': `https://oauth2.googleapis.com/token`,
|
||||||
|
'iat': now,
|
||||||
|
'exp': now + 3600,
|
||||||
|
},
|
||||||
|
credentials.privateKey as string,
|
||||||
|
{
|
||||||
|
algorithm: 'RS256',
|
||||||
|
header: {
|
||||||
|
'kid': credentials.privateKey as string,
|
||||||
|
'typ': 'JWT',
|
||||||
|
'alg': 'RS256',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
form: {
|
||||||
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
|
assertion: signature,
|
||||||
|
},
|
||||||
|
uri: 'https://oauth2.googleapis.com/token',
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
return this.helpers.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,12 +78,46 @@ export class Gmail implements INodeType {
|
|||||||
inputs: ['main'],
|
inputs: ['main'],
|
||||||
outputs: ['main'],
|
outputs: ['main'],
|
||||||
credentials: [
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'googleApi',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
'serviceAccount',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'gmailOAuth2',
|
name: 'gmailOAuth2',
|
||||||
required: true,
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
'oAuth2',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
properties: [
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Service Account',
|
||||||
|
value: 'serviceAccount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OAuth2',
|
||||||
|
value: 'oAuth2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'oAuth2',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Resource',
|
displayName: 'Resource',
|
||||||
name: 'resource',
|
name: 'resource',
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
|
|||||||
const signature = jwt.sign(
|
const signature = jwt.sign(
|
||||||
{
|
{
|
||||||
'iss': credentials.email as string,
|
'iss': credentials.email as string,
|
||||||
'sub': credentials.email as string,
|
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||||
'scope': scopes.join(' '),
|
'scope': scopes.join(' '),
|
||||||
'aud': `https://oauth2.googleapis.com/token`,
|
'aud': `https://oauth2.googleapis.com/token`,
|
||||||
'iat': now,
|
'iat': now,
|
||||||
|
|||||||
Reference in New Issue
Block a user