Gestire le autorizzazioni OAuth granulari per le app Google Chat

Le app di chat che utilizzano l'autenticazione utente devono supportare autorizzazioni OAuth granulari per consentire agli utenti di concedere un sottoinsieme di ambiti richiesti. Ad esempio, un utente potrebbe concedere l'accesso al proprio nome, ma rifiutare l'accesso al proprio calendario.

La gestione delle autorizzazioni OAuth granulari dipende da come crei la tua app di chat:

Apps Script

Se crei la tua app di Chat utilizzando Apps Script, Apps Script gestisce automaticamente le autorizzazioni OAuth granulari. Tuttavia, assicurati che il codice gestisca i casi in cui un utente non concede tutti gli ambiti richiesti. Il metodo dipende dal fatto che Apps Script sia un componente aggiuntivo di Google Workspace che estende Google Chat utilizzando Apps Script o un'app di chat autonoma creata con Apps Script ed eventi di interazione.

Componenti aggiuntivi di Google Workspace che estendono Chat

Se crei la tua app di chat come componente aggiuntivo di Google Workspace che estende Google Chat utilizzando Apps Script, segui le istruzioni riportate in Gestire le autorizzazioni OAuth granulari in Apps Script.

App Apps Script Chat autonome

Se crei la tua app di chat utilizzando Apps Script ed eventi di interazione, le istruzioni riportate in Gestire le autorizzazioni OAuth granulari in Apps Script funzionano con una sola considerazione:

ScriptApp.requireScopes interrompe l'esecuzione dello script se gli ambiti specificati non vengono concessi, ma l'utente vede una scheda di configurazione in Chat anziché la schermata per il consenso OAuth. La scheda di configurazione richiede sempre all'utente di concedere tutti gli ambiti richiesti anziché solo quelli non concessi.

Per fornire controlli a livello di ambito di autorizzazione individuali, utilizza ScriptApp.getAuthorizationInfo per verificare l'autorizzazione e, se necessario, richiederla utilizzando un messaggio privato.

L'esempio seguente mostra come verificare la presenza di un'autorizzazione specifica (come l'accesso al calendario) e, in caso di assenza, restituire un messaggio privato con l'URL di autorizzazione richiesto.

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

Endpoint HTTP

Se crei la tua app Chat utilizzando endpoint HTTP, la tua app Chat deve supportare autorizzazioni OAuth granulari.

Componenti aggiuntivi di Google Workspace che estendono Chat

Se crei la tua app di Chat come componente aggiuntivo di Google Workspace (ad esempio, se estende altre app Google Workspace come Google Drive o Gmail), configura il file manifest e il codice per gestire le autorizzazioni OAuth granulari:

  1. Nel file manifest del componente aggiuntivo, imposta il campo granularOauthPermissionSupport su OPT_IN. Per saperne di più sul campo granularOauthPermissionSupport, consulta Eseguire la migrazione al flusso di autorizzazioni OAuth granulari.

    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. Per vedere quali ambiti ha concesso l'utente, controlla il campo authorizationEventObject.authorizedScopes nel codice. Se manca un ambito obbligatorio, restituisci un'azione requesting_google_scopes per richiedere all'utente gli ambiti mancanti.

    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();
    }
    

    Per richiedere tutti gli ambiti associati al componente aggiuntivo, imposta all_scopes su 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();
    

Per istruzioni dettagliate, vedi Gestire le autorizzazioni granulari per i componenti aggiuntivi HTTP di Google Workspace.

App di chat HTTP autonome

Se la tua app di chat è un servizio HTTP autonomo (non un componente aggiuntivo di Google Workspace), gestisci autonomamente il flusso OAuth 2.0.

Quando recuperi un token memorizzato o scambi un codice di autorizzazione, controlla quali ambiti sono stati concessi. Se gli ambiti richiesti non sono presenti, chiedi all'utente di autorizzarli.

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();

Per ulteriori informazioni, consulta Autorizzazioni OAuth granulari.