Quản lý các quyền OAuth chi tiết cho ứng dụng Google Chat

Các ứng dụng trò chuyện sử dụng quy trình xác thực người dùng phải hỗ trợ quyền OAuth chi tiết để cho phép người dùng cấp một số phạm vi được yêu cầu. Ví dụ: người dùng có thể cấp quyền truy cập vào tên của họ nhưng từ chối quyền truy cập vào lịch của họ.

Việc xử lý các quyền OAuth chi tiết phụ thuộc vào cách bạn tạo ứng dụng Chat:

Apps Script

Nếu bạn tạo ứng dụng Chat bằng Apps Script, thì Apps Script sẽ tự động xử lý các quyền OAuth chi tiết. Tuy nhiên, hãy đảm bảo mã của bạn xử lý các trường hợp người dùng không cấp tất cả các phạm vi được yêu cầu. Phương thức này phụ thuộc vào việc Apps Script của bạn có phải là một tiện ích bổ sung của Google Workspace mở rộng Google Chat bằng Apps Script hay một ứng dụng Chat độc lập được tạo bằng Apps Script và các sự kiện tương tác.

Tiện ích bổ sung của Google Workspace giúp mở rộng Chat

Nếu bạn tạo ứng dụng Chat dưới dạng tiện ích bổ sung Google Workspace mở rộng Google Chat bằng Apps Script, hãy làm theo hướng dẫn trong phần Xử lý quyền OAuth chi tiết trong Apps Script.

Ứng dụng Chat độc lập dựa trên Apps Script

Nếu bạn tạo ứng dụng Chat bằng Apps Script và sự kiện tương tác, thì hướng dẫn trong bài viết Xử lý quyền OAuth chi tiết trong Apps Script sẽ hoạt động với một điểm cần lưu ý:

ScriptApp.requireScopes dừng thực thi tập lệnh nếu các phạm vi được chỉ định không được cấp, nhưng người dùng sẽ thấy thẻ cấu hình trong Chat thay vì Màn hình xin phép bằng OAuth. Thẻ cấu hình luôn nhắc người dùng cấp tất cả các phạm vi được yêu cầu thay vì chỉ những phạm vi chưa được cấp.

Để cung cấp các quy trình kiểm tra riêng lẻ ở cấp phạm vi uỷ quyền, hãy sử dụng ScriptApp.getAuthorizationInfo để kiểm tra việc uỷ quyền và nếu cần, hãy yêu cầu uỷ quyền bằng thông báo riêng tư.

Ví dụ sau đây cho thấy cách kiểm tra một quyền cụ thể (chẳng hạn như quyền truy cập vào lịch) và nếu thiếu, hãy trả về một thông báo riêng tư có URL uỷ quyền bắt buộc.

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

Điểm cuối HTTP

Nếu bạn tạo ứng dụng Chat bằng điểm cuối HTTP, thì ứng dụng Chat của bạn phải hỗ trợ các quyền OAuth chi tiết.

Tiện ích bổ sung của Google Workspace giúp mở rộng Chat

Nếu bạn tạo ứng dụng Chat dưới dạng tiện ích bổ sung của Google Workspace (ví dụ: nếu ứng dụng đó mở rộng các ứng dụng khác của Google Workspace như Google Drive hoặc Gmail), hãy định cấu hình tệp kê khai và mã để xử lý các quyền OAuth chi tiết:

  1. Trong tệp kê khai của tiện ích bổ sung, hãy đặt trường granularOauthPermissionSupport thành OPT_IN. Để tìm hiểu thêm về trường granularOauthPermissionSupport, hãy xem phần Di chuyển sang quy trình cấp quyền OAuth ở cấp độ chi tiết.

    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. Để xem những phạm vi mà người dùng đã cấp, trong mã của bạn, hãy kiểm tra trường authorizationEventObject.authorizedScopes. Nếu thiếu một phạm vi bắt buộc, hãy trả về một thao tác requesting_google_scopes để nhắc người dùng cung cấp các phạm vi còn thiếu.

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

    Để yêu cầu tất cả các phạm vi liên kết với tiện ích bổ sung, hãy đặt all_scopes thành 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();
    

Để biết hướng dẫn chi tiết, hãy xem bài viết Quản lý quyền chi tiết cho các tiện ích bổ sung HTTP của Google Workspace.

Ứng dụng Chat HTTP độc lập

Nếu ứng dụng Chat của bạn là một dịch vụ HTTP độc lập (không phải là một tiện ích bổ sung của Google Workspace), thì bạn sẽ tự quản lý quy trình OAuth 2.0.

Khi bạn truy xuất một mã thông báo đã lưu trữ hoặc trao đổi mã uỷ quyền, hãy kiểm tra xem những phạm vi nào đã được cấp. Nếu thiếu các phạm vi bắt buộc, hãy nhắc người dùng uỷ quyền cho các phạm vi đó.

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

Để biết thêm thông tin, hãy xem phần Quyền OAuth ở cấp độ chi tiết.