当有人以我的身份运行谷歌应用程序脚本网络应用程序时,获取用户信息



我有一个作为web应用程序部署的独立谷歌应用程序脚本。该应用程序是以我的身份执行的,因为我希望它访问存储在我的驱动器上的文件,并且我希望它生成Google Sheets文件,这些文件具有一些保护范围,不受用户的影响,仍然可以通过脚本编辑。但是,我希望这些文件被隔离到文件夹中,每个文件夹都分配给一个用户,所以每次运行应用程序时我都需要知道用户是谁。

Session.getActiveUser().getEmail()不起作用,因为web应用程序是以我的身份部署的,而不是以用户的身份部署。我的另一个想法是让这个应用程序"对每个人都可用,甚至是匿名的"(现在它只是"对所有人都可用")跳过谷歌的登录屏幕,使用某种第三方身份验证服务或脚本。构建自己的应用程序似乎有些过头了,因为它似乎已经存在了,但到目前为止,我的研究只发现了像Auth0这样的东西,它们似乎与我简单的基于谷歌应用程序脚本的应用程序不兼容,或者我太缺乏经验,不知道如何使用它们。

有人对如何验证这种网络应用程序的用户有什么建议吗?最好是初学者友好的教程或文档?或者,有没有其他方法让我在以自己的身份执行应用程序的同时,找出谁在运行应用程序?

我对这个问题还很陌生,我甚至不确定我问这个问题的方式是否正确,所以我们非常感激所建议的编辑。

我可以想出两种方法来解决这个问题,其中Web应用程序被部署为在用户访问时执行:

  1. 场景A:创建一个服务帐户以访问存储在硬盘上的文件并生成谷歌工作表
  2. 场景B:创建一个单独的应用程序脚本项目,部署为API可执行文件,并从主Web应用程序调用其函数

这些方法是可行的,但每种方法都有很多优点和缺点。

两者都需要OAuth2身份验证,但由于Eric Koleda的OAuth2库,这一点很容易处理。

此外,在这两种情况下,您都需要将您的主Apps Script项目绑定/链接到GCP项目,并启用适当的服务,在您的情况下是Google Sheets和Google Drive API(有关更多详细信息,请参阅文档)。

对于场景A,必须在同一GCP项目下创建服务帐户。对于场景B,API可执行文件的辅助应用程序脚本项目也必须绑定到同一GCP项目。


场景A特有的问题

您需要与服务帐户共享要访问/修改(和/或在中创建内容)的文件和文件夹。该服务帐户有自己的电子邮件地址,你可以与它共享谷歌驱动器文件/文件夹,就像与任何其他gmail帐户一样。

对于新创建的内容,权限可能是一个问题,但值得庆幸的是,在文件夹下创建的文件会继承该文件夹的权限,所以你应该在这方面做得很好。

但是,您必须直接使用驱动器和工作表服务的RESTAPI;通过UrlFetch以及服务帐户的访问令牌(使用OAuth2库生成)调用它们。


场景B特有的问题

您需要设置一个单独的应用程序脚本项目,并构建一个可以由第三方调用的公共API(非私有函数的集合)。

一旦脚本绑定到与主Web应用程序相同的GCP项目,您将需要从IAM(身份访问管理)面板下的GCP控制台生成额外的OAuth2凭据。

您将使用客户端ID和客户端机密来生成特定于您的帐户的刷新令牌(使用OAuth2库)。然后,您将在主Web应用程序中使用此刷新令牌来生成API可执行文件的必需访问令牌(也使用OAuth2库)。与前面的场景一样,您需要使用UrlFetch来使用生成的访问令牌调用API可执行文件上的方法。

需要注意的一点是,不能在API可执行代码中使用触发器,因为它们是不允许的。


很明显,我已经掩盖了很多细节,但这应该足以让你开始。

祝你好运。

既然我已经成功地实现了TheAddonDepot建议的场景B,我想分享一些可能对其他新手有所帮助的细节。

以下是我的web应用程序项目中的代码:

