Administra los permisos detallados de OAuth para las apps de Google Chat

Las apps de chat que usan la autenticación de usuarios deben admitir permisos de OAuth detallados para permitir que los usuarios otorguen un subconjunto de los permisos solicitados. Por ejemplo, un usuario podría otorgar acceso a su nombre, pero rechazar el acceso a su calendario.

El manejo de permisos detallados de OAuth depende de cómo compiles tu app de Chat:

Apps Script

Si compilas tu app de Chat con Apps Script, Apps Script controla los permisos detallados de OAuth automáticamente. Sin embargo, asegúrate de que tu código controle los casos en los que un usuario no otorga todos los permisos solicitados. El método depende de si tu Apps Script es un complemento de Google Workspace que extiende Google Chat con Apps Script o una app de Chat independiente creada con Apps Script y eventos de interacción.

Complementos de Google Workspace que extienden Chat

Si compilas tu app de Chat como un complemento de Google Workspace que extiende Google Chat con Apps Script, sigue las instrucciones que se indican en Cómo controlar los permisos detallados de OAuth en Apps Script.

Apps independientes de Apps Script para Chat

Si compilas tu app de Chat con Apps Script y eventos de interacción, las instrucciones que se indican en Cómo controlar los permisos detallados de OAuth en Apps Script funcionan con una consideración:

ScriptApp.requireScopes detiene la ejecución de la secuencia de comandos si no se otorgan los permisos especificados, pero el usuario ve una tarjeta de configuración en Chat en lugar de la pantalla de consentimiento de OAuth. La tarjeta de configuración siempre le solicita al usuario que otorgue todos los permisos solicitados en lugar de solo los que no se otorgaron.

Para proporcionar verificaciones individuales a nivel del alcance de la autorización, usa ScriptApp.getAuthorizationInfo para verificar la autorización y, si es necesario, solicitarla con un mensaje privado.

En el siguiente ejemplo, se muestra cómo verificar si falta un permiso específico (como el acceso al calendario) y, si es así, devolver un mensaje privado con la URL de autorización requerida.

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.
  // ...
}

Extremos HTTP

Si compilas tu app de Chat con endpoints HTTP, esta debe admitir permisos de OAuth detallados.

Complementos de Google Workspace que extienden Chat

Si creas tu app de Chat como un complemento de Google Workspace (por ejemplo, si extiende otras apps de Google Workspace, como Google Drive o Gmail), configura tu archivo de manifiesto y tu código para controlar los permisos detallados de OAuth:

  1. En el archivo de manifiesto de tu complemento, establece el campo granularOauthPermissionSupport en OPT_IN. Para obtener más información sobre el campo granularOauthPermissionSupport, consulta Migra al flujo de permisos detallados de 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 ver qué permisos otorgó el usuario, consulta el campo authorizationEventObject.authorizedScopes en tu código. Si falta un alcance obligatorio, devuelve una acción requesting_google_scopes para solicitarle al usuario los alcances faltantes.

    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 los permisos asociados con el complemento, establece all_scopes en 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 obtener instrucciones detalladas, consulta Cómo administrar permisos detallados para complementos de Google Workspace basados en HTTP.

Apps de Chat HTTP independientes

Si tu app de Chat es un servicio HTTP independiente (no un complemento de Google Workspace), tú mismo administras el flujo de OAuth 2.0.

Cuando recuperes un token almacenado o intercambies un código de autorización, verifica qué permisos se otorgaron. Si faltan los alcances obligatorios, solicita al usuario que los autorice.

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 obtener más información, consulta Permisos de OAuth detallados.