Gerenciar permissões OAuth detalhadas para apps do Google Chat

Os apps de chat que usam autenticação de usuário precisam oferecer suporte a permissões granulares do OAuth para permitir que os usuários concedam um subconjunto dos escopos solicitados. Por exemplo, um usuário pode conceder acesso ao nome, mas negar acesso à agenda.

O processamento de permissões granulares do OAuth depende de como você cria seu app de chat:

Apps Script

Se você criar seu app do Chat usando o Apps Script, ele vai processar as permissões granulares do OAuth automaticamente. No entanto, verifique se o código processa casos em que um usuário não concede todos os escopos solicitados. O método depende de o Apps Script ser um complemento do Google Workspace que estende o Google Chat usando o Apps Script ou um app independente do Chat criado com o Apps Script e eventos de interação.

Complementos do Google Workspace que estendem o Chat

Se você criar seu app do Chat como um complemento do Google Workspace que estende o Google Chat usando o Apps Script, siga as instruções em Processar permissões OAuth detalhadas no Apps Script.

Apps do Chat independentes do Google Apps Script

Se você criar seu app do Chat usando o Apps Script e eventos de interação, as instruções em Processar permissões OAuth detalhadas no Apps Script funcionam com uma consideração:

ScriptApp.requireScopes interrompe a execução do script se os escopos especificados não forem concedidos, mas o usuário vê um card de configuração no Chat em vez da tela de permissão do OAuth. O card de configuração sempre pede que o usuário conceda todos os escopos solicitados, em vez de apenas os não concedidos.

Para fornecer verificações individuais de autorização no nível do escopo, use ScriptApp.getAuthorizationInfo para verificar a autorização e, se necessário, solicite a autorização usando uma mensagem privada.

O exemplo a seguir mostra como verificar uma permissão específica (como acesso à agenda) e, se ela estiver faltando, retornar uma mensagem particular com o URL de autorização necessário.

Apps Script

/**
* Responds to a MESSAGE event in Google Chat.
* Checks for required permissions and if missing asks for them.
*
* @param {Object} event the event object from Chat
* @return {Object} JSON response
*/
function onMessage(event) {
  // Check if the script has the necessary permissions.
  // In this example, the script checks for the "calendar.events" scope.
  var requiredScopes = ['https://www.googleapis.com/auth/calendar.events'];
  var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL, requiredScopes);

  // If permissions are missing, return a message with the authorization URL.
  if (authInfo.getAuthorizationStatus() === ScriptApp.AuthorizationStatus.REQUIRED) {
    var authUrl = authInfo.getAuthorizationUrl();
    return {
      "text": "This action requires authorization. Please <" + authUrl + "|click here to authorize>.",
      "privateMessageViewer": {
        "name": event.user.name
      }
    };
  }

  // Permission granted; proceed with the application logic.
  // ...
}

Endpoints HTTP

Se você criar seu app Chat usando endpoints HTTP, ele precisará oferecer suporte a permissões granulares do OAuth.

Complementos do Google Workspace que estendem o Chat

Se você criar seu app do Chat como um complemento do Google Workspace (por exemplo, se ele estender outros apps do Google Workspace, como o Google Drive ou o Gmail), configure o arquivo de manifesto e o código para processar permissões granulares do OAuth:

  1. No arquivo de manifesto do complemento, defina o campo granularOauthPermissionSupport como OPT_IN. Para saber mais sobre o campo granularOauthPermissionSupport, consulte Migrar para o fluxo de permissões granulares do OAuth.

    JSON

    {
      "oauthScopes": [
        "https://www.googleapis.com/auth/chat.messages",
        "https://www.googleapis.com/auth/calendar.events"
      ],
      "addOns": {
        "common": {
          "name": "My Chat App",
          "logoUrl": "https://lh3.googleusercontent.com/..."
        },
        "chat": {},
        "httpOptions": {
          "granularOauthPermissionSupport": "OPT_IN"
        }
      }
    }
    
  2. Para conferir quais escopos o usuário concedeu, verifique o campo authorizationEventObject.authorizedScopes no seu código. Se um escopo obrigatório estiver ausente, retorne uma ação requesting_google_scopes para pedir ao usuário os escopos ausentes.

    Node.js

    // Check for authorized scopes.
    const authorizedScopes = req.body.authorizationEventObject.authorizedScopes || [];
    if (!authorizedScopes.includes('https://www.googleapis.com/auth/chat.messages')) {
      // Respond with a request for the missing scope.
      res.send({
        'requesting_google_scopes': {
          'scopes': ['https://www.googleapis.com/auth/chat.messages']
        }
      });
      return;
    }
    

    Python

    from flask import jsonify, request
    
    # Check for authorized scopes.
    event_data = request.get_json()
    authorized_scopes = event_data.get('authorizationEventObject', {}).get('authorizedScopes', [])
    if 'https://www.googleapis.com/auth/chat.messages' not in authorized_scopes:
        # Respond with a request for the missing scope.
        return jsonify({
            'requesting_google_scopes': {
                'scopes': ['https://www.googleapis.com/auth/chat.messages']
            }
        })
    

    Java

    import com.google.gson.JsonArray;
    import com.google.gson.JsonObject;
    import java.util.List;
    
    // Check for authorized scopes.
    List<String> authorizedScopes = event.getAuthorizationEventObject().getAuthorizedScopes();
    if (!authorizedScopes.contains("https://www.googleapis.com/auth/chat.messages")) {
      // Respond with a request for the missing scope.
      JsonObject requestingGoogleScopes = new JsonObject();
      JsonArray scopes = new JsonArray();
      scopes.add("https://www.googleapis.com/auth/chat.messages");
      requestingGoogleScopes.add("scopes", scopes);
    
      JsonObject response = new JsonObject();
      response.add("requesting_google_scopes", requestingGoogleScopes);
      return response.toString();
    }
    

    Para solicitar todos os escopos associados ao complemento, defina all_scopes como true:

    Node.js

    res.send({
      'requesting_google_scopes': { 'all_scopes': true }
    });
    

    Python

    from flask import jsonify
    
    return jsonify({
        'requesting_google_scopes': { 'all_scopes': True }
    })
    

    Java

    import com.google.gson.JsonObject;
    
    JsonObject requestingGoogleScopes = new JsonObject();
    requestingGoogleScopes.addProperty("all_scopes", true);
    
    JsonObject response = new JsonObject();
    response.add("requesting_google_scopes", requestingGoogleScopes);
    return response.toString();
    