function doGet(e) {
// Use user email to identify user folder and pass to var data
var userEmail = Session.getActiveUser().getEmail();
// Check user email against database to fetch user folder name and level of access
var userData = executeAsMe('getUserData', [userEmail]);
console.log(userData);
var appsScriptService = getAppsScriptService();
if (!appsScriptService.hasAccess()) { // This block should only run once, when I authenticate as myself to create the refresh token.
var authorizationUrl = appsScriptService.getAuthorizationUrl();
var htmlOutput = HtmlService.createHtmlOutput('<a href="' + authorizationUrl + '" target="_blank">Authorize</a>.');
htmlOutput.setTitle('FMID Authentication');
return htmlOutput;
} else {
var htmlOutput = HtmlService.createHtmlOutputFromFile('Index');
htmlOutput.setTitle('Web App Page Title');
if (userData == 'user not found') {
var data = { "userEmail": userEmail, "userFolder": null };
} else {
var data = { "userEmail": userData[0], "userFolder": userData[1] };
}
return appendDataToHtmlOutput(data, htmlOutput);
}
}
function appendDataToHtmlOutput(data, htmlOutput, idData) { // Passes data from Google Apps Script to HTML via a hidden div with id=idData
if (!idData)
idData = "mydata_htmlservice";
// data is encoded after stringifying to guarantee a safe string that will never conflict with the html
var strAppend = "<div id='" + idData + "' style='display:none;'>" + Utilities.base64Encode(JSON.stringify(data)) + "</div>";
return htmlOutput.append(strAppend);
}
function getAppsScriptService() { // Used to generate script OAuth access token for API call
// See https://github.com/gsuitedevs/apps-script-oauth2 for documentation
// The OAuth2Service class contains the configuration information for a given OAuth2 provider, including its endpoints, client IDs and secrets, etc.
// This information is not persisted to any data store, so you'll need to create this object each time you want to use it.
// Create a new service with the given name. The name will be used when persisting the authorized token, so ensure it is unique within the scope
// of the property store.
return OAuth2.createService('appsScript')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('[client ID]')
.setClientSecret('[client secret]')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Enable caching to avoid exhausting PropertiesService quotas
.setCache(CacheService.getScriptCache())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/spreadsheets')
// Requests offline access.
.setParam('access_type', 'offline')
// Consent prompt is required to ensure a refresh token is always
// returned when requesting offline access.
.setParam('prompt', 'consent');
}
function authCallback(request) { // This should only run once, when I authenticate as WF Analyst to create the refresh token.
var appsScriptService = getAppsScriptService();
var isAuthorized = appsScriptService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab.');
}
}
function executeAsMe(functionName, paramsArray) {
try {
console.log('Using Apps Script API to call function ' + functionName.toString() + ' with parameter(s) ' + paramsArray.toString());
var url = '[API URL]';
var payload = JSON.stringify({"function": functionName, "parameters": paramsArray, "devMode": true})
var params = {method:"POST",
headers: {Authorization: 'Bearer ' + getAppsScriptService().getAccessToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
var jsonResponse = JSON.parse(results).response;
if (jsonResponse == undefined) {
var jsonResults = undefined;
} else {
var jsonResults = jsonResponse.result;
}
return jsonResults;
} catch(error) {
console.log('error = ' + error);
if (error.toString().indexOf('Timeout') > 0) {
console.log('Throwing new error');
throw new Error('timeout');
} else {
throw new Error('unknown');
}
} finally {
}
}

我在生成了OAuth2凭据https://console.cloud.google.com/在API&服务>凭据>创建凭据>OAuth客户端ID,选择"Web应用程序"。我不得不加上https://script.google.com/macros/d/[some long ID]/usercallback'作为一个授权的重定向URI,但我很抱歉,因为我两周前就这么做了,我不记得我是如何弄清楚在那里使用什么的:/无论如何,这就是你在函数getAppsScriptService()中获取客户端ID和客户端机密以生成访问令牌的地方。

我想留给其他人的另一个主要提示是,虽然Google应用程序脚本可以在超时前运行6分钟,但URLFetchApp.fetch()的超时时间为60秒,这在使用它通过API调用脚本时是一个问题,执行时间超过60秒。你调用的应用程序脚本仍然会在后台成功完成,所以你只需要弄清楚如何处理超时错误,并调用后续函数来获得原始函数应该返回的内容。我不确定这是否有意义,但以下是我就这个问题提出(并回答)的问题。

最新更新