Подключение к сервисам, не принадлежащим Google, из надстройки Google Workspace

Ваш проект надстройки Google Workspace может напрямую подключаться ко многим продуктам Google с помощью встроенных и расширенных сервисов Apps Script.

Вы также можете получить доступ к API и сервисам, не принадлежащим Google. Если служба не требует авторизации, обычно вы можете просто сделать соответствующий запрос UrlFetch , а затем надстройка интерпретирует ответ.

Однако если служба, не принадлежащая Google, требует авторизации, вам необходимо настроить OAuth для этой службы. Вы можете упростить этот процесс, используя библиотеку сценариев OAuth2 для приложений (также существует версия OAuth1 ).

Использование службы OAuth

При использовании объекта службы OAuth для подключения к службе, не принадлежащей Google, вашему дополнению Google Workspace необходимо определить, когда требуется авторизация, и, когда это необходимо, вызвать поток авторизации.

Процесс авторизации состоит из:

  1. Предупреждение пользователя о необходимости аутентификации и предоставление ссылки для запуска процесса.
  2. Получение авторизации от стороннего сервиса.
  3. Обновление надстройки для повторной попытки доступа к защищенному ресурсу.

Если требуется авторизация, не принадлежащая Google, эти данные обрабатываются инфраструктурой надстройки Google Workspace. Вашему дополнению необходимо только определять, когда необходима авторизация, и при необходимости вызывать поток авторизации.

Обнаружение необходимости авторизации

Запрос может не иметь разрешения на доступ к защищенному ресурсу по ряду причин, таких как:

  • Токен доступа еще не создан или срок его действия истек.
  • Токен доступа не распространяется на запрошенный ресурс.
  • Токен доступа не охватывает требуемые области запроса.

Ваш дополнительный код должен обнаруживать такие случаи. Функция hasAccess() библиотеки OAuth может сообщить вам, есть ли у вас в настоящее время доступ к службе. Альтернативно, при использовании запросов UrlFetchApp fetch() вы можете установить для параметра muteHttpExceptions значение true . Это предотвращает выдачу исключения в случае сбоя запроса и позволяет проверять код ответа на запрос и содержимое возвращаемого объекта HttpResponse .

Когда надстройка обнаруживает, что требуется авторизация, она должна запустить поток авторизации.

Вызов потока авторизации

Вы вызываете поток авторизации, используя службу Card для создания объекта AuthorizationException , устанавливая его свойства, а затем вызывая функцию throwException() . Прежде чем создавать исключение, вы предоставляете следующее:

  1. Необходимый. URL-адрес авторизации. Оно указывается службой, не принадлежащей Google, и представляет собой место, куда попадает пользователь при запуске процесса авторизации. Этот URL-адрес устанавливается с помощью функции setAuthorizationUrl() .
  2. Необходимый. Строка отображаемого имени ресурса. Идентифицирует ресурс для пользователя при запросе авторизации. Вы устанавливаете это имя с помощью функции setResourceDisplayName() .
  3. Имя функции обратного вызова, которая создает настраиваемый запрос авторизации . Этот обратный вызов возвращает массив встроенных объектов Card , которые составляют пользовательский интерфейс для обработки авторизации. Это необязательно; если не установлено, используется карта авторизации по умолчанию. Вы устанавливаете функцию обратного вызова с помощью функции setCustomUiCallback() .

Пример конфигурации OAuth, отличной от Google

В этом примере кода показано, как настроить надстройку для использования стороннего API, требующего OAuth. Он использует сценарий OAuth2 для приложений для создания службы доступа к API.

/**
 * Attempts to access a non-Google API using a constructed service
 * object.
 *
 * If your add-on needs access to non-Google APIs that require OAuth,
 * you need to implement this method. You can use the OAuth1 and
 * OAuth2 Apps Script libraries to help implement it.
 *
 * @param {String} url         The URL to access.
 * @param {String} method_opt  The HTTP method. Defaults to GET.
 * @param {Object} headers_opt The HTTP headers. Defaults to an empty
 *                             object. The Authorization field is added
 *                             to the headers in this method.
 * @return {HttpResponse} the result from the UrlFetchApp.fetch() call.
 */
