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:
Ricardo Espinoza 2021-01-10 14:49:47 -05:00 committed by GitHub
parent a7dee0aba7
commit 76cee5577b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 6 deletions

View file

@ -10,7 +10,7 @@ export class GoogleApi implements ICredentialType {
documentationUrl = 'google';
properties = [
{
displayName: 'Email',
displayName: 'Service Account Email',
name: 'email',
type: 'string' as NodePropertyTypes,
default: '',
@ -25,5 +25,25 @@ export class GoogleApi implements ICredentialType {
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-----',
},
{
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.',
},
];
}

View file

@ -103,7 +103,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
const signature = jwt.sign(
{
'iss': credentials.email as string,
'sub': credentials.email as string,
'sub': credentials.delegatedEmail || credentials.email as string,
'scope': scopes.join(' '),
'aud': `https://oauth2.googleapis.com/token`,
'iat': now,

View file

@ -66,6 +66,8 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
} else if (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}`);
@ -107,7 +109,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
const signature = jwt.sign(
{
'iss': credentials.email as string,
'sub': credentials.email as string,
'sub': credentials.delegatedEmail || credentials.email as string,
'scope': scopes.join(' '),
'aud': `https://oauth2.googleapis.com/token`,
'iat': now,

View file

@ -23,10 +23,15 @@ import {
IEmail,
} from './Gmail.node';
import * as moment from 'moment-timezone';
import * as jwt from 'jsonwebtoken';
const mailComposer = require('nodemailer/lib/mail-composer');
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
const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
let options: OptionsWithUri = {
headers: {
'Accept': 'application/json',
@ -46,8 +51,22 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
delete options.body;
}
//@ts-ignore
return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
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
return await this.helpers.requestOAuth2.call(this, 'gmailOAuth2', options);
}
} catch (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) {
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}`);
@ -190,3 +211,50 @@ export function extractEmail(s: string) {
const data = s.split('<')[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);
}

View file

@ -78,12 +78,46 @@ export class Gmail implements INodeType {
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'googleApi',
required: true,
displayOptions: {
show: {
authentication: [
'serviceAccount',
],
},
},
},
{
name: 'gmailOAuth2',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Service Account',
value: 'serviceAccount',
},
{
name: 'OAuth2',
value: 'oAuth2',
},
],
default: 'oAuth2',
},
{
displayName: 'Resource',
name: 'resource',

View file

@ -94,7 +94,7 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa
const signature = jwt.sign(
{
'iss': credentials.email as string,
'sub': credentials.email as string,
'sub': credentials.delegatedEmail || credentials.email as string,
'scope': scopes.join(' '),
'aud': `https://oauth2.googleapis.com/token`,
'iat': now,