【问题标题】:How to get a list of G Suite users which are using Google Apps Script?如何获取使用 Google Apps 脚本的 G Suite 用户列表?
【发布时间】:2020-11-30 04:23:14
【问题描述】:

我想知道,是否有机会获得使用 Google Apps 脚本的 G Suite 用户(属于一个域)列表?

【问题讨论】:

  • 有点复杂,但使用服务帐户,您可以列出所有用户驱动器查询 MIME 类型 application/vnd.google-apps.script 的文件 - 这将返回所有单独的应用程序脚本文件,而不是绑定到的文件不过是一份文件。
  • @OlegValter:如何在不知道 scriptid 的情况下获得项目列表?我没有看到这个端点。
  • 将尝试 @ziganotschka 建议,使用“MIME type application/vnd.google-apps.script”谢谢,ziganotschka 和 OlegValter!
  • @Oleg Valter 我不熟悉 clasp,它是否允许您在没有服务帐户的情况下访问所有用户的驱动器文件?我们仍然存在的唯一问题是找到绑定的脚本,例如到电子表格 - 查询 q: 'mimeType="application/vnd.google-apps.script"' 时不会显示
  • 我不确定我是否误解了这个问题,但我认为 OP 只想知道用户是否曾经使用过 Apps Script。如果用户在他的驱动器上有一个 MIME 类型 application/vnd.google-apps.script 的文件 - 可以假设他正在/正在使用 Apps 脚本。但再一次 - 只有当他有单独的项目时,这才有效。

标签: google-apps-script google-api google-admin-sdk google-workspace


【解决方案1】:

在您从用户的 Google 云端硬盘中检索到 script projects 列表后,您必须首先从 Apps Script API 请求他们的项目元数据。目前获取项目的唯一途径就是一个一个地去,提供scriptId的请求。

诀窍是脚本项目 file 的 Id 恰好与脚本 Id 相同(如果您查看 CLASP 项目的源代码 @ 987654329@命令,你会看到他们utilize this fact显示项目ID)。

要获取Project资源,我们需要调用getmethod

GET https://script.googleapis.com/v1/projects/{scriptId}

下面是一个简单的实用程序,用于从 API 检索单个 Project 资源。请注意,您的清单文件必须至少包含 https://www.googleapis.com/auth/script.projects.readonly 范围,否则 API 将返回 403 响应代码。

/**
 * @typedef {{
 *  domain : string,
 *  email : string,
 *  name : string
 * }} GSuiteUser
 * 
 * @typedef {{
 *  scriptId : string,
 *  title : string,
 *  createTime : string,
 *  updateTime : string,
 *  creator : GSuiteUser,
 *  lastModifyUser : GSuiteUser
 * }} ScriptProject
 * 
 * @summary gets script project metadata
 * @param {{
 *  id : string,
 *  token : string
 * }}
 * @returns {ScriptProject}
 */
const getProject = ({
  id = ScriptApp.getScriptId(),
  token = ScriptApp.getOAuthToken()
}) => {

  const uri = `https://script.googleapis.com/v1/projects/${id}`;

  /** @type {GoogleAppsScript.URL_Fetch.URLFetchRequestOptions} */
  const params = {
    contentType : "application/json",
    headers : {
      Authorization: `Bearer ${token}`
    },
    muteHttpExceptions : true,
    method : "get"
  };

  const response = UrlFetchApp.fetch(uri, params);

  const successChecker = getSuccessChecker();

  const success = successChecker(response);

  if(!success) {
    return {};
  }

  return JSON.parse(response.getContentText());
};

将它映射到您使用 ziganotschka 方法获得的脚本文件列表上,您将获得有关项目的详细信息。接下来,如果 using 您的意思是 running 项目,您可以调用 processes.list API method 代替:

GET https://script.googleapis.com/v1/processes

要求的 OAuth 范围是 https://www.googleapis.com/auth/script.processes

/**
 * @typedef {{
 *  projectName : string,
 *  functionName : string,
 *  processType : string,
 *  processStatus : string,
 *  userAccessLevel : string,
 *  startTime : string,
 *  duration : string
 * }} ScriptProcess
 * 
 * @summary lists script processes for a user
 * @param {{
 *  id : (string|"any"),
 *  pageSize : (number|50),
 *  token : string,
 *  start : (Date|undefined),
 *  end : (Date|undefined),
 *  statuses : string[],
 *  types : string[]
 * }} 
 * @returns {ScriptProcess[]}
 */
const listScriptProcesses = ({
  id = ScriptApp.getScriptId(),
  token = ScriptApp.getOAuthToken(),
  pageSize = 50,
  start, end,
  statuses = [],
  types = []
} = {}) => {

  const query = [
    `pageSize=${pageSize}`,
    `userProcessFilter.startTime=${toZuluTimestamp(start)}`,
    `userProcessFilter.endTime=${toZuluTimestamp(end)}`
  ];

  id !== "any" && query.push(`userProcessFilter.scriptId=${id}`);
  types.length && query.push(`userProcessFilter.types=${types.join(",")}`);
  statuses.length && query.push(`userProcessFilter.statuses=${statuses.join(",")}`);

  const uri = `https://script.googleapis.com/v1/processes?${query.join("&")}`;

  /** @type {GoogleAppsScript.URL_Fetch.URLFetchRequestOptions} */
  const params = {
    contentType: "application/json",
    headers: {
      Authorization: `Bearer ${token}`
    },
    muteHttpExceptions: true,
    method: "get"
  };

  const response = UrlFetchApp.fetch(uri, params);
  const content = response.getContentText();

  const successChecker = getSuccessChecker();
  const success = successChecker(response);

  if (!success) {
    console.warn(response.getResponseCode(), content);
    return [];
  }

  const { processes = [] } = JSON.parse(content);

  return processes;
};

