编辑器插件授权

许多基于 Apps 脚本的应用的授权过程非常简单,因为当有人尝试使用脚本项目时,该项目会请求所需的任何缺失权限。

编辑器插件的授权模型之所以更复杂,原因有多种:

  • 当用户创建文件时,即使尚未授权,用户安装的所有插件也会列在扩展程序菜单中。

  • 这些插件适用于 Google 云端硬盘中可与协作者共享的文件。如果协作者的设备上未安装“编辑者”插件,但文件创建者在文档中使用了该插件,则协作者会在相应文档中看到该插件。

  • 编辑器插件会在文档打开时自动运行其 onOpen() 函数。

为了保护用户数据,系统应用了授权模式,导致某些服务无法供 onOpen() 使用。本指南可帮助您了解代码的功能和使用时机。

授权模型

编辑器插件的授权模式取决于其状态,而状态取决于使用该插件的用户:安装该插件的用户或协作者。

编辑器插件状态

扩展程序菜单中的编辑器插件已安装、已启用或同时已安装和已启用。

  • 插件在用户或其管理员从 Google Workspace Marketplace 获取插件并授权其访问用户的 Google 数据后,会为特定用户安装
  • 当有人在文档、表单、演示文稿或电子表格中使用某个插件时,该插件就会在其中启用
  • 当多人协作处理某个文件时,如果其中一人使用插件,则该插件会为该用户安装,并为该文件启用

下表总结了“已安装”和“已启用”之间的区别。请注意,当您将脚本作为插件进行测试时,可以在其中一种或两种状态下运行测试。

已安装 已启用
适用对象 用户 文档、表单、演示文稿或电子表格
原因 从商店获取加购项 在文档、表单、演示文稿或电子表格中使用时,从商店获取插件
在文档、表单、演示文稿或电子表格中使用之前安装的插件
菜单对以下用户可见 仅限该用户在打开或创建的所有文档、表单、演示文稿或电子表格中 相应文档、表单、演示文稿或电子表格的所有协作者
onOpen() 的授权模式 AuthMode.NONE
(除非它也处于启用状态,在这种情况下,AuthMode.LIMITED)
AuthMode.LIMITED

授权模式

当用户打开文档、表单、演示文稿或电子表格时,编辑器插件的 onOpen() 函数会自动运行。为了保护用户的数据,Apps 脚本限制了 onOpen() 函数的功能。编辑器插件状态决定了 onOpen() 函数的运行授权模式。

如果文件、表单、演示文稿或电子表格中启用了编辑器插件,onOpen() 会在 AuthMode.LIMITED 中运行。如果插件未启用,且仅处于已安装状态,则 onOpen() 会在 AuthMode.NONE 中运行。

AuthMode.NONE 中,插件在用户通过点击或运行自定义函数与插件互动之前,无法运行某些服务。如果插件尝试在 onOpen()onInstall() 或全局范围内使用这些服务,权限会失败,并且其他调用(例如填充菜单)会停止。唯一支持的选项是“帮助”。

如需运行受限的服务调用,您必须使用 AuthMode.FULL 授权模式。用户互动功能(例如点击菜单选项)仅在此模式下运行。在 AuthMode.FULL 模式下运行代码后,该插件可以使用用户授权的所有范围。

Apps 脚本将授权模式作为 Apps 脚本事件参数 authMode 的属性传递;e.authMode 的值对应于 Apps 脚本 ScriptApp.AuthMode 枚举中的常量。e

授权模式适用于所有 Apps 脚本执行方法,包括从脚本编辑器、菜单项或 Apps 脚本 google.script.run 调用运行。不过,只有当脚本因 触发器(例如 onOpen()onEdit()onInstall())而运行时,才能检查 e.authMode 属性。Google 表格中的自定义函数使用自己的授权模式 AuthMode.CUSTOM_FUNCTION,该模式与 LIMITED 类似,但限制略有不同。在所有其他情况下,脚本都会在 AuthMode.FULL 中运行,如下表中所述。

NONE LIMITED CUSTOM_FUNCTION FULL
发生时间 onOpen()(如果用户已安装插件,但未在文档、表单、演示文稿或电子表格中启用该插件) onOpen()(所有其他时间)
onEdit()(仅限 Google 表格)
自定义函数 所有其他时间,包括:
可安装的触发器
onInstall()
google.script.run
用户数据访问权限 仅限语言区域 仅限语言区域 仅限语言区域
对文档、表单、演示文稿或电子表格的访问权限 是 - 只读
访问界面 添加菜单项 添加菜单项
可使用 Properties
有权访问 JdbcUrlFetch
其他服务 Logger
Utilities
不访问用户数据的任何服务 不访问用户数据的任何服务 所有服务

