@@ -370,6 +370,20 @@ Service_.prototype.setExpirationMinutes = function(expirationMinutes) {
370370 return this ;
371371} ;
372372
373+ /**
374+ * Sets the OAuth2 grant_type to use when obtaining an access token. This does
375+ * not need to be set when using either the authorization code flow (AKA
376+ * 3-legged OAuth) or the service account flow. The most common usage is to set
377+ * it to "client_credentials" and then also set the token headers to include
378+ * the Authorization header required by the OAuth2 provider.
379+ * @param {string } grantType The OAuth2 grant_type value.
380+ * @return {Service_ } This service, for chaining.
381+ */
382+ Service_ . prototype . setGrantType = function ( grantType ) {
383+ this . grantType_ = grantType ;
384+ return this ;
385+ } ;
386+
373387/**
374388 * Gets the authorization URL. The first step in getting an OAuth2 token is to
375389 * have the user visit this URL and approve the authorization request. The
@@ -425,29 +439,14 @@ Service_.prototype.handleCallback = function(callbackRequest) {
425439 'Token URL' : this . tokenUrl_
426440 } ) ;
427441 var redirectUri = getRedirectUri ( this . scriptId_ ) ;
428- var headers = {
429- 'Accept' : this . tokenFormat_
430- } ;
431- if ( this . tokenHeaders_ ) {
432- headers = extend_ ( headers , this . tokenHeaders_ ) ;
433- }
434- var tokenPayload = {
442+ var payload = {
435443 code : code ,
436444 client_id : this . clientId_ ,
437445 client_secret : this . clientSecret_ ,
438446 redirect_uri : redirectUri ,
439447 grant_type : 'authorization_code'
440448 } ;
441- if ( this . tokenPayloadHandler_ ) {
442- tokenPayload = this . tokenPayloadHandler_ ( tokenPayload ) ;
443- }
444- var response = UrlFetchApp . fetch ( this . tokenUrl_ , {
445- method : 'post' ,
446- headers : headers ,
447- payload : tokenPayload ,
448- muteHttpExceptions : true
449- } ) ;
450- var token = this . getTokenFromResponse_ ( response ) ;
449+ var token = this . fetchToken_ ( payload ) ;
451450 this . saveToken_ ( token ) ;
452451 return true ;
453452} ;
@@ -463,21 +462,18 @@ Service_.prototype.hasAccess = function() {
463462 return this . lockable_ ( function ( ) {
464463 var token = this . getToken ( ) ;
465464 if ( ! token || this . isExpired_ ( token ) ) {
466- if ( token && this . canRefresh_ ( token ) ) {
467- try {
465+ try {
466+ if ( token && this . canRefresh_ ( token ) ) {
468467 this . refresh ( ) ;
469- } catch ( e ) {
470- this . lastError_ = e ;
471- return false ;
472- }
473- } else if ( this . privateKey_ ) {
474- try {
468+ } else if ( this . privateKey_ ) {
475469 this . exchangeJwt_ ( ) ;
476- } catch ( e ) {
477- this . lastError_ = e ;
470+ } else if ( this . grantType_ ) {
471+ this . exchangeGrant_ ( ) ;
472+ } else {
478473 return false ;
479474 }
480- } else {
475+ } catch ( e ) {
476+ this . lastError_ = e ;
481477 return false ;
482478 }
483479 }
@@ -525,6 +521,34 @@ Service_.prototype.getRedirectUri = function() {
525521 return getRedirectUri ( this . scriptId_ ) ;
526522} ;
527523
524+
525+ /**
526+ * Fetches a new token from the OAuth server.
527+ * @param {Object } payload The token request payload.
528+ * @param {string } [optUrl] The URL of the token endpoint.
529+ * @return {Object } The parsed token.
530+ */
531+ Service_ . prototype . fetchToken_ = function ( payload , optUrl ) {
532+ // Use the configured token URL unless one is specified.
533+ var url = optUrl || this . tokenUrl_ ;
534+ var headers = {
535+ 'Accept' : this . tokenFormat_
536+ } ;
537+ if ( this . tokenHeaders_ ) {
538+ headers = extend_ ( headers , this . tokenHeaders_ ) ;
539+ }
540+ if ( this . tokenPayloadHandler_ ) {
541+ tokenPayload = this . tokenPayloadHandler_ ( payload ) ;
542+ }
543+ var response = UrlFetchApp . fetch ( url , {
544+ method : 'post' ,
545+ headers : headers ,
546+ payload : payload ,
547+ muteHttpExceptions : true
548+ } ) ;
549+ return this . getTokenFromResponse_ ( response ) ;
550+ } ;
551+
528552/**
529553 * Gets the token from a UrlFetchApp response.
530554 * @param {UrlFetchApp.HTTPResponse } response The response object.
@@ -595,30 +619,13 @@ Service_.prototype.refresh = function() {
595619 if ( ! token . refresh_token ) {
596620 throw new Error ( 'Offline access is required.' ) ;
597621 }
598- var headers = {
599- Accept : this . tokenFormat_
600- } ;
601- if ( this . tokenHeaders_ ) {
602- headers = extend_ ( headers , this . tokenHeaders_ ) ;
603- }
604- var tokenPayload = {
622+ var payload = {
605623 refresh_token : token . refresh_token ,
606624 client_id : this . clientId_ ,
607625 client_secret : this . clientSecret_ ,
608626 grant_type : 'refresh_token'
609627 } ;
610- if ( this . tokenPayloadHandler_ ) {
611- tokenPayload = this . tokenPayloadHandler_ ( tokenPayload ) ;
612- }
613- // Use the refresh URL if specified, otherwise fallback to the token URL.
614- var url = this . refreshUrl_ || this . tokenUrl_ ;
615- var response = UrlFetchApp . fetch ( url , {
616- method : 'post' ,
617- headers : headers ,
618- payload : tokenPayload ,
619- muteHttpExceptions : true
620- } ) ;
621- var newToken = this . getTokenFromResponse_ ( response ) ;
628+ var newToken = this . fetchToken_ ( payload , this . refreshUrl_ ) ;
622629 if ( ! newToken . refresh_token ) {
623630 newToken . refresh_token = token . refresh_token ;
624631 }
@@ -706,22 +713,11 @@ Service_.prototype.exchangeJwt_ = function() {
706713 'Token URL' : this . tokenUrl_
707714 } ) ;
708715 var jwt = this . createJwt_ ( ) ;
709- var headers = {
710- 'Accept' : this . tokenFormat_
716+ var payload = {
717+ assertion : jwt ,
718+ grant_type : 'urn:ietf:params:oauth:grant-type:jwt-bearer'
711719 } ;
712- if ( this . tokenHeaders_ ) {
713- headers = extend_ ( headers , this . tokenHeaders_ ) ;
714- }
715- var response = UrlFetchApp . fetch ( this . tokenUrl_ , {
716- method : 'post' ,
717- headers : headers ,
718- payload : {
719- assertion : jwt ,
720- grant_type : 'urn:ietf:params:oauth:grant-type:jwt-bearer'
721- } ,
722- muteHttpExceptions : true
723- } ) ;
724- var token = this . getTokenFromResponse_ ( response ) ;
720+ var token = this . fetchToken_ ( payload ) ;
725721 this . saveToken_ ( token ) ;
726722} ;
727723
@@ -783,6 +779,24 @@ Service_.prototype.lockable_ = function(func) {
783779 return result ;
784780} ;
785781
782+ /**
783+ * Obtain an access token using the custom grant type specified. Most often
784+ * this will be "client_credentials", in which case make sure to also specify an
785+ * Authorization header if required by your OAuth provider.
786+ */
787+ Service_ . prototype . exchangeGrant_ = function ( ) {
788+ validate_ ( {
789+ 'Grant Type' : this . grantType_ ,
790+ 'Token URL' : this . tokenUrl_
791+ } ) ;
792+ var payload = {
793+ grant_type : this . grantType_
794+ } ;
795+ payload = extend_ ( payload , this . params_ ) ;
796+ var token = this . fetchToken_ ( payload ) ;
797+ this . saveToken_ ( token ) ;
798+ } ;
799+
786800// Copyright 2017 Google Inc. All Rights Reserved.
787801//
788802// Licensed under the Apache License, Version 2.0 (the "License");
0 commit comments