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

感谢这里的另一位用户,我能够允许其他用户在不取消保护的情况下将新SKU添加到工作表中。此处显示的原始帖子:Restrict Editors to Specific Ranges Script

现在,我正在尝试相反的做法,允许用户删除SKU而不取消对工作表的保护。

我从以下内容开始,工作正常:

function deleteEachRow(){
  const ss = SpreadsheetApp.getActive();
  var SHEET = ss.getSheetByName("Ordering");
  var RANGE = SHEET.getDataRange();
  const ui = SpreadsheetApp.getUi();
  const response = ui.prompt('WARNING: 
 
 Ensure the following sheets DO NOT contain data before proceeding: 
 
 Accessory INV 
 Apparel INV 
 Pending TOs 
 
 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: 
 
 Ensure the following sheets DO NOT contain data before proceeding: 
 
 Accessory INV 
 Apparel INV 
 Pending TOs 
 
 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,该程序以安装触发器的用户身份运行。因此,如果所有者安装触发器,则无论谁编辑工作表,触发器都将以所有者身份运行,从而允许访问包括受保护区域在内的特权资源。

即使其他具有编辑访问权限的用户打开电子表格,可安装版本也会在创建触发器的用户的授权下运行。

备注:

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

相关文章