2026-05-21 05:40:06 +08:00
|
|
|
|
# 5-36 OnlyOffice local-folder 编辑保存 callback 契约
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
## 状态
|
|
|
|
|
|
|
|
|
|
|
|
- 状态:done
|
|
|
|
|
|
|
2026-05-21 05:40:06 +08:00
|
|
|
|
## 目标
|
|
|
|
|
|
|
|
|
|
|
|
为 local-folder Office 编辑保存建立 callback 写回契约。P1 可以先落设计和测试骨架;若实现,必须覆盖 status 2/6 的下载写回和路径安全。
|
|
|
|
|
|
|
|
|
|
|
|
## 原因
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
ONLYOFFICE 官方保存链路要求 callback `status === 2` 或 `status === 6` 时,集成后端下载 `body.url` 并写回原文件。此前 `mnote-web` callback 仍代理 legacy Next,local-folder 写回未闭环。本轮已补齐 local-folder callback 写回路径。
|
2026-05-21 05:40:06 +08:00
|
|
|
|
|
|
|
|
|
|
## 允许修改
|
|
|
|
|
|
|
|
|
|
|
|
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
|
|
|
|
|
|
- `rust/crates/adapter-onlyoffice/src/lib.rs`
|
|
|
|
|
|
- 相关单测
|
|
|
|
|
|
- 如需记录设计,可补充本 checklist
|
|
|
|
|
|
|
|
|
|
|
|
## 禁止事项
|
|
|
|
|
|
|
|
|
|
|
|
- 不绕过 allowed roots 写任意路径。
|
|
|
|
|
|
- 不在没有冲突保护时覆盖用户无关文件。
|
|
|
|
|
|
- 不强行删除 legacy callback 代理。
|
|
|
|
|
|
|
|
|
|
|
|
## Checklist
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
- [x] 明确 local asset callback 定位:callback URL 携带 `rootUri` / `path` / `sessionId` / `token`,并用 bridge session 校验 asset match。
|
|
|
|
|
|
- [x] status 非 2/6 时返回 `{ "error": 0 }`,不写文件。
|
|
|
|
|
|
- [x] status 2/6 时下载 rewritten `body.url`。
|
|
|
|
|
|
- [x] 写回前校验目标路径属于当前 local root allowed roots。
|
|
|
|
|
|
- [x] 写回后写入原始本地文件;后续页面刷新由现有 local-folder watcher / projection refresh 感知。浏览器编辑保存端到端 smoke 另列后续验收,不作为本 bug 阻塞项。
|
|
|
|
|
|
- [x] 编辑入口仍保留 guard,不让用户误解为完整协作编辑能力已产品化。
|
2026-05-21 05:40:06 +08:00
|
|
|
|
|
|
|
|
|
|
## 验收
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1`
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1`
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1`
|
2026-05-21 05:40:06 +08:00
|
|
|
|
- 后续浏览器编辑保存验收需单独设计,不纳入 P0。
|
|
|
|
|
|
|
|
|
|
|
|
## 本轮执行记录
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
- 2026-06-01:Codex 复核当前实现并补齐 callback 写回回归测试。
|
|
|
|
|
|
- `OnlyOfficeCallbackQuery` 已携带 `root_uri` / `path` / `session_id` / `token`。
|
|
|
|
|
|
- `buildCallbackUrl()` 已把 local-folder Office asset 的 root/path/session/token 写入 callback query。
|
|
|
|
|
|
- `local_folder_onlyoffice_callback()` 已校验 root/path、bridge session token、session asset match,并通过 `resolve_onlyoffice_local_file_path()` 阻断 root escape。
|
|
|
|
|
|
- status 2/6 通过 `prepare_callback()` rewrite 后下载 `body.url` 并写回原始本地文件;非 2/6 返回成功且不写文件。
|
|
|
|
|
|
- 新增/确认测试覆盖:
|
|
|
|
|
|
- `onlyoffice_local_callback_rejects_unauthenticated_local_write`
|
|
|
|
|
|
- `onlyoffice_local_callback_writes_status_two_body_to_original_file`
|
|
|
|
|
|
- `onlyoffice_local_callback_writes_status_six_body_to_original_file`
|
|
|
|
|
|
- `onlyoffice_local_callback_rejects_root_escape_path`
|
|
|
|
|
|
- `onlyoffice_local_callback_ignores_non_write_status`
|
|
|
|
|
|
- 验证通过:
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1`
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1`
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1`
|
|
|
|
|
|
|
2026-05-21 05:40:06 +08:00
|
|
|
|
- 2026-05-21:Reasonix worker C 分析完成。
|
|
|
|
|
|
**结论:本轮不应实现完整写回。**
|
|
|
|
|
|
|
|
|
|
|
|
**阻塞输入缺口:**
|
|
|
|
|
|
|
|
|
|
|
|
1. callback URL 当前只携带 `assetId` 和 `userId`,缺少 `rootUri` 和 `path`。
|
|
|
|
|
|
- `buildCallbackUrl()` 在 `onlyoffice.rs` 页脚 JS 中硬编码只传两个参数。
|
|
|
|
|
|
- 对于 local-folder asset(assetId `local:asset:path/to/file.docx`),无法从 assetId 推导 rootUri。
|
|
|
|
|
|
- 没有 rootUri 就无法 `resolve_onlyoffice_local_file_path` → 无法安全写回。
|
|
|
|
|
|
2. `adapter-onlyoffice` 的 `prepare_callback` 不感知 local-folder 路径。
|
|
|
|
|
|
- `OnlyOfficeCallbackPreparationInput` 没有 rootUri / path 字段。
|
|
|
|
|
|
- `prepare_callback` 只做下载 URL rewrite 和 session 定位,不含路径校验。
|
|
|
|
|
|
3. callback Rust handler 的 `proxy_legacy_onlyoffice_json` 目前代理 legacy Next;local-folder 无 legacy 时返回 NOT_IMPLEMENTED。
|
|
|
|
|
|
|
|
|
|
|
|
**实现写回所需的最小增量(下轮实现):**
|
|
|
|
|
|
|
|
|
|
|
|
- `page()` 页脚的 `buildCallbackUrl()`:检测 local asset,追加 `rootUri` 和 `path` query 参数。
|
|
|
|
|
|
- `OnlyOfficeCallbackQuery` struct:增加 `root_uri: Option<String>` 和 `path: Option<String>`。
|
|
|
|
|
|
- `callback()` handler:检测到 local asset 时(assetId 含 `local:` / `local-file:` 前缀),从 query 提取 rootUri 和 path,调用 `resolve_onlyoffice_local_file_path` 校验路径在 allowed roots 内。
|
|
|
|
|
|
- `adapter-onlyoffice`:在 `OnlyOfficeCallbackPreparationInput` 和 `prepare_callback` 增加 local path 字段。
|
|
|
|
|
|
- status 2/6:下载 rewritten body.url,写回校验过的本地路径。
|
|
|
|
|
|
- status 非 2/6:返回 `{"error": 0}`,不写文件。
|
|
|
|
|
|
|
|
|
|
|
|
**当前 guard 状态:**
|
|
|
|
|
|
- Worker B 已在 `layout.rs` 中实现 `withOfficeEditModeGuard`:confirm 对话框 + `data-mnote-last-office-edit-mode-requested` / `data-mnote-last-office-edit-mode-guard` 数据属性。
|
|
|
|
|
|
- 编辑入口在 local-folder writeback 未闭环时带 guard 提示,符合安全策略。
|