作为响应,您将代表获取有关脚本执行的元数据,该用户的凭据与不记名令牌一起传递(您需要为每个用户提供一个服务帐户)。

剩下的很简单:如果响应不为空,则用户在某个时间点运行脚本项目(请注意,上面的实用程序默认startend 时间戳参数为now)。如果您提供 any 作为脚本 ID,请求将返回代表用户执行的每个

该方法的另一个好处是返回每种类型的脚本项目执行,包括 Web 应用程序、附加组件和绑定项目(有关详细信息,请参阅 ProcessType 枚举)。 p>

这种方法的唯一困难是部署为“像我一样执行”的 Web 应用程序,它始终在脚本项目所有者的权限下运行,因此您必须单独跟踪 Web 应用程序的用户。


以上片段使用以下实用程序脚本:

/**
 * @summary checks HTTPResponse for being successful
 * @param {GoogleAppsScript.URL_Fetch.HTTPResponse} resp 
 * @returns {boolean}
 */
const getSuccessChecker = ({ successOn = [200] } = {}) => (resp) => {
    const code = resp.getResponseCode();
    return successOn.some(c => c === code);
}; 
/**
 * @summary converts input into RFC3339 UTC "Zulu" format
 * @param {Date|number|string} [date] 
 * @returns {string}
 */
const toZuluTimestamp = (date = Date.now()) => new Date(date).toISOString().replace('Z','000000Z');

您需要启用V8 runtime 以使上面的 sn-ps 工作(或将它们转换为 ES5 语法)。

【讨论】:

    【解决方案2】:

    如何验证任一域用户在其 Google 云端硬盘中是否有 Standalone Apps 脚本项目

    1. 要以管理员身份访问用户的云端硬盘,您需要在 GCP Console 中设置 service account
    2. 确保启用 domain-wide delegation - 这允许 oyu 以域用户身份进行身份验证并列出他的云端硬盘文件
    3. 转到您的Admin console 并转到 Main menu menu> Security > API controls 并为新创建的服务帐户添加必要的范围 - 如here 所述
    4. 下载带有服务帐户凭据的 .json 文件 - 它应包含 "private_key" 字段
    5. 在你的情况下必要的范围是https://www.googleapis.com/auth/drive.readonly
    6. 要在 Apps 脚本中使用服务帐户,您需要从 as described herehere 安装适用于 Apps 脚本库的 OAuth2
    7. 为此,请转到您的 Apps 脚本编辑器 "Resources > Libraries... > Find a library" 并输入代码 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
    8. 选择一个版本(目前最新的是38)
    9. 现在,您需要以编程方式执行的第一件事是启用 adminSDK 以列出所有域用户 - 正如 @TheMaster 所建议的那样
    10. 第二步是遍历所有用户,并代表他们创建一个服务帐户令牌(将其设置为subject
    11. 使用访问令牌,您可以为每个用户列出带有查询参数 q: 'mimeType="application/vnd.google-apps.script"' 的文件,以找出用户的云端硬盘上是否有独立的 Apps 脚本文件

    代码示例:

    function myFunction() {
      var users = AdminDirectory.Users.list({"domain":"PASTE HERE YOUR DOMAIN NAME"}).users;
      users.forEach(function(user){  
        user = user.primaryEmail;
        getService(user).reset();
        var service = getService(user);
        
        if (service.hasAccess()) {
          Logger.log("service has access");
          var url = "https://www.googleapis.com/drive/v3/files";
          var query = '?q=mimeType%3D%22application%2Fvnd.google-apps.script%22';
          var headers ={
            "Authorization": 'Bearer ' + service.getAccessToken()
          };       
          var options = {
            'headers': headers,
            'method' : 'get',
            'muteHttpExceptions': true    
          };      
          var response=UrlFetchApp.fetch(url+query, options).getContentText();
          if(JSON.parse(response).files.length==0){
            Logger.log("User " + user + " does not have any Standalone Apps Script projects on his Drive");
          }else{
            Logger.log("User " + user + " has Standalone Apps Script projects on his Drive");
          }
          
        }
        else {
          Logger.log(service.getLastError());
        }
      }
     )
    }
    
    var PRIVATE_KEY ="-----BEGIN PRIVATE KEY-----PASTE HERE YOUR PRIVATE KEY FROM THE JSON FILE-----END PRIVATE KEY-----\n";
    var CLIENT_EMAIL = 'PASTE HERE THE EMAIL OF THE SERVICE ACCOUNT';
    
    function getService(user) {
      return OAuth2.createService('List users')
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')
      .setPrivateKey(PRIVATE_KEY)
      .setIssuer(CLIENT_EMAIL)
      .setSubject(user)
      .setPropertyStore(PropertiesService.getScriptProperties())
      .setParam('access_type', 'offline')
      .setScope("https://www.googleapis.com/auth/drive.readonly");
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-01
      • 1970-01-01
      相关资源
      最近更新 更多