管理 Google Chat 應用程式的細部 OAuth 權限

使用使用者驗證的 Chat 應用程式必須支援精細 OAuth 權限,讓使用者授予部分要求的範圍。舉例來說,使用者可能會授予存取名稱的權限,但拒絕存取日曆。

處理精細 OAuth 權限的方式,取決於您建構 Chat 應用程式的方式:

Apps Script

如果您使用 Apps Script 建構 Chat 應用程式,Apps Script 會自動處理精細的 OAuth 權限。不過,請確保程式碼能處理使用者未授予所有要求範圍的情況。方法取決於您的 Apps Script 是使用 Apps Script 擴充 Google Chat 的 Google Workspace 外掛程式,還是使用 Apps Script 和互動事件建構的獨立 Chat 擴充應用程式。

擴充 Chat 功能的 Google Workspace 外掛程式

如果您將 Chat 應用程式建構為使用 Apps Script 擴充 Google Chat 的 Google Workspace 外掛程式,請按照「在 Apps Script 中處理精細 OAuth 權限」一文中的操作說明進行。

獨立的 Apps Script Chat 應用程式

如果您使用 Apps Script 和互動事件建構 Chat 應用程式,請注意,在 Apps Script 中處理精細的 OAuth 權限一文中的操作說明適用於以下情況:

ScriptApp.requireScopes 如果未授予指定範圍,系統會停止執行指令碼,但使用者會在 Chat 中看到設定資訊卡,而不是 OAuth 同意畫面。設定卡片一律會提示使用者授予所有要求的範圍,而非僅授予未授予的範圍。

如要提供個別授權範圍層級檢查,請使用 ScriptApp.getAuthorizationInfo 檢查授權,並視需要使用私人訊息要求授權。

以下範例說明如何檢查特定權限 (例如日曆存取權),如果缺少權限,則傳回含有必要授權網址的私人訊息。

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

HTTP 端點

如果您使用 HTTP 端點建構 Chat 應用程式,Chat 應用程式應支援精細的 OAuth 權限。

擴充 Chat 功能的 Google Workspace 外掛程式

如果將 Chat 應用程式建構為 Google Workspace 外掛程式,請設定程式碼來處理精細 OAuth 權限。檢查使用者授予的授權範圍,並視需要要求授權缺少或所有範圍。

  1. 在外掛程式的資訊清單檔案中,於 oauthScopes 欄位指定必要的授權範圍。這個欄位是projects.deployments資源的一部分。

    以下範例需要 chat.messages 和 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. 如要查看使用者授予的範圍,請檢查 authorizationEventObject.authorizedScopes 欄位。如果缺少必要範圍,請傳回 requesting_google_scopes 動作,提示使用者提供缺少的範圍。

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

    如要要求與外掛程式相關聯的所有範圍,請將 all_scopes 設為 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();
    

如需詳細的操作說明,請參閱「管理 HTTP Google Workspace 外掛程式的精細權限」。

獨立 HTTP Chat 應用程式

如果 Chat 應用程式是獨立的 HTTP 服務 (而非 Google Workspace 外掛程式),您必須自行管理 OAuth 2.0 流程。

擷取已儲存的權杖或交換授權碼時,請檢查已授予哪些範圍。如果缺少必要範圍,請提示使用者授權。

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

詳情請參閱「精細 OAuth 權限」。