function accessProtectedResource(url, method_opt, headers_opt) {
  var service = getOAuthService();
  var maybeAuthorized = service.hasAccess();
  if (maybeAuthorized) {
    // A token is present, but it may be expired or invalid. Make a
    // request and check the response code to be sure.

    // Make the UrlFetch request and return the result.
    var accessToken = service.getAccessToken();
    var method = method_opt || 'get';
    var headers = headers_opt || {};
    headers['Authorization'] =
        Utilities.formatString('Bearer %s', accessToken);
    var resp = UrlFetchApp.fetch(url, {
      'headers': headers,
      'method' : method,
      'muteHttpExceptions': true, // Prevents thrown HTTP exceptions.
    });

    var code = resp.getResponseCode();
    if (code >= 200 && code < 300) {
      return resp.getContentText("utf-8"); // Success
    } else if (code == 401 || code == 403) {
       // Not fully authorized for this action.
       maybeAuthorized = false;
    } else {
       // Handle other response codes by logging them and throwing an
       // exception.
       console.error("Backend server error (%s): %s", code.toString(),
                     resp.getContentText("utf-8"));
       throw ("Backend server error: " + code);
    }
  }

  if (!maybeAuthorized) {
    // Invoke the authorization flow using the default authorization
    // prompt card.
    CardService.newAuthorizationException()
        .setAuthorizationUrl(service.getAuthorizationUrl())
        .setResourceDisplayName("Display name to show to the user")
        .throwException();
  }
}

/**
 * Create a new OAuth service to facilitate accessing an API.
 * This example assumes there is a single service that the add-on needs to
 * access. Its name is used when persisting the authorized token, so ensure
 * it is unique within the scope of the property store. You must set the
 * client secret and client ID, which are obtained when registering your
 * add-on with the API.
 *
 * See the Apps Script OAuth2 Library documentation for more
 * information:
 *   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
 *
 *  @return A configured OAuth2 service object.
 */
function getOAuthService() {
  return OAuth2.createService('SERVICE_NAME')
      .setAuthorizationBaseUrl('SERVICE_AUTH_URL')
      .setTokenUrl('SERVICE_AUTH_TOKEN_URL')
      .setClientId('CLIENT_ID')
      .setClientSecret('CLIENT_SECRET')
      .setScope('SERVICE_SCOPE_REQUESTS')
      .setCallbackFunction('authCallback')
      .setCache(CacheService.getUserCache())
      .setPropertyStore(PropertiesService.getUserProperties());
}

/**
 * Boilerplate code to determine if a request is authorized and returns
 * a corresponding HTML message. When the user completes the OAuth2 flow
 * on the service provider's website, this function is invoked from the
 * service. In order for authorization to succeed you must make sure that
 * the service knows how to call this function by setting the correct
 * redirect URL.
 *
 * The redirect URL to enter is:
 * https://script.google.com/macros/d/<Apps Script ID>/usercallback
 *
 * See the Apps Script OAuth2 Library documentation for more
 * information:
 *   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
 *
 *  @param {Object} callbackRequest The request data received from the
 *                  callback function. Pass it to the service's
 *                  handleCallback() method to complete the
 *                  authorization process.
 *  @return {HtmlOutput} a success or denied HTML message to display to
 *          the user. Also sets a timer to close the window
 *          automatically.
 */
function authCallback(callbackRequest) {
  var authorized = getOAuthService().handleCallback(callbackRequest);
  if (authorized) {
    return HtmlService.createHtmlOutput(
      'Success! <script>setTimeout(function() { top.window.close() }, 1);</script>');
  } else {
    return HtmlService.createHtmlOutput('Denied');
  }
}

/**
 * Unauthorizes the non-Google service. This is useful for OAuth
 * development/testing.  Run this method (Run > resetOAuth in the script
 * editor) to reset OAuth to re-prompt the user for OAuth.
 */
function resetOAuth() {
  getOAuthService().reset();
}

Создание пользовательского запроса на авторизацию

карта авторизации сторонних сервисов

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

Пользовательское приглашение определяется путем реализации пользовательской функции обратного вызова пользовательского интерфейса , которая возвращает массив встроенных объектов Card . Этот массив должен содержать только одну карту. Если указано больше, их заголовки отображаются в виде списка, что может запутать пользователя.

