move from github to git.ictoi.io

This commit is contained in:
Vaclav VESELY 2021-11-28 19:58:00 +01:00
commit fd9641db4b
11 changed files with 2584 additions and 0 deletions

624
code.gs Normal file
View 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
View 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
View 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&gl;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
View 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
View 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

File diff suppressed because one or more lines are too long

831
jwsjs.gs Normal file
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long