إدارة أذونات OAuth الدقيقة لتطبيقات Google Chat

يجب أن تتوافق تطبيقات المحادثات التي تستخدم مصادقة المستخدم مع أذونات OAuth الدقيقة للسماح للمستخدمين بمنح مجموعة فرعية من النطاقات المطلوبة. على سبيل المثال، قد يمنح المستخدم إذن الوصول إلى اسمه ولكن يرفض منح إذن الوصول إلى تقويمه.

تعتمد طريقة التعامل مع أذونات OAuth الدقيقة على طريقة إنشاء تطبيق Chat:

برمجة التطبيقات

إذا أنشأت تطبيق Chat باستخدام برمجة تطبيقات Google، ستتعامل "برمجة تطبيقات Google" مع أذونات OAuth الدقيقة تلقائيًا. ومع ذلك، تأكَّد من أنّ الرمز البرمجي يعالج الحالات التي لا يمنح فيها المستخدم جميع النطاقات المطلوبة. تعتمد الطريقة على ما إذا كانت برمجة التطبيقات هي إضافة Google Workspace توسّع نطاق Google Chat باستخدام برمجة التطبيقات أو تطبيق Chat مستقل تم إنشاؤه باستخدام برمجة التطبيقات وأحداث التفاعل.

إضافات Google Workspace التي توسّع نطاق Chat

إذا أنشأت تطبيق Chat كـ إضافة Google Workspace توسّع نطاق Google Chat باستخدام "برمجة تطبيقات Google"، اتّبِع التعليمات الواردة في التعامل مع أذونات OAuth الدقيقة في "برمجة تطبيقات Google".

تطبيقات Chat المستقلة المستندة إلى Apps Script

إذا أنشأت تطبيق Chat باستخدام برمجة تطبيقات Google وأحداث التفاعل، ستعمل التعليمات الواردة في التعامل مع أذونات OAuth الدقيقة في "برمجة تطبيقات Google" مع مراعاة ما يلي:

يؤدي ScriptApp.requireScopes إلى إيقاف تنفيذ البرنامج النصي إذا لم يتم منح النطاقات المحدّدة، ولكن يرى المستخدم بطاقة إعداد في Chat بدلاً من شاشة الموافقة على OAuth. تطلب بطاقة الإعداد دائمًا من المستخدم منح جميع النطاقات المطلوبة بدلاً من النطاقات التي لم يتم منحها فقط.

لتقديم عمليات تحقّق على مستوى نطاق التفويض الفردي، استخدِم ScriptApp.getAuthorizationInfo للتحقّق من التفويض، وإذا لزم الأمر، اطلب التفويض باستخدام رسالة خاصة.

يوضّح المثال التالي كيفية التحقّق من توفّر إذن معيّن (مثل إذن الوصول إلى التقويم)، وفي حال عدم توفّره، يتم عرض رسالة خاصة تتضمّن عنوان URL الخاص بالتفويض المطلوب.

برمجة التطبيقات

/**
* 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

إذا أنشأت تطبيق Chat باستخدام نقاط نهاية HTTP، يجب أن يتيح تطبيق Chat أذونات OAuth دقيقة.

إضافات Google Workspace التي توسّع نطاق Chat

إذا أنشأت تطبيق Chat كـ إضافة Google Workspace (على سبيل المثال، إذا كان يوسّع تطبيقات Google Workspace الأخرى، مثل Google Drive أو Gmail)، عليك ضبط ملف البيان والرمز البرمجي للتعامل مع أذونات 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']
            }
        })
    

    جافا

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

    جافا

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

للحصول على إرشادات مفصّلة، يُرجى الاطّلاع على إدارة الأذونات الدقيقة لإضافات Google Workspace التي تستخدم HTTP.

تطبيقات محادثة HTTP المستقلة

إذا كان تطبيق 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'
)

جافا

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 الدقيقة.