You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Drive API上传身份异常:部分用户操作以开发者账号执行

问题排查请求:Google Drive/Gmail API工具打包后异常

我开发的Python3工具原本支持用户上传PDF到团队Google Drive,并发送内部审核邮件。自本周一起,约1/3到1/2的用户出现异常:上传和邮件操作以我的Google账号执行,还偶尔抛出HttpError 403 when requesting https://gmail.googleapis.com/gmail/v1/users/username/messages/send?alt=json returned 'Delegation denied for 'my.email@email.com'错误。工具通过PyInstaller打包为exe分发,打包命令是pyinstaller --onefile path/to/code.py。

已尝试的排查步骤

  • 以管理员身份运行exe
  • 重启用户电脑
  • 直接运行Python源码文件可正常工作
  • 移除PyInstaller打包时的--hidden-import='googleapiclient'参数
  • 将邮件模块中的userId='me'改为用户实际邮箱地址

相关代码

授权范围与凭证配置

self.SCOPES = ['https://www.googleapis.com/auth/gmail.modify',
               'https://www.googleapis.com/auth/spreadsheets',
               'https://www.googleapis.com/auth/drive']

credentials.json内容:

{
    "installed":{
        "client_id":"client_id.apps.googleusercontent.com",
        "project_id":"project_ID",
        "auth_uri":"https://accounts.google.com/o/oauth2/auth",
        "token_uri":"https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
        "client_secret":"secret_code",
        "redirect_uris":[
            "urn:ietf:wg:oauth:2.0:oob",
            "http://localhost"
        ]
    }
}

上传核心函数

def upload(self, letterfile, plansfile, parentdrive):
    # Check for json credentials for google drive and API access
    try:
        x = os.stat(os.path.join(self.prefs['data'], 'token.json'))
        result = time.time() - x.st_mtime
        if result > 129600:
            os.remove(os.path.join(self.prefs['data'], 'token.json'))
    except:
        pass
    creds = None
    jsonpath = os.path.join(self.prefs['data'], 'token.json')
    if os.path.exists(jsonpath):
        creds = Credentials.from_authorized_user_file(jsonpath,
                                                      self.SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    try:
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    os.path.join(self.prefs['data'], 'credentials.json'),
                    self.SCOPES)
                creds = flow.run_local_server(port=0)
            # Save the credentials for the next run
            with open(os.path.join(self.prefs['data'], 'token.json'), 'w') as token:
                token.write(creds.to_json())
    except BaseException as e:
        print(f'Error with credentials.json check: {e}')
    # Build the services to access the Gmail API and Drive API
    driveService = build('drive', 'v3', credentials=creds)
    file = letterfile
    folderLink = f'https://drive.google.com/drive/folders/{parentdrive}'
    # Work with different attachment formats (Text, Image, or PDF). This
    # will need to be a FOR loop to iterate through all imported files
    # from the app
    content_type, encoding = mimetypes.guess_type(file)
    if content_type is None or encoding is not None:
        content_type = 'application/octet-stream'
    main_type, sub_type = content_type.split('/', 1)
    if main_type == 'text':
        with open(file, 'rb') as fp:
            msg = MIMEText(fp.read(), _subtype=sub_type)
            fp.close()
    elif main_type == 'image':
        with open(file, 'rb') as fp:
            msg = MIMEImage(fp.read(), _subtype=sub_type)
            fp.close()
    else:
        with open(file, 'rb') as fp:
            msg = MIMEBase(main_type, sub_type)
            msg.set_payload(fp.read())
            fp.close()
    calcfile = os.path.basename(letterfile)
    try:
        planname = os.path.basename(plansfile)
    except BaseException as e:
        planname = []
        for planfile in plansfile:
            planname.append(os.path.basename(planfile))
    # Upload file to Drive Project Folder
    file_metadata = {'name': calcfile, 'parents': [parentdrive]}
    media = MediaFileUpload(letterfile,
                            mimetype=content_type,
                            resumable=True)
    tempcalcfile = driveService.files().create(body=file_metadata,
                                               media_body=media,
                                               supportsAllDrives=True).execute()
    try:
        plan_metadata = {'name': planname, 'parents': [parentdrive]}
        media = MediaFileUpload(plansfile,
                                mimetype=content_type,
                                resumable=True)
        tempplansfile = driveService.files().create(body=plan_metadata,
                                                    media_body=media,
                                                    supportsAllDrives=True).execute()
    except BaseException as e:
        for i in range(len(planname)):
            plan_metadata = {'name': planname[i], 'parents': [parentdrive]}
            media = MediaFileUpload(plansfile[i],
                                    mimetype=content_type,
                                    resumable=True)
            tempplansfile = driveService.files().create(body=plan_metadata,
                                                        media_body=media,
                                                        supportsAllDrives=True).execute()
    print('Files sent to Drive Folder through API')

排查建议

  • 检查token.json的分发问题:打包后的exe可能意外包含了你本地的token.json(存储着你的账号授权信息),导致部分用户直接复用你的凭证。确认打包时是否排除了token.json,并确保工具首次运行时强制用户完成自己的OAuth授权流程。
  • 验证OAuth应用类型:当前使用的是installed类型的OAuth客户端,适合单用户桌面应用。多用户团队场景下,建议改用Web应用类型,或启用域范围委派(如果是Google Workspace域),避免用户间凭证冲突。
  • 排查PyInstaller路径问题:--onefile打包模式下,运行时会解压到临时目录,工具读取credentials.json和token.json的路径可能与源码运行时不一致。添加日志输出凭证文件的实际路径,确认用户端是否在正确位置生成/读取文件,防止多用户共享同一凭证。
  • 修复凭证过期逻辑:当前删除过期token的逻辑(超过1.5天自动删除)可能存在漏洞,比如文件权限导致无法删除旧token,或os.stat抛出异常后直接跳过,导致无效token被复用。添加日志记录token检查过程,确保过期token被正确清理,触发用户重新授权。
  • 检查Gmail API授权权限:Delegation denied错误通常与权限委派有关,确认你的账号是否被授权代表其他用户发送邮件,或用户Gmail账号是否允许第三方应用访问。如果使用域范围委派,需在Google Admin控制台为服务账号授权对应API范围。
  • 确保依赖完整打包:PyInstaller可能未正确打包Google API相关依赖,尝试添加--hidden-import='googleapiclient.discovery'、--hidden-import='google_auth_oauthlib.flow'等参数,或使用--collect-all googleapiclient确保所有相关模块被打包。

内容的提问来源于stack exchange,提问作者Ryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 22:37:00