Łączenie się z usługami innych firm za pomocą dodatku Google Workspace

Projekt dodatku do Google Workspace może łączyć się bezpośrednio z wieloma usługami Google za pomocą wbudowanych i zaawansowanych usług Apps Script.

Możesz też korzystać z interfejsów API i usług innych niż Google. Jeśli usługa nie wymaga autoryzacji, możesz po prostu wysłać odpowiednie żądanie UrlFetch, a dodatek zinterpretuje odpowiedź.

Jeśli jednak usługa innej niż Google wymaga autoryzacji, musisz skonfigurować dla niej OAuth. Możesz ułatwić ten proces, korzystając z biblioteki OAuth2 dla Apps Script (jest też dostępna wersja protokołu OAuth1).

Korzystanie z usługi OAuth

Gdy do łączenia się z usługą inną niż Google używasz obiektu usługi OAuth, dodatek do Google Workspace musi wykrywać, kiedy wymagana jest autoryzacja, a kiedy to następuje.

Proces autoryzacji składa się z tych elementów:

  1. Powiadomienie użytkownika o konieczności uwierzytelnienia i podanie linku umożliwiającego rozpoczęcie procesu.
  2. Uzyskiwanie autoryzacji od usługi firmy innej niż Google.
  3. Odświeżam dodatek, aby ponownie spróbować uzyskać dostęp do chronionego zasobu.

Gdy potrzebna jest autoryzacja poza Google, informacje te są obsługiwane przez infrastrukturę dodatków do Google Workspace. Dodatek wykrywa tylko, kiedy jest potrzebna autoryzacja, i w razie potrzeby wywołuje proces autoryzacji.

Wykrywam, że wymagana jest autoryzacja

Prośba może nie mieć upoważnienia do korzystania z chronionego zasobu z różnych powodów, takich jak:

  • Token dostępu nie został jeszcze wygenerowany lub wygasł.
  • Token dostępu nie obejmuje żądanego zasobu.
  • Token dostępu nie obejmuje wymaganych zakresów żądania.

Kod dodatku powinien je wykrywać. Funkcja hasAccess() w bibliotece OAuth informuje, czy masz obecnie dostęp do usługi. Podczas korzystania z żądań UrlFetchApp fetch() możesz też ustawić wartość parametru muteHttpExceptions na true. Zapobiega to wywoływaniu przez żądanie wyjątku w przypadku niepowodzenia i umożliwia sprawdzenie kodu odpowiedzi na żądanie oraz treści w zwracanym obiekcie HttpResponse.

Gdy dodatek wykryje, że jest wymagana autoryzacja, powinien uruchomić proces autoryzacji.

Wywoływanie procesu autoryzacji

Aby wywołać proces autoryzacji, użyj usługi karty, aby utworzyć obiekt AuthorizationException, ustawić jego właściwości, a następnie wywołać funkcję throwException(). Przed zgłoszeniem wyjątku podaj te informacje:

  1. Wymagane. Adres URL autoryzacji. Jest ona określana przez usługę inną niż Google i wskazuje lokalizację, do której zostanie przekierowany użytkownik po rozpoczęciu przepływu autoryzacji. Do określania tego adresu URL służy funkcja setAuthorizationUrl().
  2. Wymagane. Ciąg tekstowy z wyświetlaną nazwą zasobu. Identyfikuje zasób użytkownikowi, gdy żądana jest autoryzacja. Tę nazwę ustawia się za pomocą funkcji setResourceDisplayName().
  3. Nazwa funkcji wywołania zwrotnego, która tworzy niestandardową prośbę o autoryzację. To wywołanie zwrotne zwraca tablicę skompilowanych obiektów Card, które tworzą interfejs użytkownika do obsługi autoryzacji. Ta opcja jest opcjonalna. Jeśli nie zostanie skonfigurowana, używana jest domyślna karta autoryzacji. Funkcję wywołania zwrotnego ustawia się za pomocą funkcji setCustomUiCallback().

Przykład konfiguracji innej niż Google OAuth

