diff --git a/samples/Domo.gs b/samples/Domo.gs new file mode 100644 index 00000000..78dee77d --- /dev/null +++ b/samples/Domo.gs @@ -0,0 +1,48 @@ +var CONSUMER_KEY = '60e79f78-696e-4977-b8af-22584dee428a'; +var CONSUMER_SECRET = '4bc8eb4d4db6a3b594177c334c4916799d463594c6771dae14a3644ecb3ebc01'; + +/** + * Authorizes and makes a request to the Twitter Application Only 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 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'); +} \ No newline at end of file diff --git a/samples/Twitter.gs b/samples/Twitter.gs new file mode 100644 index 00000000..4723d9d8 --- /dev/null +++ b/samples/Twitter.gs @@ -0,0 +1,45 @@ +var CONSUMER_KEY = 'tYRP6vIZuY8K7yzy2vBnnhRxB'; +var CONSUMER_SECRET = 'YoUMS8YOR9UxPEX0ZwYx5esV7rqHXRMnuqPV0xwDGkkAKqEu0G'; + +/** + * Authorizes and makes a request to the Twitter Application Only API. + */ +function run() { + var service = getService(); + if (service.hasAccess()) { + 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() + } + }); + 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 consumer key and secret. + .setConsumerKey(CONSUMER_KEY) + .setConsumerSecret(CONSUMER_SECRET) + + // Set the property store where authorized tokens should be persisted. + .setPropertyStore(PropertiesService.getScriptProperties()); +} \ No newline at end of file diff --git a/src/Service.gs b/src/Service.gs index ed8a34de..0c729647 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; } @@ -579,6 +618,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. @@ -643,3 +714,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