Para instruções detalhadas, consulte Gerenciar permissões granulares para complementos HTTP do Google Workspace.

Apps de chat HTTP independentes

Se o app do Chat for um serviço HTTP independente (não um complemento do Google Workspace), você vai gerenciar o fluxo do OAuth 2.0.

Ao recuperar um token armazenado ou trocar um código de autorização, verifique quais escopos foram concedidos. Se os escopos necessários estiverem ausentes, peça ao usuário para autorizá-los.

Node.js

// 1. List authorized scopes.
const fs = require('fs');
const tokens = JSON.parse(fs.readFileSync('token.json'));
const grantedScopes = tokens.scope.split(' ');

// 2. Detect missing scopes.
const requiredScopes = ['https://www.googleapis.com/auth/chat.messages'];
const missingScopes = requiredScopes.filter(scope => !grantedScopes.includes(scope));

if (missingScopes.length > 0) {
  // 3. Request missing scopes.
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: missingScopes,
    include_granted_scopes: true
  });
  res.redirect(authUrl);
}

// To request all scopes instead of just the missing ones:
const allScopesAuthUrl = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: requiredScopes,
  include_granted_scopes: true
});

Python

from flask import redirect
from google.oauth2.credentials import Credentials

# 1. List authorized scopes.
credentials = Credentials.from_authorized_user_file('token.json')
granted_scopes = set(credentials.scopes)

# 2. Detect missing scopes.
required_scopes = {'https://www.googleapis.com/auth/chat.messages'}
missing_scopes = required_scopes - granted_scopes

if missing_scopes:
    # 3. Request missing scopes.
    flow.scope = list(missing_scopes)
    auth_url, _ = flow.authorization_url(
        access_type='offline',
        include_granted_scopes=True
    )
    return redirect(auth_url)

# To request all scopes instead of just the missing ones:
flow.scope = list(required_scopes)
all_scopes_auth_url, _ = flow.authorization_url(
    access_type='offline',
    include_granted_scopes='true'
)

Java

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

// 1. List authorized scopes.
// The "user" string is the user ID for which to load credentials.
Credential credential = flow.loadCredential("user");
Collection<String> grantedScopes = credential.getScopes();

// 2. Detect missing scopes.
// The `requiredScopes` variable contains a list of the OAuth scopes
// that your app requires to function. Define this variable with the
// scopes needed by your application.
List<String> requiredScopes = Arrays.asList("https://www.googleapis.com/auth/chat.messages");
List<String> missingScopes = new ArrayList<>();
for (String scope : requiredScopes) {
  if (!grantedScopes.contains(scope)) {
    missingScopes.add(scope);
  }
}

if (!missingScopes.isEmpty()) {
  // 3. Request missing scopes.
  GoogleAuthorizationCodeRequestUrl urlBuilder = new GoogleAuthorizationCodeRequestUrl(
      clientId, redirectUri, missingScopes)
      .setAccessType("offline")
      .set("include_granted_scopes", "true");
  String authUrl = urlBuilder.build();
  response.sendRedirect(authUrl);
}

// To request all scopes instead of just the missing ones:
GoogleAuthorizationCodeRequestUrl allScopesUrlBuilder = new GoogleAuthorizationCodeRequestUrl(
    clientId, redirectUri, requiredScopes)
    .setAccessType("offline")
    .set("include_granted_scopes", "true");
String allScopesAuthUrl = allScopesUrlBuilder.build();

Para mais informações, consulte Permissões granulares do OAuth.