如何运行特定用户权限下的某些脚本?

How to run certain scripts under the authority of a specific user?

感谢这里的另一位用户,我能够允许其他用户将新的 SKU 添加到 sheet 而无需解除保护。原始 post 在这里看到:

现在,我正在尝试做相反的事情,允许用户删除 SKU 而无需取消保护 sheet。

我从以下开始,它按预期工作:

function deleteEachRow(){
  const ss = SpreadsheetApp.getActive();
  var SHEET = ss.getSheetByName("Ordering");
  var RANGE = SHEET.getDataRange();
  const ui = SpreadsheetApp.getUi();
  const response = ui.prompt('WARNING: \r\n \r\n Ensure the following sheets DO NOT contain data before proceeding: \r\n \r\n Accessory INV \r\n Apparel INV \r\n Pending TOs \r\n \r\n Enter New SKU:', ui.ButtonSet.OK_CANCEL);
  if (response.getSelectedButton() === ui.Button.OK) {
    const text = response.getResponseText();
    var rangeVals = RANGE.getValues();
  
    //Reverse the 'for' loop.
    for(var i = rangeVals.length-1; i >= 0; i--){
    if(rangeVals[i][0] === text){
      
      SHEET.deleteRow(i+1);
    };
    };
  };
};

因为我是 javascript 的新手,而且对 Web 应用程序非常陌生,所以我尝试将上面的代码添加到我在上面 URL 中提供的答案中。现在,脚本运行无误,但未能按预期删除输入的 SKU。这是脚本我是运行:

function deleteEachRow1(){
  const ss = SpreadsheetApp.getActive();
  var SHEET = ss.getSheetByName("Ordering");
  var RANGE = SHEET.getDataRange();
  const ui = SpreadsheetApp.getUi();
  const response = ui.prompt('WARNING: \r\n \r\n Ensure the following sheets DO NOT contain data before proceeding: \r\n \r\n Accessory INV \r\n Apparel INV \r\n Pending TOs \r\n \r\n Delete Which SKU?:', ui.ButtonSet.OK_CANCEL);
  if (response.getSelectedButton() === ui.Button.OK) {
    const text = response.getResponseText();

    const webAppsUrl = "WEB APP URL"; // Pleas set your Web Apps URL.

    const url = webAppsUrl + "?text=" + text;
    const res = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
    // ui.alert(res.getContentText()); // You can see the response value using this line.
  }
}

function doGet(e) {
  const text = e.parameter.text;
  const sheet = SpreadsheetApp.getActive().getSheetByName('Ordering');
  var rangeVals = RANGE.getValues();
  
    //Reverse the 'for' loop.
    for(var i = rangeVals.length-1; i >= 0; i--){
    if(rangeVals[i][0] === text){
      
      SHEET.deleteRow(i+1);
    };
    };
  myFunction();
  return ContentService.createTextOutput(text);
}

// This script is from https://tanaikech.github.io/2017/07/31/converting-a1notation-to-gridrange-for-google-sheets-api/
function a1notation2gridrange1(a1notation) {
  var data = a1notation.match(/(^.+)!(.+):(.+$)/);
  var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(data[1]);
  var range = ss.getRange(data[2] + ":" + data[3]);
  var gridRange = {
    sheetId: ss.getSheetId(),
    startRowIndex: range.getRow() - 1,
    endRowIndex: range.getRow() - 1 + range.getNumRows(),
    startColumnIndex: range.getColumn() - 1,
    endColumnIndex: range.getColumn() - 1 + range.getNumColumns(),
  };
  if (!data[2].match(/[0-9]/)) delete gridRange.startRowIndex;
  if (!data[3].match(/[0-9]/)) delete gridRange.endRowIndex;
  return gridRange;
}

// Please run this function.
function myFunction() {
  const email = "MY EMAIL"; // <--- Please set your email address.

  // Please set your sheet names and unprotected ranges you want to use.
  const obj = [ 
  { sheetName: "Ordering", unprotectedRanges: ["O5:P", "C2:E2"] },  
  { sheetName: "Accessory INV", unprotectedRanges: ["E5:H"] },  
  { sheetName: "Apparel INV", unprotectedRanges: ["E5:F"] },  
  {sheetName: "Pending TOs", unprotectedRanges: ["E6:H"] }, 
  {sheetName: "INV REF", unprotectedRanges: ["C6:C"] },
];

  // 1. Retrieve sheet IDs and protected range IDs.
  const spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId();
  const sheets = Sheets.Spreadsheets.get(spreadsheetId, { ranges: obj.map(({ sheetName }) => sheetName), fields: "sheets(protectedRanges(protectedRangeId),properties(sheetId))" }).sheets;
  const { protectedRangeIds, sheetIds } = sheets.reduce((o, { protectedRanges, properties: { sheetId } }) => {
    if (protectedRanges && protectedRanges.length > 0) o.protectedRangeIds.push(protectedRanges.map(({ protectedRangeId }) => protectedRangeId));
    o.sheetIds.push(sheetId);
    return o;
  }, { protectedRangeIds: [], sheetIds: [] });
  
  // 2. Convert A1Notation to Gridrange.
  const gridranges = obj.map(({ sheetName, unprotectedRanges }, i) => unprotectedRanges.map(f => a1notation2gridrange1(`${sheetName}!${f}`)));

  // 3. Create request body.
  const deleteProptectedRanges = protectedRangeIds.flatMap(e => e.map(id => ({ deleteProtectedRange: { protectedRangeId: id } })));
  const protects = sheetIds.map((sheetId, i) => ({ addProtectedRange: { protectedRange: { editors: {users: [email]}, range: { sheetId }, unprotectedRanges: gridranges[i] } } }));
  
  // 4. Request to Sheets API with the created request body.
  Sheets.Spreadsheets.batchUpdate({ requests: [...deleteProptectedRanges, ...protects] }, spreadsheetId);
}

非常感谢任何见解。

可能最简单的方法是避免使用按钮和 using a checkbox with a installable edit trigger,这对移动支持也有很大的副作用。

建议的解决方案:

  • 使用复选框
  • 将其挂接到 installable edit trigger,它以安装触发器的用户身份运行。因此,如果所有者安装了触发器,无论谁编辑 sheet,触发器都会以所有者身份运行,授予对包括受保护范围在内的特权资源的访问权限。

The installable version runs with the authorization of the user who created the trigger, even if another user with edit access opens the spreadsheet.

备注:

  • 优点:
    代码简单性和可维护性。无需 webapp 或任何复杂的设置。
  • 缺点:安全性(有可能的解决方法)
    如果代码绑定到 sheet,sheet 的编辑者可以直接访问 sheet 的脚本。因此,任何有恶意的编辑者都可以修改代码。如果带有可安装触发器的函数具有 gmail 权限,则任何编辑者都可以记录所有者的所有电子邮件。因此,需要特别注意请求的权限。请注意,您的 Web 应用程序设置已经是这种情况。任何编辑者都可以修改 doGet 以访问受保护的数据。如果 webapp 位于单独的独立脚本中,则这不是问题。您也可以通过将触发器设置为预定版本而不是 Head 版本来解决此问题。有关详细信息,请参阅