Gerenciar permissões OAuth detalhadas para apps do Google Chat

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

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

Apps Script

Se você criar o app do Chat usando 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 se o Apps Script é um complemento do Google Workspace que estende o Google Chat usando o Apps Script ou um app do Chat independente criado com o Apps Script e eventos de interação.

Complementos do Google Workspace que estendem o Chat

Se você criar o 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 granulares do OAuth no Apps Script.

Apps do Chat independentes do Apps Script

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

ScriptApp.requireScopesinterrompe a execução do script se os escopos especificados não forem concedidos, mas o usuário verá um card de configuração no Chat em vez da tela de consentimento 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 no nível do escopo de autorização, use ScriptApp.getAuthorizationInfo para verificar a autorização e, se necessário, solicitar a autorização usando uma mensagem particular.

O exemplo a seguir mostra como verificar uma permissão específica (como acesso ao calendário) e, se estiver ausente, 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 o app do Chat usando endpoints HTTP, seu app do Chat precisará oferecer suporte a permissões granulares do OAuth.

Complementos do Google Workspace que estendem o Chat

Se você criar o app do Chat como um complemento do Google Workspace, configure o código para processar permissões granulares do OAuth. Verifique quais escopos de autorização o usuário concedeu e, se necessário, solicite a autorização para os escopos ausentes ou todos os escopos.

  1. No arquivo de manifesto do complemento, especifique os escopos de autorização necessários no campo oauthScopes. Esse campo faz parte do projects.deployments recurso.

    O exemplo a seguir exige os escopos de autorização chat.messages e calendar.events:

    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": {},
        "calendar": {},
        "httpOptions": {}
      }
    }
    
  2. Para conferir quais escopos o usuário concedeu, verifique o campo authorizationEventObject.authorizedScopes. Se um escopo necessário estiver ausente, retorne uma requesting_google_scopes ação 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() != null
        ? event.getAuthorizationEventObject().getAuthorizedScopes()
        : null;
    if (authorizedScopes == null || !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 do 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.