Como se conectar a serviços de terceiros usando um complemento do Google Workspace

Seu projeto de complemento do Google Workspace pode se conectar diretamente a muitos produtos do Google com os serviços integrados e avançados do Apps Script.

Também é possível acessar APIs e serviços que não são do Google. Se o serviço não precisar de autorização, normalmente é possível fazer uma solicitação UrlFetch apropriada e, em seguida, fazer com que seu complemento interprete a resposta.

No entanto, se o serviço que não é do Google exigir autorização, será necessário configurar o OAuth para esse serviço. Para facilitar esse processo, use a biblioteca OAuth2 para Apps Script (há também uma versão do OAuth1).

Como usar um serviço do OAuth

Ao usar um objeto de serviço OAuth para se conectar a um serviço de terceiros, seu complemento do Google Workspace precisa detectar quando a autorização é necessária e, quando necessário, invocar o fluxo de autorização.

O fluxo de autorização consiste em:

  1. Informando ao usuário que a autenticação é necessária e fornecendo um link para iniciar o processo.
  2. Conseguir autorização do serviço de terceiros.
  3. Atualize o complemento para tentar acessar o recurso protegido novamente.

Quando uma autorização de terceiros é necessária, a infraestrutura de complementos do Google Workspace processa esses detalhes. Seu complemento só precisa detectar quando a autorização é necessária e invocar o fluxo de autorização quando necessário.

Detectar que a autorização é necessária

Uma solicitação pode não ter autorização para acessar um recurso protegido por vários motivos, como:

  • O token de acesso ainda não foi gerado ou expirou.
  • O token de acesso não cobre o recurso solicitado.
  • O token de acesso não cobre os escopos necessários da solicitação.

O código do complemento deve detectar esses casos. A função hasAccess() da biblioteca OAuth pode informar se você tem acesso a um serviço. Como alternativa, ao usar solicitações UrlFetchApp fetch(), defina o parâmetro muteHttpExceptions como true. Isso impede que a solicitação gere uma exceção em caso de falha e permite examinar o código e o conteúdo de resposta da solicitação no objeto HttpResponse retornado.

Quando o complemento detecta que a autorização é necessária, ele precisa acionar o fluxo de autorização.

Como invocar o fluxo de autorização

Invoque o fluxo de autorização usando o serviço Card para criar um objeto AuthorizationException, definindo as propriedades dele e chamando a função throwException(). Antes de gerar a exceção, forneça o seguinte:

  1. Obrigatório. Um URL de autorização. Essa informação é especificada pelo serviço que não é do Google e é para onde o usuário é levado quando o fluxo de autorização é iniciado. Defina esse URL usando a função setAuthorizationUrl().
  2. Obrigatório. Uma string de nome de exibição de recursos. Identifica o recurso para o usuário quando a autorização é solicitada. Esse nome é definido usando a função setResourceDisplayName().
  3. O nome de uma função de callback que cria um prompt de autorização personalizado. Esse callback retorna uma matriz de objetos Card criados que compõem uma IU para processar a autorização. Isso é opcional. Se não for definido, o cartão de autorização padrão será usado. Defina a função de callback usando a função setCustomUiCallback().

Exemplo de configuração do OAuth que não é do Google

Este exemplo de código mostra como configurar um complemento para usar uma API que não é do Google e que requer OAuth. Ele usa o OAuth2 para Apps Script para criar um serviço e acessar a 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();
}

Criação de um prompt de autorização personalizado

cartão de autorização de serviço de terceiros

Por padrão, um prompt de autorização não tem marca e usa apenas a string do nome de exibição para indicar qual recurso o complemento está tentando acessar. No entanto, seu complemento pode definir um cartão de autorização personalizado que tenha a mesma finalidade e pode incluir outras informações e branding.

Para definir uma solicitação personalizada, implemente uma função de callback da IU personalizada que retorna uma matriz de objetos Card criados. Essa matriz precisa conter apenas um cartão. Se mais forem fornecidos, os cabeçalhos serão exibidos em uma lista, o que pode resultar em uma experiência confusa para o usuário.

O cartão devolvido precisa:

  • Deixe claro para o usuário que o complemento está solicitando permissão para acessar um serviço de terceiros em nome dele.
  • Deixe claro o que o complemento pode fazer se autorizado.
  • conter um botão ou widget semelhante que leva o usuário ao URL de autorização do serviço; Certifique-se de que a função do widget seja óbvia para o usuário.
  • O widget acima precisa usar a configuração OnClose.RELOAD_ADD_ON no objeto OpenLink para garantir que o complemento seja recarregado após a autorização ser recebida.
  • Todos os links abertos no prompt de autorização precisam usar HTTPS.

Direcione o fluxo de autorização para usar seu cartão chamando a função setCustomUiCallback() no objeto AuthorizationException.

O exemplo a seguir mostra uma função de callback de solicitação de autorização personalizada:

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

Gerenciamento de logins de terceiros nos apps do Google Workspace

Um aplicativo comum para complementos do Google Workspace é fornecer uma interface para interagir com um sistema de terceiros em um aplicativo host do Google Workspace. A biblioteca OAuth2 para Apps Script ajuda você a criar e gerenciar conexões com serviços de terceiros.

Os sistemas de terceiros geralmente exigem que o usuário faça login com um ID de usuário, senha ou outra credencial. Quando um usuário fizer login no seu serviço de terceiros enquanto estiver usando um host do Google Workspace, você precisará garantir que ele não precise fazer login novamente quando mudar para outro host do Google Workspace. Para evitar solicitações repetidas de login, use as propriedades do usuário ou os tokens de ID. Vamos explicar isso nas seções a seguir.

Propriedades do usuário

É possível armazenar os dados de login de um usuário nas propriedades do usuário do Apps Script. Por exemplo, é possível criar seu próprio JWT com base no serviço de login e registrá-lo em uma propriedade do usuário ou registrar o nome de usuário e a senha do serviço.

O escopo das propriedades do usuário é definido para que elas só possam ser acessadas por esse usuário no script do complemento. Outros usuários e scripts não podem acessar essas propriedades. Veja PropertiesService para mais detalhes.

Tokens de ID

É possível usar um token de ID do Google como credencial de login para seu serviço. Essa é uma maneira de conseguir o Logon único. Os usuários já estão conectados ao Google porque estão em um aplicativo host do Google.