Как управлять детализированными разрешениями OAuth для приложений Google Chat

Приложения для Chat, использующие аутентификацию пользователей, должны поддерживать детализированные разрешения OAuth, чтобы пользователи могли предоставлять только часть запрошенных областей действия. Например, пользователь может разрешить доступ к своему имени, но запретить доступ к календарю.

Обработка детализированных разрешений OAuth зависит от того, как вы создаете приложение Chat:

Apps Script

Если вы создаете приложение для Chat с помощью Apps Script, то Apps Script автоматически обрабатывает детализированные разрешения OAuth. Однако убедитесь, что ваш код обрабатывает случаи, когда пользователь не предоставляет все запрошенные области действия. Следуйте инструкциям в статье Как работать с детализированными разрешениями OAuth в Apps Script.

Конечные точки HTTP

Если вы создаете приложение Chat с помощью конечных точек HTTP, настройте код так, чтобы он обрабатывал детализированные разрешения OAuth. Проверьте, какие области действия авторизации предоставил пользователь, и при необходимости запросите авторизацию для недостающих областей действия или всех областей действия.

  1. В файле манифеста приложения Chat укажите необходимые области авторизации в поле 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();
    }
    

    Чтобы запросить все области действия, связанные с приложением Chat, задайте для параметра 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();
    

Подробнее о том, как управлять детализированными разрешениями для дополнений Google Workspace, использующих конечные точки HTTP…

Приложения для Chat, не являющиеся дополнениями: управление детализированными разрешениями OAuth для приложений Google Chat

Если вы поддерживаете приложение Chat, которое не является дополнением Google Workspace, следуйте приведенным ниже инструкциям, чтобы управлять детализированными разрешениями OAuth.

приложения Chat, созданные с помощью Apps Script и не являющиеся дополнениями;

Если вы создали приложение Chat, которое не является дополнением, с помощью Apps Script, инструкции из статьи Как работать с детализированными разрешениями OAuth в Apps Script будут работать с одним условием:

ScriptApp.requireScopes останавливает выполнение скрипта, если указанные области не предоставлены, но пользователь видит в Chat карточку конфигурации вместо экрана запроса доступа OAuth. На карточке конфигурации всегда предлагается предоставить все запрошенные области действия, а не только те, которые ещё не были предоставлены.

Чтобы выполнять проверки на уровне отдельных областей авторизации, используйте ScriptApp.getAuthorizationInfo. С помощью этого метода можно проверить наличие авторизации и при необходимости запросить ее, используя личное сообщение.

В следующем примере показано, как проверить наличие определенного разрешения (например, на доступ к календарю) и, если оно отсутствует, вернуть личное сообщение с URL для авторизации.

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

Приложения Chat HTTP, которые не являются дополнениями

Если ваше приложение Chat, не являющееся дополнением, представляет собой сервис HTTP, вы сами управляете потоком 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();