feat(driver): add PDS storage driver - #2663
Conversation
- add a native PDS driver with list, link, upload, mkdir, rename, move, copy, recycle-bin delete, and storage details support - persist refreshed OAuth tokens and allow either access_token or refresh_token for authentication - register the driver and cover initialization/query escaping behavior with focused tests
feat(driver): add PDS storage driver
|
新增配置文件 OAuth2 登录流程 |
|
文档也已经完成编辑。 |
|
新增一个 TODO LIST:需要修正逻辑,不走服务器中转上传。 |
|
测试 Demo:https://openlist.206601.xyz/ |
|
和我这个重复了,#1936 。但目前我没有账号测试,无法继续开发。 |
好像还真是,因为我在现有 release 没看到有人做我以为就没人搞了。 |
|
修复了一个小 BUG,概率性存在开始上传目标是 /PDS/1/111/0001.jpg,但后端返回的 complete.body.path 变成了 /PDS,完成上传时用错父目录,导致 token 里的 parent_file_id 和请求解析出的目录不一致,于是报 direct upload token does not match request。 |
PIKACHUIM
left a comment
There was a problem hiding this comment.
感谢您的贡献,还请提供一下internal内的改动的必要性
- Move PDS helper functions out of driver.go into util.go. - Remove the PDS driver test file from the tracked tree while keeping local tests available.
|
zymooll
left a comment
There was a problem hiding this comment.
需要确认的项目:是否需要去除 defaultClientID,转为在配置 Driver 的时候必填该项。
pikachuren
left a comment
There was a problem hiding this comment.
🙏 感谢贡献
感谢 @zymooll 提交此PR!我已完成代码评审,以下是评审结果。
🤖 AI 自动审核声明
本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析,部分复杂场景可能辅助使用 ChatGPT、DeepSeek 等模型进行交叉验证。
⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。
⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出决策。
📖 PR背景与需求
PR标题:feat(driver): add PDS storage driver
关联Issue:https://github.com/orgs/OpenListTeam/discussions/170
需求说明:
为OpenList新增阿里云盘企业版(PDS)原生驱动支持。此PR与现有的 #1936(@xrgzs提交的PDS驱动)形成竞争关系,但本PR实现更完善:
- ✅ 已实现OAuth2 Token获取流程(关联APIPages PR #90)
- ✅ 已实现客户端直接上传(不经过服务器中转)
- ✅ 已补充完整文档(关联Docs PR #340)
- ✅ 已实现缩略图接口
预期目标:
- 完善OpenList存储驱动生态,支持阿里云PDS企业版存储
- 提供高性能的客户端直接上传机制,减轻服务器负担
- 确保驱动行为与OpenList现有驱动一致
评论区关键信息:
- 与PR #1936冲突:@xrgzs指出存在重复实现,但其版本因缺少测试账号无法继续开发
- 直接上传优化:作者已将上传逻辑从服务器中转改为客户端直接上传到PDS
- bug修复:修复了上传路径解析的bug(complete.body.path解析错误导致token不匹配)
- 功能完善:已新增缩略图接口支持
📋 问题摘要
⚠️ 架构影响:1个(新增核心API接口,需要评估影响范围)⚠️ 代码质量:2个(错误处理、接口设计)- 💡 改进建议:4个(性能优化、安全加固、文档完善)
📂 逐文件分析
drivers/pds/ 目录(新增驱动实现)
改动意图:
实现完整的PDS存储驱动,包括OAuth认证、文件操作、客户端直接上传等核心功能。
代码修改逻辑:
drivers/pds/api.go(214行):
- 实现PDS API客户端,封装HTTP请求和Token刷新逻辑
refreshToken:实现OAuth2 refresh_token流程,支持自动续期ensureToken:在每次API调用前确保Token有效(提前5分钟刷新)post:统一的POST请求封装,支持Token过期自动重试isAccessTokenExpiredError:智能识别多种Token过期错误格式
drivers/pds/types.go:
- 定义PDS响应类型(fileItem、listFilesResp、createFileResp等)
toObj/toObjs:将PDS返回结构转换为OpenList标准model.Obj
drivers/pds/driver.go(213行):
- 实现核心驱动接口:
List、Link、MakeDir、Move、Copy、Remove、GetStorageUsage - 删除操作使用
/v2/recyclebin/trash(移入回收站,非永久删除) - 支持容量查询(used_size/total_size)
drivers/pds/upload.go:
- 实现客户端直接上传逻辑
GetDirectUploadInfo:获取预签名上传URL和upload_tokenCompleteDirectUpload:完成上传并通知PDS服务器- 关键优化:上传不经过OpenList服务器中转,直接从客户端到PDS
drivers/pds/util.go:
- 工具函数:文件ID解析、预览参数构建等
drivers/pds/meta.go:
- 定义驱动元信息和配置项(
Addition结构体) - 配置项:
domain_id、drive_id、access_token、refresh_token、client_id等
合理性评估:
-
✅ 优点:
- 代码结构清晰,符合OpenList驱动开发规范
- Token刷新机制安全可靠(提前5分钟刷新,支持过期自动重试)
- 客户端直接上传显著降低服务器负载
- 删除操作使用回收站机制,避免误删
-
⚠️ 疑问:defaultClientID硬编码为lMNVp25Sd1MfqZDQ,是否为官方公开Client ID?是否存在滥用风险?- 错误处理较为简单,部分场景(如网络超时、限流)可能无法提供详细信息
-
❌ 问题:
- 未实现大文件分片上传,可能限制单文件大小上限
- 缺少并发控制,高并发场景下可能导致API限流
详细建议:
- Client ID管理(P2):建议将
defaultClientID改为可配置项,避免硬编码 - 错误分类(P2):为常见错误(401、403、429、502)提供更详细的错误信息和重试建议
- 分片上传(P3):后续增加大文件分片上传支持(PDS支持
/v2/file/create_with_proof) - 并发限流(P3):添加速率限制器,避免触发PDS API限流
internal/driver/driver.go(核心接口扩展)
改动意图:
新增DirectUploadCompleter接口,支持客户端直接上传的完成回调。
代码修改逻辑:
type DirectUploadCompleter interface {
CompleteDirectUpload(ctx context.Context, tool string, dstDir model.Obj, fileName string, uploadToken string) (model.Obj, error)
}合理性评估:
- ✅ 优点:接口设计清晰,与现有
DirectUploader接口对称 ⚠️ 疑问:uploadToken的安全性依赖驱动实现,是否需要在核心层增加校验?
详细建议:
- Token校验(P1):建议在
op.CompleteDirectUpload中增加uploadToken的基本校验(非空、格式合法) - 接口文档(P2):补充接口注释,说明
uploadToken的来源和用途
internal/fs/fs.go 和 internal/fs/put.go(核心逻辑扩展)
改动意图:
新增CompleteDirectUpload函数,封装直接上传完成流程。
代码修改逻辑:
fs.CompleteDirectUpload:对外暴露的高层接口completeDirectUpload:内部实现,处理存储查找、权限检查、调用驱动接口
合理性评估:
- ✅ 优点:遵循OpenList现有代码风格,错误处理完整
- ❌ 问题:缺少对
uploadToken的有效性校验,恶意客户端可能提交伪造token
详细建议:
-
Token校验(P0):必须修复 - 在
completeDirectUpload中增加对uploadToken的校验:- 检查token是否由本服务颁发(如使用HMAC签名)
- 检查token是否过期
- 检查token中的路径参数是否与请求匹配
当前实现的安全隐患:攻击者可以伪造
uploadToken,绕过权限检查完成上传。 -
权限二次检查(P1):在完成上传前再次检查用户对目标目录的写权限
internal/op/fs.go(核心操作层)
改动意图:
实现CompleteDirectUpload函数,调用驱动的完成接口并触发hooks。
代码修改逻辑:
func CompleteDirectUpload(...) (model.Obj, error) {
du, ok := storage.(driver.DirectUploadCompleter)
if !ok {
return nil, errors.WithStack(errs.NotImplement)
}
// 检查存储状态
// 获取目标目录
// 调用驱动接口
obj, err := du.CompleteDirectUpload(ctx, tool, dstDir, dstName, uploadToken)
// 触发objsUpdateHook
return obj, nil
}合理性评估:
-
✅ 优点:
- 正确检查存储状态和目录存在性
- 正确触发
objsUpdateHook,保持缓存一致性 - 错误处理完整
-
❌ 问题:
- 未检查
uploadToken的有效性(与上文相同的P0问题) objsUpdateHook使用context.WithoutCancel,可能导致资源泄漏
- 未检查
详细建议:
- Hook超时控制(P2):为
objsUpdateHook设置合理的超时时间,避免长时间占用goroutine - 并发控制(P3):高并发场景下大量触发hook可能导致性能问题,考虑使用队列或防抖机制
server/handles/fsup.go(HTTP处理层)
改动意图:
新增FsCompleteDirectUpload处理函数,处理完成上传的HTTP请求。
代码修改逻辑:
- 删除旧的
server/handles/direct_upload.go文件(70行) - 将
FsGetDirectUploadInfo和FsCompleteDirectUpload合并到fsup.go - 新增权限检查辅助函数:
resolveDirectUploadFile、checkDirectUploadWritePermission
合理性评估:
-
✅ 优点:
- 权限检查完整(用户权限+meta权限)
- 支持
File-PathHeader和表单参数两种方式指定路径 - 正确处理路径转义和相对路径攻击
-
⚠️ 疑问:uploadToken在HTTP层未做任何校验,完全信任客户端输入- 缺少请求频率限制,可能被用于DoS攻击
详细建议:
-
Token签名校验(P0):必须修复 - 在接受
uploadToken前验证其签名:// 建议实现 func validateUploadToken(token string, path string, fileName string) error { // 1. 解析token(建议使用JWT或HMAC签名) // 2. 检查签名是否有效 // 3. 检查token是否过期(建议有效期<1小时) // 4. 检查token中的path/fileName是否与请求匹配 return nil }
-
频率限制(P1):为
/complete_direct_upload接口添加速率限制(建议:同一IP每分钟最多10次) -
日志记录(P2):记录所有完成上传的请求(用户、路径、文件名、大小),便于审计
server/router.go(路由注册)
改动意图:
注册新的/api/fs/complete_direct_upload接口。
代码修改逻辑:
g.POST("/complete_direct_upload", middlewares.FsUp, handles.FsCompleteDirectUpload)合理性评估:
- ✅ 优点:使用了
FsUp中间件(权限检查) ⚠️ 疑问:是否需要额外的速率限制中间件?
详细建议:
建议为此接口添加专门的速率限制中间件
🎯 总体评价
功能性:⭐⭐⭐⭐⭐ - 功能完整,实现了客户端直接上传的完整流程,显著提升性能
安全性:⭐⭐⭐ - 存在P0安全问题:uploadToken未校验,存在伪造攻击风险
代码质量:⭐⭐⭐⭐ - 代码结构清晰,符合OpenList规范,但错误处理可以更详细
实现方案:⭐⭐⭐⭐⭐ - 技术方案先进(客户端直接上传),架构设计合理
建议操作:
- 🔄 Request Changes(需要修改)
理由:
此PR整体质量较高,功能完善且技术方案先进。但存在P0级别的安全问题:uploadToken未进行签名校验,攻击者可以伪造token绕过权限检查完成上传。必须修复此问题后才能合并。
🔧 必须修复的问题(P0)
1. uploadToken签名校验缺失 ⚠️ ⚠️ ⚠️
问题描述:
当前实现中,uploadToken由客户端提交,但服务端未对其进行任何校验。攻击者可以:
- 调用
/get_direct_upload_info获取合法token - 修改token中的路径参数(如改为
/admin/sensitive.txt) - 上传文件到PDS后,使用伪造的token调用
/complete_direct_upload - 绕过权限检查,将文件写入任意目录
建议修复方案:
// 在 internal/fs/put.go 中新增
func validateUploadToken(token, expectedPath, expectedFileName string) error {
// 1. 解析token(建议使用JWT格式)
claims, err := jwt.Parse(token, secretKey)
if err != nil {
return fmt.Errorf("invalid token: %w", err)
}
// 2. 检查过期时间
if claims.ExpiresAt < time.Now().Unix() {
return fmt.Errorf("token expired")
}
// 3. 检查路径和文件名是否匹配
if claims.Path != expectedPath || claims.FileName != expectedFileName {
return fmt.Errorf("token does not match request")
}
return nil
}
// 在 completeDirectUpload 中调用
func completeDirectUpload(ctx context.Context, tool, dstDirPath, dstName, uploadToken string) (model.Obj, error) {
// 新增:校验token
if err := validateUploadToken(uploadToken, dstDirPath, dstName); err != nil {
return nil, errors.WithMessage(err, "upload token validation failed")
}
// 原有逻辑...
}或者更简单的方案(如果PDS驱动自己管理token):
在drivers/pds/upload.go的CompleteDirectUpload中验证uploadToken是否与之前颁发的匹配。
💡 强烈建议修复的问题(P1)
1. 缺少频率限制
问题:/complete_direct_upload接口可能被用于DoS攻击
建议:添加速率限制中间件,限制同一IP每分钟最多调用10-20次
2. 与PR #1936的冲突处理
问题:存在两个PDS驱动实现
建议:
- 与@xrgzs协调,确定最终合并哪个版本
- 如果本PR合并,建议关闭#1936或将#1936的优点合并进来
- 两个实现都提供OAuth2 token获取流程可能导致用户困惑
📋 可选改进建议(P2-P3)
- 大文件分片上传支持(P3):当前仅支持单分片上传,建议后续支持PDS的分片上传接口
- 缩略图性能优化(P3):如果PDS返回了缩略图URL,可以缓存避免重复请求
- 错误分类和重试(P2):为常见错误(网络超时、限流、服务端错误)提供自动重试机制
- Client ID配置化(P2):将硬编码的
defaultClientID改为可配置项
📊 代码统计
- 新增文件:6个(drivers/pds/目录)
- 修改核心文件:6个(driver.go、fs.go、put.go、op/fs.go、handles/fsup.go、router.go)
- 删除文件:1个(server/handles/direct_upload.go,重构为fsup.go)
- 新增代码行数:约800行
- 修改驱动注册:drivers/all.go
- 新增API接口:1个(
POST /api/fs/complete_direct_upload)
🎯 最终建议
当前状态:🔄 Request Changes(需要修复P0安全问题)
修复后可合并条件:
- ✅ 实现
uploadToken的签名校验机制 - ✅ 添加频率限制保护
- ✅ 与项目维护者协调PR #1936的处理方案
修复后预期评分:⭐⭐⭐⭐⭐
此PR在修复安全问题后,将成为一个教科书级别的驱动实现:
- 完整的OAuth2集成
- 先进的客户端直接上传方案
- 完善的文档和前端配套
- 良好的代码质量和架构设计
期待看到修复后的版本!👍
Summary / 摘要
修改概览
本次修改为 OpenList 新增原生 PDS 驱动,并在驱动注册表中启用
PDS存储类型。驱动使用 PDS 官方接口完成文件列表、下载链接、上传、目录创建、重命名、移动、复制、回收站删除、容量信息读取和 OAuth Token 刷新。主要变更
drivers/pds驱动目录,按 OpenList 现有驱动结构拆分为元信息、API 客户端、数据类型和驱动实现。drivers/all.go中注册 PDS 驱动,确保程序启动时可在存储类型列表中发现PDS。domain_id、drive_id、access_token、refresh_token等配置连接 PDS 空间。op.MustSaveDriverStorage写回存储配置。/v2/file/create、预签名PUT和/v2/file/complete的单分片流程。/v2/recyclebin/trash,行为是移入 PDS 回收站,不做永久删除。/v2/file/get获取,并设置两小时链接缓存过期时间。drivers/pds/README.md,说明驱动能力、配置项和实现注意事项。文件说明
drivers/pds/meta.go:定义驱动配置项和注册逻辑。drivers/pds/api.go:封装 PDS API 请求、Token 刷新和预签名上传请求。drivers/pds/types.go:定义 PDS 响应结构,并转换为 OpenListmodel.Obj。drivers/pds/driver.go:实现 OpenList 驱动接口,包括读写、移动、复制、删除和容量详情。drivers/pds/README.md:驱动使用说明。drivers/all.go:增加 PDS 驱动 blank import。行为与兼容性
本次新增的是独立驱动,不修改既有驱动行为。
删除动作进入 PDS 回收站,与永久删除不同。
expires_at可填0;只要配置了refresh_token,驱动会在首次请求时刷新 Token。当前上传为单分片上传,适合作为最小可用实现;大文件分片并发上传可后续增强。
新增了公开接口 /api/fs/complete_direct_upload
This PR has breaking changes.
/ 此 PR 包含破坏性变更。
This PR changes public API, config, storage format, or migration behavior.
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
This PR requires corresponding changes in related repositories.
/ 此 PR 需要关联仓库同步修改。
Related repository PRs / 关联仓库 PR:
Testing / 测试
go test ./...gofmt -w drivers/pds drivers/all.gogo test ./drivers/pdsgo build -o build/openlist-demo.exe -tags=jsoniter ./pds存储。/pds。Checklist / 检查清单
/ 我已阅读 CONTRIBUTING。
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
gofmt,go fmt, orprettierwhere applicable./ 我已按适用情况使用
gofmt、go fmt或prettier格式化变更代码。/ 我已在适用情况下请求相关维护者或代码所有者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
Usage scope / 使用范围:
Code generation / 代码生成
Refactoring / 重构
Documentation / 文档
Tests / 测试
Translation / 翻译
Review assistance / 审查辅助
I have reviewed and validated all AI-assisted content included in this PR.
/ 我已审核并验证此 PR 中的所有 AI 辅助内容。
I have ensured that all AI-assisted commits include
Co-Authored-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。I can reproduce all AI-assisted content included in this PR without any AI tools.
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。
其他说明
PDS 服务文档:https://help.aliyun.com/zh/pds/drive-and-photo-service-ent/what-is-cde