编辑器插件的授权生命周期

当为当前用户安装或在当前文件中启用插件后,该插件会在打开相应文档、表单、演示文稿或电子表格时加载。该插件会列在扩展程序菜单中,并开始监听简单触发器 onInstall()onOpen()onEdit()。如果用户点击扩展程序菜单项,该菜单项就会运行

编辑器插件已安装

从商店安装编辑器插件后,其 onInstall() 函数会在 AuthMode.FULL 中运行。在此授权模式下,插件可以运行复杂的设置例程。您还应使用 onInstall() 创建菜单项,因为文档、表单、演示文稿或电子表格已打开,而您的 onOpen() 函数尚未运行。以下示例展示了如何从 onInstall() 函数调用 onOpen() 函数:

function onInstall(e) {
  onOpen(e);
  // Perform additional setup as needed.
}

编辑器插件已打开

当文档、表单、演示文稿或电子表格打开时,系统会加载当前用户已安装或任何协作者已在文件中启用的每个编辑器插件,并调用每个插件的 onOpen() 函数。onOpen() 的运行授权模式取决于插件是已安装还是已启用

如果插件仅创建基本菜单,则模式无关紧要。以下示例展示了一个基本的 onOpen() 函数:

function onOpen(e) {
  SpreadsheetApp.getUi().createAddonMenu() // Or DocumentApp.
      .addItem('Insert chart', 'insertChart')
      .addItem('Update charts', 'updateCharts')
      .addToUi();
}

如需添加基于存储的应用脚本属性的动态菜单项、读取当前文件的内容或执行其他高级任务,您必须确定授权模式并妥善处理。

以下示例展示了一个高级 onOpen() 函数,该函数会根据授权模式更改其操作:

function onOpen(e) {
  var menu = SpreadsheetApp.getUi().createAddonMenu(); // Or DocumentApp.
  if (e && e.authMode == ScriptApp.AuthMode.NONE) {
    // Add a normal menu item (works in all authorization modes).
    menu.addItem('Start workflow', 'startWorkflow');
  } else {
    // Add a menu item based on properties (doesn't work in AuthMode.NONE).
    var properties = PropertiesService.getDocumentProperties();
    var workflowStarted = properties.getProperty('workflowStarted');
    if (workflowStarted) {
      menu.addItem('Check workflow status', 'checkWorkflow');
    } else {
      menu.addItem('Start workflow', 'startWorkflow');
    }
  }
  menu.addToUi();
}

请注意,在 AuthMode.LIMITED 中执行时,插件无法打开边栏或对话框。您可以使用菜单项打开边栏和对话框,因为这些菜单项AuthMode.FULL 中运行

用户运行编辑器插件

当用户点击扩展程序菜单项时,Apps 脚本会先检查用户是否已安装该插件,如果未安装,则会提示用户进行安装。如果用户已授权该插件,脚本会运行 AuthMode.FULL 中与相应菜单项对应的函数。如果尚未启用,则在文档、表单、演示文稿或电子表格中启用该插件。

排查插件菜单无法呈现的问题

如果您的代码未正确管理授权模式,则插件菜单可能无法呈现。例如:

  • 某插件尝试运行当前授权模式不支持的 Apps 脚本服务。

  • 插件尝试在用户与其互动之前运行服务调用。

如需移除或重新排列导致 AuthMode.NONE 中出现权限错误的服务调用,请尝试以下操作:

  1. 打开插件的 Apps 脚本项目,然后找到 onOpen() 函数。
  2. onOpen() 函数中搜索提及 Apps 脚本服务或与其关联的对象(例如 PropertiesServiceSpreadsheetAppGmailApp)的内容。
  3. 如果某个服务用于创建界面元素以外的任何用途,请将其移除或封装在注释块中。仅保留以下方法:.getUi().createMenu().addItem().addToUi()。同时查找并移除函数之外的任何服务。
  4. 确定可能包含上一步中注释掉或移除的代码行的函数,尤其是那些使用它们生成的信息的函数,并将服务调用移至需要它们的函数。重新整理或重写代码库,以适应在上一步中进行的更改。
  5. 保存代码并创建测试部署。

    创建测试部署时,请确保配置字段为为当前用户安装,并且“配置”框下方的文字显示为AuthMode.None 中进行测试

  6. 启动测试部署,然后打开扩展程序菜单。

  7. 如果所有菜单项都显示出来,则问题已解决。 如果您只看到帮助菜单,请返回到第 1 步。 您可能错过了服务电话。