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

Seu projeto Google Workspace complemento 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 requer 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 for do Google precisar de autorização, será necessário configurar o OAuth para esse serviço. Facilite esse processo usando a biblioteca do OAuth2 para Apps Script (também há uma versão do OAuth1).

Uso de 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 for, invocar o fluxo de autorização.

O fluxo de autorização consiste em:

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

Quando a autorização que não é do Google é necessária, a infraestrutura de complementoGoogle 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.

Para detectar que uma autorização é necessária

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

  • 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 complementar 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(), é possível definir o parâmetro muteHttpExceptions como true. Isso impede que a solicitação gere uma exceção em caso de falha, e permite analisar o código de resposta e o conteúdo 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

Para invocar o fluxo de autorização, use o serviço de cartão para criar um objeto AuthorizationException, defina as propriedades dele e chame a função throwException(). Antes de gerar a exceção, forneça o seguinte:

  1. Obrigatório. Um URL de autorização. Especificado pelo serviço que não é do Google e pelo local a que o usuário é direcionado quando começa o fluxo de autorização. Para definir esse URL, use 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. Para definir esse nome, use 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, 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 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 que exige o OAuth. O OAuth2 para Apps Script é usado para criar um serviço de acesso à 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();
}

Criar uma solicitação de autorização personalizada

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

Por padrão, uma solicitação de autorização não tem marca e só usa a string de 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 possa incluir outras informações e branding.

Defina uma solicitação personalizada implementando uma função de callback de 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 retornado precisa:

  • Deixe claro para o usuário que o complemento está solicitando permissão para acessar um serviço que não é do Google em nome dele.
  • Deixe claro o que o complemento pode fazer se autorizado.
  • conter um botão ou widget semelhante que leve o usuário ao URL de autorização do serviço. A função desse widget deve ser ó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 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();
  }
}

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 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 usando um ID do usuário, uma senha ou outra credencial. Quando um usuário faz login no serviço de terceiros enquanto estiver usando um Google Workspace host , você precisa garantir que ele não precise fazer login novamente quando mudar para outro Google Workspace host. Para evitar solicitações de login repetidas, use as propriedades do usuário ou os tokens de ID. Eles são explicados nas seções a seguir.

Propriedades do usuário

Você pode armazenar os dados de login dos usuários nas propriedades do Apps Script. Por exemplo, é possível criar seu próprio JWT a partir do serviço de login e registrá-lo em uma propriedade do usuário ou gravar 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 dos complementos. Outros usuários e outros scripts não podem acessar essas propriedades. Veja PropertiesService para mais detalhes.

Tokens de ID

Você pode usar um token de código do Google como credencial de login para seu serviço. Essa é uma maneira de fazer logon único. Os usuários já estão conectados ao Google porque estão em um app host do Google.