@@ -287,6 +287,20 @@ Service_.prototype.setExpirationMinutes = function(expirationMinutes) {
287287 return this ;
288288} ;
289289
290+ /**
291+ * Sets the OAuth2 grant_type to use when obtaining an access token. This does
292+ * not need to be set when using either the authorization code flow (AKA
293+ * 3-legged OAuth) or the service account flow. The most common usage is to set
294+ * it to "client_credentials" and then also set the token headers to include
295+ * the Authorization header required by the OAuth2 provider.
296+ * @param {string } grantType The OAuth2 grant_type value.
297+ * @return {Service_ } This service, for chaining.
298+ */
299+ Service_ . prototype . setGrantType = function ( grantType ) {
300+ this . grantType_ = grantType ;
301+ return this ;
302+ } ;
303+
290304/**
291305 * Gets the authorization URL. The first step in getting an OAuth2 token is to
292306 * have the user visit this URL and approve the authorization request. The
@@ -342,29 +356,14 @@ Service_.prototype.handleCallback = function(callbackRequest) {
342356 'Token URL' : this . tokenUrl_
343357 } ) ;
344358 var redirectUri = getRedirectUri ( this . scriptId_ ) ;
345- var headers = {
346- 'Accept' : this . tokenFormat_
347- } ;
348- if ( this . tokenHeaders_ ) {
349- headers = extend_ ( headers , this . tokenHeaders_ ) ;
350- }
351- var tokenPayload = {
359+ var payload = {
352360 code : code ,
353361 client_id : this . clientId_ ,
354362 client_secret : this . clientSecret_ ,
355363 redirect_uri : redirectUri ,
356364 grant_type : 'authorization_code'
357365 } ;
358- if ( this . tokenPayloadHandler_ ) {
359- tokenPayload = this . tokenPayloadHandler_ ( tokenPayload ) ;
360- }
361- var response = UrlFetchApp . fetch ( this . tokenUrl_ , {
362- method : 'post' ,
363- headers : headers ,
364- payload : tokenPayload ,
365- muteHttpExceptions : true
366- } ) ;
367- var token = this . getTokenFromResponse_ ( response ) ;
366+ var token = this . fetchToken_ ( payload ) ;
368367 this . saveToken_ ( token ) ;
369368 return true ;
370369} ;
@@ -380,21 +379,18 @@ Service_.prototype.hasAccess = function() {
380379 return this . lockable_ ( function ( ) {
381380 var token = this . getToken ( ) ;
382381 if ( ! token || this . isExpired_ ( token ) ) {
383- if ( token && this . canRefresh_ ( token ) ) {
384- try {
382+ try {
383+ if ( token && this . canRefresh_ ( token ) ) {
385384 this . refresh ( ) ;
386- } catch ( e ) {
387- this . lastError_ = e ;
388- return false ;
389- }
390- } else if ( this . privateKey_ ) {
391- try {
385+ } else if ( this . privateKey_ ) {
392386 this . exchangeJwt_ ( ) ;
393- } catch ( e ) {
394- this . lastError_ = e ;
387+ } else if ( this . grantType_ ) {
388+ this . exchangeGrant_ ( ) ;
389+ } else {
395390 return false ;
396391 }
397- } else {
392+ } catch ( e ) {
393+ this . lastError_ = e ;
398394 return false ;
399395 }
400396 }
@@ -442,6 +438,34 @@ Service_.prototype.getRedirectUri = function() {
442438 return getRedirectUri ( this . scriptId_ ) ;
443439} ;
444440
441+
442+ /**
443+ * Fetches a new token from the OAuth server.
444+ * @param {Object } payload The token request payload.
445+ * @param {string } [optUrl] The URL of the token endpoint.
446+ * @return {Object } The parsed token.
447+ */
448+ Service_ . prototype . fetchToken_ = function ( payload , optUrl ) {
449+ // Use the configured token URL unless one is specified.
450+ var url = optUrl || this . tokenUrl_ ;
451+ var headers = {
452+ 'Accept' : this . tokenFormat_
453+ } ;
454+ if ( this . tokenHeaders_ ) {
455+ headers = extend_ ( headers , this . tokenHeaders_ ) ;
456+ }
457+ if ( this . tokenPayloadHandler_ ) {
458+ tokenPayload = this . tokenPayloadHandler_ ( payload ) ;
459+ }
460+ var response = UrlFetchApp . fetch ( url , {
461+ method : 'post' ,
462+ headers : headers ,
463+ payload : payload ,
464+ muteHttpExceptions : true
465+ } ) ;
466+ return this . getTokenFromResponse_ ( response ) ;
467+ } ;
468+
445469/**
446470 * Gets the token from a UrlFetchApp response.
447471 * @param {UrlFetchApp.HTTPResponse } response The response object.
@@ -512,30 +536,13 @@ Service_.prototype.refresh = function() {
512536 if ( ! token . refresh_token ) {
513537 throw new Error ( 'Offline access is required.' ) ;
514538 }
515- var headers = {
516- Accept : this . tokenFormat_
517- } ;
518- if ( this . tokenHeaders_ ) {
519- headers = extend_ ( headers , this . tokenHeaders_ ) ;
520- }
521- var tokenPayload = {
539+ var payload = {
522540 refresh_token : token . refresh_token ,
523541 client_id : this . clientId_ ,
524542 client_secret : this . clientSecret_ ,
525543 grant_type : 'refresh_token'
526544 } ;
527- if ( this . tokenPayloadHandler_ ) {
528- tokenPayload = this . tokenPayloadHandler_ ( tokenPayload ) ;
529- }
530- // Use the refresh URL if specified, otherwise fallback to the token URL.
531- var url = this . refreshUrl_ || this . tokenUrl_ ;
532- var response = UrlFetchApp . fetch ( url , {
533- method : 'post' ,
534- headers : headers ,
535- payload : tokenPayload ,
536- muteHttpExceptions : true
537- } ) ;
538- var newToken = this . getTokenFromResponse_ ( response ) ;
545+ var newToken = this . fetchToken_ ( payload , this . refreshUrl_ ) ;
539546 if ( ! newToken . refresh_token ) {
540547 newToken . refresh_token = token . refresh_token ;
541548 }
@@ -623,22 +630,11 @@ Service_.prototype.exchangeJwt_ = function() {
623630 'Token URL' : this . tokenUrl_
624631 } ) ;
625632 var jwt = this . createJwt_ ( ) ;
626- var headers = {
627- 'Accept' : this . tokenFormat_
633+ var payload = {
634+ assertion : jwt ,
635+ grant_type : 'urn:ietf:params:oauth:grant-type:jwt-bearer'
628636 } ;
629- if ( this . tokenHeaders_ ) {
630- headers = extend_ ( headers , this . tokenHeaders_ ) ;
631- }
632- var response = UrlFetchApp . fetch ( this . tokenUrl_ , {
633- method : 'post' ,
634- headers : headers ,
635- payload : {
636- assertion : jwt ,
637- grant_type : 'urn:ietf:params:oauth:grant-type:jwt-bearer'
638- } ,
639- muteHttpExceptions : true
640- } ) ;
641- var token = this . getTokenFromResponse_ ( response ) ;
637+ var token = this . fetchToken_ ( payload ) ;
642638 this . saveToken_ ( token ) ;
643639} ;
644640
@@ -699,3 +695,21 @@ Service_.prototype.lockable_ = function(func) {
699695 }
700696 return result ;
701697} ;
698+
699+ /**
700+ * Obtain an access token using the custom grant type specified. Most often
701+ * this will be "client_credentials", in which case make sure to also specify an
702+ * Authorization header if required by your OAuth provider.
703+ */
704+ Service_ . prototype . exchangeGrant_ = function ( ) {
705+ validate_ ( {
706+ 'Grant Type' : this . grantType_ ,
707+ 'Token URL' : this . tokenUrl_
708+ } ) ;
709+ var payload = {
710+ grant_type : this . grantType_
711+ } ;
712+ payload = extend_ ( payload , this . params_ ) ;
713+ var token = this . fetchToken_ ( payload ) ;
714+ this . saveToken_ ( token ) ;
715+ } ;
0 commit comments