Conectar-se a serviços que não são do Google de um complemento do Google Workspace

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Seu Google Workspace projeto complementar 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 de terceiros. Se o serviço não exigir autorização, normalmente é possível fazer uma solicitação UrlFetch apropriada e fazer com que o complemento interprete a resposta.

No entanto, se o serviço que não é do Google precisar de autorização, será necessário configurar o OAuth para ele. É possível facilitar esse processo usando a biblioteca OAuth2 para Apps Script. Também há uma versão do OAuth1.

Como usar um serviço OAuth

Ao usar um objeto de serviço OAuth para se conectar a um serviço que não é do Google, seu complementoGoogle Workspace precisa detectar quando a autorização é necessária e, quando isso acontecer, 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. Solicitação de autorização do serviço que não é do Google
  3. Atualizando o complemento para tentar acessar o recurso protegido novamente.

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

É necessário detectar essa autorização

Uma solicitação pode não ter autorização para acessar um recurso protegido por várias razões, 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 obrigatórios da solicitação.

O código do complemento precisa 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(), você pode definir 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 de resposta da solicitação e o conteúdo no objeto HttpResponse retornado.

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

Como invocar o fluxo de autorização

Você invoca o fluxo de autorização usando o serviço de cartão para criar um objeto AuthorizationException, definir as propriedades e chamar a função throwException(). Antes de gerar a exceção, forneça o seguinte:

  1. Obrigatório. Um URL de autorização. Ele é especificado pelo serviço que não é do Google e é o local 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 do recurso. 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 uma solicitação de autorização personalizada. 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, será usado o cartão de autorização padrão. Defina a função de callback usando a função setCustomUiCallback().

Exemplo de configuração de 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 exigir o OAuth. Ele usa o OAuth2 para Apps Script para criar um serviço para 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();
}

Como criar uma solicitação de autorização personalizada

cartão de autorização de serviço que não é do Google

Por padrão, uma solicitação 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 com a mesma finalidade e pode incluir outras informações e branding.

Para definir uma solicitação personalizada, implemente uma função de callback de IU personalizada que retorne uma matriz de objetos Card criados. Essa matriz deve conter apenas um único 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 retornado precisa fazer o seguinte:

  • Deixe claro para o usuário que o complemento está pedindo permissão para acessar um serviço que não seja do Google.
  • 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. Verifique se a função desse widget é ó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 o recebimento da autorização.
  • Todos os links abertos na solicitação de autorização precisam usar HTTPS.

Você direciona o fluxo de autorização para usar o 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();
  }
}

Como gerenciar logins de terceiros em Google Workspace apps

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

Os sistemas de terceiros geralmente exigem que o usuário faça login usando um ID do usuário, uma senha ou outra credencial. Quando um usuário faz login no seu serviço de terceiros enquanto ele usa um Google Workspace host, você precisa garantir que ele não precise fazer login novamente ao alternar para outro Google Workspace host. Para evitar solicitações de login repetidas, use propriedades de usuário ou tokens de ID. Eles são explicados nas seções a seguir.

Propriedades do usuário

Você pode 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 dele e registrá-lo em uma propriedade do usuário ou registrar o nome de usuário e a senha do serviço.

As propriedades do usuário têm escopo para que só possam ser acessadas por ele no script do complemento. Outros usuários e outros 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 a credencial de login do seu serviço. Essa é uma maneira de conectar o Logon único. Os usuários já estão conectados ao Google porque estão em um app host do Google.