@@ -44,6 +44,20 @@ var Service_ = function(serviceName) {
4444 */
4545Service_ . EXPIRATION_BUFFER_SECONDS_ = 60 ;
4646
47+ /**
48+ * The number of seconds that a token should remain in the cache.
49+ * @type {number }
50+ * @private
51+ */
52+ Service_ . CACHE_EXPIRATION_SECONDS_ = 6 * 60 * 60 ;
53+
54+ /**
55+ * The number of seconds that a token should remain in the cache.
56+ * @type {number }
57+ * @private
58+ */
59+ Service_ . LOCK_EXPIRATION_MILLISECONDS_ = 30 * 1000 ;
60+
4761/**
4862 * Sets the service's authorization base URL (required). For Google services this URL should be
4963 * https://accounts.google.com/o/oauth2/auth.
@@ -173,6 +187,18 @@ Service_.prototype.setCache = function(cache) {
173187 return this ;
174188} ;
175189
190+ /**
191+ * Sets the lock to use when checking and refreshing credentials (optional). Using a lock will
192+ * ensure that only one execution will be able to access the stored credentials at a time. This can
193+ * prevent race conditions that arise when two executions attempt to refresh an expired token.
194+ * @param {LockService.Lock } cache The lock to use when accessing credentials.
195+ * @return {Service_ } This service, for chaining.
196+ */
197+ Service_ . prototype . setLock = function ( lock ) {
198+ this . lock_ = lock ;
199+ return this ;
200+ } ;
201+
176202/**
177203 * Sets the scope or scopes to request during the authorization flow (optional). If the scope value
178204 * is an array it will be joined using the separator before being sent to the server, which is
@@ -329,27 +355,32 @@ Service_.prototype.handleCallback = function(callbackRequest) {
329355 * @return {boolean } true if the user has access to the service, false otherwise.
330356 */
331357Service_ . prototype . hasAccess = function ( ) {
358+ if ( this . lock_ ) {
359+ this . lock_ . waitLock ( Service_ . LOCK_EXPIRATION_MILLISECONDS_ ) ;
360+ }
361+ var result = true ;
332362 var token = this . getToken ( ) ;
333363 if ( ! token || this . isExpired_ ( token ) ) {
334364 if ( token && token . refresh_token ) {
335365 try {
336366 this . refresh ( ) ;
337367 } catch ( e ) {
338368 this . lastError_ = e ;
339- return false ;
369+ result = false ;
340370 }
341371 } else if ( this . privateKey_ ) {
342372 try {
343373 this . exchangeJwt_ ( ) ;
344374 } catch ( e ) {
345375 this . lastError_ = e ;
346- return false ;
376+ result = false ;
347377 }
348- } else {
349- return false ;
350378 }
351379 }
352- return true ;
380+ if ( this . lock_ ) {
381+ this . lock_ . releaseLock ( ) ;
382+ }
383+ return result ;
353384} ;
354385
355386/**
@@ -458,6 +489,9 @@ Service_.prototype.parseToken_ = function(content) {
458489 * requested when the token was authorized.
459490 */
460491Service_ . prototype . refresh = function ( ) {
492+ if ( this . lock_ ) {
493+ this . lock_ . waitLock ( Service_ . LOCK_EXPIRATION_MILLISECONDS_ ) ;
494+ }
461495 validate_ ( {
462496 'Client ID' : this . clientId_ ,
463497 'Client Secret' : this . clientSecret_ ,
@@ -494,6 +528,9 @@ Service_.prototype.refresh = function() {
494528 newToken . refresh_token = token . refresh_token ;
495529 }
496530 this . saveToken_ ( newToken ) ;
531+ if ( this . lock_ ) {
532+ this . lock_ . releaseLock ( ) ;
533+ }
497534} ;
498535
499536/**
@@ -509,7 +546,7 @@ Service_.prototype.saveToken_ = function(token) {
509546 var value = JSON . stringify ( token ) ;
510547 this . propertyStore_ . setProperty ( key , value ) ;
511548 if ( this . cache_ ) {
512- this . cache_ . put ( key , value , 21600 ) ;
549+ this . cache_ . put ( key , value , Service_ . CACHE_EXPIRATION_SECONDS_ ) ;
513550 }
514551 this . token_ = token ;
515552} ;
@@ -541,7 +578,7 @@ Service_.prototype.getToken = function() {
541578 // Check PropertiesService store.
542579 if ( ( token = this . propertyStore_ . getProperty ( key ) ) ) {
543580 if ( this . cache_ ) {
544- this . cache_ . put ( key , token , 21600 ) ;
581+ this . cache_ . put ( key , token , Service_ . CACHE_EXPIRATION_SECONDS_ ) ;
545582 }
546583 token = JSON . parse ( token ) ;
547584 this . token_ = token ;
0 commit comments