From b8788b1e17ef0440f28faea6211d9b4b99a99dc3 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 5 Oct 2017 08:47:07 +0300 Subject: [PATCH 1/5] Subject: Add Client Credentials Grant flow Issue: #82 --- samples/Domo.gs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Service.gs | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 samples/Domo.gs diff --git a/samples/Domo.gs b/samples/Domo.gs new file mode 100644 index 00000000..8a760c5b --- /dev/null +++ b/samples/Domo.gs @@ -0,0 +1,55 @@ + +var CLIENT_ID = '...'; +var CLIENT_SECRET = '...'; + +/** + * Authorizes and makes a request to the Domo API. + */ +function run() { + var service = getService(); + if (service.hasAccess()) { + var url = 'https://api.domo.com/v1/users'; + var response = UrlFetchApp.fetch(url, { + headers: { + Authorization: 'Bearer ' + service.getAccessToken() + } + }); + var result = JSON.parse(response.getContentText()); + Logger.log(JSON.stringify(result, null, 2)); + } else { + Logger.log(service.getLastError()); + } +} + +/** + * Reset the authorization state, so that it can be re-tested. + */ +function reset() { + var service = getService(); + service.reset(); +} + +/** + * Configures the service. + */ +function getService() { + return OAuth2.createService('Domo') + // Set the endpoint URL. + .setTokenUrl('https://api.domo.com/oauth/token') + + // Set the client ID and secret. + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + + // Set the property store where authorized tokens should be persisted. + .setPropertyStore(PropertiesService.getScriptProperties()) + + // Set the scope and additional headers required by the Domo API. + .setScope('data user') + .setTokenHeaders({ + 'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET) + }) + + // grant_type REQUIRED for this type requests. Value MUST be set to "client_credentials". + .setParam('grant_type', 'client_credentials'); +} \ No newline at end of file diff --git a/src/Service.gs b/src/Service.gs index a6259a47..9e76a664 100644 --- a/src/Service.gs +++ b/src/Service.gs @@ -334,6 +334,13 @@ Service_.prototype.hasAccess = function() { this.lastError_ = e; return false; } + } else if (this.params_.grant_type === 'client_credentials'){ + try { + this.requestCCG_(); + } catch (e) { + this.lastError_ = e; + return false; + } } else { return false; } @@ -550,6 +557,38 @@ Service_.prototype.isExpired_ = function(token) { } }; +/** + * Uses Client Credentials Grant flow for getting an access token. + * https://tools.ietf.org/html/rfc6749#section-4.4 + */ +Service_.prototype.requestCCG_ = function() { + validate_({ + 'Token URL': this.tokenUrl_ + }); + var headers = { + 'Accept': this.tokenFormat_ + }; + if (this.tokenHeaders_) { + headers = _.extend(headers, this.tokenHeaders_); + } + var tokenPayload = { + scope: this.params_.scope, + grant_type: 'client_credentials' + }; + if (this.tokenPayloadHandler_) { + tokenPayload = this.tokenPayloadHandler_(tokenPayload); + Logger.log('Token payload from tokenPayloadHandler: %s', JSON.stringify(tokenPayload)); + } + var response = UrlFetchApp.fetch(this.tokenUrl_, { + method: 'post', + headers: headers, + payload: tokenPayload, + muteHttpExceptions: true + }); + var token = this.getTokenFromResponse_(response); + this.saveToken_(token); +}; + /** * Uses the service account flow to exchange a signed JSON Web Token (JWT) for an * access token. From 927c9594f0533ce1b64b813c74f78cf2e0508b81 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Sat, 7 Oct 2017 03:14:20 +0300 Subject: [PATCH 2/5] Subject: Add Twitter API Application Only Issue: #82 Comment: --- samples/Twitter.gs | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 samples/Twitter.gs diff --git a/samples/Twitter.gs b/samples/Twitter.gs new file mode 100644 index 00000000..6a373036 --- /dev/null +++ b/samples/Twitter.gs @@ -0,0 +1,54 @@ + +var CLIENT_ID = '...'; +var CLIENT_SECRET = '...'; + +/** + * Authorizes and makes a request to the Twitter Application Only API. + */ +function run() { + var service = getService(); + if (service.hasAccess()) { + var url = 'ttps://api.twitter.com/1.1/application/rate_limit_status.json?resources=help,users,search,statuses'; + var response = UrlFetchApp.fetch(url, { + headers: { + Authorization: 'Bearer ' + service.getAccessToken() + } + }); + var result = JSON.parse(response.getContentText()); + Logger.log(JSON.stringify(result, null, 2)); + } else { + Logger.log(service.getLastError()); + } +} + +/** + * Reset the authorization state, so that it can be re-tested. + */ +function reset() { + var service = getService(); + service.reset(); +} + +/** + * Configures the service. + */ +function getService() { + return OAuth2.createService('Twitter_Application_Only') + // Set the endpoint URL. + .setTokenUrl('https://api.twitter.com/oauth2/token') + + // Set the client ID and secret. + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + + // Set the property store where authorized tokens should be persisted. + .setPropertyStore(PropertiesService.getScriptProperties()) + + // Set additional headers required by the Twitter Application Only. + .setTokenHeaders({ + 'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET) + }) + + // grant_type REQUIRED for this type requests. Value MUST be set to "client_credentials". + .setParam('grant_type', 'client_credentials'); +} \ No newline at end of file From a50684fa6ed68178da1647e9eafa4da15ab699c1 Mon Sep 17 00:00:00 2001 From: Eric Koleda Date: Tue, 17 Oct 2017 09:32:50 -0400 Subject: [PATCH 3/5] Release version 23 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 716be22f..d32e762c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apps-script-oauth2", - "version": "1.22.0", + "version": "1.23.0", "description": "OAuth2 for Apps Script is a library for Google Apps Script that provides the ability to create and authorize OAuth2 tokens as well as refresh them when they expire.", "repository": { "type": "git", From 8619f899bb4285c3cc3cae48586a6e60fa27ce14 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 19 Oct 2017 21:34:39 +0300 Subject: [PATCH 4/5] Subject: Client Credentials Grant with Consumer Key and Consumer Secret Issue: #82 Comment: --- src/Service.gs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/Service.gs b/src/Service.gs index 366154fa..354d6c15 100644 --- a/src/Service.gs +++ b/src/Service.gs @@ -241,6 +241,38 @@ Service_.prototype.setExpirationMinutes = function(expirationMinutes) { return this; }; +/** + * Sets the consumer secret to use for grant flow authorization. + * @param {string} consummerSecret The consumer secret. + * @return {Service_} This service, for chaining. + */ +Service_.prototype.setConsumerSecret = function(consummerSecret) { + this.consumerSecret_ = consummerSecret; + return this; +}; + +/** + * Sets the consumer key to use for grant flow authorization. + * If not set the client ID will be used instead. + * @param {string} consumerKey This consumer key + * @return {Service_} This service, for chaining. + */ +Service_.prototype.setConsumerKey = function(consumerKey) { + this.consumerKey_ = consumerKey; + return this; +}; + +/** + * Sets the grant_type for an extension of grant flow authorization + * @param {string} grantType The extension grant type. + * @return {Service_} This service, for chaining. + */ + +Service_.prototype.setGrantType = function(grantType) { + this.grantType_ = grantType; + return this; +}; + /** * Gets the authorization URL. The first step in getting an OAuth2 token is to * have the user visit this URL and approve the authorization request. The @@ -345,6 +377,13 @@ Service_.prototype.hasAccess = function() { this.lastError_ = e; return false; } + } else if (this.consumerSecret_){ + try { + this.requestCCG_(); + } catch (e) { + this.lastError_ = e; + return false; + } } else { return false; } @@ -629,3 +668,37 @@ Service_.prototype.createJwt_ = function() { var signature = Utilities.base64EncodeWebSafe(signatureBytes); return toSign + '.' + signature; }; + +/** + * Uses Client Credentials Grant flow for getting an access token. + * https://tools.ietf.org/html/rfc6749#section-4.4 + */ +Service_.prototype.requestCCG_ = function() { + validate_({ + 'Token URL': this.tokenUrl_ + }); + var consumerKey = this.consumerKey_ || this.clientId_; + var headers = { + 'Accept': this.tokenFormat_, + 'Authorization': 'Basic ' + Utilities.base64Encode(consumerKey + ':' + this.consumerSecret_) + }; + if (this.tokenHeaders_) { + headers = _.extend(headers, this.tokenHeaders_); + } + var tokenPayload = { + scope: this.params_.scope, + grant_type: 'client_credentials' || this.grantType_ + }; + if (this.tokenPayloadHandler_) { + tokenPayload = this.tokenPayloadHandler_(tokenPayload); + Logger.log('Token payload from tokenPayloadHandler: %s', JSON.stringify(tokenPayload)); + } + var response = UrlFetchApp.fetch(this.tokenUrl_, { + method: 'post', + headers: headers, + payload: tokenPayload, + muteHttpExceptions: true + }); + var token = this.getTokenFromResponse_(response); + this.saveToken_(token); +}; \ No newline at end of file From f56e9e725e863f7bd28fa6d731ff6877f9bab483 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 19 Oct 2017 22:29:56 +0300 Subject: [PATCH 5/5] Subject: Update samples Domo, Twitter Issue: #82 Comment: --- samples/Domo.gs | 23 ++++++++--------------- samples/Twitter.gs | 23 +++++++---------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/samples/Domo.gs b/samples/Domo.gs index 8a760c5b..78dee77d 100644 --- a/samples/Domo.gs +++ b/samples/Domo.gs @@ -1,9 +1,8 @@ - -var CLIENT_ID = '...'; -var CLIENT_SECRET = '...'; +var CONSUMER_KEY = '60e79f78-696e-4977-b8af-22584dee428a'; +var CONSUMER_SECRET = '4bc8eb4d4db6a3b594177c334c4916799d463594c6771dae14a3644ecb3ebc01'; /** - * Authorizes and makes a request to the Domo API. + * Authorizes and makes a request to the Twitter Application Only API. */ function run() { var service = getService(); @@ -37,19 +36,13 @@ function getService() { // Set the endpoint URL. .setTokenUrl('https://api.domo.com/oauth/token') - // Set the client ID and secret. - .setClientId(CLIENT_ID) - .setClientSecret(CLIENT_SECRET) + // Set the consumer key and secret. + .setConsumerKey(CONSUMER_KEY) + .setConsumerSecret(CONSUMER_SECRET) // Set the property store where authorized tokens should be persisted. .setPropertyStore(PropertiesService.getScriptProperties()) - + // Set the scope and additional headers required by the Domo API. - .setScope('data user') - .setTokenHeaders({ - 'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET) - }) - - // grant_type REQUIRED for this type requests. Value MUST be set to "client_credentials". - .setParam('grant_type', 'client_credentials'); + .setScope('data user'); } \ No newline at end of file diff --git a/samples/Twitter.gs b/samples/Twitter.gs index 6a373036..4723d9d8 100644 --- a/samples/Twitter.gs +++ b/samples/Twitter.gs @@ -1,6 +1,5 @@ - -var CLIENT_ID = '...'; -var CLIENT_SECRET = '...'; +var CONSUMER_KEY = 'tYRP6vIZuY8K7yzy2vBnnhRxB'; +var CONSUMER_SECRET = 'YoUMS8YOR9UxPEX0ZwYx5esV7rqHXRMnuqPV0xwDGkkAKqEu0G'; /** * Authorizes and makes a request to the Twitter Application Only API. @@ -8,7 +7,7 @@ var CLIENT_SECRET = '...'; function run() { var service = getService(); if (service.hasAccess()) { - var url = 'ttps://api.twitter.com/1.1/application/rate_limit_status.json?resources=help,users,search,statuses'; + var url = 'https://api.twitter.com/1.1/application/rate_limit_status.json?resources=help,users,search,statuses'; var response = UrlFetchApp.fetch(url, { headers: { Authorization: 'Bearer ' + service.getAccessToken() @@ -37,18 +36,10 @@ function getService() { // Set the endpoint URL. .setTokenUrl('https://api.twitter.com/oauth2/token') - // Set the client ID and secret. - .setClientId(CLIENT_ID) - .setClientSecret(CLIENT_SECRET) + // Set the consumer key and secret. + .setConsumerKey(CONSUMER_KEY) + .setConsumerSecret(CONSUMER_SECRET) // Set the property store where authorized tokens should be persisted. - .setPropertyStore(PropertiesService.getScriptProperties()) - - // Set additional headers required by the Twitter Application Only. - .setTokenHeaders({ - 'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET) - }) - - // grant_type REQUIRED for this type requests. Value MUST be set to "client_credentials". - .setParam('grant_type', 'client_credentials'); + .setPropertyStore(PropertiesService.getScriptProperties()); } \ No newline at end of file