Получить информацию о пользователе, когда кто-то запускает веб-приложение Google Apps Script от имени меня - PullRequest
0 голосов
/ 29 января 2020

У меня есть автономный скрипт Google Apps, развернутый как веб-приложение. Приложение выполняется как я, потому что я хочу, чтобы оно получало доступ к файлам, хранящимся на моем диске, и потому что я хочу, чтобы оно генерировало файлы Google Sheets, у которых есть некоторые диапазоны, защищенные от пользователя, которые все еще доступны для редактирования сценарием. Однако я хочу, чтобы эти файлы были разделены на папки, и каждая папка назначается пользователю, поэтому мне нужно знать, кто является пользователем при каждом запуске приложения.

Session.getActiveUser (). GetEmail ( ) не работает, поскольку веб-приложение развернуто как я, а не как пользователь. Моя другая мысль состояла в том, чтобы сделать приложение «доступным для всех, даже анонимным» (сейчас оно просто «доступно для всех»), чтобы пропустить экран входа в Google и использовать какой-либо сторонний сервис аутентификации или скрипт. Создание собственного кажется излишним, потому что кажется, что оно уже должно существовать, но до сих пор мои исследования выявили только такие вещи, как Auth0, которые кажутся несовместимыми с моим простым приложением на основе скриптов Google Apps, или я слишком неопытен, чтобы понять как их использовать.

У кого-нибудь есть предложения о том, как аутентифицировать пользователей для такого рода веб-приложений? Желательно что-нибудь, что идет с учебником или документацией для начинающих? Или есть другой способ узнать, кто запускает приложение, и в то же время выполнять его как я?

Я новичок в этом, я даже не уверен, что задаю этот вопрос правильно Предлагаемые изменения принимаются с благодарностью.

Ответы [ 2 ]

1 голос
/ 29 января 2020

Я могу подумать о двух возможных подходах к этому, когда веб-приложение развернуто для выполнения от имени пользователя, обращающегося к нему:

  1. Сценарий A: Создание учетная запись службы для доступа к файлам, хранящимся на вашем диске, и для создания листов Google.
  2. Сценарий B: Создание отдельного проекта скрипта приложений, развернутого как исполняемый файл API и вызывать его функции из основного веб-приложения.

Эти методы жизнеспособны, но у каждого есть ряд плюсов и минусов.

Оба требуют аутентификации OAuth2, но этот бит довольно легко обрабатывать благодаря Eri c библиотеке OAuth2 Коледы .

Кроме того, в обоих сценариях ios вам необходимо привязать / связать ваш основной проект скрипта приложений с проектом GCP и включить соответствующие службы, в вашем случае Google Sheets и API Google Drive (* 1023) * см. документацию для более подробной информации ).

Для Сценарий A , учетная запись службы должна быть создана в том же проекте GCP. Для сценария B вторичный проект скрипта приложений для исполняемого файла API также должен быть привязан к тому же проекту GCP.


Проблемы, указанные c для сценария A

Вам необходимо предоставить доступ к файлам и папкам, к которым вы хотите получить доступ / изменить (и / или создать контент), с учетной записью службы. У учетной записи службы есть собственный адрес электронной почты, и вы можете делиться с ней файлами / папками на диске Google, как и с любой другой учетной записью Gmail.

Для вновь созданного контента могут возникнуть проблемы с разрешениями, но, к счастью, файлы, созданные в папке, наследуют разрешения этой папки, так что вы должны быть хороши на этом фронте.

Однако вам придется напрямую использовать API REST для служб Drive и Sheets; вызов их через UrlFetch вместе с токеном доступа (сгенерированным с использованием библиотеки OAuth2) для учетной записи службы.


Проблемы, определяемые c для сценария B

Вам нужно будет настроить отдельный проект Apps Script и создать API publi c (набор не закрытых функций). ) который может быть вызван третьей стороной.

Когда сценарий привязан к тому же проекту GCP, что и основное веб-приложение, вам потребуется сгенерировать дополнительные учетные данные OAuth2 из консоли GCP под панелью IAM (Identity Access Management).

Вы будете использовать Client ID и Client Secret, чтобы сгенерировать sh token, указывающий c для вашей учетной записи (с использованием библиотеки OAuth2). Затем вы будете использовать этот токен refre sh в своем основном веб-приложении для генерации необходимого токена доступа для исполняемого файла API (также с использованием библиотеки OAuth2). Как и в предыдущем сценарии, вам нужно использовать UrlFetch для вызова методов исполняемого файла API с использованием сгенерированного токена доступа.

Следует отметить, что вы не можете использовать триггеры в исполняемом коде API, поскольку они недопустимы.


Очевидно, я упустил из виду многие детали, но это должно хватит, чтобы начать.

Желаем удачи.

0 голосов
/ 21 февраля 2020

Теперь, когда я успешно реализовал сценарий B, предложенный TheAddonDepot, я хотел поделиться несколькими деталями, которые могут помочь другим новичкам.

Вот как выглядит код в моем проекте веб-приложения:

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, выбрав «Веб-приложение». Мне пришлось добавить 'https://script.google.com/macros/d/ [некоторый длинный идентификатор] / usercallback' в качестве авторизованного URI перенаправления, но я извиняюсь, как сделал это за две недели go и не могу вспомнить, как я понял что использовать там: / Во всяком случае, здесь вы получаете идентификатор клиента и секрет клиента, используемые в функции getAppsScriptService () для генерации токена доступа.

Другие главные советы, которые я хотел бы оставить здесь для других, несмотря на то, что сценарии Google Apps могут запускаться в течение 6 минут до истечения времени ожидания, URLFetchApp.fetch () имеет тайм-аут 60 с, что является проблемой при его использовании для вызова скрипта через API, выполнение которого занимает более 60 с. Сценарий приложения, который вы вызываете, все равно будет успешно завершен в фоновом режиме, так что вам просто нужно выяснить, как обработать ошибку тайм-аута, и вызвать последующую функцию, чтобы получить то, что должна была вернуть исходная функция. Я не уверен, имеет ли это смысл, но вот вопрос, который я задал (и ответил) по этому вопросу .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...