Возвращенная карта должна выполнить следующие действия:

  • Дайте понять пользователю, что надстройка запрашивает разрешение на доступ к службе, не принадлежащей Google, от его имени.
  • Дайте понять, что может делать дополнение, если оно авторизовано.
  • Содержит кнопку или аналогичный виджет, который направляет пользователя на URL-адрес авторизации службы. Убедитесь, что функция этого виджета очевидна для пользователя.
  • Вышеуказанный виджет должен использовать параметр OnClose.RELOAD_ADD_ON в своем объекте OpenLink , чтобы обеспечить перезагрузку надстройки после получения авторизации.
  • Все ссылки, открываемые из запроса авторизации, должны использовать HTTPS .

Вы направляете поток авторизации на использование вашей карты, вызывая функцию setCustomUiCallback() для вашего объекта AuthorizationException .

В следующем примере показана пользовательская функция обратного вызова запроса на авторизацию:

/**
 * Returns an array of cards that comprise the customized authorization
 * prompt. Includes a button that opens the proper authorization link
 * for a non-Google service.
 *
 * When creating the text button, using the
 * setOnClose(CardService.OnClose.RELOAD_ADD_ON) function forces the add-on
 * to refresh once the authorization flow completes.
 *
 * @return {Card[]} The card representing the custom authorization prompt.
 */
function create3PAuthorizationUi() {
  var service = getOAuthService();
  var authUrl = service.getAuthorizationUrl();
  var authButton = CardService.newTextButton()
      .setText('Begin Authorization')
      .setAuthorizationAction(CardService.newAuthorizationAction()
          .setAuthorizationUrl(authUrl));

  var promptText =
      'To show you information from your 3P account that is relevant' +
      ' to the recipients of the email, this add-on needs authorization' +
      ' to: <ul><li>Read recipients of the email</li>' +
      '         <li>Read contact information from 3P account</li></ul>.';

  var card = CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader()
          .setTitle('Authorization Required'))
      .addSection(CardService.newCardSection()
          .setHeader('This add-on needs access to your 3P account.')
          .addWidget(CardService.newTextParagraph()
              .setText(promptText))
          .addWidget(CardService.newButtonSet()
              .addButton(authButton)))
      .build();
  return [card];
}

/**
 * When connecting to the non-Google service, pass the name of the
 * custom UI callback function to the AuthorizationException object
 */
function accessProtectedResource(url, method_opt, headers_opt) {
  var service = getOAuthService();
  if (service.hasAccess()) {
    // Make the UrlFetch request and return the result.
    // ...
  } else {
    // Invoke the authorization flow using a custom authorization
    // prompt card.
    CardService.newAuthorizationException()
        .setAuthorizationUrl(service.getAuthorizationUrl())
        .setResourceDisplayName("Display name to show to the user")
        .setCustomUiCallback('create3PAuthorizationUi')
        .throwException();
  }
}

Управление сторонними входами в приложения Google Workspace

Одним из распространенных применений надстроек Google Workspace является предоставление интерфейса для взаимодействия со сторонней системой из хост-приложения Google Workspace. Библиотека OAuth2 для Apps Script может помочь вам создавать подключения к сторонним службам и управлять ими.

Сторонние системы часто требуют, чтобы пользователь вошел в систему, используя идентификатор пользователя, пароль или другие учетные данные. Когда пользователь входит в вашу стороннюю службу, используя один хост Google Workspace, вы должны убедиться, что ему не придется входить в систему повторно при переключении на другой хост Google Workspace. Чтобы предотвратить повторные запросы на вход в систему, используйте либо свойства пользователя, либо токены идентификатора. Они описаны в следующих разделах.

Свойства пользователя

Вы можете хранить данные для входа пользователя в свойствах пользователя Apps Script. Например, вы можете создать свой собственный JWT из их службы входа и записать это в свойстве пользователя или записать имя пользователя и пароль для их службы.

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

жетоны удостоверения личности

Вы можете использовать токен Google ID в качестве учетных данных для входа в свой сервис. Это способ добиться единого входа. Пользователи уже вошли в Google, поскольку они находятся в хост-приложении Google.