Files
mnote/bugs/05-editor-mainline/done/5-36-onlyoffice-local-edit-save-callback-contract-v1.md
T
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

88 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 5-36 OnlyOffice local-folder 编辑保存 callback 契约
## 状态
- 状态:done
## 目标
为 local-folder Office 编辑保存建立 callback 写回契约。P1 可以先落设计和测试骨架;若实现,必须覆盖 status 2/6 的下载写回和路径安全。
## 原因
ONLYOFFICE 官方保存链路要求 callback `status === 2``status === 6` 时,集成后端下载 `body.url` 并写回原文件。此前 `mnote-web` callback 仍代理 legacy Nextlocal-folder 写回未闭环。本轮已补齐 local-folder callback 写回路径。
## 允许修改
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `rust/crates/adapter-onlyoffice/src/lib.rs`
- 相关单测
- 如需记录设计,可补充本 checklist
## 禁止事项
- 不绕过 allowed roots 写任意路径。
- 不在没有冲突保护时覆盖用户无关文件。
- 不强行删除 legacy callback 代理。
## Checklist
- [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,不让用户误解为完整协作编辑能力已产品化。
## 验收
- `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`
- 后续浏览器编辑保存验收需单独设计,不纳入 P0。
## 本轮执行记录
- 2026-06-01Codex 复核当前实现并补齐 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-21Reasonix worker C 分析完成。
**结论:本轮不应实现完整写回。**
**阻塞输入缺口:**
1. callback URL 当前只携带 `assetId``userId`,缺少 `rootUri``path`
- `buildCallbackUrl()``onlyoffice.rs` 页脚 JS 中硬编码只传两个参数。
- 对于 local-folder assetassetId `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 Nextlocal-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 提示,符合安全策略。