move from github to git.ictoi.io
This commit is contained in:
commit
fd9641db4b
624
code.gs
Normal file
624
code.gs
Normal file
@ -0,0 +1,624 @@
|
|||||||
|
function setTransferOwnership() {
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
setDeleteAllTriggersOfHandlerFunction("setTransferOwnership");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not delete triggers!" + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
Flog("Getting cache files.");
|
||||||
|
var startTime = new Date(), cacheWriteChunkSize = 20;
|
||||||
|
// get data from cache or init cache with all domain users
|
||||||
|
var cacheFileId = getCacheFileId(false, "dirScan");
|
||||||
|
var resultFileId = getCacheFileId(false, "transResult");
|
||||||
|
var fileCache = null, fileCacheTxt = null, fileResult = null, fileResultTxt = null;
|
||||||
|
fileCache = DriveApp.getFileById(cacheFileId), fileResult = DriveApp.getFileById(resultFileId);
|
||||||
|
fileCacheTxt = fileCache.getBlob().getDataAsString(), fileResultTxt = fileResult.getBlob().getDataAsString(); // FILE CACHE READ & FILE RESULT READ
|
||||||
|
Flog("Getting cache files. Done!");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not get cache files! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
Flog("Parsing cache files.");
|
||||||
|
var inObj = {}, outObj = {};
|
||||||
|
if (fileCacheTxt != "") {
|
||||||
|
inObj = JSON.parse(fileCacheTxt);
|
||||||
|
} else {
|
||||||
|
// blank cache file means terminate
|
||||||
|
Flog("Blank cache file. Run 'Scan dir to cache file' function first!");
|
||||||
|
throw new Error("Blank cache file. Run scan dir first!"); // ERROR
|
||||||
|
}
|
||||||
|
//fileResult.setContent(""); // debug only
|
||||||
|
if (fileResultTxt === "") {
|
||||||
|
outObj["domainUsers"] = {};
|
||||||
|
}
|
||||||
|
if (fileResultTxt !== "") {
|
||||||
|
outObj = JSON.parse(fileResultTxt);
|
||||||
|
}
|
||||||
|
Flog("Parsing cache files. Done!");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not parse cache files! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
Flog("Work objects init.");
|
||||||
|
var cUserId = null, cUserObj = null, cUserFilesArr = [], doneFiles = [], todoFiles = [];
|
||||||
|
for (cUserId in inObj["domainUsers"]){
|
||||||
|
if (inObj["domainUsers"][cUserId]["queryComplete"] === true && inObj["domainUsers"][cUserId]["userFiles"].length === 0) {
|
||||||
|
//Flog("No files for owner " + cUserId + ". Continue!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Flog("Set user " + cUserId + ".");
|
||||||
|
if (inObj["domainUsers"][cUserId]["queryComplete"] === true && inObj["domainUsers"][cUserId]["userFiles"].length !== 0) {
|
||||||
|
if (typeof outObj["domainUsers"][cUserId] === "undefined") {
|
||||||
|
outObj["domainUsers"][cUserId] = {
|
||||||
|
"ownerComplete" : false,
|
||||||
|
"todoFiles" : inObj["domainUsers"][cUserId]["userFiles"],
|
||||||
|
"doneFiles" : []
|
||||||
|
};
|
||||||
|
Flog("Set user " + cUserId + ". Done!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (outObj["domainUsers"][cUserId]["ownerComplete"] === true) {
|
||||||
|
Flog("Owner complete " + cUserId + ". Continue!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (outObj["domainUsers"][cUserId]["ownerComplete"] !== true) {
|
||||||
|
doneFiles = outObj["domainUsers"][cUserId]["doneFiles"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Flog("All users done. Success!");
|
||||||
|
return true; // RETURN
|
||||||
|
}
|
||||||
|
Flog("All users done. Success!");
|
||||||
|
return true; // RETURN
|
||||||
|
}
|
||||||
|
Flog("Work objects init. Done!");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not set files to parse! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// get impersonated token and transfer ownership
|
||||||
|
//Flog(JSON.stringify(outObj["domainUsers"][cUserId])); // debug only
|
||||||
|
todoFiles = outObj["domainUsers"][cUserId]["todoFiles"];
|
||||||
|
var lenI = outObj["domainUsers"][cUserId]["todoFiles"].length;
|
||||||
|
Flog("Begin loop for " + cUserId + " length " + lenI + ".");
|
||||||
|
for (var i = 0; i < lenI; i++) {
|
||||||
|
///Flog("Count: " + i); // debug only
|
||||||
|
//Utilities.sleep(1000); // debug only
|
||||||
|
if (todoFiles[i] === undefined) {break};
|
||||||
|
try {
|
||||||
|
var impResult = setImpersonatedOwnership(todoFiles[i], cUserId, GVAR.TRANSFER_OWNERSHIP_TO);
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not transfer ownership for actual file!");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DriveApp.removeFile(DriveApp.getFileById(todoFiles[i]));
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not remove file from the root folder!");
|
||||||
|
}
|
||||||
|
//var impResult = true; // debug only
|
||||||
|
if (impResult) {
|
||||||
|
var cItem = todoFiles.shift();
|
||||||
|
doneFiles.push(cItem);
|
||||||
|
outObj["domainUsers"][cUserId]["doneFiles"] = doneFiles;
|
||||||
|
outObj["domainUsers"][cUserId]["todoFiles"] = todoFiles;
|
||||||
|
} else {
|
||||||
|
Flog("Error while transferring ownership of object id: " + todoFiles[i] + "!");
|
||||||
|
break;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var timeElapsed = countdown(startTime, new Date(), countdown.DEFAULTS);
|
||||||
|
var timeElapsedValue = timeElapsed.value;
|
||||||
|
if (timeElapsedValue >= 242000) { // 4.04 minutes; 6 minutes max. but 5 minutes trigger run interval
|
||||||
|
// set continuation trigger
|
||||||
|
var contTrigger = ScriptApp.newTrigger("setTransferOwnership").timeBased().everyMinutes(1).create();
|
||||||
|
//var contTriggerId = contTrigger.getUniqueId();
|
||||||
|
//outObj["domainUsers"][cUserId]["contTriggerId"] = contTriggerId;
|
||||||
|
fileResult = fileResult.setContent(JSON.stringify(outObj)); // FILE CACHE WRITE
|
||||||
|
Flog("Timeout trigger set. Script will continue!");
|
||||||
|
//throw new Error("Timeout");
|
||||||
|
return false; // RETURN
|
||||||
|
}
|
||||||
|
// save in chunks
|
||||||
|
if (i % cacheWriteChunkSize === 0 && i > 0) {
|
||||||
|
fileResult = fileResult.setContent(JSON.stringify(outObj)); // FILE CACHE WRITE
|
||||||
|
var timeElapsedHuman = timeElapsed.toString();
|
||||||
|
Flog("Chunk " + i + " in " + timeElapsedHuman + ". Done " + doneFiles.length + "; remains " + todoFiles.length + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not loop files! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Flog("Transfer loop. Done!");
|
||||||
|
outObj["domainUsers"][cUserId]["ownerComplete"] = true;
|
||||||
|
fileResult = fileResult.setContent(JSON.stringify(outObj));
|
||||||
|
Flog("Chunk " + i + "." + " Done " + doneFiles.length + "; remains " + todoFiles.length + ".");
|
||||||
|
// recursive call
|
||||||
|
setTransferOwnership();
|
||||||
|
//return true;
|
||||||
|
// set continuation trigger
|
||||||
|
/*var contTrigger = ScriptApp.newTrigger("setImpersonatedOwnershipDirScan").timeBased().everyMinutes(5).create();
|
||||||
|
var contTriggerId = contTrigger.getUniqueId();
|
||||||
|
outObj["contTriggerId"] = contTriggerId;
|
||||||
|
fileResult = fileResult.setContent(JSON.stringify(outObj));*/
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDirScanToCacheFile() {
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
setDeleteAllTriggersOfHandlerFunction("setDirScanToCacheFile");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not delete triggers!" + e);
|
||||||
|
}
|
||||||
|
var startTime = new Date();
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// get data from cache or init cache with all domain users
|
||||||
|
Flog("Get and parse cache file.");
|
||||||
|
var cacheFileId = getCacheFileId(false, "dirScan");
|
||||||
|
var fileCache = null, fileCacheTxt = null, loopCount = 0;
|
||||||
|
fileCache = DriveApp.getFileById(cacheFileId);
|
||||||
|
//fileCache.setContent(""); // debug only
|
||||||
|
fileCacheTxt = fileCache.getBlob().getDataAsString(); // FILE CACHE READ
|
||||||
|
var masterObj = {};
|
||||||
|
if (fileCacheTxt != "") {
|
||||||
|
masterObj = JSON.parse(fileCacheTxt);
|
||||||
|
} else {
|
||||||
|
// get all domain users
|
||||||
|
var allDomainUsersEmail = getAllDomainUsersEmail();
|
||||||
|
masterObj = {"domainUserList" : allDomainUsersEmail, "domainUsers" : {}};
|
||||||
|
fileCache = fileCache.setContent(JSON.stringify(masterObj)); // FILE CACHE WRITE
|
||||||
|
}
|
||||||
|
Flog("Get and parse cache file. Done!");
|
||||||
|
} catch (e) {
|
||||||
|
Flog("Can not get domain users or parse cache file!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// loop all users and generate file list
|
||||||
|
Flog(masterObj["domainUserList"]);
|
||||||
|
if (typeof masterObj["domainUserList"] !== "undefined" && masterObj["domainUserList"].length > 0) {
|
||||||
|
for (var i = 0, lenI = masterObj["domainUserList"].length; i < lenI; i++) {
|
||||||
|
if (typeof masterObj["domainUsers"][masterObj["domainUserList"][i]] !== "undefined"
|
||||||
|
&& masterObj["domainUsers"][masterObj["domainUserList"][i]]["queryComplete"] === true) {
|
||||||
|
Flog(masterObj["domainUserList"][i] + " item " + i + " of " + lenI + ". Done!"); // debug only
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
Flog("Starting item " + i + " of " + lenI + ". Done!");
|
||||||
|
//var actionResult = setAllOwnerFilesCacheFile("jan.hus@ictoi.com", false, cacheFileId, startTime, 20, GVAR.CACHE_FILE_LIFETIME); // debug only
|
||||||
|
//var actionResult = setAllOwnerFilesCacheFile(masterObj["domainUserList"][i], false, cacheFileId, startTime, 20, GVAR.CACHE_FILE_LIFETIME); // debug only
|
||||||
|
var actionResult = setAllOwnerFilesCacheFile(masterObj["domainUserList"][i], false, cacheFileId, startTime, 25, GVAR.CACHE_FILE_LIFETIME); // debug only
|
||||||
|
//var actionResult = setAllOwnerFilesCacheFile(masterObj["domainUserList"][i], false, cacheFileId, startTime, 30, GVAR.CACHE_FILE_LIFETIME); // debug only
|
||||||
|
//var actionResult = setAllOwnerFilesCacheFile(masterObj["domainUserList"][i], false, cacheFileId, startTime, 40, GVAR.CACHE_FILE_LIFETIME); // debug only
|
||||||
|
//var actionResult = setAllOwnerFilesCacheFile(masterObj["domainUserList"][i], false, cacheFileId, startTime, 2, GVAR.CACHE_FILE_LIFETIME); // debug only
|
||||||
|
if (!actionResult) {
|
||||||
|
Flog("Action result is false. Error!");
|
||||||
|
return false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Flog("Scan dir for all owners completed successfully. Done!");
|
||||||
|
// prepare email variables and send transactional email
|
||||||
|
var mailVariablesObj = {
|
||||||
|
"~backgroundColor~" : "#738ffe", // #738ffe = Blue 400
|
||||||
|
"~titleText~" : "Scan dir for all owners completed successfully",
|
||||||
|
"~headerMessage~" : "Scan dir for all owners completed successfully",
|
||||||
|
"~mainMessage~" : "Scan dir for ownership transfer completed successfully for all owners.",
|
||||||
|
"~buttonText~" : "See generated cache file",
|
||||||
|
"~buttonUrl~" : "https://drive.google.com/open?id=" + cacheFileId + "&authuser=0",
|
||||||
|
"~footerText~" : "Do not reply to this email."
|
||||||
|
};
|
||||||
|
var mailSendResult = setSendTransactionalEmail(GVAR.TRANSFER_OWNERSHIP_TO, mailVariablesObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAllOwnerFilesCacheFile(ownerEmail, returnNegatives, cacheFileId, startTime, cacheWriteChunkSize, queryLifeTimeHours) {
|
||||||
|
Flog("File init for " + ownerEmail + ".");
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// get lock
|
||||||
|
var userLock = LockService.getUserLock();
|
||||||
|
userLock.waitLock(10000);
|
||||||
|
if (!userLock.hasLock()) {
|
||||||
|
Flog("Can not run second instance of the script.");
|
||||||
|
//throw new Error("Can not run second instance of the script."); // ERROR
|
||||||
|
return false; // RETURN
|
||||||
|
};
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not get user lock! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// init cache
|
||||||
|
//Flog("Get and parse cache file.");
|
||||||
|
var fileCache = null, fileCacheTxt = null, loopCount = 0, cLifeTime = null;
|
||||||
|
fileCache = DriveApp.getFileById(cacheFileId);
|
||||||
|
//fileCache.setContent(""); // debug only
|
||||||
|
fileCacheTxt = fileCache.getBlob().getDataAsString(); // FILE CACHE READ
|
||||||
|
var masterObj = {};
|
||||||
|
// check cache file blank and parse
|
||||||
|
if (fileCacheTxt != "") {
|
||||||
|
masterObj = JSON.parse(fileCacheTxt);
|
||||||
|
}
|
||||||
|
//Flog("Get and parse cache file. Done!");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not get and parse cache file! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Flog("File init for " + ownerEmail + ". Done!");
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// init domain users node
|
||||||
|
Flog("Check tasks for " + ownerEmail + "!");
|
||||||
|
if (typeof masterObj["domainUsers"] === "undefined") {masterObj["domainUsers"] = {}};
|
||||||
|
if (typeof masterObj["domainUsers"][ownerEmail] !== "undefined") {
|
||||||
|
// do not run query if already completed and lifetime is not reached
|
||||||
|
cLifeTime = Math.abs(new Date(masterObj["domainUsers"][ownerEmail]["queryFirstInitTime"]) - startTime) / 36e5;
|
||||||
|
if (masterObj["domainUsers"][ownerEmail]["queryComplete"] === true && (cLifeTime < queryLifeTimeHours)) {
|
||||||
|
Flog("Query complete. Done!");
|
||||||
|
setDeleteTriggerById(masterObj["domainUsers"][ownerEmail]["contTriggerId"]);
|
||||||
|
// release lock
|
||||||
|
userLock.releaseLock();
|
||||||
|
return true; // RETURN
|
||||||
|
}
|
||||||
|
if (masterObj["domainUsers"][ownerEmail]["queryComplete"] === true && (cLifeTime >= queryLifeTimeHours)) {
|
||||||
|
masterObj["domainUsers"][ownerEmail] = undefined, fileCacheTxt = "";
|
||||||
|
}
|
||||||
|
Flog("Check tasks. Done!");
|
||||||
|
}
|
||||||
|
// check cache file not blank or domain users node present or owner email node not present
|
||||||
|
if (fileCacheTxt == "" || typeof masterObj["domainUsers"][ownerEmail] === "undefined") {
|
||||||
|
Flog("Init user object.");
|
||||||
|
var filesOwnedByUser = null, contTokenObj = {};
|
||||||
|
// init master object for non cached run or if new owner passed
|
||||||
|
masterObj["domainUsers"][ownerEmail] = {
|
||||||
|
"queryComplete" : false,
|
||||||
|
"queryFirstInitTime" : new Date(),
|
||||||
|
"queryEndSuccessTime" : null,
|
||||||
|
"queryElapsedTime" : null,
|
||||||
|
"filesAlreadyCached" : 0, // debug only
|
||||||
|
"chunksDone" : [], // debug only
|
||||||
|
//"lastChunkEndTime" : null, // debug only
|
||||||
|
//"lastChunkElapsedTime" : null,
|
||||||
|
"resumedRunCount" : 0,
|
||||||
|
"contToken" : null,
|
||||||
|
//"previousContToken" : null, // debug only
|
||||||
|
"contTokenCreationTime" : null,
|
||||||
|
"contTriggerId" : null,
|
||||||
|
"impToken" : null,
|
||||||
|
"userFiles" : []
|
||||||
|
}
|
||||||
|
//fileCache = fileCache.setContent(JSON.stringify(masterObj)); // FILE CACHE WRITE
|
||||||
|
Flog("Init user object. Done!");
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not get and parse cache file! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// get tokens for iterators
|
||||||
|
Flog("Run query.");
|
||||||
|
var userFilesIter = null, queryFirstInitTime = null;
|
||||||
|
var contTokenLifeTime = Math.abs(new Date(masterObj["domainUsers"][ownerEmail]["contTokenCreationTime"]) - startTime) / 36e5;
|
||||||
|
if (masterObj["domainUsers"][ownerEmail]["contToken"] === null || contTokenLifeTime >= 24) {
|
||||||
|
userFilesIter = DriveApp.searchFiles("trashed != true and not ('" + GVAR.TRANSFER_OWNERSHIP_TO + "' in owners) and '" + ownerEmail + "' in owners");
|
||||||
|
/*userFilesIter = DriveApp.searchFiles("trashed != true and not ('" + GVAR.TRANSFER_OWNERSHIP_TO + "' in owners) and '" + ownerEmail + "' in owners " +
|
||||||
|
"and " + "(" +
|
||||||
|
"mimeType = 'application/vnd.google-apps.document'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.drawing'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.forms'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.fusiontable'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.presentation'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.script'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.sites'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.spreadsheet'"
|
||||||
|
+ ")"
|
||||||
|
); // free domains only*/
|
||||||
|
queryFirstInitTime = new Date();
|
||||||
|
} else {
|
||||||
|
userFilesIter = DriveApp.continueFileIterator(masterObj["domainUsers"][ownerEmail]["contToken"]);
|
||||||
|
// delete continuation trigger
|
||||||
|
setDeleteTriggerById(masterObj["domainUsers"][ownerEmail]["contTriggerId"]);
|
||||||
|
queryFirstInitTime = masterObj["domainUsers"][ownerEmail]["queryFirstInitTime"];
|
||||||
|
}
|
||||||
|
Flog("Run query. Done!");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not initiate or call query! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
Flog("Loop objects.");
|
||||||
|
while (userFilesIter.hasNext()) {
|
||||||
|
// init vars
|
||||||
|
var fileObj = null, fileId = null, fileName = null;
|
||||||
|
loopCount++;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["filesAlreadyCached"]++;
|
||||||
|
// iterate next
|
||||||
|
var fileObj = userFilesIter.next()
|
||||||
|
// get file basic info
|
||||||
|
fileId = fileObj.getId()
|
||||||
|
//fileName = fileObj.getName();
|
||||||
|
var ancResult = false, fillArray = [], iterLevel = 0, ancArray = null, timeElapsed = null;
|
||||||
|
// get all drive object ancestors
|
||||||
|
//var timerA = new Date(); // debug only
|
||||||
|
ancResult = getFileHasAncestor(fileObj, fillArray, iterLevel);
|
||||||
|
//if (loopCount % cacheWriteChunkSize === 0) {Flog("Get ancestors" + "; count: " + loopCount + "; subtime elapsed: " + countdown(timerA, new Date(), countdown.DEFAULTS).toString())}; // debug only
|
||||||
|
//if (ancResult) {ancArray = fillArray} // debug only
|
||||||
|
fillArray = null; // null result array
|
||||||
|
// fill master object with search data
|
||||||
|
//masterObj["domainUsers"][ownerEmail]["userFiles"].push([fileId, fileName, ancResult, ancArray]); // debug only
|
||||||
|
if (ancResult || (returnNegatives && ancResult === null)) {
|
||||||
|
//masterObj["domainUsers"][ownerEmail]["userFiles"].push([fileId, fileName, ancResult]); // debug only
|
||||||
|
masterObj["domainUsers"][ownerEmail]["userFiles"].push(fileId);
|
||||||
|
}
|
||||||
|
//Utilities.sleep(4000); // debug only
|
||||||
|
// run in chunks
|
||||||
|
timeElapsed = countdown(startTime, new Date(), countdown.DEFAULTS).value;
|
||||||
|
if (loopCount % cacheWriteChunkSize === 0) {
|
||||||
|
// first run or first continuation run
|
||||||
|
if (loopCount / cacheWriteChunkSize === 1) {masterObj["domainUsers"][ownerEmail]["queryFirstInitTime"] = queryFirstInitTime};
|
||||||
|
var timeElapsedHuman = countdown(startTime, new Date(), countdown.DEFAULTS).toString();
|
||||||
|
//masterObj["domainUsers"][ownerEmail]["chunksDone"].push([loopCount, timeElapsed, timeElapsedHuman]); // debug only
|
||||||
|
masterObj["domainUsers"][ownerEmail]["chunksDone"].push([loopCount, timeElapsed]); // debug only
|
||||||
|
fileCache = fileCache.setContent(JSON.stringify(masterObj)); // FILE CACHE WRITE
|
||||||
|
Flog("Chunk " + loopCount + " done in " + timeElapsedHuman);
|
||||||
|
// cont token and previous cont token equal means failure
|
||||||
|
/*if (masterObj["domainUsers"][ownerEmail]["filesAlreadyCached"] > 10000) {
|
||||||
|
// prepare email variables and send transactional email
|
||||||
|
var mailVariablesObj = {
|
||||||
|
"~backgroundColor~" : "#ff7043", // #ff7043 = Deep Orange 400
|
||||||
|
"~titleText~" : "Scan dir for " + ownerEmail + " exceeded limit file count",
|
||||||
|
"~headerMessage~" : "Scan dir for " + ownerEmail + " exceeded limit file count",
|
||||||
|
"~mainMessage~" : "This results in fatal error that may lead to infinite loop. If you see this email, contact your administrator!",
|
||||||
|
"~buttonText~" : "See generated cache file",
|
||||||
|
"~buttonUrl~" : "https://drive.google.com/open?id=" + cacheFileId + "&authuser=0",
|
||||||
|
"~footerText~" : "Do not reply to this email."
|
||||||
|
};
|
||||||
|
var mailSendResult = setSendTransactionalEmail(GVAR.TRANSFER_OWNERSHIP_TO, mailVariablesObj);
|
||||||
|
var deleteResult = setDeleteAllTriggersOfHandlerFunction("setAllOwnerFilesCacheFile");
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
};*/
|
||||||
|
}
|
||||||
|
// check max script time run and terminate with continuation token cache write
|
||||||
|
//if (timeElapsed >= 270000) { // 4.5 minutes; 6 minutes max. but 5 minutes trigger run interval
|
||||||
|
if (timeElapsed >= 252000) { // 4.2 minutes; 6 minutes max. but 5 minutes trigger run interval
|
||||||
|
//if (timeElapsed >= 242000) { // 4.04 minutes; 6 minutes max. but 5 minutes trigger run interval
|
||||||
|
//if (timeElapsed >= 235000) { // 3.92 minutes; 6 minutes max. but 5 minutes trigger run interval
|
||||||
|
//masterObj["domainUsers"][ownerEmail]["previousContToken"] = masterObj["domainUsers"][ownerEmail]["contToken"];
|
||||||
|
var contToken = userFilesIter.getContinuationToken();
|
||||||
|
masterObj["domainUsers"][ownerEmail]["contToken"] = contToken;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["contTokenCreationTime"] = new Date();
|
||||||
|
masterObj["domainUsers"][ownerEmail]["resumedRunCount"]++;
|
||||||
|
//masterObj["domainUsers"][ownerEmail]["filesAlreadyCached"] = loopCount;
|
||||||
|
// set continuation trigger
|
||||||
|
var contTrigger = ScriptApp.newTrigger("setDirScanToCacheFile").timeBased().everyMinutes(1).create();
|
||||||
|
var contTriggerId = contTrigger.getUniqueId();
|
||||||
|
masterObj["domainUsers"][ownerEmail]["contTriggerId"] = contTriggerId;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["chunksDone"].push([masterObj["domainUsers"][ownerEmail]["contToken"], masterObj["domainUsers"][ownerEmail]["contTokenCreationTime"], cLifeTime]); // debug only
|
||||||
|
fileCache = fileCache.setContent(JSON.stringify(masterObj)); // FILE CACHE WRITE
|
||||||
|
Flog("Timeout trigger set. Script will continue!");
|
||||||
|
// release lock
|
||||||
|
userLock.releaseLock();
|
||||||
|
//throw new Error("Loop timeout but resume trigger has been set."); // ERROR
|
||||||
|
return false; // RETURN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Flog("Loop objects. Done!");
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not loop objects! " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Flog("Finalize user section and inform.");
|
||||||
|
masterObj["domainUsers"][ownerEmail]["contTriggerId"] = null;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["contToken"] = null;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["contTokenCreationTime"] = null;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["queryComplete"] = true;
|
||||||
|
masterObj["domainUsers"][ownerEmail]["queryEndSuccessTime"] = new Date();
|
||||||
|
masterObj["domainUsers"][ownerEmail]["queryElapsedTime"] = countdown(new Date(masterObj["domainUsers"][ownerEmail]["queryFirstInitTime"]), masterObj["domainUsers"][ownerEmail]["queryEndSuccessTime"], countdown.DEFAULTS).toString();
|
||||||
|
//masterObj["domainUsers"][ownerEmail]["lastChunkElapsedTime"] = countdown(new Date(masterObj["domainUsers"][ownerEmail]["lastChunkEndTime"]), new Date(), countdown.DEFAULTS).toString(); // debug only
|
||||||
|
fileCache = fileCache.setContent(JSON.stringify(masterObj)); // FILE CACHE WRITE
|
||||||
|
// prepare email variables and send transactional email
|
||||||
|
var mailVariablesObj = {
|
||||||
|
"~backgroundColor~" : "#ffb300", // #ffb300 = Amber 600
|
||||||
|
"~titleText~" : "Scan dir for " + ownerEmail + " completed successfully",
|
||||||
|
"~headerMessage~" : "Scan dir for " + ownerEmail + " completed successfully",
|
||||||
|
"~mainMessage~" : "Scan dir for ownership transfer completed successfully in " + masterObj["domainUsers"][ownerEmail]["queryElapsedTime"] + " for actual owner " + ownerEmail + " with " + masterObj["domainUsers"][ownerEmail]["filesAlreadyCached"] + " cached files.",
|
||||||
|
"~buttonText~" : "See generated cache file",
|
||||||
|
"~buttonUrl~" : "https://drive.google.com/open?id=" + cacheFileId + "&authuser=0",
|
||||||
|
"~footerText~" : "Do not reply to this email."
|
||||||
|
};
|
||||||
|
var mailSendResult = setSendTransactionalEmail(GVAR.TRANSFER_OWNERSHIP_TO, mailVariablesObj); // release lock
|
||||||
|
Flog("Finalize user section and inform. Done!");
|
||||||
|
userLock.releaseLock();
|
||||||
|
return true; // RETURN
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* releases user lock if exists
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setReleaseUserLock() {
|
||||||
|
var userLock = LockService.getUserLock();
|
||||||
|
userLock.tryLock(10000);
|
||||||
|
if (!userLock.hasLock()) {
|
||||||
|
userLock.releaseLock();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets ownership on given drive object (file, folder) via impersonization
|
||||||
|
*
|
||||||
|
* @param {String} driveObjectId drive object id (file, folder)
|
||||||
|
* @param {String} impersonatedUserEmail the email address of the user for which the application is requesting delegated access
|
||||||
|
* @param {String} transferOwnershipToEmail the email address to whom the ownership will be transfered
|
||||||
|
* @requires crypto (https://code.google.com/p/crypto-js/)
|
||||||
|
* @requires jsrsasign (http://kjur.github.io/jsrsasign/)
|
||||||
|
* @requires jwsjs (http://kjur.github.io/jsjws/)
|
||||||
|
* @requires countdownjs (http://countdownjs.org/)
|
||||||
|
* @returns {Bool} true if ownership transfered successfully
|
||||||
|
*/
|
||||||
|
function setImpersonatedOwnership(driveObjectId, impersonatedUserEmail, transferOwnershipToEmail) {
|
||||||
|
// same ownership switch
|
||||||
|
if (impersonatedUserEmail === transferOwnershipToEmail) {return true};
|
||||||
|
if (typeof driveObjectId === "undefined") {return false};
|
||||||
|
// get impersonated oauth token
|
||||||
|
var oauthToken = getImpersonatedAccessToken(impersonatedUserEmail, GVAR.SERVICE_ACCOUNT_EMAIL, GVAR.SCOPES_SPACE_SEPARATED, GVAR.GOOGLE_DEV_CONSOLE_OAUTH_P12_BASE64, true);
|
||||||
|
// generate fetch data
|
||||||
|
var payloadObj = {
|
||||||
|
"role" : "owner",
|
||||||
|
"type" : "user",
|
||||||
|
"value" : transferOwnershipToEmail
|
||||||
|
};
|
||||||
|
/*var payloadObj = {};*/
|
||||||
|
//var payloadJson = encodeURIComponent(JSON.stringify(payloadObj)); // does not work
|
||||||
|
var payloadJson = JSON.stringify(payloadObj);
|
||||||
|
var fetchOpt = {
|
||||||
|
"method" : "post",
|
||||||
|
//"method" : "get",
|
||||||
|
"contentType" : "application/json",
|
||||||
|
"muteHttpExceptions" : false,
|
||||||
|
"headers" : { //http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
|
||||||
|
"User-Agent" : "curl/7.38.0", // not documented but key element to get impersonization in google apps script to work
|
||||||
|
"Authorization" : "Bearer " + oauthToken
|
||||||
|
},
|
||||||
|
"payload" : payloadJson
|
||||||
|
}
|
||||||
|
//var fetchUrl = "https://www.googleapis.com/drive/v2/permissionIds/" + transferOwnershipToEmail; // debug only
|
||||||
|
//var fetchUrl = "https://www.googleapis.com/drive/v2/files/" + driveObjectId + "/touch"; // debug only
|
||||||
|
var fetchUrl = "https://www.googleapis.com/drive/v2/files/" + driveObjectId + "/permissions";
|
||||||
|
var fetchResponse = UrlFetchApp.fetch(fetchUrl, fetchOpt);
|
||||||
|
// parse response
|
||||||
|
if (fetchResponse.getResponseCode() == 200){
|
||||||
|
//var responseContent = JSON.parse(fetchResponse.getContentText()); // debug only
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (fetchResponse.getResponseCode() == 500){
|
||||||
|
Flog("Error 500 for drive object id: " + driveObjectId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fetchResponse.getResponseCode() != 200 || fetchResponse.getResponseCode() != 500){
|
||||||
|
throw new Error("Oops! Failed to parse response or invalid response code obtained.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns impersonated oauth token
|
||||||
|
*
|
||||||
|
* @param {String} impersonatedUserEmail the email address of the user for which the application is requesting delegated access
|
||||||
|
* @param {String} serviceAccountEmail service account email address
|
||||||
|
* @param {String} scopesSpaceSeparated oauth scopes separated by space
|
||||||
|
* @param {String} oauthServiceAccountPrivateKeyBase64 service account p12 key generated from google developer console
|
||||||
|
* @param {Bool} cacheTokenFromToUserCache cache token from/to user cache switch
|
||||||
|
* @requires crypto (https://code.google.com/p/crypto-js/)
|
||||||
|
* @requires jsrsasign (http://kjur.github.io/jsrsasign/)
|
||||||
|
* @requires jwsjs (http://kjur.github.io/jsjws/)
|
||||||
|
* @requires countdownjs (http://countdownjs.org/)
|
||||||
|
* @returns {String|Bool} accessToken impersonated oauth access token or false if error
|
||||||
|
*/
|
||||||
|
function getImpersonatedAccessToken(impersonatedUserEmail, serviceAccountEmail, scopesSpaceSeparated, oauthServiceAccountPrivateKeyBase64, cacheTokenFromToUserCache){
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// get token from cache
|
||||||
|
var cacheHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, (serviceAccountEmail + "." + impersonatedUserEmail + "." + scopesSpaceSeparated), Utilities.Charset.US_ASCII);
|
||||||
|
var cachedToken = CacheService.getUserCache().get(cacheHash);
|
||||||
|
if (cachedToken && cacheTokenFromToUserCache) {return cachedToken;}
|
||||||
|
// log duration
|
||||||
|
//Flog("Time to check and get cache: " + countdown(startTime, new Date(), countdown.ALL).toString()); // debug only
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Cano not get token from cache or cache token! " + e);
|
||||||
|
throw new Error("Oops! Failed to get token from cache or cache token.");
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// generate header
|
||||||
|
var jwtHeader = {
|
||||||
|
"alg" : "RS256",
|
||||||
|
"typ" : "JWT"
|
||||||
|
};
|
||||||
|
var tStart = Math.floor((new Date().getTime()) / 1000);
|
||||||
|
var tStop = tStart + 3600;
|
||||||
|
// generate claim set payload
|
||||||
|
var jwtClaimSet = {
|
||||||
|
"iss" : serviceAccountEmail,
|
||||||
|
"sub" : impersonatedUserEmail,
|
||||||
|
"scope" : scopesSpaceSeparated,
|
||||||
|
"aud" : "https://accounts.google.com/o/oauth2/token",
|
||||||
|
"exp" : tStop,
|
||||||
|
"iat" : tStart
|
||||||
|
};
|
||||||
|
var jwtHeaderBase64 = Utilities.base64Encode(JSON.stringify(jwtHeader));
|
||||||
|
var jwtClaimBase64 = Utilities.base64Encode(JSON.stringify(jwtClaimSet));
|
||||||
|
var jwtPemCert = Utilities.newBlob(Utilities.base64Decode(oauthServiceAccountPrivateKeyBase64, Utilities.Charset.UTF_8)).getDataAsString();
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not generate JWT variables! " + e);
|
||||||
|
throw new Error("Oops! Failed to generate JWT variables.");
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// generate jws
|
||||||
|
var jwsjsObj = new KJUR.jws.JWS();
|
||||||
|
var rsaKey = new RSAKey();
|
||||||
|
rsaKey.readPrivateKeyFromPEMString(jwtPemCert);
|
||||||
|
var jwsResult = rsaKey.signStringWithSHA256(jwtHeaderBase64 + "." + jwtClaimBase64);
|
||||||
|
var signedJwsResultBase64 = hex2b64(jwsResult);
|
||||||
|
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
|
||||||
|
var assertionStr = jwtHeaderBase64 + "." + jwtClaimBase64 + "." + signedJwsResultBase64; // {Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}
|
||||||
|
// log duration
|
||||||
|
//Flog("Time to generate JWS: " + countdown(startTime, new Date(), countdown.ALL).toString()); // debug only
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not generate JWS! " + e);
|
||||||
|
throw new Error("Oops! Failed to generate JWS.");
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// get token and parse response
|
||||||
|
var fetchOpt = {
|
||||||
|
"method" : "post",
|
||||||
|
"payload" : {
|
||||||
|
"grant_type" : "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||||
|
//"access_type" : "offline", // not allowed for impersonization
|
||||||
|
"assertion" : assertionStr
|
||||||
|
}};
|
||||||
|
var fetchResponse = UrlFetchApp.fetch("https://accounts.google.com/o/oauth2/token", fetchOpt);
|
||||||
|
// log duration
|
||||||
|
//Flog("Time to get token: " + countdown(startTime, new Date(), countdown.ALL).toString()); // debug only
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not fetch oAuth 2.0 URL!" + e);
|
||||||
|
throw new Error("Oops! Failed to fetch oAuth 2.0 URL.");
|
||||||
|
}
|
||||||
|
// parse response
|
||||||
|
if(fetchResponse.getResponseCode() == 200){
|
||||||
|
var responseContent = JSON.parse(fetchResponse.getContentText());
|
||||||
|
} else {
|
||||||
|
throw new Error("Oops! Failed to parse response or invalid response code obtained.");
|
||||||
|
}
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// cache token
|
||||||
|
if (cacheTokenFromToUserCache) {CacheService.getUserCache().put(cacheHash, responseContent.access_token, 3550)};
|
||||||
|
} catch(e) {
|
||||||
|
Flog("Can not put token to cache! " + e);
|
||||||
|
throw new Error("Oops! Failed to put token to cache.");
|
||||||
|
}
|
||||||
|
// return success
|
||||||
|
return responseContent.access_token;
|
||||||
|
}
|
28
countdown.gs
Normal file
28
countdown.gs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
countdown.js v2.3.4 http://countdownjs.org
|
||||||
|
Copyright (c)2006-2012 Stephen M. McKamey.
|
||||||
|
Licensed under The MIT License.
|
||||||
|
*/
|
||||||
|
var module;
|
||||||
|
var countdown=function(module){var MILLISECONDS=1;var SECONDS=2;var MINUTES=4;var HOURS=8;var DAYS=16;var WEEKS=32;var MONTHS=64;var YEARS=128;var DECADES=256;var CENTURIES=512;var MILLENNIA=1024;var DEFAULTS=YEARS|MONTHS|DAYS|HOURS|MINUTES|SECONDS;var MILLISECONDS_PER_SECOND=1E3;var SECONDS_PER_MINUTE=60;var MINUTES_PER_HOUR=60;var HOURS_PER_DAY=24;var MILLISECONDS_PER_DAY=HOURS_PER_DAY*MINUTES_PER_HOUR*SECONDS_PER_MINUTE*MILLISECONDS_PER_SECOND;var DAYS_PER_WEEK=7;var MONTHS_PER_YEAR=12;var YEARS_PER_DECADE=
|
||||||
|
10;var DECADES_PER_CENTURY=10;var CENTURIES_PER_MILLENNIUM=10;var ceil=Math.ceil;var floor=Math.floor;function borrowMonths(ref,shift){var prevTime=ref.getTime();ref.setUTCMonth(ref.getUTCMonth()+shift);return Math.round((ref.getTime()-prevTime)/MILLISECONDS_PER_DAY)}function daysPerMonth(ref){var a=ref.getTime();var b=new Date(a);b.setUTCMonth(ref.getUTCMonth()+1);return Math.round((b.getTime()-a)/MILLISECONDS_PER_DAY)}function daysPerYear(ref){var a=ref.getTime();var b=new Date(a);b.setUTCFullYear(ref.getUTCFullYear()+
|
||||||
|
1);return Math.round((b.getTime()-a)/MILLISECONDS_PER_DAY)}var LABEL_MILLISECONDS=0;var LABEL_SECONDS=1;var LABEL_MINUTES=2;var LABEL_HOURS=3;var LABEL_DAYS=4;var LABEL_WEEKS=5;var LABEL_MONTHS=6;var LABEL_YEARS=7;var LABEL_DECADES=8;var LABEL_CENTURIES=9;var LABEL_MILLENNIA=10;var LABELS_SINGLUAR;var LABELS_PLURAL;function plurality(value,unit){return value+" "+(value===1?LABELS_SINGLUAR[unit]:LABELS_PLURAL[unit])}var formatList;function Timespan(){}Timespan.prototype.toString=function(){var label=
|
||||||
|
formatList(this);var count=label.length;if(!count)return"";if(count>1)label[count-1]="and "+label[count-1];return label.join(", ")};Timespan.prototype.toHTML=function(tag){tag=tag||"span";var label=formatList(this);var count=label.length;if(!count)return"";for(var i=0;i<count;i++)label[i]="<"+tag+">"+label[i]+"</"+tag+">";if(--count)label[count]="and "+label[count];return label.join(", ")};formatList=function(ts){var list=[];var value=ts.millennia;if(value)list.push(plurality(value,LABEL_MILLENNIA));
|
||||||
|
value=ts.centuries;if(value)list.push(plurality(value,LABEL_CENTURIES));value=ts.decades;if(value)list.push(plurality(value,LABEL_DECADES));value=ts.years;if(value)list.push(plurality(value,LABEL_YEARS));value=ts.months;if(value)list.push(plurality(value,LABEL_MONTHS));value=ts.weeks;if(value)list.push(plurality(value,LABEL_WEEKS));value=ts.days;if(value)list.push(plurality(value,LABEL_DAYS));value=ts.hours;if(value)list.push(plurality(value,LABEL_HOURS));value=ts.minutes;if(value)list.push(plurality(value,
|
||||||
|
LABEL_MINUTES));value=ts.seconds;if(value)list.push(plurality(value,LABEL_SECONDS));value=ts.milliseconds;if(value)list.push(plurality(value,LABEL_MILLISECONDS));return list};function rippleRounded(ts,toUnit){switch(toUnit){case "seconds":if(ts.seconds!==SECONDS_PER_MINUTE||isNaN(ts.minutes))return;ts.minutes++;ts.seconds=0;case "minutes":if(ts.minutes!==MINUTES_PER_HOUR||isNaN(ts.hours))return;ts.hours++;ts.minutes=0;case "hours":if(ts.hours!==HOURS_PER_DAY||isNaN(ts.days))return;ts.days++;ts.hours=
|
||||||
|
0;case "days":if(ts.days!==DAYS_PER_WEEK||isNaN(ts.weeks))return;ts.weeks++;ts.days=0;case "weeks":if(ts.weeks!==daysPerMonth(ts.refMonth)/DAYS_PER_WEEK||isNaN(ts.months))return;ts.months++;ts.weeks=0;case "months":if(ts.months!==MONTHS_PER_YEAR||isNaN(ts.years))return;ts.years++;ts.months=0;case "years":if(ts.years!==YEARS_PER_DECADE||isNaN(ts.decades))return;ts.decades++;ts.years=0;case "decades":if(ts.decades!==DECADES_PER_CENTURY||isNaN(ts.centuries))return;ts.centuries++;ts.decades=0;case "centuries":if(ts.centuries!==
|
||||||
|
CENTURIES_PER_MILLENNIUM||isNaN(ts.millennia))return;ts.millennia++;ts.centuries=0}}function fraction(ts,frac,fromUnit,toUnit,conversion,digits){if(ts[fromUnit]>=0){frac+=ts[fromUnit];delete ts[fromUnit]}frac/=conversion;if(frac+1<=1)return 0;if(ts[toUnit]>=0){ts[toUnit]=+(ts[toUnit]+frac).toFixed(digits);rippleRounded(ts,toUnit);return 0}return frac}function fractional(ts,digits){var frac=fraction(ts,0,"milliseconds","seconds",MILLISECONDS_PER_SECOND,digits);if(!frac)return;frac=fraction(ts,frac,
|
||||||
|
"seconds","minutes",SECONDS_PER_MINUTE,digits);if(!frac)return;frac=fraction(ts,frac,"minutes","hours",MINUTES_PER_HOUR,digits);if(!frac)return;frac=fraction(ts,frac,"hours","days",HOURS_PER_DAY,digits);if(!frac)return;frac=fraction(ts,frac,"days","weeks",DAYS_PER_WEEK,digits);if(!frac)return;frac=fraction(ts,frac,"weeks","months",daysPerMonth(ts.refMonth)/DAYS_PER_WEEK,digits);if(!frac)return;frac=fraction(ts,frac,"months","years",daysPerYear(ts.refMonth)/daysPerMonth(ts.refMonth),digits);if(!frac)return;
|
||||||
|
frac=fraction(ts,frac,"years","decades",YEARS_PER_DECADE,digits);if(!frac)return;frac=fraction(ts,frac,"decades","centuries",DECADES_PER_CENTURY,digits);if(!frac)return;frac=fraction(ts,frac,"centuries","millennia",CENTURIES_PER_MILLENNIUM,digits);if(frac)throw new Error("Fractional unit overflow");}function ripple(ts){var x;if(ts.milliseconds<0){x=ceil(-ts.milliseconds/MILLISECONDS_PER_SECOND);ts.seconds-=x;ts.milliseconds+=x*MILLISECONDS_PER_SECOND}else if(ts.milliseconds>=MILLISECONDS_PER_SECOND){ts.seconds+=
|
||||||
|
floor(ts.milliseconds/MILLISECONDS_PER_SECOND);ts.milliseconds%=MILLISECONDS_PER_SECOND}if(ts.seconds<0){x=ceil(-ts.seconds/SECONDS_PER_MINUTE);ts.minutes-=x;ts.seconds+=x*SECONDS_PER_MINUTE}else if(ts.seconds>=SECONDS_PER_MINUTE){ts.minutes+=floor(ts.seconds/SECONDS_PER_MINUTE);ts.seconds%=SECONDS_PER_MINUTE}if(ts.minutes<0){x=ceil(-ts.minutes/MINUTES_PER_HOUR);ts.hours-=x;ts.minutes+=x*MINUTES_PER_HOUR}else if(ts.minutes>=MINUTES_PER_HOUR){ts.hours+=floor(ts.minutes/MINUTES_PER_HOUR);ts.minutes%=
|
||||||
|
MINUTES_PER_HOUR}if(ts.hours<0){x=ceil(-ts.hours/HOURS_PER_DAY);ts.days-=x;ts.hours+=x*HOURS_PER_DAY}else if(ts.hours>=HOURS_PER_DAY){ts.days+=floor(ts.hours/HOURS_PER_DAY);ts.hours%=HOURS_PER_DAY}while(ts.days<0){ts.months--;ts.days+=borrowMonths(ts.refMonth,1)}if(ts.days>=DAYS_PER_WEEK){ts.weeks+=floor(ts.days/DAYS_PER_WEEK);ts.days%=DAYS_PER_WEEK}if(ts.months<0){x=ceil(-ts.months/MONTHS_PER_YEAR);ts.years-=x;ts.months+=x*MONTHS_PER_YEAR}else if(ts.months>=MONTHS_PER_YEAR){ts.years+=floor(ts.months/
|
||||||
|
MONTHS_PER_YEAR);ts.months%=MONTHS_PER_YEAR}if(ts.years>=YEARS_PER_DECADE){ts.decades+=floor(ts.years/YEARS_PER_DECADE);ts.years%=YEARS_PER_DECADE;if(ts.decades>=DECADES_PER_CENTURY){ts.centuries+=floor(ts.decades/DECADES_PER_CENTURY);ts.decades%=DECADES_PER_CENTURY;if(ts.centuries>=CENTURIES_PER_MILLENNIUM){ts.millennia+=floor(ts.centuries/CENTURIES_PER_MILLENNIUM);ts.centuries%=CENTURIES_PER_MILLENNIUM}}}}function pruneUnits(ts,units,max,digits){var count=0;if(!(units&MILLENNIA)||count>=max){ts.centuries+=
|
||||||
|
ts.millennia*CENTURIES_PER_MILLENNIUM;delete ts.millennia}else if(ts.millennia)count++;if(!(units&CENTURIES)||count>=max){ts.decades+=ts.centuries*DECADES_PER_CENTURY;delete ts.centuries}else if(ts.centuries)count++;if(!(units&DECADES)||count>=max){ts.years+=ts.decades*YEARS_PER_DECADE;delete ts.decades}else if(ts.decades)count++;if(!(units&YEARS)||count>=max){ts.months+=ts.years*MONTHS_PER_YEAR;delete ts.years}else if(ts.years)count++;if(!(units&MONTHS)||count>=max){if(ts.months)ts.days+=borrowMonths(ts.refMonth,
|
||||||
|
ts.months);delete ts.months;if(ts.days>=DAYS_PER_WEEK){ts.weeks+=floor(ts.days/DAYS_PER_WEEK);ts.days%=DAYS_PER_WEEK}}else if(ts.months)count++;if(!(units&WEEKS)||count>=max){ts.days+=ts.weeks*DAYS_PER_WEEK;delete ts.weeks}else if(ts.weeks)count++;if(!(units&DAYS)||count>=max){ts.hours+=ts.days*HOURS_PER_DAY;delete ts.days}else if(ts.days)count++;if(!(units&HOURS)||count>=max){ts.minutes+=ts.hours*MINUTES_PER_HOUR;delete ts.hours}else if(ts.hours)count++;if(!(units&MINUTES)||count>=max){ts.seconds+=
|
||||||
|
ts.minutes*SECONDS_PER_MINUTE;delete ts.minutes}else if(ts.minutes)count++;if(!(units&SECONDS)||count>=max){ts.milliseconds+=ts.seconds*MILLISECONDS_PER_SECOND;delete ts.seconds}else if(ts.seconds)count++;if(!(units&MILLISECONDS)||count>=max)fractional(ts,digits)}function populate(ts,start,end,units,max,digits){ts.start=start;ts.end=end;ts.units=units;ts.value=end.getTime()-start.getTime();if(ts.value<0){var temp=end;end=start;start=temp}ts.refMonth=new Date(start.getFullYear(),start.getMonth(),15);
|
||||||
|
try{ts.millennia=0;ts.centuries=0;ts.decades=0;ts.years=end.getUTCFullYear()-start.getUTCFullYear();ts.months=end.getUTCMonth()-start.getUTCMonth();ts.weeks=0;ts.days=end.getUTCDate()-start.getUTCDate();ts.hours=end.getUTCHours()-start.getUTCHours();ts.minutes=end.getUTCMinutes()-start.getUTCMinutes();ts.seconds=end.getUTCSeconds()-start.getUTCSeconds();ts.milliseconds=end.getUTCMilliseconds()-start.getUTCMilliseconds();ripple(ts);pruneUnits(ts,units,max,digits)}finally{delete ts.refMonth}return ts}
|
||||||
|
function getDelay(units){if(units&MILLISECONDS)return MILLISECONDS_PER_SECOND/30;if(units&SECONDS)return MILLISECONDS_PER_SECOND;if(units&MINUTES)return MILLISECONDS_PER_SECOND*SECONDS_PER_MINUTE;if(units&HOURS)return MILLISECONDS_PER_SECOND*SECONDS_PER_MINUTE*MINUTES_PER_HOUR;if(units&DAYS)return MILLISECONDS_PER_SECOND*SECONDS_PER_MINUTE*MINUTES_PER_HOUR*HOURS_PER_DAY;return MILLISECONDS_PER_SECOND*SECONDS_PER_MINUTE*MINUTES_PER_HOUR*HOURS_PER_DAY*DAYS_PER_WEEK}function countdown(start,end,units,
|
||||||
|
max,digits){var callback;units=+units||DEFAULTS;max=max>0?max:NaN;digits=digits>0?digits<20?Math.round(digits):20:0;if("function"===typeof start){callback=start;start=null}else if(!(start instanceof Date))start=start!==null&&isFinite(start)?new Date(start):null;if("function"===typeof end){callback=end;end=null}else if(!(end instanceof Date))end=end!==null&&isFinite(end)?new Date(end):null;if(!start&&!end)return new Timespan;if(!callback)return populate(new Timespan,(start||new Date),(end||new Date),
|
||||||
|
units,max,digits);var delay=getDelay(units),timerId,fn=function(){callback(populate(new Timespan,(start||new Date),(end||new Date),units,max,digits),timerId)};fn();return timerId=setInterval(fn,delay)}countdown.MILLISECONDS=MILLISECONDS;countdown.SECONDS=SECONDS;countdown.MINUTES=MINUTES;countdown.HOURS=HOURS;countdown.DAYS=DAYS;countdown.WEEKS=WEEKS;countdown.MONTHS=MONTHS;countdown.YEARS=YEARS;countdown.DECADES=DECADES;countdown.CENTURIES=CENTURIES;countdown.MILLENNIA=MILLENNIA;countdown.DEFAULTS=
|
||||||
|
DEFAULTS;countdown.ALL=MILLENNIA|CENTURIES|DECADES|YEARS|MONTHS|WEEKS|DAYS|HOURS|MINUTES|SECONDS|MILLISECONDS;var setLabels=countdown.setLabels=function(singular,plural){singular=singular||[];if(singular.split)singular=singular.split("|");plural=plural||[];if(plural.split)plural=plural.split("|");for(var i=LABEL_MILLISECONDS;i<=LABEL_MILLENNIA;i++){LABELS_SINGLUAR[i]=singular[i]||LABELS_SINGLUAR[i];LABELS_PLURAL[i]=plural[i]||LABELS_PLURAL[i]}};var resetLabels=countdown.resetLabels=function(){LABELS_SINGLUAR=
|
||||||
|
"millisecond|second|minute|hour|day|week|month|year|decade|century|millennium".split("|");LABELS_PLURAL="milliseconds|seconds|minutes|hours|days|weeks|months|years|decades|centuries|millennia".split("|")};resetLabels();return countdown}(module);
|
57
crypto.gs
Normal file
57
crypto.gs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/** @preserve
|
||||||
|
CryptoJS v3.1.2
|
||||||
|
code.google.com/p/crypto-js
|
||||||
|
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||||
|
code.google.com/p/crypto-js/wiki/License
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @preserve
|
||||||
|
(c) 2012 by Cedric Mesnil. All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://crypto-js.googlecode.com/svn-history/r668/branches/3.x/src/core.js
|
||||||
|
var CryptoJS=CryptoJS||function(Math,undefined){var C={};var C_lib=C.lib={};var Base=C_lib.Base=function(){function F(){}return{extend:function(overrides){F.prototype=this;var subtype=new F;if(overrides)subtype.mixIn(overrides);if(!subtype.hasOwnProperty("init"))subtype.init=function(){subtype.$super.init.apply(this,arguments)};subtype.init.prototype=subtype;subtype.$super=this;return subtype},create:function(){var instance=this.extend();instance.init.apply(instance,arguments);return instance},init:function(){},
|
||||||
|
mixIn:function(properties){for(var propertyName in properties)if(properties.hasOwnProperty(propertyName))this[propertyName]=properties[propertyName];if(properties.hasOwnProperty("toString"))this.toString=properties.toString},clone:function(){return this.init.prototype.extend(this)}}}();var WordArray=C_lib.WordArray=Base.extend({init:function(words,sigBytes){words=this.words=words||[];if(sigBytes!=undefined)this.sigBytes=sigBytes;else this.sigBytes=words.length*4},toString:function(encoder){return(encoder||
|
||||||
|
Hex).stringify(this)},concat:function(wordArray){var thisWords=this.words;var thatWords=wordArray.words;var thisSigBytes=this.sigBytes;var thatSigBytes=wordArray.sigBytes;this.clamp();if(thisSigBytes%4)for(var i=0;i<thatSigBytes;i++){var thatByte=thatWords[i>>>2]>>>24-i%4*8&255;thisWords[thisSigBytes+i>>>2]|=thatByte<<24-(thisSigBytes+i)%4*8}else for(var i=0;i<thatSigBytes;i+=4)thisWords[thisSigBytes+i>>>2]=thatWords[i>>>2];this.sigBytes+=thatSigBytes;return this},clamp:function(){var words=this.words;
|
||||||
|
var sigBytes=this.sigBytes;words[sigBytes>>>2]&=4294967295<<32-sigBytes%4*8;words.length=Math.ceil(sigBytes/4)},clone:function(){var clone=Base.clone.call(this);clone.words=this.words.slice(0);return clone},random:function(nBytes){var words=[];for(var i=0;i<nBytes;i+=4)words.push(Math.random()*4294967296|0);return new WordArray.init(words,nBytes)}});var C_enc=C.enc={};var Hex=C_enc.Hex={stringify:function(wordArray){var words=wordArray.words;var sigBytes=wordArray.sigBytes;var hexChars=[];for(var i=
|
||||||
|
0;i<sigBytes;i++){var bite=words[i>>>2]>>>24-i%4*8&255;hexChars.push((bite>>>4).toString(16));hexChars.push((bite&15).toString(16))}return hexChars.join("")},parse:function(hexStr){var hexStrLength=hexStr.length;var words=[];for(var i=0;i<hexStrLength;i+=2)words[i>>>3]|=parseInt(hexStr.substr(i,2),16)<<24-i%8*4;return new WordArray.init(words,hexStrLength/2)}};var Latin1=C_enc.Latin1={stringify:function(wordArray){var words=wordArray.words;var sigBytes=wordArray.sigBytes;var latin1Chars=[];for(var i=
|
||||||
|
0;i<sigBytes;i++){var bite=words[i>>>2]>>>24-i%4*8&255;latin1Chars.push(String.fromCharCode(bite))}return latin1Chars.join("")},parse:function(latin1Str){var latin1StrLength=latin1Str.length;var words=[];for(var i=0;i<latin1StrLength;i++)words[i>>>2]|=(latin1Str.charCodeAt(i)&255)<<24-i%4*8;return new WordArray.init(words,latin1StrLength)}};var Utf8=C_enc.Utf8={stringify:function(wordArray){try{return decodeURIComponent(escape(Latin1.stringify(wordArray)))}catch(e){throw new Error("Malformed UTF-8 data");
|
||||||
|
}},parse:function(utf8Str){return Latin1.parse(unescape(encodeURIComponent(utf8Str)))}};var BufferedBlockAlgorithm=C_lib.BufferedBlockAlgorithm=Base.extend({reset:function(){this._data=new WordArray.init;this._nDataBytes=0},_append:function(data){if(typeof data=="string")data=Utf8.parse(data);this._data.concat(data);this._nDataBytes+=data.sigBytes},_process:function(doFlush){var data=this._data;var dataWords=data.words;var dataSigBytes=data.sigBytes;var blockSize=this.blockSize;var blockSizeBytes=
|
||||||
|
blockSize*4;var nBlocksReady=dataSigBytes/blockSizeBytes;if(doFlush)nBlocksReady=Math.ceil(nBlocksReady);else nBlocksReady=Math.max((nBlocksReady|0)-this._minBufferSize,0);var nWordsReady=nBlocksReady*blockSize;var nBytesReady=Math.min(nWordsReady*4,dataSigBytes);if(nWordsReady){for(var offset=0;offset<nWordsReady;offset+=blockSize)this._doProcessBlock(dataWords,offset);var processedWords=dataWords.splice(0,nWordsReady);data.sigBytes-=nBytesReady}return new WordArray.init(processedWords,nBytesReady)},
|
||||||
|
clone:function(){var clone=Base.clone.call(this);clone._data=this._data.clone();return clone},_minBufferSize:0});var Hasher=C_lib.Hasher=BufferedBlockAlgorithm.extend({cfg:Base.extend(),init:function(cfg){this.cfg=this.cfg.extend(cfg);this.reset()},reset:function(){BufferedBlockAlgorithm.reset.call(this);this._doReset()},update:function(messageUpdate){this._append(messageUpdate);this._process();return this},finalize:function(messageUpdate){if(messageUpdate)this._append(messageUpdate);var hash=this._doFinalize();
|
||||||
|
return hash},blockSize:512/32,_createHelper:function(hasher){return function(message,cfg){return(new hasher.init(cfg)).finalize(message)}},_createHmacHelper:function(hasher){return function(message,key){return(new C_algo.HMAC.init(hasher,key)).finalize(message)}}});var C_algo=C.algo={};return C}(Math);
|
||||||
|
|
||||||
|
// https://crypto-js.googlecode.com/svn-history/r668/branches/3.x/src/sha1.js
|
||||||
|
(function(){var C=CryptoJS;var C_lib=C.lib;var WordArray=C_lib.WordArray;var Hasher=C_lib.Hasher;var C_algo=C.algo;var W=[];var SHA1=C_algo.SHA1=Hasher.extend({_doReset:function(){this._hash=new WordArray.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(M,offset){var H=this._hash.words;var a=H[0];var b=H[1];var c=H[2];var d=H[3];var e=H[4];for(var i=0;i<80;i++){if(i<16)W[i]=M[offset+i]|0;else{var n=W[i-3]^W[i-8]^W[i-14]^W[i-16];W[i]=n<<1|n>>>31}var t=(a<<5|a>>>
|
||||||
|
27)+e+W[i];if(i<20)t+=(b&c|~b&d)+1518500249;else if(i<40)t+=(b^c^d)+1859775393;else if(i<60)t+=(b&c|b&d|c&d)-1894007588;else t+=(b^c^d)-899497514;e=d;d=c;c=b<<30|b>>>2;b=a;a=t}H[0]=H[0]+a|0;H[1]=H[1]+b|0;H[2]=H[2]+c|0;H[3]=H[3]+d|0;H[4]=H[4]+e|0},_doFinalize:function(){var data=this._data;var dataWords=data.words;var nBitsTotal=this._nDataBytes*8;var nBitsLeft=data.sigBytes*8;dataWords[nBitsLeft>>>5]|=128<<24-nBitsLeft%32;dataWords[(nBitsLeft+64>>>9<<4)+14]=Math.floor(nBitsTotal/4294967296);dataWords[(nBitsLeft+
|
||||||
|
64>>>9<<4)+15]=nBitsTotal;data.sigBytes=dataWords.length*4;this._process();return this._hash},clone:function(){var clone=Hasher.clone.call(this);clone._hash=this._hash.clone();return clone}});C.SHA1=Hasher._createHelper(SHA1);C.HmacSHA1=Hasher._createHmacHelper(SHA1)})();
|
||||||
|
|
||||||
|
// https://crypto-js.googlecode.com/svn-history/r668/branches/3.x/src/sha256.js
|
||||||
|
(function(Math){var C=CryptoJS;var C_lib=C.lib;var WordArray=C_lib.WordArray;var Hasher=C_lib.Hasher;var C_algo=C.algo;var H=[];var K=[];(function(){function isPrime(n){var sqrtN=Math.sqrt(n);for(var factor=2;factor<=sqrtN;factor++)if(!(n%factor))return false;return true}function getFractionalBits(n){return(n-(n|0))*4294967296|0}var n=2;var nPrime=0;while(nPrime<64){if(isPrime(n)){if(nPrime<8)H[nPrime]=getFractionalBits(Math.pow(n,1/2));K[nPrime]=getFractionalBits(Math.pow(n,1/3));nPrime++}n++}})();
|
||||||
|
var W=[];var SHA256=C_algo.SHA256=Hasher.extend({_doReset:function(){this._hash=new WordArray.init(H.slice(0))},_doProcessBlock:function(M,offset){var H=this._hash.words;var a=H[0];var b=H[1];var c=H[2];var d=H[3];var e=H[4];var f=H[5];var g=H[6];var h=H[7];for(var i=0;i<64;i++){if(i<16)W[i]=M[offset+i]|0;else{var gamma0x=W[i-15];var gamma0=(gamma0x<<25|gamma0x>>>7)^(gamma0x<<14|gamma0x>>>18)^gamma0x>>>3;var gamma1x=W[i-2];var gamma1=(gamma1x<<15|gamma1x>>>17)^(gamma1x<<13|gamma1x>>>19)^gamma1x>>>
|
||||||
|
10;W[i]=gamma0+W[i-7]+gamma1+W[i-16]}var ch=e&f^~e&g;var maj=a&b^a&c^b&c;var sigma0=(a<<30|a>>>2)^(a<<19|a>>>13)^(a<<10|a>>>22);var sigma1=(e<<26|e>>>6)^(e<<21|e>>>11)^(e<<7|e>>>25);var t1=h+sigma1+ch+K[i]+W[i];var t2=sigma0+maj;h=g;g=f;f=e;e=d+t1|0;d=c;c=b;b=a;a=t1+t2|0}H[0]=H[0]+a|0;H[1]=H[1]+b|0;H[2]=H[2]+c|0;H[3]=H[3]+d|0;H[4]=H[4]+e|0;H[5]=H[5]+f|0;H[6]=H[6]+g|0;H[7]=H[7]+h|0},_doFinalize:function(){var data=this._data;var dataWords=data.words;var nBitsTotal=this._nDataBytes*8;var nBitsLeft=
|
||||||
|
data.sigBytes*8;dataWords[nBitsLeft>>>5]|=128<<24-nBitsLeft%32;dataWords[(nBitsLeft+64>>>9<<4)+14]=Math.floor(nBitsTotal/4294967296);dataWords[(nBitsLeft+64>>>9<<4)+15]=nBitsTotal;data.sigBytes=dataWords.length*4;this._process();return this._hash},clone:function(){var clone=Hasher.clone.call(this);clone._hash=this._hash.clone();return clone}});C.SHA256=Hasher._createHelper(SHA256);C.HmacSHA256=Hasher._createHmacHelper(SHA256)})(Math);
|
||||||
|
|
||||||
|
// https://crypto-js.googlecode.com/svn-history/r668/branches/3.x/src/x64-core.js
|
||||||
|
(function(undefined){var C=CryptoJS;var C_lib=C.lib;var Base=C_lib.Base;var X32WordArray=C_lib.WordArray;var C_x64=C.x64={};var X64Word=C_x64.Word=Base.extend({init:function(high,low){this.high=high;this.low=low}});var X64WordArray=C_x64.WordArray=Base.extend({init:function(words,sigBytes){words=this.words=words||[];if(sigBytes!=undefined)this.sigBytes=sigBytes;else this.sigBytes=words.length*8},toX32:function(){var x64Words=this.words;var x64WordsLength=x64Words.length;var x32Words=[];for(var i=
|
||||||
|
0;i<x64WordsLength;i++){var x64Word=x64Words[i];x32Words.push(x64Word.high);x32Words.push(x64Word.low)}return X32WordArray.create(x32Words,this.sigBytes)},clone:function(){var clone=Base.clone.call(this);var words=clone.words=this.words.slice(0);var wordsLength=words.length;for(var i=0;i<wordsLength;i++)words[i]=words[i].clone();return clone}})})();
|
||||||
|
|
||||||
|
// https://crypto-js.googlecode.com/svn-history/r668/branches/3.x/src/sha512.js
|
||||||
|
(function(){var C=CryptoJS;var C_lib=C.lib;var Hasher=C_lib.Hasher;var C_x64=C.x64;var X64Word=C_x64.Word;var X64WordArray=C_x64.WordArray;var C_algo=C.algo;function X64Word_create(){return X64Word.create.apply(X64Word,arguments)}var K=[X64Word_create(1116352408,3609767458),X64Word_create(1899447441,602891725),X64Word_create(3049323471,3964484399),X64Word_create(3921009573,2173295548),X64Word_create(961987163,4081628472),X64Word_create(1508970993,3053834265),X64Word_create(2453635748,2937671579),
|
||||||
|
X64Word_create(2870763221,3664609560),X64Word_create(3624381080,2734883394),X64Word_create(310598401,1164996542),X64Word_create(607225278,1323610764),X64Word_create(1426881987,3590304994),X64Word_create(1925078388,4068182383),X64Word_create(2162078206,991336113),X64Word_create(2614888103,633803317),X64Word_create(3248222580,3479774868),X64Word_create(3835390401,2666613458),X64Word_create(4022224774,944711139),X64Word_create(264347078,2341262773),X64Word_create(604807628,2007800933),X64Word_create(770255983,
|
||||||
|
1495990901),X64Word_create(1249150122,1856431235),X64Word_create(1555081692,3175218132),X64Word_create(1996064986,2198950837),X64Word_create(2554220882,3999719339),X64Word_create(2821834349,766784016),X64Word_create(2952996808,2566594879),X64Word_create(3210313671,3203337956),X64Word_create(3336571891,1034457026),X64Word_create(3584528711,2466948901),X64Word_create(113926993,3758326383),X64Word_create(338241895,168717936),X64Word_create(666307205,1188179964),X64Word_create(773529912,1546045734),X64Word_create(1294757372,
|
||||||
|
1522805485),X64Word_create(1396182291,2643833823),X64Word_create(1695183700,2343527390),X64Word_create(1986661051,1014477480),X64Word_create(2177026350,1206759142),X64Word_create(2456956037,344077627),X64Word_create(2730485921,1290863460),X64Word_create(2820302411,3158454273),X64Word_create(3259730800,3505952657),X64Word_create(3345764771,106217008),X64Word_create(3516065817,3606008344),X64Word_create(3600352804,1432725776),X64Word_create(4094571909,1467031594),X64Word_create(275423344,851169720),
|
||||||
|
X64Word_create(430227734,3100823752),X64Word_create(506948616,1363258195),X64Word_create(659060556,3750685593),X64Word_create(883997877,3785050280),X64Word_create(958139571,3318307427),X64Word_create(1322822218,3812723403),X64Word_create(1537002063,2003034995),X64Word_create(1747873779,3602036899),X64Word_create(1955562222,1575990012),X64Word_create(2024104815,1125592928),X64Word_create(2227730452,2716904306),X64Word_create(2361852424,442776044),X64Word_create(2428436474,593698344),X64Word_create(2756734187,
|
||||||
|
3733110249),X64Word_create(3204031479,2999351573),X64Word_create(3329325298,3815920427),X64Word_create(3391569614,3928383900),X64Word_create(3515267271,566280711),X64Word_create(3940187606,3454069534),X64Word_create(4118630271,4000239992),X64Word_create(116418474,1914138554),X64Word_create(174292421,2731055270),X64Word_create(289380356,3203993006),X64Word_create(460393269,320620315),X64Word_create(685471733,587496836),X64Word_create(852142971,1086792851),X64Word_create(1017036298,365543100),X64Word_create(1126000580,
|
||||||
|
2618297676),X64Word_create(1288033470,3409855158),X64Word_create(1501505948,4234509866),X64Word_create(1607167915,987167468),X64Word_create(1816402316,1246189591)];var W=[];(function(){for(var i=0;i<80;i++)W[i]=X64Word_create()})();var SHA512=C_algo.SHA512=Hasher.extend({_doReset:function(){this._hash=new X64WordArray.init([new X64Word.init(1779033703,4089235720),new X64Word.init(3144134277,2227873595),new X64Word.init(1013904242,4271175723),new X64Word.init(2773480762,1595750129),new X64Word.init(1359893119,
|
||||||
|
2917565137),new X64Word.init(2600822924,725511199),new X64Word.init(528734635,4215389547),new X64Word.init(1541459225,327033209)])},_doProcessBlock:function(M,offset){var H=this._hash.words;var H0=H[0];var H1=H[1];var H2=H[2];var H3=H[3];var H4=H[4];var H5=H[5];var H6=H[6];var H7=H[7];var H0h=H0.high;var H0l=H0.low;var H1h=H1.high;var H1l=H1.low;var H2h=H2.high;var H2l=H2.low;var H3h=H3.high;var H3l=H3.low;var H4h=H4.high;var H4l=H4.low;var H5h=H5.high;var H5l=H5.low;var H6h=H6.high;var H6l=H6.low;
|
||||||
|
var H7h=H7.high;var H7l=H7.low;var ah=H0h;var al=H0l;var bh=H1h;var bl=H1l;var ch=H2h;var cl=H2l;var dh=H3h;var dl=H3l;var eh=H4h;var el=H4l;var fh=H5h;var fl=H5l;var gh=H6h;var gl=H6l;var hh=H7h;var hl=H7l;for(var i=0;i<80;i++){var Wi=W[i];if(i<16){var Wih=Wi.high=M[offset+i*2]|0;var Wil=Wi.low=M[offset+i*2+1]|0}else{var gamma0x=W[i-15];var gamma0xh=gamma0x.high;var gamma0xl=gamma0x.low;var gamma0h=(gamma0xh>>>1|gamma0xl<<31)^(gamma0xh>>>8|gamma0xl<<24)^gamma0xh>>>7;var gamma0l=(gamma0xl>>>1|gamma0xh<<
|
||||||
|
31)^(gamma0xl>>>8|gamma0xh<<24)^(gamma0xl>>>7|gamma0xh<<25);var gamma1x=W[i-2];var gamma1xh=gamma1x.high;var gamma1xl=gamma1x.low;var gamma1h=(gamma1xh>>>19|gamma1xl<<13)^(gamma1xh<<3|gamma1xl>>>29)^gamma1xh>>>6;var gamma1l=(gamma1xl>>>19|gamma1xh<<13)^(gamma1xl<<3|gamma1xh>>>29)^(gamma1xl>>>6|gamma1xh<<26);var Wi7=W[i-7];var Wi7h=Wi7.high;var Wi7l=Wi7.low;var Wi16=W[i-16];var Wi16h=Wi16.high;var Wi16l=Wi16.low;var Wil=gamma0l+Wi7l;var Wih=gamma0h+Wi7h+(Wil>>>0<gamma0l>>>0?1:0);var Wil=Wil+gamma1l;
|
||||||
|
var Wih=Wih+gamma1h+(Wil>>>0<gamma1l>>>0?1:0);var Wil=Wil+Wi16l;var Wih=Wih+Wi16h+(Wil>>>0<Wi16l>>>0?1:0);Wi.high=Wih;Wi.low=Wil}var chh=eh&fh^~eh&gh;var chl=el&fl^~el≷var majh=ah&bh^ah&ch^bh&ch;var majl=al&bl^al&cl^bl&cl;var sigma0h=(ah>>>28|al<<4)^(ah<<30|al>>>2)^(ah<<25|al>>>7);var sigma0l=(al>>>28|ah<<4)^(al<<30|ah>>>2)^(al<<25|ah>>>7);var sigma1h=(eh>>>14|el<<18)^(eh>>>18|el<<14)^(eh<<23|el>>>9);var sigma1l=(el>>>14|eh<<18)^(el>>>18|eh<<14)^(el<<23|eh>>>9);var Ki=K[i];var Kih=Ki.high;var Kil=
|
||||||
|
Ki.low;var t1l=hl+sigma1l;var t1h=hh+sigma1h+(t1l>>>0<hl>>>0?1:0);var t1l=t1l+chl;var t1h=t1h+chh+(t1l>>>0<chl>>>0?1:0);var t1l=t1l+Kil;var t1h=t1h+Kih+(t1l>>>0<Kil>>>0?1:0);var t1l=t1l+Wil;var t1h=t1h+Wih+(t1l>>>0<Wil>>>0?1:0);var t2l=sigma0l+majl;var t2h=sigma0h+majh+(t2l>>>0<sigma0l>>>0?1:0);hh=gh;hl=gl;gh=fh;gl=fl;fh=eh;fl=el;el=dl+t1l|0;eh=dh+t1h+(el>>>0<dl>>>0?1:0)|0;dh=ch;dl=cl;ch=bh;cl=bl;bh=ah;bl=al;al=t1l+t2l|0;ah=t1h+t2h+(al>>>0<t1l>>>0?1:0)|0}H0l=H0.low=H0l+al;H0.high=H0h+ah+(H0l>>>0<
|
||||||
|
al>>>0?1:0);H1l=H1.low=H1l+bl;H1.high=H1h+bh+(H1l>>>0<bl>>>0?1:0);H2l=H2.low=H2l+cl;H2.high=H2h+ch+(H2l>>>0<cl>>>0?1:0);H3l=H3.low=H3l+dl;H3.high=H3h+dh+(H3l>>>0<dl>>>0?1:0);H4l=H4.low=H4l+el;H4.high=H4h+eh+(H4l>>>0<el>>>0?1:0);H5l=H5.low=H5l+fl;H5.high=H5h+fh+(H5l>>>0<fl>>>0?1:0);H6l=H6.low=H6l+gl;H6.high=H6h+gh+(H6l>>>0<gl>>>0?1:0);H7l=H7.low=H7l+hl;H7.high=H7h+hh+(H7l>>>0<hl>>>0?1:0)},_doFinalize:function(){var data=this._data;var dataWords=data.words;var nBitsTotal=this._nDataBytes*8;var nBitsLeft=
|
||||||
|
data.sigBytes*8;dataWords[nBitsLeft>>>5]|=128<<24-nBitsLeft%32;dataWords[(nBitsLeft+128>>>10<<5)+30]=Math.floor(nBitsTotal/4294967296);dataWords[(nBitsLeft+128>>>10<<5)+31]=nBitsTotal;data.sigBytes=dataWords.length*4;this._process();var hash=this._hash.toX32();return hash},clone:function(){var clone=Hasher.clone.call(this);clone._hash=this._hash.clone();return clone},blockSize:1024/32});C.SHA512=Hasher._createHelper(SHA512);C.HmacSHA512=Hasher._createHmacHelper(SHA512)})();
|
68
globals.gs
Normal file
68
globals.gs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
function setGlobalVariableToScriptProperties() {
|
||||||
|
var scriptProperties = PropertiesService.getScriptProperties();
|
||||||
|
scriptProperties.setProperty("GVAR", JSON.stringify(GVAR));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDeleteAllScriptProperties() {
|
||||||
|
var scriptProperties = PropertiesService.getScriptProperties().deleteAllProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* global variable object contains all global variables
|
||||||
|
* @type {Object}
|
||||||
|
* @const
|
||||||
|
*/
|
||||||
|
var GVAR = {
|
||||||
|
// script name for various use
|
||||||
|
"SCRIPT_NAME" : "Transfer Ownership", // used by frontend only
|
||||||
|
|
||||||
|
// email address of the user for which the application is requesting delegated access
|
||||||
|
"IMPERSONATED_USER_EMAIL" : "user_name.surname@domain.tld", // debug only
|
||||||
|
|
||||||
|
// id of the drive object for debug and testing
|
||||||
|
"IMPERSONATED_USER_DRIVE_OBJECT_ID" : "SOME_FILE_ID", // debug only
|
||||||
|
|
||||||
|
// oauth scopes separated by space as in https://developers.google.com/drive/web/scopes for drive
|
||||||
|
// needs full access to all files in the user's drive
|
||||||
|
"SCOPES_SPACE_SEPARATED" : "https://www.googleapis.com/auth/drive", // change for production
|
||||||
|
|
||||||
|
// service account email address generated from google developer console > apis & auth > credentials > oauth > create new client id > service account
|
||||||
|
"SERVICE_ACCOUNT_EMAIL" : "service_account_email@developer.gserviceaccount.com", // change for production
|
||||||
|
|
||||||
|
// service account p12 key generated from google developer console > apis & auth > credentials > oauth > create new client id > service account
|
||||||
|
// transformed to base64 pem via "openssl pkcs12 -in ~/certfilename.p12 -nodes | openssl rsa | base64 > ~/certfilename.pem.b64"
|
||||||
|
"GOOGLE_DEV_CONSOLE_OAUTH_P12_BASE64" : "THIS_IS_JUST_TEMPLATE_fg4897gf98457gf984g7f8947gf984g75f8947g5f98475gf98475gf89347g5f893475gf98347gf93487gf8945gf73489gf74875fg984f7g9483gf89437gf98347gf8934gf798457gf8947gf9347gf89347gf8947g5f98743g5f89743g5f8934g75f897g4895fg734985f7g34895fgf93475gf49f7g94835gf4985f7g845gf48957gf4985gf7f49385gf794857gf4943758fg9345fg48975gf948375gf49857gf8347g5f934875gf4897g5f84957gf475gf894357gf89435gf49875gf48975gf4785gf938457gf934875gf934875gf8437g5f94785fg8934gf89437gf934857gf89437gf89473g5f9847g5f894375gf89475gf894375gf8947g5f8947gf83475gf983475gf893475gf8934f89437g5f893475gf98347g5f98437gf589437g5f89347g5f8947g5f8947g5f89437g5f9834gf89473gf89347g5f8934g7589f7g49875gf89347gf89437g5f9847gf589437g5f89734gf89734g985gf73489gf98347gf8943gf89475gf834g5f87g34985f7g43897gf89347gf89345gf8574gf983475gf98347f5g8934g7f8934gf89347g5f89347g5f98347g5f89473g5f89734g5f98g34589f7g4589f7g98457gf98347g5f89347gf893475gf89347gf89347g5ff34f7y94837yf89347yf98437y5f8734y5f89473y9f83y4895f7y34987yf8347y5f98347yf98347yf98347y5f8974y35f89y3498f7y893475yf98347yf893457yf984375yf98347y5f98743y59f87y4398fy34895f7y98457yf893457y98f7y345985f7y89347yf89347y598fy3498f7y34897y5f893477fyf98_THIS_IS_JUST_TEMPLATE=", // change for production
|
||||||
|
|
||||||
|
// script url id
|
||||||
|
"SCRIPT_URL_ID" : "14t54yXwWL92IellyMjwhJtRgqPIznFn4q18XmLFWkPq-638cKaVkNn_6", // change for production
|
||||||
|
|
||||||
|
// script project key
|
||||||
|
"SCRIPT_PROJECT_KEY" : "OfYRv8X9K-VuJTzw32qfU0xkPvQ8bevdh", // change for productions
|
||||||
|
|
||||||
|
// script web service key
|
||||||
|
"WEB_SERVICE_KEY" : "C4A4E45C877B17AA97CCC642D98C406B", // change for production
|
||||||
|
|
||||||
|
// email address of the user to whom transfer ownership
|
||||||
|
"TRANSFER_OWNERSHIP_TO" : "admin_name.surname@domain.tld", // change for production
|
||||||
|
|
||||||
|
// domain of google apps to work with
|
||||||
|
"DOMAIN_OF_GOOGLE_APPS" : "domain.tld", // change for production
|
||||||
|
|
||||||
|
// dir scan cache file lifetime in hours
|
||||||
|
"CACHE_FILE_LIFETIME" : 96, // change for production
|
||||||
|
|
||||||
|
// root folder id for ownership transfer
|
||||||
|
"ROOT_FOLDER_ID" : "SOME_FOLDER_ID", // change for production
|
||||||
|
|
||||||
|
// cache folder id to store script data as cache
|
||||||
|
"CACHE_FOLDER_ID" : "SOME_FOLDER_ID", // change for production
|
||||||
|
|
||||||
|
// log file id
|
||||||
|
"LOG_FILE_ID" : "SOME_FILE_ID" // change for production
|
||||||
|
|
||||||
|
// mail ui file name
|
||||||
|
"MAIL_UI_FILENAME" : "mailui.html", // change for production
|
||||||
|
|
||||||
|
// webservice ui file name
|
||||||
|
"WEBSERVICE_UI_FILENAME" : "webui.html" // change for production
|
||||||
|
};
|
288
helpers.gs
Normal file
288
helpers.gs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/**
|
||||||
|
* sends transactional email
|
||||||
|
*
|
||||||
|
* @param {String} recipientEmailAddress recipient email address
|
||||||
|
* @param {String} mailVariablesObj mail variables object
|
||||||
|
* @requires MailUi.html file
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setSendTransactionalEmail(recipientEmailAddress, mailVariablesObj){
|
||||||
|
recipientEmailAddress = (recipientEmailAddress || GVAR.TRANSFER_OWNERSHIP_TO);
|
||||||
|
// #2baf2b = Green 400, #738ffe = Blue 400, #ffb300 = Amber 600, #ff7043 = Deep Orange 400; always use black text - see http://www.google.com/design/spec/style/color.html#color-ui-color-palette
|
||||||
|
mailVariablesObj = (mailVariablesObj || {
|
||||||
|
"~backgroundColor~" : "#ff7043", // #ff7043 = Deep Orange 400
|
||||||
|
"~titleText~" : "Fatal error",
|
||||||
|
"~headerMessage~" : "Fatal error",
|
||||||
|
"~mainMessage~" : "Fatal error",
|
||||||
|
"~buttonText~" : "Contact your administrator.",
|
||||||
|
"~buttonUrl~" : "https://drive.google.com/",
|
||||||
|
"~footerText~" : "Do not reply to this email."
|
||||||
|
});
|
||||||
|
// get html temlpate
|
||||||
|
var aVar = null, htmlBody = null, plainBody = "";
|
||||||
|
var htmlBody = HtmlService.createHtmlOutputFromFile(GVAR.MAIL_UI_FILENAME).getContent();
|
||||||
|
// get all email variables and replace with data values
|
||||||
|
var mailVariables = htmlBody.match(/([~])(?:(?=(\\?))\2.)*?\1/g);
|
||||||
|
if (mailVariables !== null) {
|
||||||
|
for (var i = 0, lenI = mailVariables.length; i < lenI; i++) {
|
||||||
|
htmlBody = htmlBody.replace(mailVariables[i], mailVariablesObj[mailVariables[i]]);
|
||||||
|
plainBody = htmlBody.replace(mailVariables[i], mailVariablesObj[mailVariables[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// send email
|
||||||
|
var gmailAppObj = GmailApp.sendEmail(recipientEmailAddress,
|
||||||
|
mailVariablesObj["~titleText~"],
|
||||||
|
plainBody, {
|
||||||
|
htmlBody : htmlBody,
|
||||||
|
//bcc: "name.surname@domain.tld", // debug only
|
||||||
|
noReply : true});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets total number of files shared to account
|
||||||
|
*
|
||||||
|
* @returns {Number} file count
|
||||||
|
*/
|
||||||
|
function getUserSharedFileCount(ownerEmail) {
|
||||||
|
//ownerEmail = (ownerEmail || "name.surname@domain.tld"); // debug only
|
||||||
|
var fileIterator = null, file = null, i = 0, startTime = new Date();
|
||||||
|
fileIterator = DriveApp.searchFiles("trashed != true and not ('" + GVAR.TRANSFER_OWNERSHIP_TO + "' in owners) and '" + ownerEmail + "' in owners");
|
||||||
|
/*fileIterator = DriveApp.searchFiles("trashed != true and not ('" + GVAR.TRANSFER_OWNERSHIP_TO + "' in owners) and '" + ownerEmail + "' in owners " +
|
||||||
|
"and " + "(" +
|
||||||
|
"mimeType = 'application/vnd.google-apps.document'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.drawing'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.forms'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.fusiontable'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.presentation'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.script'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.sites'"
|
||||||
|
+ " or " +
|
||||||
|
"mimeType = 'application/vnd.google-apps.spreadsheet'"
|
||||||
|
+ ")"
|
||||||
|
); // free domains only*/
|
||||||
|
while (fileIterator.hasNext()) {
|
||||||
|
file = fileIterator.next();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
var elapsedTime = countdown(startTime, new Date(), countdown.DEFAULTS).toString();
|
||||||
|
// prepare email variables and send transactional email
|
||||||
|
var mailVariablesObj = {
|
||||||
|
"~backgroundColor~" : "#738ffe", // #738ffe = Blue 400
|
||||||
|
"~titleText~" : "File count " + ownerEmail + " " + i + " in " + elapsedTime,
|
||||||
|
"~headerMessage~" : "File count " + ownerEmail + " " + i + " in " + elapsedTime,
|
||||||
|
"~mainMessage~" : "File count " + ownerEmail + " " + i + " in " + elapsedTime,
|
||||||
|
"~buttonText~" : "Stay relaxed",
|
||||||
|
"~buttonUrl~" : "https://drive.google.com/",
|
||||||
|
"~footerText~" : "Do not reply to this email."
|
||||||
|
};
|
||||||
|
var mailSendResult = setSendTransactionalEmail(GVAR.TRANSFER_OWNERSHIP_TO, mailVariablesObj);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets all drive object (file / folder) ancestors and results in true if root folder is ancestor
|
||||||
|
* @param {File|Folder} driveObj drive object id (file, folder)
|
||||||
|
* @param {Array} fillArray array to fill with ancestors
|
||||||
|
* @param {Number} iterLevel current iteration level
|
||||||
|
* @returns {Bool|Null} true if root folder found in ancestors | null if not present
|
||||||
|
*/
|
||||||
|
function getFileHasAncestor(driveObj, fillArray, iterLevel){
|
||||||
|
// process all parents
|
||||||
|
var parentIter = driveObj.getParents();
|
||||||
|
while (parentIter.hasNext()) {
|
||||||
|
var parentFolder = parentIter.next();
|
||||||
|
var parentFolderId = parentFolder.getId();
|
||||||
|
fillArray.push([parentFolderId, iterLevel]);
|
||||||
|
// drive object has root folder in ancestors
|
||||||
|
if (parentFolderId === GVAR.ROOT_FOLDER_ID) {return true};
|
||||||
|
// recursive call
|
||||||
|
if (getFileHasAncestor(parentFolder, fillArray, iterLevel)) {return true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets cache file id from script properties or settles new cache file and writes to script properties
|
||||||
|
* @param {Bool} settleNewCacheFile switch to settle new cache file
|
||||||
|
* @param {String} cacheFilePurpose cache file purpose switch
|
||||||
|
* @returns {String} cache file id
|
||||||
|
*/
|
||||||
|
function getCacheFileId(settleNewCacheFile, cacheFilePurpose) {
|
||||||
|
// defaults
|
||||||
|
if (arguments.length === 0) {settleNewCacheFile = true, cacheFilePurpose = "dirScan"};
|
||||||
|
// catch exception
|
||||||
|
try {
|
||||||
|
// set variables
|
||||||
|
var cacheFileId = null;
|
||||||
|
// prepopulate folder object with root folder
|
||||||
|
var scriptProperties = PropertiesService.getScriptProperties();
|
||||||
|
//scriptProperties.deleteProperty("cacheFileId"); // debug only
|
||||||
|
if (scriptProperties.getProperty("cacheFileId" + "_" + cacheFilePurpose) !== null && settleNewCacheFile === false) {
|
||||||
|
cacheFileId = scriptProperties.getProperty("cacheFileId" + "_" + cacheFilePurpose);
|
||||||
|
} else {
|
||||||
|
var cacheFileName = "cache_" + Utilities.formatDate((new Date()), "Europe/Prague", "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + "_" + cacheFilePurpose + ".json";
|
||||||
|
cacheFileId = DriveApp.getFolderById(GVAR.CACHE_FOLDER_ID).createFile(cacheFileName, "", MimeType.JSON).getId();
|
||||||
|
scriptProperties.setProperty("cacheFileId" + "_" + cacheFilePurpose, cacheFileId);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
Logger.log(e);
|
||||||
|
//Flog(e);
|
||||||
|
throw new Error("Oops! Can not get cache file or read from cache.");
|
||||||
|
}
|
||||||
|
// get folder structure file list
|
||||||
|
return cacheFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cleans cache file
|
||||||
|
* @requires getCacheFileId
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setCleanCacheFile(cacheFilePurpose) {
|
||||||
|
cacheFilePurpose = (cacheFilePurpose || "transResult");
|
||||||
|
var cacheFileId = getCacheFileId(false, cacheFilePurpose);
|
||||||
|
var fileCache = null;
|
||||||
|
fileCache = DriveApp.getFileById(cacheFileId);
|
||||||
|
fileCache.setContent("");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes all project triggers of given handler function
|
||||||
|
* @param {String} handlerFunction name of the handler function to be deleted
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setDeleteAllTriggersOfHandlerFunction(handlerFunction) {
|
||||||
|
var allTriggers = ScriptApp.getProjectTriggers();
|
||||||
|
for (var i = 0; i < allTriggers.length; i++) {
|
||||||
|
if (allTriggers[i].getHandlerFunction() === handlerFunction) {
|
||||||
|
ScriptApp.deleteTrigger(allTriggers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes trigger by its id
|
||||||
|
* @returns {String} triggerId id of the trigger to be deleted
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setDeleteTriggerById(triggerId) {
|
||||||
|
var allTriggers = ScriptApp.getProjectTriggers();
|
||||||
|
for (var i = 0; i < allTriggers.length; i++) {
|
||||||
|
if (allTriggers[i].getUniqueId() == triggerId) {
|
||||||
|
ScriptApp.deleteTrigger(allTriggers[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes all project triggers
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setDeleteAllTriggers(){
|
||||||
|
var allTriggers = ScriptApp.getProjectTriggers();
|
||||||
|
for (var i = 0; i < allTriggers.length; i++) {
|
||||||
|
ScriptApp.deleteTrigger(allTriggers[i]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets all domain user emails via admin sdk directory api
|
||||||
|
* @requires advanced google services Admin Directory API to be allowed and enabled in developer console
|
||||||
|
* @returns {Array} usersArray array of all domain users
|
||||||
|
*/
|
||||||
|
function getAllDomainUsersEmail() {
|
||||||
|
var pageToken, page, usersArray = [];
|
||||||
|
do {
|
||||||
|
//https://developers.google.com/admin-sdk/directory/v1/reference/users/list
|
||||||
|
page = AdminDirectory.Users.list({
|
||||||
|
domain: GVAR.DOMAIN_OF_GOOGLE_APPS,
|
||||||
|
orderBy: "email",
|
||||||
|
maxResults: 500,
|
||||||
|
pageToken: pageToken
|
||||||
|
});
|
||||||
|
var users = page.users;
|
||||||
|
if (users) {
|
||||||
|
for (var i = 0, lenI = users.length; i < lenI; i++) {
|
||||||
|
var user = users[i];
|
||||||
|
usersArray.push(user.primaryEmail);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.log("No users found.");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
pageToken = page.nextPageToken;
|
||||||
|
} while (pageToken);
|
||||||
|
return usersArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns all triggers as array
|
||||||
|
* @returns {Array} triggerArray user trigger array
|
||||||
|
*/
|
||||||
|
function getScriptTriggersArray() {
|
||||||
|
var allTriggers = ScriptApp.getProjectTriggers();
|
||||||
|
var triggerArray = [];
|
||||||
|
for (var i = 0; i < allTriggers.length; i++) {
|
||||||
|
triggerArray.push(
|
||||||
|
"Type: " + allTriggers[i].getEventType()
|
||||||
|
+ "; function: " + allTriggers[i].getHandlerFunction()
|
||||||
|
+ "; source: " + allTriggers[i].getTriggerSource()
|
||||||
|
+ "; id: " + allTriggers[i].getUniqueId()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return triggerArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logs message to log file
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function Flog(logMessage, initFile){
|
||||||
|
logMessage = (logMessage || new Date());
|
||||||
|
var logFile = null, logFileTxt = null;
|
||||||
|
logFile = DriveApp.getFileById(GVAR.LOG_FILE_ID);
|
||||||
|
if (initFile) {
|
||||||
|
logFile.setContent("Begin log file.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logFileTxt = logFile.getBlob().getDataAsString();
|
||||||
|
logFileTxt = Utilities.formatDate((new Date()), "Europe/Prague", "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + " : " + logMessage + "\n" + logFileTxt;
|
||||||
|
logFile.setContent(logFileTxt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns flog as array
|
||||||
|
* @returns {Array} logArray user log array
|
||||||
|
*/
|
||||||
|
function getFlog() {
|
||||||
|
var logFile = null, logFileTxt = null;
|
||||||
|
logFile = DriveApp.getFileById(GVAR.LOG_FILE_ID);
|
||||||
|
logFileTxt = logFile.getBlob().getDataAsString();
|
||||||
|
var logArray = logFileTxt.split("\n");
|
||||||
|
return logArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* releases user lock if exists
|
||||||
|
* @returns {Bool} success
|
||||||
|
*/
|
||||||
|
function setReleaseUserLock() {
|
||||||
|
var userLock = LockService.getUserLock();
|
||||||
|
userLock.tryLock(10000);
|
||||||
|
if (!userLock.hasLock()) {
|
||||||
|
userLock.releaseLock();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
162
jsrsasign.gs
Normal file
162
jsrsasign.gs
Normal file
File diff suppressed because one or more lines are too long
831
jwsjs.gs
Normal file
831
jwsjs.gs
Normal file
@ -0,0 +1,831 @@
|
|||||||
|
/** @preserve
|
||||||
|
* jwsjs.js - JSON Web Signature JSON Serialization (JWSJS) Class
|
||||||
|
*
|
||||||
|
* version: 2.0.0 (2013 Jul 20)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com)
|
||||||
|
*
|
||||||
|
* This software is licensed under the terms of the MIT License.
|
||||||
|
* http://kjur.github.com/jsjws/license/
|
||||||
|
*
|
||||||
|
* The above copyright and license notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileOverview
|
||||||
|
* @name jwsjs-2.0.js
|
||||||
|
* @author Kenji Urushima kenji.urushima@gmail.com
|
||||||
|
* @version 2.0.0 (2013 Jul 20)
|
||||||
|
* @since jsjws 1.2
|
||||||
|
* @license <a href="http://kjur.github.io/jsjws/license/">MIT License</a>
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof KJUR == "undefined" || !KJUR) KJUR = {};
|
||||||
|
if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Web Signature JSON Serialization (JWSJS) class.<br/>
|
||||||
|
* @class JSON Web Signature JSON Serialization (JWSJS) class
|
||||||
|
* @name KJUR.jws.JWSJS
|
||||||
|
* @property {array of String} aHeader array of Encoded JWS Headers
|
||||||
|
* @property {String} sPayload Encoded JWS payload
|
||||||
|
* @property {array of String} aSignature array of Encoded JWS signature value
|
||||||
|
* @author Kenji Urushima
|
||||||
|
* @version 1.0 (18 May 2012)
|
||||||
|
* @requires base64x.js, json-sans-eval.js, jws.js and jsrsasign library
|
||||||
|
* @see <a href="http://kjur.github.com/jsjws/">'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/</a>
|
||||||
|
* @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a>
|
||||||
|
* @see <a href="http://tools.ietf.org/html/draft-jones-json-web-signature-json-serialization-01">IETF I-D JSON Web Signature JSON Serialization (JWS-JS) specification</a>
|
||||||
|
*/
|
||||||
|
KJUR.jws.JWSJS = function() {
|
||||||
|
this.aHeader = [];
|
||||||
|
this.sPayload = "";
|
||||||
|
this.aSignature = [];
|
||||||
|
|
||||||
|
// == initialize ===================================================================
|
||||||
|
/**
|
||||||
|
* (re-)initialize this object.<br/>
|
||||||
|
* @name init
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
this.init = function() {
|
||||||
|
this.aHeader = [];
|
||||||
|
this.sPayload = "";
|
||||||
|
this.aSignature = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (re-)initialize and set first signature with JWS.<br/>
|
||||||
|
* @name initWithJWS
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @param {String} sJWS JWS signature to set
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
this.initWithJWS = function(sJWS) {
|
||||||
|
this.init();
|
||||||
|
|
||||||
|
var jws = new KJUR.jws.JWS();
|
||||||
|
jws.parseJWS(sJWS);
|
||||||
|
|
||||||
|
this.aHeader.push(jws.parsedJWS.headB64U);
|
||||||
|
this.sPayload = jws.parsedJWS.payloadB64U;
|
||||||
|
this.aSignature.push(jws.parsedJWS.sigvalB64U);
|
||||||
|
};
|
||||||
|
|
||||||
|
// == add signature ===================================================================
|
||||||
|
/**
|
||||||
|
* add a signature to existing JWS-JS by Header and PKCS1 private key.<br/>
|
||||||
|
* @name addSignatureByHeaderKey
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
* @param {String} sHead JSON string of JWS Header for adding signature.
|
||||||
|
* @param {String} sPemPrvKey string of PKCS1 private key
|
||||||
|
*/
|
||||||
|
this.addSignatureByHeaderKey = function(sHead, sPemPrvKey) {
|
||||||
|
var sPayload = b64utoutf8(this.sPayload);
|
||||||
|
|
||||||
|
var jws = new KJUR.jws.JWS();
|
||||||
|
var sJWS = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
|
||||||
|
|
||||||
|
this.aHeader.push(jws.parsedJWS.headB64U);
|
||||||
|
this.aSignature.push(jws.parsedJWS.sigvalB64U);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a signature to existing JWS-JS by Header, Payload and PKCS1 private key.<br/>
|
||||||
|
* This is to add first signature to JWS-JS object.
|
||||||
|
* @name addSignatureByHeaderPayloadKey
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
* @param {String} sHead JSON string of JWS Header for adding signature.
|
||||||
|
* @param {String} sPayload string of JWS Payload for adding signature.
|
||||||
|
* @param {String} sPemPrvKey string of PKCS1 private key
|
||||||
|
*/
|
||||||
|
this.addSignatureByHeaderPayloadKey = function(sHead, sPayload, sPemPrvKey) {
|
||||||
|
var jws = new KJUR.jws.JWS();
|
||||||
|
var sJWS = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
|
||||||
|
|
||||||
|
this.aHeader.push(jws.parsedJWS.headB64U);
|
||||||
|
this.sPayload = jws.parsedJWS.payloadB64U;
|
||||||
|
this.aSignature.push(jws.parsedJWS.sigvalB64U);
|
||||||
|
};
|
||||||
|
|
||||||
|
// == verify signature ===================================================================
|
||||||
|
/**
|
||||||
|
* verify JWS-JS object with array of certificate string.<br/>
|
||||||
|
* @name verifyWithCerts
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
* @param {array of String} aCert array of string for X.509 PEM certificate.
|
||||||
|
* @return 1 if signature is valid.
|
||||||
|
* @throw if JWS-JS signature is invalid.
|
||||||
|
*/
|
||||||
|
this.verifyWithCerts = function(aCert) {
|
||||||
|
if (this.aHeader.length != aCert.length)
|
||||||
|
throw "num headers does not match with num certs";
|
||||||
|
if (this.aSignature.length != aCert.length)
|
||||||
|
throw "num signatures does not match with num certs";
|
||||||
|
|
||||||
|
var payload = this.sPayload;
|
||||||
|
var errMsg = "";
|
||||||
|
for (var i = 0; i < aCert.length; i++) {
|
||||||
|
var cert = aCert[i];
|
||||||
|
var header = this.aHeader[i];
|
||||||
|
var sig = this.aSignature[i];
|
||||||
|
var sJWS = header + "." + payload + "." + sig;
|
||||||
|
|
||||||
|
var jws = new KJUR.jws.JWS();
|
||||||
|
try {
|
||||||
|
var result = jws.verifyJWSByPemX509Cert(sJWS, cert);
|
||||||
|
if (result != 1) {
|
||||||
|
errMsg += (i + 1) + "th signature unmatch. ";
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
errMsg += (i + 1) + "th signature fail(" + ex + "). ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errMsg == "") {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
throw errMsg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read JWS-JS string.<br/>
|
||||||
|
* @name raedJWSJS
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
* @param {String} string of JWS-JS to load.
|
||||||
|
* @throw if sJWSJS is malformed or not JSON string.
|
||||||
|
*/
|
||||||
|
this.readJWSJS = function(sJWSJS) {
|
||||||
|
var jws = new KJUR.jws.JWS();
|
||||||
|
var oJWSJS = jws.readSafeJSONString(sJWSJS);
|
||||||
|
if (oJWSJS == null) throw "argument is not JSON string: " + sJWSJS;
|
||||||
|
|
||||||
|
this.aHeader = oJWSJS.headers;
|
||||||
|
this.sPayload = oJWSJS.payload;
|
||||||
|
this.aSignature = oJWSJS.signatures;
|
||||||
|
};
|
||||||
|
|
||||||
|
// == utility ===================================================================
|
||||||
|
/**
|
||||||
|
* get JSON object for this JWS-JS object.<br/>
|
||||||
|
* @name getJSON
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
this.getJSON = function() {
|
||||||
|
return { "headers": this.aHeader,
|
||||||
|
"payload": this.sPayload,
|
||||||
|
"signatures": this.aSignature };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if this JWS-JS object is empty.<br/>
|
||||||
|
* @name isEmpty
|
||||||
|
* @memberOf KJUR.jws.JWSJS
|
||||||
|
* @function
|
||||||
|
* @return 1 if there is no signatures in this object, otherwise 0.
|
||||||
|
*/
|
||||||
|
this.isEmpty = function() {
|
||||||
|
if (this.aHeader.length == 0) return 1;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! jws-2.0.3 (c) 2012 Kenji Urushima | kjur.github.com/jsjws/license
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* jws.js - JSON Web Signature Class
|
||||||
|
*
|
||||||
|
* version: 2.0.3 (2013 Jul 30)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com)
|
||||||
|
*
|
||||||
|
* This software is licensed under the terms of the MIT License.
|
||||||
|
* http://kjur.github.com/jsjws/license/
|
||||||
|
*
|
||||||
|
* The above copyright and license notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileOverview
|
||||||
|
* @name jws-2.0.js
|
||||||
|
* @author Kenji Urushima kenji.urushima@gmail.com
|
||||||
|
* @version 2.0.3 (2013-Jul-30)
|
||||||
|
* @since jsjws 1.0
|
||||||
|
* @license <a href="http://kjur.github.io/jsjws/license/">MIT License</a>
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof KJUR == "undefined" || !KJUR) KJUR = {};
|
||||||
|
if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Web Signature(JWS) class.<br/>
|
||||||
|
* @class JSON Web Signature(JWS) class
|
||||||
|
* @property {Dictionary} parsedJWS This property is set after JWS signature verification. <br/>
|
||||||
|
* Following "parsedJWS_*" properties can be accessed as "parsedJWS.*" because of
|
||||||
|
* JsDoc restriction.
|
||||||
|
* @property {String} parsedJWS_headB64U string of Encrypted JWS Header
|
||||||
|
* @property {String} parsedJWS_payloadB64U string of Encrypted JWS Payload
|
||||||
|
* @property {String} parsedJWS_sigvalB64U string of Encrypted JWS signature value
|
||||||
|
* @property {String} parsedJWS_si string of Signature Input
|
||||||
|
* @property {String} parsedJWS_sigvalH hexadecimal string of JWS signature value
|
||||||
|
* @property {String} parsedJWS_sigvalBI BigInteger(defined in jsbn.js) object of JWS signature value
|
||||||
|
* @property {String} parsedJWS_headS string of decoded JWS Header
|
||||||
|
* @property {String} parsedJWS_headS string of decoded JWS Payload
|
||||||
|
* @author Kenji Urushima
|
||||||
|
* @version 1.1 (07 May 2012)
|
||||||
|
* @requires base64x.js, json-sans-eval.js and jsrsasign library
|
||||||
|
* @see <a href="http://kjur.github.com/jsjws/">'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/</a>
|
||||||
|
* @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a>
|
||||||
|
*/
|
||||||
|
KJUR.jws.JWS = function() {
|
||||||
|
|
||||||
|
// === utility =============================================================
|
||||||
|
/**
|
||||||
|
* check whether a String "s" is a safe JSON string or not.<br/>
|
||||||
|
* If a String "s" is a malformed JSON string or an other object type
|
||||||
|
* this returns 0, otherwise this returns 1.
|
||||||
|
* @name isSafeJSONString
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} s JSON string
|
||||||
|
* @return {Number} 1 or 0
|
||||||
|
*/
|
||||||
|
this.isSafeJSONString = function(s, h, p) {
|
||||||
|
var o = null;
|
||||||
|
try {
|
||||||
|
o = jsonParse(s);
|
||||||
|
if (typeof o != "object") return 0;
|
||||||
|
if (o.constructor === Array) return 0;
|
||||||
|
if (h) h[p] = o;
|
||||||
|
return 1;
|
||||||
|
} catch (ex) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read a String "s" as JSON object if it is safe.<br/>
|
||||||
|
* If a String "s" is a malformed JSON string or not JSON string,
|
||||||
|
* this returns null, otherwise returns JSON object.
|
||||||
|
* @name readSafeJSONString
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} s JSON string
|
||||||
|
* @return {Object} JSON object or null
|
||||||
|
* @since 1.1.1
|
||||||
|
*/
|
||||||
|
this.readSafeJSONString = function(s) {
|
||||||
|
var o = null;
|
||||||
|
try {
|
||||||
|
o = jsonParse(s);
|
||||||
|
if (typeof o != "object") return null;
|
||||||
|
if (o.constructor === Array) return null;
|
||||||
|
return o;
|
||||||
|
} catch (ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get Encoed Signature Value from JWS string.<br/>
|
||||||
|
* @name getEncodedSignatureValueFromJWS
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sJWS JWS signature string to be verified
|
||||||
|
* @return {String} string of Encoded Signature Value
|
||||||
|
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
|
||||||
|
*/
|
||||||
|
this.getEncodedSignatureValueFromJWS = function(sJWS) {
|
||||||
|
if (sJWS.match(/^[^.]+\.[^.]+\.([^.]+)$/) == null) {
|
||||||
|
throw "JWS signature is not a form of 'Head.Payload.SigValue'.";
|
||||||
|
}
|
||||||
|
return RegExp.$1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse JWS string and set public property 'parsedJWS' dictionary.<br/>
|
||||||
|
* @name parseJWS
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sJWS JWS signature string to be parsed.
|
||||||
|
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
|
||||||
|
* @throws if JWS Header is a malformed JSON string.
|
||||||
|
* @since 1.1
|
||||||
|
*/
|
||||||
|
this.parseJWS = function(sJWS, sigValNotNeeded) {
|
||||||
|
if ((this.parsedJWS !== undefined) &&
|
||||||
|
(sigValNotNeeded || (this.parsedJWS.sigvalH !== undefined))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sJWS.match(/^([^.]+)\.([^.]+)\.([^.]+)$/) == null) {
|
||||||
|
throw "JWS signature is not a form of 'Head.Payload.SigValue'.";
|
||||||
|
}
|
||||||
|
var b6Head = RegExp.$1;
|
||||||
|
var b6Payload = RegExp.$2;
|
||||||
|
var b6SigVal = RegExp.$3;
|
||||||
|
var sSI = b6Head + "." + b6Payload;
|
||||||
|
this.parsedJWS = {};
|
||||||
|
this.parsedJWS.headB64U = b6Head;
|
||||||
|
this.parsedJWS.payloadB64U = b6Payload;
|
||||||
|
this.parsedJWS.sigvalB64U = b6SigVal;
|
||||||
|
this.parsedJWS.si = sSI;
|
||||||
|
|
||||||
|
if (!sigValNotNeeded) {
|
||||||
|
var hSigVal = b64utohex(b6SigVal);
|
||||||
|
var biSigVal = parseBigInt(hSigVal, 16);
|
||||||
|
this.parsedJWS.sigvalH = hSigVal;
|
||||||
|
this.parsedJWS.sigvalBI = biSigVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sHead = b64utoutf8(b6Head);
|
||||||
|
var sPayload = b64utoutf8(b6Payload);
|
||||||
|
this.parsedJWS.headS = sHead;
|
||||||
|
this.parsedJWS.payloadS = sPayload;
|
||||||
|
|
||||||
|
if (! this.isSafeJSONString(sHead, this.parsedJWS, 'headP'))
|
||||||
|
throw "malformed JSON string for JWS Head: " + sHead;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==== JWS Validation =========================================================
|
||||||
|
function _getSignatureInputByString(sHead, sPayload) {
|
||||||
|
return utf8tob64u(sHead) + "." + utf8tob64u(sPayload);
|
||||||
|
};
|
||||||
|
|
||||||
|
function _getHashBySignatureInput(sSignatureInput, sHashAlg) {
|
||||||
|
var hashfunc = function(s) { return KJUR.crypto.Util.hashString(s, sHashAlg); };
|
||||||
|
if (hashfunc == null) throw "hash function not defined in jsrsasign: " + sHashAlg;
|
||||||
|
return hashfunc(sSignatureInput);
|
||||||
|
};
|
||||||
|
|
||||||
|
function _jws_verifySignature(sHead, sPayload, hSig, hN, hE) {
|
||||||
|
var sSignatureInput = _getSignatureInputByString(sHead, sPayload);
|
||||||
|
var biSig = parseBigInt(hSig, 16);
|
||||||
|
return _rsasign_verifySignatureWithArgs(sSignatureInput, biSig, hN, hE);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify JWS signature with naked RSA public key.<br/>
|
||||||
|
* This only supports "RS256" and "RS512" algorithm.
|
||||||
|
* @name verifyJWSByNE
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sJWS JWS signature string to be verified
|
||||||
|
* @param {String} hN hexadecimal string for modulus of RSA public key
|
||||||
|
* @param {String} hE hexadecimal string for public exponent of RSA public key
|
||||||
|
* @return {String} returns 1 when JWS signature is valid, otherwise returns 0
|
||||||
|
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
|
||||||
|
* @throws if JWS Header is a malformed JSON string.
|
||||||
|
*/
|
||||||
|
this.verifyJWSByNE = function(sJWS, hN, hE) {
|
||||||
|
this.parseJWS(sJWS);
|
||||||
|
return _rsasign_verifySignatureWithArgs(this.parsedJWS.si, this.parsedJWS.sigvalBI, hN, hE);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify JWS signature with RSA public key.<br/>
|
||||||
|
* This only supports "RS256", "RS512", "PS256" and "PS512" algorithms.
|
||||||
|
* @name verifyJWSByKey
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sJWS JWS signature string to be verified
|
||||||
|
* @param {RSAKey} key RSA public key
|
||||||
|
* @return {Boolean} returns true when JWS signature is valid, otherwise returns false
|
||||||
|
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
|
||||||
|
* @throws if JWS Header is a malformed JSON string.
|
||||||
|
*/
|
||||||
|
this.verifyJWSByKey = function(sJWS, key) {
|
||||||
|
this.parseJWS(sJWS);
|
||||||
|
var hashAlg = _jws_getHashAlgFromParsedHead(this.parsedJWS.headP);
|
||||||
|
var isPSS = this.parsedJWS.headP['alg'].substr(0, 2) == "PS";
|
||||||
|
|
||||||
|
if (key.hashAndVerify) {
|
||||||
|
return key.hashAndVerify(hashAlg,
|
||||||
|
new Buffer(this.parsedJWS.si, 'utf8').toString('base64'),
|
||||||
|
b64utob64(this.parsedJWS.sigvalB64U),
|
||||||
|
'base64',
|
||||||
|
isPSS);
|
||||||
|
} else if (isPSS) {
|
||||||
|
return key.verifyStringPSS(this.parsedJWS.si,
|
||||||
|
this.parsedJWS.sigvalH, hashAlg);
|
||||||
|
} else {
|
||||||
|
return key.verifyString(this.parsedJWS.si,
|
||||||
|
this.parsedJWS.sigvalH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify JWS signature by PEM formatted X.509 certificate.<br/>
|
||||||
|
* This only supports "RS256" and "RS512" algorithm.
|
||||||
|
* @name verifyJWSByPemX509Cert
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sJWS JWS signature string to be verified
|
||||||
|
* @param {String} sPemX509Cert string of PEM formatted X.509 certificate
|
||||||
|
* @return {String} returns 1 when JWS signature is valid, otherwise returns 0
|
||||||
|
* @throws if sJWS is not comma separated string such like "Header.Payload.Signature".
|
||||||
|
* @throws if JWS Header is a malformed JSON string.
|
||||||
|
* @since 1.1
|
||||||
|
*/
|
||||||
|
this.verifyJWSByPemX509Cert = function(sJWS, sPemX509Cert) {
|
||||||
|
this.parseJWS(sJWS);
|
||||||
|
var x509 = new X509();
|
||||||
|
x509.readCertPEM(sPemX509Cert);
|
||||||
|
return x509.subjectPublicKeyRSA.verifyString(this.parsedJWS.si, this.parsedJWS.sigvalH);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==== JWS Generation =========================================================
|
||||||
|
function _jws_getHashAlgFromParsedHead(head) {
|
||||||
|
var sigAlg = head["alg"];
|
||||||
|
var hashAlg = "";
|
||||||
|
|
||||||
|
if (sigAlg != "RS256" && sigAlg != "RS512" &&
|
||||||
|
sigAlg != "PS256" && sigAlg != "PS512")
|
||||||
|
throw "JWS signature algorithm not supported: " + sigAlg;
|
||||||
|
if (sigAlg.substr(2) == "256") hashAlg = "sha256";
|
||||||
|
if (sigAlg.substr(2) == "512") hashAlg = "sha512";
|
||||||
|
return hashAlg;
|
||||||
|
};
|
||||||
|
|
||||||
|
function _jws_getHashAlgFromHead(sHead) {
|
||||||
|
return _jws_getHashAlgFromParsedHead(jsonParse(sHead));
|
||||||
|
};
|
||||||
|
|
||||||
|
function _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD) {
|
||||||
|
var rsa = new RSAKey();
|
||||||
|
rsa.setPrivate(hN, hE, hD);
|
||||||
|
|
||||||
|
var hashAlg = _jws_getHashAlgFromHead(sHead);
|
||||||
|
var sigValue = rsa.signString(sSI, hashAlg);
|
||||||
|
return sigValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
function _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, head) {
|
||||||
|
var hashAlg = null;
|
||||||
|
if (typeof head == "undefined") {
|
||||||
|
hashAlg = _jws_getHashAlgFromHead(sHead);
|
||||||
|
} else {
|
||||||
|
hashAlg = _jws_getHashAlgFromParsedHead(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPSS = head['alg'].substr(0, 2) == "PS";
|
||||||
|
|
||||||
|
if (key.hashAndSign) {
|
||||||
|
return b64tob64u(key.hashAndSign(hashAlg, sSI, 'binary', 'base64', isPSS));
|
||||||
|
} else if (isPSS) {
|
||||||
|
return hextob64u(key.signStringPSS(sSI, hashAlg));
|
||||||
|
} else {
|
||||||
|
return hextob64u(key.signString(sSI, hashAlg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function _jws_generateSignatureValueByNED(sHead, sPayload, hN, hE, hD) {
|
||||||
|
var sSI = _getSignatureInputByString(sHead, sPayload);
|
||||||
|
return _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate JWS signature by Header, Payload and a naked RSA private key.<br/>
|
||||||
|
* This only supports "RS256" and "RS512" algorithm.
|
||||||
|
* @name generateJWSByNED
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sHead string of JWS Header
|
||||||
|
* @param {String} sPayload string of JWS Payload
|
||||||
|
* @param {String} hN hexadecimal string for modulus of RSA public key
|
||||||
|
* @param {String} hE hexadecimal string for public exponent of RSA public key
|
||||||
|
* @param {String} hD hexadecimal string for private exponent of RSA private key
|
||||||
|
* @return {String} JWS signature string
|
||||||
|
* @throws if sHead is a malformed JSON string.
|
||||||
|
* @throws if supported signature algorithm was not specified in JSON Header.
|
||||||
|
*/
|
||||||
|
this.generateJWSByNED = function(sHead, sPayload, hN, hE, hD) {
|
||||||
|
if (! this.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead;
|
||||||
|
var sSI = _getSignatureInputByString(sHead, sPayload);
|
||||||
|
var hSigValue = _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD);
|
||||||
|
var b64SigValue = hextob64u(hSigValue);
|
||||||
|
|
||||||
|
this.parsedJWS = {};
|
||||||
|
this.parsedJWS.headB64U = sSI.split(".")[0];
|
||||||
|
this.parsedJWS.payloadB64U = sSI.split(".")[1];
|
||||||
|
this.parsedJWS.sigvalB64U = b64SigValue;
|
||||||
|
|
||||||
|
return sSI + "." + b64SigValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate JWS signature by Header, Payload and a RSA private key.<br/>
|
||||||
|
* This only supports "RS256", "RS512", "PS256" and "PS512" algorithms.
|
||||||
|
* @name generateJWSByKey
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sHead string of JWS Header
|
||||||
|
* @param {String} sPayload string of JWS Payload
|
||||||
|
* @param {RSAKey} RSA private key
|
||||||
|
* @return {String} JWS signature string
|
||||||
|
* @throws if sHead is a malformed JSON string.
|
||||||
|
* @throws if supported signature algorithm was not specified in JSON Header.
|
||||||
|
*/
|
||||||
|
this.generateJWSByKey = function(sHead, sPayload, key) {
|
||||||
|
var obj = {};
|
||||||
|
if (!this.isSafeJSONString(sHead, obj, 'headP'))
|
||||||
|
throw "JWS Head is not safe JSON string: " + sHead;
|
||||||
|
var sSI = _getSignatureInputByString(sHead, sPayload);
|
||||||
|
var b64SigValue = _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, obj.headP);
|
||||||
|
|
||||||
|
this.parsedJWS = {};
|
||||||
|
this.parsedJWS.headB64U = sSI.split(".")[0];
|
||||||
|
this.parsedJWS.payloadB64U = sSI.split(".")[1];
|
||||||
|
this.parsedJWS.sigvalB64U = b64SigValue;
|
||||||
|
|
||||||
|
return sSI + "." + b64SigValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// === sign with PKCS#1 RSA private key =====================================================
|
||||||
|
function _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey) {
|
||||||
|
var rsa = new RSAKey();
|
||||||
|
rsa.readPrivateKeyFromPEMString(sPemPrvKey);
|
||||||
|
var hashAlg = _jws_getHashAlgFromHead(sHead);
|
||||||
|
var sigValue = rsa.signString(sSI, hashAlg);
|
||||||
|
return sigValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate JWS signature by Header, Payload and a PEM formatted PKCS#1 RSA private key.<br/>
|
||||||
|
* This only supports "RS256" and "RS512" algorithm.
|
||||||
|
* @name generateJWSByP1PrvKey
|
||||||
|
* @memberOf KJUR.jws.JWS
|
||||||
|
* @function
|
||||||
|
* @param {String} sHead string of JWS Header
|
||||||
|
* @param {String} sPayload string of JWS Payload
|
||||||
|
* @param {String} string for sPemPrvKey PEM formatted PKCS#1 RSA private key<br/>
|
||||||
|
* Heading and trailing space characters in PEM key will be ignored.
|
||||||
|
* @return {String} JWS signature string
|
||||||
|
* @throws if sHead is a malformed JSON string.
|
||||||
|
* @throws if supported signature algorithm was not specified in JSON Header.
|
||||||
|
* @since 1.1
|
||||||
|
*/
|
||||||
|
this.generateJWSByP1PrvKey = function(sHead, sPayload, sPemPrvKey) {
|
||||||
|
if (! this.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead;
|
||||||
|
var sSI = _getSignatureInputByString(sHead, sPayload);
|
||||||
|
var hSigValue = _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey);
|
||||||
|
var b64SigValue = hextob64u(hSigValue);
|
||||||
|
|
||||||
|
this.parsedJWS = {};
|
||||||
|
this.parsedJWS.headB64U = sSI.split(".")[0];
|
||||||
|
this.parsedJWS.payloadB64U = sSI.split(".")[1];
|
||||||
|
this.parsedJWS.sigvalB64U = b64SigValue;
|
||||||
|
|
||||||
|
return sSI + "." + b64SigValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! Mike Samuel (c) 2009 | code.google.com/p/json-sans-eval
|
||||||
|
*/
|
||||||
|
// This source code is free for use in the public domain.
|
||||||
|
// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||||
|
|
||||||
|
// http://code.google.com/p/json-sans-eval/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string of well-formed JSON text.
|
||||||
|
*
|
||||||
|
* If the input is not well-formed, then behavior is undefined, but it is
|
||||||
|
* deterministic and is guaranteed not to modify any object other than its
|
||||||
|
* return value.
|
||||||
|
*
|
||||||
|
* This does not use `eval` so is less likely to have obscure security bugs than
|
||||||
|
* json2.js.
|
||||||
|
* It is optimized for speed, so is much faster than json_parse.js.
|
||||||
|
*
|
||||||
|
* This library should be used whenever security is a concern (when JSON may
|
||||||
|
* come from an untrusted source), speed is a concern, and erroring on malformed
|
||||||
|
* JSON is *not* a concern.
|
||||||
|
*
|
||||||
|
* Pros Cons
|
||||||
|
* +-----------------------+-----------------------+
|
||||||
|
* json_sans_eval.js | Fast, secure | Not validating |
|
||||||
|
* +-----------------------+-----------------------+
|
||||||
|
* json_parse.js | Validating, secure | Slow |
|
||||||
|
* +-----------------------+-----------------------+
|
||||||
|
* json2.js | Fast, some validation | Potentially insecure |
|
||||||
|
* +-----------------------+-----------------------+
|
||||||
|
*
|
||||||
|
* json2.js is very fast, but potentially insecure since it calls `eval` to
|
||||||
|
* parse JSON data, so an attacker might be able to supply strange JS that
|
||||||
|
* looks like JSON, but that executes arbitrary javascript.
|
||||||
|
* If you do have to use json2.js with untrusted data, make sure you keep
|
||||||
|
* your version of json2.js up to date so that you get patches as they're
|
||||||
|
* released.
|
||||||
|
*
|
||||||
|
* @param {string} json per RFC 4627
|
||||||
|
* @param {function (this:Object, string, *):*} opt_reviver optional function
|
||||||
|
* that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1.
|
||||||
|
* If supplied, the function is called with a string key, and a value.
|
||||||
|
* The value is the property of 'this'. The reviver should return
|
||||||
|
* the value to use in its place. So if dates were serialized as
|
||||||
|
* {@code { "type": "Date", "time": 1234 }}, then a reviver might look like
|
||||||
|
* {@code
|
||||||
|
* function (key, value) {
|
||||||
|
* if (value && typeof value === 'object' && 'Date' === value.type) {
|
||||||
|
* return new Date(value.time);
|
||||||
|
* } else {
|
||||||
|
* return value;
|
||||||
|
* }
|
||||||
|
* }}.
|
||||||
|
* If the reviver returns {@code undefined} then the property named by key
|
||||||
|
* will be deleted from its container.
|
||||||
|
* {@code this} is bound to the object containing the specified property.
|
||||||
|
* @return {Object|Array}
|
||||||
|
* @author Mike Samuel <mikesamuel@gmail.com>
|
||||||
|
*/
|
||||||
|
var jsonParse = (function () {
|
||||||
|
var number
|
||||||
|
= '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
|
||||||
|
var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]'
|
||||||
|
+ '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
|
||||||
|
var string = '(?:\"' + oneChar + '*\")';
|
||||||
|
|
||||||
|
// Will match a value in a well-formed JSON file.
|
||||||
|
// If the input is not well-formed, may match strangely, but not in an unsafe
|
||||||
|
// way.
|
||||||
|
// Since this only matches value tokens, it does not match whitespace, colons,
|
||||||
|
// or commas.
|
||||||
|
var jsonToken = new RegExp(
|
||||||
|
'(?:false|true|null|[\\{\\}\\[\\]]'
|
||||||
|
+ '|' + number
|
||||||
|
+ '|' + string
|
||||||
|
+ ')', 'g');
|
||||||
|
|
||||||
|
// Matches escape sequences in a string literal
|
||||||
|
var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');
|
||||||
|
|
||||||
|
// Decodes escape sequences in object literals
|
||||||
|
var escapes = {
|
||||||
|
'"': '"',
|
||||||
|
'/': '/',
|
||||||
|
'\\': '\\',
|
||||||
|
'b': '\b',
|
||||||
|
'f': '\f',
|
||||||
|
'n': '\n',
|
||||||
|
'r': '\r',
|
||||||
|
't': '\t'
|
||||||
|
};
|
||||||
|
function unescapeOne(_, ch, hex) {
|
||||||
|
return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A non-falsy value that coerces to the empty string when used as a key.
|
||||||
|
var EMPTY_STRING = new String('');
|
||||||
|
var SLASH = '\\';
|
||||||
|
|
||||||
|
// Constructor to use based on an open token.
|
||||||
|
var firstTokenCtors = { '{': Object, '[': Array };
|
||||||
|
|
||||||
|
var hop = Object.hasOwnProperty;
|
||||||
|
|
||||||
|
return function (json, opt_reviver) {
|
||||||
|
// Split into tokens
|
||||||
|
var toks = json.match(jsonToken);
|
||||||
|
// Construct the object to return
|
||||||
|
var result;
|
||||||
|
var tok = toks[0];
|
||||||
|
var topLevelPrimitive = false;
|
||||||
|
if ('{' === tok) {
|
||||||
|
result = {};
|
||||||
|
} else if ('[' === tok) {
|
||||||
|
result = [];
|
||||||
|
} else {
|
||||||
|
// The RFC only allows arrays or objects at the top level, but the JSON.parse
|
||||||
|
// defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null
|
||||||
|
// at the top level.
|
||||||
|
result = [];
|
||||||
|
topLevelPrimitive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If undefined, the key in an object key/value record to use for the next
|
||||||
|
// value parsed.
|
||||||
|
var key;
|
||||||
|
// Loop over remaining tokens maintaining a stack of uncompleted objects and
|
||||||
|
// arrays.
|
||||||
|
var stack = [result];
|
||||||
|
for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) {
|
||||||
|
tok = toks[i];
|
||||||
|
|
||||||
|
var cont;
|
||||||
|
switch (tok.charCodeAt(0)) {
|
||||||
|
default: // sign or digit
|
||||||
|
cont = stack[0];
|
||||||
|
cont[key || cont.length] = +(tok);
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x22: // '"'
|
||||||
|
tok = tok.substring(1, tok.length - 1);
|
||||||
|
if (tok.indexOf(SLASH) !== -1) {
|
||||||
|
tok = tok.replace(escapeSequence, unescapeOne);
|
||||||
|
}
|
||||||
|
cont = stack[0];
|
||||||
|
if (!key) {
|
||||||
|
if (cont instanceof Array) {
|
||||||
|
key = cont.length;
|
||||||
|
} else {
|
||||||
|
key = tok || EMPTY_STRING; // Use as key for next value seen.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cont[key] = tok;
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x5b: // '['
|
||||||
|
cont = stack[0];
|
||||||
|
stack.unshift(cont[key || cont.length] = []);
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x5d: // ']'
|
||||||
|
stack.shift();
|
||||||
|
break;
|
||||||
|
case 0x66: // 'f'
|
||||||
|
cont = stack[0];
|
||||||
|
cont[key || cont.length] = false;
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x6e: // 'n'
|
||||||
|
cont = stack[0];
|
||||||
|
cont[key || cont.length] = null;
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x74: // 't'
|
||||||
|
cont = stack[0];
|
||||||
|
cont[key || cont.length] = true;
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x7b: // '{'
|
||||||
|
cont = stack[0];
|
||||||
|
stack.unshift(cont[key || cont.length] = {});
|
||||||
|
key = void 0;
|
||||||
|
break;
|
||||||
|
case 0x7d: // '}'
|
||||||
|
stack.shift();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fail if we've got an uncompleted object.
|
||||||
|
if (topLevelPrimitive) {
|
||||||
|
if (stack.length !== 1) { throw new Error(); }
|
||||||
|
result = result[0];
|
||||||
|
} else {
|
||||||
|
if (stack.length) { throw new Error(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_reviver) {
|
||||||
|
// Based on walk as implemented in http://www.json.org/json2.js
|
||||||
|
var walk = function (holder, key) {
|
||||||
|
var value = holder[key];
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
var toDelete = null;
|
||||||
|
for (var k in value) {
|
||||||
|
if (hop.call(value, k) && value !== holder) {
|
||||||
|
// Recurse to properties first. This has the effect of causing
|
||||||
|
// the reviver to be called on the object graph depth-first.
|
||||||
|
|
||||||
|
// Since 'this' is bound to the holder of the property, the
|
||||||
|
// reviver can access sibling properties of k including ones
|
||||||
|
// that have not yet been revived.
|
||||||
|
|
||||||
|
// The value returned by the reviver is used in place of the
|
||||||
|
// current value of property k.
|
||||||
|
// If it returns undefined then the property is deleted.
|
||||||
|
var v = walk(value, k);
|
||||||
|
if (v !== void 0) {
|
||||||
|
value[k] = v;
|
||||||
|
} else {
|
||||||
|
// Deleting properties inside the loop has vaguely defined
|
||||||
|
// semantics in ES3 and ES3.1.
|
||||||
|
if (!toDelete) { toDelete = []; }
|
||||||
|
toDelete.push(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toDelete) {
|
||||||
|
for (var i = toDelete.length; --i >= 0;) {
|
||||||
|
delete value[toDelete[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt_reviver.call(holder, key, value);
|
||||||
|
};
|
||||||
|
result = walk({ '': result }, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
174
mailui.html
Normal file
174
mailui.html
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<title>~titleText~</title>
|
||||||
|
<style type="text/css">
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 640px) {
|
||||||
|
h1 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 22px !important;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 18px !important;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
.content-wrap {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
.invoice {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<title>~titleText~</title>
|
||||||
|
<style type="text/css">
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 640px) {
|
||||||
|
h1 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
margin: 20px 0 5px !important;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 22px !important;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 18px !important;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
.content-wrap {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
.invoice {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6; background: #f6f6f6; margin: 0; padding: 0;">
|
||||||
|
<table class="body-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background: #f6f6f6; margin: 0; padding: 0;">
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
|
||||||
|
<td class="container" width="600" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto; padding: 0;" valign="top">
|
||||||
|
<div class="content" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
|
||||||
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #000; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: ~backgroundColor~; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
|
~headerMessage~
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
|
~mainMessage~
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
|
<a href="~buttonUrl~" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #000; text-decoration: none; line-height: 2; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background: #2baf2b; margin: 0; padding: 0; border-color: #2baf2b; border-style: solid; border-width: 10px 20px;">~buttonText~</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
|
Thank you for using this script.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="footer" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
|
||||||
|
<table width="100%" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
|
<td class="aligncenter content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; text-align: center; margin: 0; padding: 0 0 20px;" align="center" valign="top">~footerText~</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
113
readme.gs
Normal file
113
readme.gs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* NAME:
|
||||||
|
*
|
||||||
|
* Transfer Ownership
|
||||||
|
*
|
||||||
|
* VERSION:
|
||||||
|
*
|
||||||
|
* 1.2.3.1 (2015-01-15)
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Václav VESELÝ ⊂ ICTOI, s.r.o.; www.ictoi.com
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Google Apps Domain-Wide Delegation of Authority help links
|
||||||
|
*
|
||||||
|
* https://developers.google.com/drive/web/delegation#delegate_domain-wide_authority_to_your_service_account
|
||||||
|
* https://developers.google.com/google-apps/documents-list/#using_google_apps_administrative_access_to_impersonate_other_domain_users
|
||||||
|
* https://developers.google.com/gmail/xoauth2_protocol
|
||||||
|
* https://developers.google.com/drive/web/push
|
||||||
|
* https://developers.google.com/accounts/docs/OAuth2ServiceAccount
|
||||||
|
* https://developers.google.com/console/help/
|
||||||
|
* https://github.com/mcdanielgilbert/gas-oauth2-gae
|
||||||
|
* http://stackoverflow.com/questions/8999932/generating-rsa-sha1-signatures-with-javascript
|
||||||
|
* http://stackoverflow.com/questions/13652706/is-it-possible-to-impersonate-domains-users-with-google-drive-api-using-google
|
||||||
|
* http://stackoverflow.com/questions/25943631/how-can-i-create-google-apps-user-account-programatically/25950294#25950294
|
||||||
|
* curl -H "Authorization: Bearer TOKEN" https://www.googleapis.com/drive/v2/files
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* KNOWN LIMITS as on 2014-11-07 on Google Apps for Work
|
||||||
|
*
|
||||||
|
* https://developers.google.com/apps-script/guides/services/quotas
|
||||||
|
*
|
||||||
|
* Script runtime = 6 min / execution
|
||||||
|
* Triggers total runtime = 6 hr / day
|
||||||
|
* URL Fetch calls = 100,000 / day
|
||||||
|
* URL Fetch data received = 100MB / day
|
||||||
|
* Properties write = 500,000 / day
|
||||||
|
* Properties total storage = 500kB / property store
|
||||||
|
* Properties value size = 9kB / val
|
||||||
|
* One cache file size = 10MB
|
||||||
|
* Continuation tokens are generally valid for one week
|
||||||
|
* Does not work on Google Apps Free edition for files other than native as in https://support.google.com/drive/answer/2494892?hl=en
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PREREQUISITES
|
||||||
|
*
|
||||||
|
* script has to be run as a domain super administrator see https://support.google.com/a/answer/2405986
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* INSTALLATION
|
||||||
|
*
|
||||||
|
* login as domain super administrator
|
||||||
|
* open new tab
|
||||||
|
* browse to google developer console > https://console.developers.google.com/project
|
||||||
|
* create new project
|
||||||
|
* choose whatever name and project id
|
||||||
|
* navigate to "APIs & auth > Credentials > OAuth" and click "Create new Client ID"
|
||||||
|
* choose "Service account"
|
||||||
|
* copy EMAIL ADDRESS to script global variable SERVICE_ACCOUNT_EMAIL (replace the sample)
|
||||||
|
* copy CLIENT ID for later use to some text editor
|
||||||
|
* get openssl (don feed gluttons use Linux :], if on Windows try http://slproweb.com/products/Win32OpenSSL.html)
|
||||||
|
* transform your P12 key to base64 pem via terminal "openssl pkcs12 -in ~/certfilename.p12 -nodes | openssl rsa | base64 > ~/certfilename.pem.b64" (as password use 'notasecret' or whatever given by console) (adjust the command with your paths and system specifics)
|
||||||
|
* copy all text contained in openssl generated file via some text editor and remove all newlines so you have one long text string
|
||||||
|
* copy the long text string from previous step to script global variable GOOGLE_DEV_CONSOLE_OAUTH_P12_BASE64 (replace the sample)
|
||||||
|
* in the script replace all global variables in Globals.gs preferable ending with "// change for production" to whatever you want on your domain
|
||||||
|
* navigate to "APIs & auth > APIs > Browse APIs" an allow all APIs that you are going to use (in this example Drive API)
|
||||||
|
* open new tab
|
||||||
|
* browse to google apps admin console > https://admin.google.com/
|
||||||
|
* navigate to "Security > Advanced settings (may be hidden under Show more) > Authentication > Manage API client access"
|
||||||
|
* copy CLIENT ID stored in previous step to "Authorized API clients"
|
||||||
|
* fill all scopes to "One or More API Scopes"; the scopes must be the same as in global variable SCOPES_SPACE_SEPARATED (see scopes here https://developers.google.com/drive/web/scopes)
|
||||||
|
* click Authorize
|
||||||
|
* open script tab
|
||||||
|
* do first dry run to authorize the script
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEFAULT SCRIPT OAUTH SCOPES
|
||||||
|
* https://mail.google.com/
|
||||||
|
* https://www.googleapis.com/auth/drive
|
||||||
|
* https://www.googleapis.com/auth/drive.apps.readonly
|
||||||
|
* https://www.googleapis.com/auth/script.external_request
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GENERATE DRIVE TEST STRUCTURE
|
||||||
|
*
|
||||||
|
* generate random dir structure with shell script and upload to drive
|
||||||
|
* http://stackoverflow.com/questions/13400312/linux-create-random-directory-file-hierarchy
|
||||||
|
*
|
||||||
|
* OUTDIR, ASCIIONLY, DIRDEPTH, MAXFIRSTLEVELDIRS, MAXDIRCHILDREN, MAXDIRNAMELEN, MAXFILECHILDREN, MAXFILENAMELEN, MAXFILESIZE
|
||||||
|
* ./rndtree.sh ./rndtree_b 1 6 8 50 8 1 8 1 // dynamic
|
||||||
|
* ./rndtree.sh ./rndtree_b 1 5 8 500 8 1 8 1 // steep
|
||||||
|
*/
|
22
webservice.gs
Normal file
22
webservice.gs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* publishes control panel
|
||||||
|
* @param {Object} webservice parameters
|
||||||
|
* @return {HtmlOutput} html output
|
||||||
|
*/
|
||||||
|
function doGet(request) {
|
||||||
|
if (request.parameters.hasOwnProperty("key")) {
|
||||||
|
// check request secret
|
||||||
|
if (request.parameters.key == GVAR.WEB_SERVICE_KEY) {
|
||||||
|
return HtmlService
|
||||||
|
.createTemplateFromFile(GVAR.WEBSERVICE_UI_FILENAME)
|
||||||
|
.evaluate()
|
||||||
|
.setTitle(GVAR.SCRIPT_NAME)
|
||||||
|
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
|
||||||
|
//.setSandboxMode(HtmlService.SandboxMode.IFRAME);
|
||||||
|
} else {
|
||||||
|
return ContentService
|
||||||
|
.createTextOutput("Unauthorized access!")
|
||||||
|
.setMimeType(ContentService.MimeType.TEXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
217
webui.html
Normal file
217
webui.html
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user