使用Ruby调用Google Drive API删除文件时的权限问题
我用Google Apps Script从电子表格生成JSON文件并存到Google Drive,再用Ruby脚本下载这些文件,但下载后无法删除,报错权限不足。
Google Apps Script 生成JSON文件代码
function makeJSON() { var spreadsheetId = 'someId'; var spreadsheet = SpreadsheetApp.openById(spreadsheetId); var sheets = spreadsheet.getSheets(); var urls = []; // 存储生成文件的URL for (var i = 1; i < sheets.length; i++) { var sheet = sheets[i]; var data = sheet.getRange(3, 1, sheet.getLastRow() - 2, sheet.getLastColumn()).getValues(); var headers = sheet.getRange(2, 1, 1, sheet.getLastColumn()).getValues()[0]; var objects = data.map(function(row) { var obj = {}; for (var j = 0; j < headers.length; j++) { // 数据处理逻辑 } return obj; }); // 将对象数组转为JSON字符串 var json = JSON.stringify(objects, null, 2); // 创建JSON Blob var blob = Utilities.newBlob(json, 'application/json', sheet.getName() + '.json'); var folder = DriveApp.getFolderById('someId'); var file = folder.createFile(blob); file.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT); // 不想设为公开可编辑,但目前为了下载只能这么做 // 获取文件ID var fileId = file.getId(); // 构造直接下载URL var directDownloadUrl = "https://drive.google.com/uc?export=download&id=" + fileId; // 将URL加入数组 urls.push(directDownloadUrl); } return urls; } function doGet() { var urls = makeJSON(); // 调用生成JSON文件的函数并获取URL列表 return ContentService.createTextOutput(JSON.stringify({urls: urls})).setMimeType(ContentService.MimeType.JSON); }
Ruby 下载脚本代码
drive_service = Google::Apis::DriveV3::DriveService.new scope = 'https://www.googleapis.com/auth/drive' authorizer = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: File.open('google_client.json'), scope: scope ) authorizer.fetch_access_token! # 为Drive API客户端设置授权 drive_service.authorization = authorizer # 返回JSON文件URL的Web App地址 web_app_url = 'https://script.google.com/macros/s/digits/exec' # 从Web App获取JSON数据 response = URI.open(web_app_url).read json_data = JSON.parse(response) # 保存文件的目录 save_directory = 'downloads' # 确保目录存在 FileUtils.mkdir_p(save_directory) unless File.directory?(save_directory) json_data['urls'].each do |url| # 从URL中提取文件ID file_id = url.split('id=')[1] # 通过Drive API获取文件元数据 file_metadata = drive_service.get_file(file_id, fields: 'name') # 提取文件名 file_name = file_metadata.name # 构造直接下载URL direct_download_url = "https://drive.google.com/uc?export=download&id=#{file_id}" # 构造文件保存路径 file_path = File.join(save_directory, file_name) # 下载文件 URI.open(direct_download_url) do |source| File.open(file_path, 'wb') do |file| file.write(source.read) end end puts "Saved #{file_name} to #{save_directory}" # 从Google Drive删除文件(此行报错) drive_service.delete_file(file_id) end
报错信息
/Users/me/.rvm/gems/ruby-2.7.2/gems/google-apis-core-0.14.0/lib/google/apis/core/http_command.rb:244:in `check_status': insufficientFilePermissions: The user does not have sufficient permissions for this file. (Google::Apis::ClientError)
问题背景
- Ruby脚本使用服务账号(
servicename@servicename-412394.iam.gserviceaccount.com)的凭据,而Drive上的JSON文件归我的普通个人账号(myregularemail@gmail.com)所有 - 已将存放文件的Drive文件夹共享给服务账号,服务账号拥有编辑权限,但仍无法删除文件
- 不想把文件设为公开可编辑,但目前为了下载只能这么做
- 不清楚如何让生成的文件直接归服务账号所有,也不知道如何用普通个人账号凭据运行Ruby脚本
解决方案
方案1:让服务账号直接拥有文件所有权(推荐)
修改Google Apps Script,将生成的JSON文件所有权直接转移给服务账号,这样Ruby脚本就能正常删除:
修改Apps Script代码
在创建文件后添加转移所有权的代码,同时可以删除公开共享的逻辑:var file = folder.createFile(blob); // 添加此行:将服务账号设为文件所有者 file.setOwner('servicename@servicename-412394.iam.gserviceaccount.com'); // 删除公开共享的代码:file.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT);注意:你的个人账号必须拥有转移所有权的权限,且服务账号已被添加到Drive文件夹的共享列表中。
验证效果
修改后生成的文件会直接归服务账号所有,Ruby脚本用服务账号凭据调用delete_file方法即可正常删除。
方案2:使用个人账号OAuth2授权运行Ruby脚本
如果不想转移文件所有权,可以改用个人账号的OAuth2授权来运行脚本:
创建OAuth2客户端凭据
在Google Cloud控制台中创建桌面应用类型的OAuth客户端ID,下载JSON凭据文件(命名为oauth_client.json)。修改Ruby脚本授权逻辑
替换原服务账号授权代码为以下OAuth2授权代码:require 'google/apis/drive_v3' require 'googleauth' require 'googleauth/stores/file_token_store' require 'fileutils' OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'.freeze APPLICATION_NAME = 'Drive File Manager'.freeze CREDENTIALS_PATH = 'oauth_client.json'.freeze TOKEN_PATH = 'token.yaml'.freeze # 存储授权令牌的文件 SCOPE = Google::Apis::DriveV3::AUTH_DRIVE def authorize client_id = Google::Auth::ClientId.from_file(CREDENTIALS_PATH) token_store = Google::Auth::Stores::FileTokenStore.new(file: TOKEN_PATH) authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, token_store) user_id = 'default' credentials = authorizer.get_credentials(user_id) if credentials.nil? url = authorizer.get_authorization_url(base_url: OOB_URI) puts "打开以下URL并输入授权后的验证码:" puts url code = gets.chomp credentials = authorizer.get_and_store_credentials_from_code( user_id: user_id, code: code, base_url: OOB_URI ) end credentials end # 初始化Drive服务 drive_service = Google::Apis::DriveV3::DriveService.new drive_service.client_options.application_name = APPLICATION_NAME drive_service.authorization = authorize # 后续下载、删除代码保持不变第一次运行脚本时,会提示你打开授权URL,用个人账号登录授权,授权令牌会自动保存到
token.yaml,后续运行无需重复授权。
方案3:在Apps Script中新增删除接口
如果上述方案都不适用,可以在Apps Script中添加删除接口,Ruby脚本下载后调用该接口删除文件:
修改Apps Script添加删除接口
function doPost(e) { var fileId = e.parameter.fileId; try { var file = DriveApp.getFileById(fileId); file.setTrashed(true); // 移到回收站,用file.remove()可彻底删除 return ContentService.createTextOutput(JSON.stringify({success: true})).setMimeType(ContentService.MimeType.JSON); } catch (error) { return ContentService.createTextOutput(JSON.stringify({success: false, error: error.message})).setMimeType(ContentService.MimeType.JSON); } }重新部署Web App,设置访问权限为**"任何人,甚至匿名"**(或限制为你的个人账号)。
修改Ruby脚本调用删除接口
替换原删除代码为以下HTTP请求:# 先安装httparty库:gem install httparty require 'httparty' # 替换 drive_service.delete_file(file_id) 为: delete_url = 'https://script.google.com/macros/s/你的Web App ID/exec' response = HTTParty.post(delete_url, body: {fileId: file_id}) puts response.parsed_response['success'] ? "Deleted #{file_name} from Drive" : "Delete failed: #{response.parsed_response['error']}"
内容的提问来源于stack exchange,提问作者soleil

