【问题标题】:Google App Script execute function when a user selects a cell in a range当用户选择范围内的单元格时,Google App Script 执行函数
【发布时间】:2020-08-28 13:07:23
【问题描述】:

我想运行一个功能,当用户单击/选择给定范围内的单元格时需要授权。由于授权问题,一个简单的 onSelectionChange(e) 触发器不起作用,并且可安装的触发器显然不包括 onSelectionChange。

请问还有其他方法吗?谢谢!

【问题讨论】:

    标签: google-apps-script google-sheets triggers


    【解决方案1】:

    对于不涉及ui/HtmlService的函数,可以使用简单的触发器来运行一些需要授权的函数(比如特权函数)通过削弱安全性

    • 流程:Trigger => onSelectionChange(没有授权来获取/执行特权函数) => 触发自定义函数(获得授权来获取/没有授权来执行特权函数) => fetch/post => webapp(完全授权到运行特权函数)

    • 此解决方案的灵感来自this,它直接使用可安装触发器和普通访问令牌来授权自定义功能。从安全角度来看,不建议这样做。

    • 尽管已努力确保执行以下脚本的用户的安全和隐私,但并未考虑所有攻击媒介。该脚本可能在很多领域都存在漏洞,尤其是在平台缺乏加密模块支持的情况下。在替代解决方案不可行的情况下使用,风险自负。

    • 在大多数情况下,首选使用菜单/按钮/时间触发器/可安装触发器(始终在完全授权下运行)的替代解决方案。 使用 onEdit 可安装触发器 + 复选框可以实现类似的流程

    要使用示例脚本,请执行以下步骤:

    • Set necessary scopes 在清单文件中。对于示例脚本,

       "oauthScopes": ["https://www.googleapis.com/auth/script.send_mail"],
      
    • Publish a webapp 用于执行需要授权的功能的明确目的

      • 以“我”的身份执行
      • 访问权限:“任何人”
    • Create a service account 没有用于从自定义函数授权 web 应用的明确目的的角色/权限

    • Create a service account key 并将其复制到示例脚本中的creds 对象。

    • 与服务帐户 (client_email) 共享您的项目/电子表格

    • 安装Oauth2 library 为服务帐户创建/签署 jwt 令牌

    • 创建一个hiddenSheet 用于设置自定义函数,该函数将设置为该工作表的A1 onSelectionChange

    • 当有人触摸您电子表格中的任何内容时,以下脚本会发送电子邮件。

    示例脚本:

    /**
     * Gets Oauth2 service based on service account with drive scope
     * Drive scope needed to access webapp with access:anyone
     * This does not grant access to the user's drive but the service
     *     account's drive, which will only contain the file shared with it
     */
    function getService_() {
      const creds = {
        private_key: '[PRIVATE_KEY]',
        client_email: '[CLIENT_EMAIL]',
      };
      const PRIVATE_KEY = creds['private_key'];
      const CLIENT_EMAIL = creds['client_email'];
      return OAuth2.createService('GoogleDrive:')
        .setTokenUrl('https://oauth2.googleapis.com/token')
        .setPrivateKey(PRIVATE_KEY)
        .setIssuer(CLIENT_EMAIL)
        .setPropertyStore(PropertiesService.getUserProperties())
        .setScope('https://www.googleapis.com/auth/drive');
    }
    /**
     * @returns {string} base64 encoded string of SHA_512 digest of random uuidstring
     */
    const getRandHashKey_ = () =>
      Utilities.base64EncodeWebSafe(
        Utilities.computeDigest(
          Utilities.DigestAlgorithm.SHA_512,
          Utilities.getUuid() //type 4 advertised crypto secure
        )
      );
    
    /**
     * @param {GoogleAppsScript.Events.SheetsOnSelectionChange} e
     */
    const onSelectionChange = e => {
      const sCache = CacheService.getScriptCache();
      e.rangestr = e.range.getSheet().getName() + '!' + e.range.getA1Notation();
      const hashRandom = getRandHashKey_();
      sCache.put(hashRandom, JSON.stringify(e), 20);//expires in 20 seconds
      e.source
        .getSheetByName('hiddenSheet')
        .getRange('A1')
        .setValue(`=CALLWEBAPP("${hashRandom}")`);
    };
    /**
     * Calls published webapp(Access:Anyone) with service account token
     * @customfunction
     * @returns void
     */
    const callwebapp = randomHash => {
      const webAppScriptId = '[SCRIPT_ID]';
      UrlFetchApp.fetch(
        `https://script.google.com/macros/s/${webAppScriptId}/exec`,
        {
          method: 'post',
          payload: { e: randomHash },
          headers: { Authorization: `Bearer ${getService_().getAccessToken()}` },
        }
      );
    };
    
    /**
     * @param {GoogleAppsScript.Events.AppsScriptHttpRequestEvent} e
     */
    const doPost = e => {
      const hashRandom = e.parameter.e;
      const sCache = CacheService.getScriptCache();
      const encodedSelectionEvent = sCache.get(hashRandom);
      if (encodedSelectionEvent) {
        const selectionEvent = JSON.parse(encodedSelectionEvent);
        MailApp.sendEmail(
          '[EMAIL_TO_SEND_NOTIFICATION_TO]',
          'Someone touched your spreadsheet',
          `Wanna take a look? ${selectionEvent.rangestr} was touched without your permission`
        );
      }
    };
    

    【讨论】:

      【解决方案2】:

      所以我最终在每一行添加了一个值为“清除运行功能”的列,如果此列中的一个值与“清除运行功能”不同,则使用简单的 onEdit(e) 触发我的函数。

      从用户体验的角度来看,这意味着清除一个单元格以运行该功能 - 不理想,但它可以工作。

      【讨论】:

      • 一个复选框会更理想
      猜你喜欢
      • 2019-03-27
      • 1970-01-01
      • 2022-09-27
      • 1970-01-01
      • 1970-01-01
      • 2020-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多