管理 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 外掛程式

如果您是使用 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 外掛程式 (例如擴充 Google 雲端硬碟或 Gmail 等其他 Google Workspace 應用程式),請設定資訊清單檔案和程式碼,以處理細部 OAuth 權限:

  1. 在外掛程式的資訊清單檔案中,將 granularOauthPermissionSupport 欄位設為 OPT_IN。如要進一步瞭解 granularOauthPermissionSupport 欄位,請參閱改用精細 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. 如要查看使用者授予的範圍,請在程式碼中檢查 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().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();
    }
    

    如要要求與外掛程式相關聯的所有範圍,請將 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 權限」。