Skip to content

Commit 5f5d7f3

Browse files
author
Eric Koleda
committed
Release version 30.
1 parent eebbd21 commit 5f5d7f3

File tree

3 files changed

+101
-30
lines changed

3 files changed

+101
-30
lines changed

dist/OAuth2.gs

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ function createService(serviceName) {
6868
* @return {string} The redirect URI.
6969
*/
7070
function getRedirectUri(scriptId) {
71-
return Utilities.formatString(
72-
'https://script.google.com/macros/d/%s/usercallback', scriptId);
71+
return 'https://script.google.com/macros/d/' + encodeURIComponent(scriptId) +
72+
'/usercallback';
7373
}
7474

7575
if (typeof module === 'object') {
@@ -459,25 +459,37 @@ Service_.prototype.handleCallback = function(callbackRequest) {
459459
* otherwise.
460460
*/
461461
Service_.prototype.hasAccess = function() {
462+
var token = this.getToken();
463+
if (token && !this.isExpired_(token)) return true; // Token still has access.
464+
var canGetToken = (token && this.canRefresh_(token)) ||
465+
this.privateKey_ || this.grantType_;
466+
if (!canGetToken) return false;
467+
462468
return this.lockable_(function() {
463-
var token = this.getToken();
464-
if (!token || this.isExpired_(token)) {
465-
try {
466-
if (token && this.canRefresh_(token)) {
467-
this.refresh();
468-
} else if (this.privateKey_) {
469-
this.exchangeJwt_();
470-
} else if (this.grantType_) {
471-
this.exchangeGrant_();
472-
} else {
473-
return false;
474-
}
475-
} catch (e) {
476-
this.lastError_ = e;
469+
// Get the token again, bypassing the local memory cache.
470+
token = this.getToken(true);
471+
// Check to see if the token is no longer missing or expired, as another
472+
// execution may have refreshed it while we were waiting for the lock.
473+
if (token && !this.isExpired_(token)) return true; // Token now has access.
474+
try {
475+
if (token && this.canRefresh_(token)) {
476+
this.refresh();
477+
return true;
478+
} else if (this.privateKey_) {
479+
this.exchangeJwt_();
480+
return true;
481+
} else if (this.grantType_) {
482+
this.exchangeGrant_();
483+
return true;
484+
} else {
485+
// This should never happen, since canGetToken should have been false
486+
// earlier.
477487
return false;
478488
}
489+
} catch (e) {
490+
this.lastError_ = e;
491+
return false;
479492
}
480-
return true;
481493
});
482494
};
483495

@@ -662,10 +674,13 @@ Service_.prototype.saveToken_ = function(token) {
662674

663675
/**
664676
* Gets the token from the service's property store or cache.
677+
* @param {boolean?} optSkipMemoryCheck If true, bypass the local memory cache
678+
* when fetching the token.
665679
* @return {Object} The token, or null if no token was found.
666680
*/
667-
Service_.prototype.getToken = function() {
668-
return this.getStorage().getValue(null);
681+
Service_.prototype.getToken = function(optSkipMemoryCheck) {
682+
// Gets the stored value under the null key, which is reserved for the token.
683+
return this.getStorage().getValue(null, optSkipMemoryCheck);
669684
};
670685

671686
/**
@@ -781,8 +796,8 @@ Service_.prototype.lockable_ = function(func) {
781796

782797
/**
783798
* 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.
799+
* this will be "client_credentials", and a client ID and secret are set an
800+
* "Authorization: Basic ..." header will be added using those values.
786801
*/
787802
Service_.prototype.exchangeGrant_ = function() {
788803
validate_({
@@ -793,6 +808,20 @@ Service_.prototype.exchangeGrant_ = function() {
793808
grant_type: this.grantType_
794809
};
795810
payload = extend_(payload, this.params_);
811+
812+
// For the client_credentials grant type, add a basic authorization header:
813+
// - If the client ID and client secret are set.
814+
// - No authorization header has been set yet.
815+
var lowerCaseHeaders = toLowerCaseKeys_(this.tokenHeaders_);
816+
if (this.grantType_ === 'client_credentials' &&
817+
this.clientId_ &&
818+
this.clientSecret_ &&
819+
(!lowerCaseHeaders || !lowerCaseHeaders.authorization)) {
820+
this.tokenHeaders_ = this.tokenHeaders_ || {};
821+
this.tokenHeaders_.authorization = 'Basic ' +
822+
Utilities.base64Encode(this.clientId_ + ':' + this.clientSecret_);
823+
}
824+
796825
var token = this.fetchToken_(payload);
797826
this.saveToken_(token);
798827
};
@@ -839,25 +868,42 @@ function Storage_(prefix, properties, optCache) {
839868
*/
840869
Storage_.CACHE_EXPIRATION_TIME_SECONDS = 21600; // 6 hours.
841870

871+
/**
872+
* The special value to use in the cache to indicate that there is no value.
873+
* @type {string}
874+
* @private
875+
*/
876+
Storage_.CACHE_NULL_VALUE = '__NULL__';
877+
842878
/**
843879
* Gets a stored value.
844880
* @param {string} key The key.
881+
* @param {boolean?} optSkipMemoryCheck Whether to bypass the local memory cache
882+
* when fetching the value (the default is false).
845883
* @return {*} The stored value.
846884
*/
847-
Storage_.prototype.getValue = function(key) {
848-
// Check memory.
849-
if (this.memory_[key]) {
850-
return this.memory_[key];
851-
}
852-
885+
Storage_.prototype.getValue = function(key, optSkipMemoryCheck) {
853886
var prefixedKey = this.getPrefixedKey_(key);
854887
var jsonValue;
855888
var value;
856889

890+
if (!optSkipMemoryCheck) {
891+
// Check in-memory cache.
892+
if (value = this.memory_[key]) {
893+
if (value === Storage_.CACHE_NULL_VALUE) {
894+
return null;
895+
}
896+
return value;
897+
}
898+
}
899+
857900
// Check cache.
858901
if (this.cache_ && (jsonValue = this.cache_.get(prefixedKey))) {
859902
value = JSON.parse(jsonValue);
860903
this.memory_[key] = value;
904+
if (value === Storage_.CACHE_NULL_VALUE) {
905+
return null;
906+
}
861907
return value;
862908
}
863909

@@ -872,7 +918,13 @@ Storage_.prototype.getValue = function(key) {
872918
return value;
873919
}
874920

875-
// Not found.
921+
// Not found. Store a special null value in the memory and cache to reduce
922+
// hits on the PropertiesService.
923+
this.memory_[key] = Storage_.CACHE_NULL_VALUE;
924+
if (this.cache_) {
925+
this.cache_.put(prefixedKey, JSON.stringify(Storage_.CACHE_NULL_VALUE),
926+
Storage_.CACHE_EXPIRATION_TIME_SECONDS);
927+
}
876928
return null;
877929
};
878930

@@ -998,6 +1050,25 @@ function extend_(destination, source) {
9981050
return destination;
9991051
}
10001052

1053+
/* exported toLowerCaseKeys_ */
1054+
/**
1055+
* Gets a copy of an object with all the keys converted to lower-case strings.
1056+
*
1057+
* @param {Object} obj The object to copy.
1058+
* @return {Object} a shallow copy of the object with all lower-case keys.
1059+
*/
1060+
function toLowerCaseKeys_(obj) {
1061+
if (obj === null || typeof obj !== 'object') {
1062+
return obj;
1063+
}
1064+
// For each key in the source object, add a lower-case version to a new
1065+
// object, and return it.
1066+
return Object.keys(obj).reduce(function(result, k) {
1067+
result[k.toLowerCase()] = obj[k];
1068+
return result;
1069+
}, {});
1070+
}
1071+
10011072
/****** code end *********/
10021073
;(
10031074
function copy(src, target, obj) {

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "apps-script-oauth2",
3-
"version": "1.29.0",
3+
"version": "1.30.0",
44
"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.",
55
"repository": {
66
"type": "git",

0 commit comments

Comments
 (0)