Z tego przykładowego kodu dowiesz się, jak skonfigurować dodatek tak, aby korzystał z interfejsu API innego niż Google wymagający protokołu OAuth. Wykorzystuje on protokół OAuth2 dla Apps Script do utworzenia usługi umożliwiającej dostęp do interfejsu 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();
}

Tworzenie niestandardowego promptu autoryzacji

karta autoryzacji usługi innej niż Google

Domyślnie prompt autoryzacyjny nie zawiera żadnych elementów marki i wykorzystuje ciąg wyświetlanej nazwy tylko do wskazania zasobu, do którego próbuje uzyskać dostęp dodatek. Dodatek może jednak zdefiniować dostosowaną kartę autoryzacyjną, która służy do tego samego celu i może zawierać dodatkowe informacje oraz elementy marki.

Niestandardowy prompt definiuje się, implementując niestandardową funkcję wywołania zwrotnego interfejsu, która zwraca tablicę utworzonych obiektów Card. Ta tablica powinna zawierać tylko 1 kartę. Jeśli podasz ich więcej, ich nagłówki będą wyświetlane na liście, co może być mylące dla użytkowników.

Zwrócona karta musi spełniać te warunki:

  • Poinformuj użytkownika, że dodatek prosi o pozwolenie na dostęp w jego imieniu do usługi firmy innej niż Google.
  • Wyjaśnij, co może robić dodatek, jeśli zostanie autoryzowany.
  • zawierać przycisk lub podobny widżet, który przekierowuje użytkownika do adresu URL autoryzacji usługi; Upewnij się, że funkcje tego widżetu są oczywiste dla użytkowników.
  • Powyższy widżet musi używać ustawienia OnClose.RELOAD_ADD_ON w obiekcie OpenLink, aby mieć pewność, że dodatek zostanie ponownie załadowany po odebraniu autoryzacji.
  • Wszystkie linki otwierane z poziomu prośby o autoryzację muszą używać protokołu HTTPS.

Przekierowujesz proces autoryzacji do użycia karty, wywołując funkcję setCustomUiCallback() w obiekcie AuthorizationException.

Poniższy przykład przedstawia funkcję wywołania zwrotnego niestandardowego promptu autoryzacji:

/**
 * 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();
  }
}

Zarządzanie logowaniem innych firm w aplikacjach Google Workspace

Jedną z popularnych aplikacji dodatków do Google Workspace jest udostępnianie interfejsu do interakcji z systemem innej firmy z poziomu aplikacji hosta Google Workspace. Biblioteka OAuth2 dla Apps Script pomoże Ci tworzyć połączenia z usługami innych firm i nimi zarządzać.

Systemy innych firm często wymagają od użytkownika logowania się za pomocą identyfikatora użytkownika, hasła lub innych danych logowania. Gdy użytkownik loguje się w usłudze innej firmy, gdy korzysta z jednego dostawcy hostingu Google Workspace, musisz zadbać o to, aby nie musiał ponownie się logować, gdy przechodzi na innego dostawcę hostingu Google Workspace. Aby uniknąć powtarzających się żądań logowania, użyj właściwości użytkownika lub tokenów identyfikatorów. Zostały one omówione w kolejnych sekcjach.

Właściwości użytkownika

Dane logowania użytkownika możesz przechowywać we właściwościach użytkownika Apps Script. Możesz na przykład utworzyć własny token JWT z usługi logowania i zapisać go we właściwości użytkownika lub zapisać nazwę użytkownika i hasło do usługi.

Właściwości użytkownika są ograniczone w taki sposób, że w skrypcie dodatku jest on dostępny tylko dla tego użytkownika. Inni użytkownicy i inne skrypty nie mają dostępu do tych właściwości. Więcej informacji znajdziesz w sekcji PropertiesService.

Tokeny identyfikacyjne

Możesz użyć tokena identyfikatora Google jako danych logowania do usługi. Jest to sposób na osiągnięcie logowania jednokrotnego. Użytkownicy są już zalogowani w Google, ponieważ korzystają z aplikacji hostującej Google.