From e17ff83993b2f1b6b72082ae42e9aa3464a304bf Mon Sep 17 00:00:00 2001 From: ace-n Date: Thu, 9 May 2019 11:33:00 -0700 Subject: [PATCH 1/2] Update storage trigger samples --- functions/composer-storage-trigger/index.js | 145 +++++++++--------- .../test/index.test.js | 5 +- 2 files changed, 77 insertions(+), 73 deletions(-) diff --git a/functions/composer-storage-trigger/index.js b/functions/composer-storage-trigger/index.js index 84482406f6..59d1ddadcd 100644 --- a/functions/composer-storage-trigger/index.js +++ b/functions/composer-storage-trigger/index.js @@ -31,7 +31,7 @@ const FormData = require('form-data'); * @param {!Object} event The Cloud Functions event. * @returns {Promise} */ -exports.triggerDag = function triggerDag(event) { +exports.triggerDag = async event => { // Fill in your Composer environment information here. // The project that holds your function @@ -50,7 +50,9 @@ exports.triggerDag = function triggerDag(event) { const BODY = {conf: JSON.stringify(event.data)}; // Make the request - return authorizeIap(CLIENT_ID, PROJECT_ID, USER_AGENT).then(iap => { + try { + const iap = await authorizeIap(CLIENT_ID, PROJECT_ID, USER_AGENT); + return makeIapPostRequest( WEBSERVER_URL, BODY, @@ -58,7 +60,10 @@ exports.triggerDag = function triggerDag(event) { USER_AGENT, iap.jwt ); - }); + } catch (err) { + console.log('baz', err); + return Promise.reject(err); + } }; /** @@ -66,7 +71,7 @@ exports.triggerDag = function triggerDag(event) { * @param {string} projectId The id for the project containing the Cloud Function. * @param {string} userAgent The user agent string which will be provided with the webserver request. */ -function authorizeIap(clientId, projectId, userAgent) { +const authorizeIap = async (clientId, projectId, userAgent) => { const SERVICE_ACCOUNT = `${projectId}@appspot.gserviceaccount.com`; const JWT_HEADER = Buffer.from( JSON.stringify({alg: 'RS256', typ: 'JWT'}) @@ -76,70 +81,68 @@ function authorizeIap(clientId, projectId, userAgent) { let jwtClaimset = ''; // Obtain an Oauth2 access token for the appspot service account - return fetch( + const res = await fetch( `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/${SERVICE_ACCOUNT}/token`, { headers: {'User-Agent': userAgent, 'Metadata-Flavor': 'Google'}, } - ) - .then(res => res.json()) - .then(tokenResponse => { - if (tokenResponse.error) { - return Promise.reject(tokenResponse.error); - } - const accessToken = tokenResponse.access_token; - const iat = Math.floor(new Date().getTime() / 1000); - const claims = { - iss: SERVICE_ACCOUNT, - aud: 'https://www.googleapis.com/oauth2/v4/token', - iat: iat, - exp: iat + 60, - target_audience: clientId, - }; - jwtClaimset = Buffer.from(JSON.stringify(claims)).toString('base64'); - const toSign = [JWT_HEADER, jwtClaimset].join('.'); - - return fetch( - `https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${SERVICE_ACCOUNT}:signBlob`, - { - method: 'POST', - body: JSON.stringify({ - bytesToSign: Buffer.from(toSign).toString('base64'), - }), - headers: { - 'User-Agent': userAgent, - Authorization: `Bearer ${accessToken}`, - }, - } - ); - }) - .then(res => res.json()) - .then(body => { - if (body.error) { - return Promise.reject(body.error); - } - // Request service account signature on header and claimset - const jwtSignature = body.signature; - jwt = [JWT_HEADER, jwtClaimset, jwtSignature].join('.'); - const form = new FormData(); - form.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'); - form.append('assertion', jwt); - return fetch('https://www.googleapis.com/oauth2/v4/token', { - method: 'POST', - body: form, - }); - }) - .then(res => res.json()) - .then(body => { - if (body.error) { - return Promise.reject(body.error); - } - return { - jwt: jwt, - idToken: body.id_token, - }; - }); -} + ); + const tokenResponse = res.json(); + if (tokenResponse.error) { + return Promise.reject(tokenResponse.error); + } + + const accessToken = tokenResponse.access_token; + const iat = Math.floor(new Date().getTime() / 1000); + const claims = { + iss: SERVICE_ACCOUNT, + aud: 'https://www.googleapis.com/oauth2/v4/token', + iat: iat, + exp: iat + 60, + target_audience: clientId, + }; + jwtClaimset = Buffer.from(JSON.stringify(claims)).toString('base64'); + const toSign = [JWT_HEADER, jwtClaimset].join('.'); + + const blob = await fetch( + `https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${SERVICE_ACCOUNT}:signBlob`, + { + method: 'POST', + body: JSON.stringify({ + bytesToSign: Buffer.from(toSign).toString('base64'), + }), + headers: { + 'User-Agent': userAgent, + Authorization: `Bearer ${accessToken}`, + }, + } + ); + const blobJson = blob.json(); + if (blobJson.error) { + return Promise.reject(blobJson.error); + } + + // Request service account signature on header and claimset + const jwtSignature = blobJson.signature; + jwt = [JWT_HEADER, jwtClaimset, jwtSignature].join('.'); + const form = new FormData(); + form.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'); + form.append('assertion', jwt); + + const token = await fetch('https://www.googleapis.com/oauth2/v4/token', { + method: 'POST', + body: form, + }); + const tokenJson = token.json(); + if (tokenJson.error) { + return Promise.reject(tokenJson.error); + } + + return { + jwt: jwt, + idToken: tokenJson.id_token, + }; +}; /** * @param {string} url The url that the post request targets. @@ -147,18 +150,18 @@ function authorizeIap(clientId, projectId, userAgent) { * @param {string} idToken Bearer token used to authorize the iap request. * @param {string} userAgent The user agent to identify the requester. */ -function makeIapPostRequest(url, body, idToken, userAgent) { - return fetch(url, { +const makeIapPostRequest = async (url, body, idToken, userAgent) => { + const res = await fetch(url, { method: 'POST', headers: { 'User-Agent': userAgent, Authorization: `Bearer ${idToken}`, }, body: JSON.stringify(body), - }).then(res => { - if (!res.ok) { - return res.text().then(body => Promise.reject(body)); - } }); -} + + if (!res.ok) { + return res.text().then(body => Promise.reject(body)); + } +}; // [END composer_trigger] diff --git a/functions/composer-storage-trigger/test/index.test.js b/functions/composer-storage-trigger/test/index.test.js index dabcb1794c..ee051ac0ae 100644 --- a/functions/composer-storage-trigger/test/index.test.js +++ b/functions/composer-storage-trigger/test/index.test.js @@ -39,14 +39,15 @@ it('Handles error in JSON body', async () => { const expectedMsg = 'Something bad happened.'; const bodyJson = {error: expectedMsg}; const body = { - json: sinon.stub().resolves(bodyJson), + json: sinon.stub().returns(bodyJson), }; const sample = getSample(sinon.stub().resolves(body)); try { await sample.program.triggerDag(event); + assert.fail('No error thrown'); } catch (err) { - assert.strictEqual(new RegExp(/Something bad happened/).test(err), true); + assert.strictEqual(err, 'Something bad happened.'); } }); From 51d95d8f0d2c4d72015fd38f243ac92da217fb84 Mon Sep 17 00:00:00 2001 From: ace-n Date: Mon, 13 May 2019 12:49:41 -0700 Subject: [PATCH 2/2] Address comments --- functions/composer-storage-trigger/index.js | 6 +++--- functions/composer-storage-trigger/test/index.test.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/functions/composer-storage-trigger/index.js b/functions/composer-storage-trigger/index.js index 59d1ddadcd..e30022c351 100644 --- a/functions/composer-storage-trigger/index.js +++ b/functions/composer-storage-trigger/index.js @@ -61,8 +61,7 @@ exports.triggerDag = async event => { iap.jwt ); } catch (err) { - console.log('baz', err); - return Promise.reject(err); + throw new Error(err); } }; @@ -161,7 +160,8 @@ const makeIapPostRequest = async (url, body, idToken, userAgent) => { }); if (!res.ok) { - return res.text().then(body => Promise.reject(body)); + const err = await res.text(); + throw new Error(err); } }; // [END composer_trigger] diff --git a/functions/composer-storage-trigger/test/index.test.js b/functions/composer-storage-trigger/test/index.test.js index ee051ac0ae..08a9a065c2 100644 --- a/functions/composer-storage-trigger/test/index.test.js +++ b/functions/composer-storage-trigger/test/index.test.js @@ -47,7 +47,7 @@ it('Handles error in JSON body', async () => { await sample.program.triggerDag(event); assert.fail('No error thrown'); } catch (err) { - assert.strictEqual(err, 'Something bad happened.'); + assert.deepStrictEqual(err, new Error('Something bad happened.')); } }); @@ -86,6 +86,6 @@ it('Handles error in IAP response.', async () => { try { await sample.program.triggerDag(event); } catch (err) { - assert.strictEqual(err, expectedMsg); + assert.deepStrictEqual(err, new Error(expectedMsg)); } });