feat(权限): 收敛普通账号写权限至本人凭证
将调度时刻、采集参数等实例级配置收归管理员,普通账号仅可维护本人 Cookie 与 User-Agent。 新增 config.writable_by 作为唯一写权限入口,set_setting 强制全局键落到 user_id=0, 消除「管理员改了只有自己生效」的静默缺陷。新增 tools/check_docs.py 文档自检, smoke 断言扩至 215 项、check_live 扩至 122 项并支持普通账号越权验收, 忽略 backups/、data/*.bak* 与 legacy-v1/,版本升至 v1.3.0。
这个提交包含在:
+89
-1
@@ -8,6 +8,94 @@
|
||||
|
||||
---
|
||||
|
||||
## [1.3.0] — 2026-09-16
|
||||
|
||||
**主题:权限收敛 · 配置作用域统一 · 目录规范化**
|
||||
|
||||
把「配置存在哪一级」与「谁能改它」对齐成一条规则,并收紧普通账号的越权面。
|
||||
**数据不会丢**:`manage.py init` 检测到 `PRAGMA user_version` 2 → 3 时自动迁移,
|
||||
把管理员个人名下的调度与采集参数**提升到实例级**(`user_id=0`)后清掉个人残留,
|
||||
全程写一条 `promote_global_settings` 审计,且可重复执行。
|
||||
|
||||
### 变更(**不兼容**)
|
||||
|
||||
- **调度与采集参数改为实例级,普通账号只读**
|
||||
- `GLOBAL_KEYS` 扩容:`api_base` / `api_path` / 注册策略 4 键
|
||||
+ `schedule_enabled` / `schedule_times` / `catch_up` / `catch_up_grace_hours`
|
||||
+ `page_size` / `rewind_minutes` / `drift_tolerance_minutes` / `max_prompt` /
|
||||
`verify_days` / `timeout` / `ssl_verify`
|
||||
- 新增 `config.USER_EDITABLE_KEYS = {cookie, user_agent}` —— 普通账号**唯一**可写的两个键
|
||||
- 新增 `config.writable_by(key, is_admin)`:前后端与测试共用的**唯一**判断入口,
|
||||
避免「页面置灰但接口还能写」这类规则漂移
|
||||
- 为什么必须放实例级(而不是「个人级但只有管理员能写」):若只写在管理员自己的
|
||||
`user_id` 下,其它账号读取时会回落到 `DEFAULTS`,**管理员改的值对别人完全不生效**
|
||||
—— 那是个静默 bug。统一放实例级,语义是「一台部署一套采集与调度策略」。
|
||||
- **`set_setting()` 强制把全局键重定向到 `user_id=0`**:从结构上消除
|
||||
「管理员改了只有自己生效」的可能
|
||||
- **`/logs` 与 `/logs/tail` 改为仅管理员**(原先普通账号能看到自己的采集历史 +
|
||||
整机应用日志尾部)。普通账号访问返回 403,导航里不显示入口
|
||||
|
||||
### 新增
|
||||
|
||||
- **`manage.py init` 自动迁移(uv 2 → 3)**:`_promote_personal_to_global(conn)`
|
||||
取首个管理员的个人级全局键值提升到 `user_id=0`,再清除 `user_id<>0` 的残留;
|
||||
幂等,可反复执行
|
||||
- **凭证类键刻意不灌实例级**:`init_db()` 灌默认值时 `continue` 掉
|
||||
`USER_EDITABLE_KEYS`,并显式 `DELETE FROM settings WHERE user_id=0 AND key IN ('cookie','user_agent')`
|
||||
—— 实例级存凭证等于给所有账号发同一张身份
|
||||
- **`/api/settings` 回传 `_userKeys` / `_role`**,`/api/manifest` 回传 `role`,
|
||||
`/api/status` 新增 `is_admin` / `can_edit_schedule` / `can_view_logs`
|
||||
—— 大屏是静态页,拿不到 Jinja 上下文,只能靠这几个字段决定显隐
|
||||
- **`app.js:formData()` 跳过 disabled 控件**(含祖先 `fieldset[disabled]`):
|
||||
disabled 的 input 仍在 `form.elements` 里,一起提交会让服务端因「越权修改只读项」
|
||||
拒掉**整单**;现在只读项既不显示也不参与提交
|
||||
- **测试**:`tools/smoke.py` 162 → 215 项断言(新增「非管理员越权面必须全部关死」
|
||||
与「全局键必须落在实例级」两节);`tools/check_live.py` 83 → 122 项,
|
||||
新增 `--as USER:PASS` 参数与第 12 节「普通账号真实 HTTP 越权验收」
|
||||
- **新增 `tools/check_docs.py`(文档自检)**:内部链接与**跨文件锚点**、图片引用、
|
||||
**绝对路径泄漏**(连带会泄漏用户名)、版本一致性(`__init__` / `Dockerfile` /
|
||||
`README` / `CHANGELOG` 四处)、模板与 JS 里的产品名硬编码。
|
||||
文档互相引用后章节一重排,锚点会**静默失效** —— Markdown 自己不报错、CI 也不管,
|
||||
只能靠这一层。**有问题即退出码 1**(只想看报告不失败用 `--no-fail`)。
|
||||
文档清单**自动发现**,不写死文件名 —— 写死列表的那版曾漏掉
|
||||
`THIRD-PARTY-NOTICES.md` / `CODE_OF_CONDUCT.md`(覆盖面 13 → 9 个文件且毫无提示)。
|
||||
锚点比对**刻意不逐字复刻 GitHub/Gitea 的 slug 算法**(各家对 `+`/`:`/连续空格的
|
||||
处理并不一致,写死一个实现换个托管平台就批量误报),改为只比较「有效字符」
|
||||
(小写字母 / 数字 / 汉字),既不受标点差异干扰,章节真被改名时又照抓不误
|
||||
|
||||
### 修复
|
||||
|
||||
- **`api_status` 引用了未定义的变量 `u`** ⇒ `/api/status` 稳定 500。
|
||||
该缺陷由 `check_live.py` 新增的真实 HTTP 验收抓到,此前 smoke 完全没有覆盖这个接口
|
||||
- **`tools/smoke.py` 的清理语句会把实例级配置当孤儿删掉**
|
||||
(`DELETE FROM settings WHERE user_id NOT IN (SELECT id FROM users)`,
|
||||
而 `user_id=0` 不是任何真实账号)⇒ 每跑一轮 smoke 就清空一次实例级配置。
|
||||
实测曾把 19 个实例级键清到只剩 1 行。已加 `user_id<>0 AND` 并补防回归断言
|
||||
- **`tools/smoke.py` 哨兵 UA 还原会留下多余行**:原本实例级无 `user_agent` 行时
|
||||
`set_setting(..., "")` 会插一行空串。改为「原本无则 DELETE」,断言也改成比行为而非比行
|
||||
|
||||
### 目录规范化
|
||||
|
||||
- 工作区根目录的 v1.0 单文件版(`fetch_usage.py`、`dashboard/`、`data/usage_records.csv`)
|
||||
收进工作区级 `legacy-v1/`,附带 README 说明「已被取代、可安全删除」;
|
||||
`config.LEGACY_CSV_CANDIDATES` 第一候选同步指向新位置
|
||||
- 新增 `backups/` 作为数据库快照的统一落点(刻意**不放在 `data/`**——
|
||||
`data/` 是 Docker 卷,`down -v` 会把备份和正本一起删掉)
|
||||
- `.gitignore` 补 `backups/*`、`data/*.bak*`、`legacy-v1/` 三条兜底规则
|
||||
- 清理 `data/shots/`(已被 `docs/images/` 取代)与全部 `__pycache__`
|
||||
|
||||
### 文档
|
||||
|
||||
- `docs/DEPLOYMENT.md` 重写:三条并列的部署路径(裸机 / Docker 自打包 /
|
||||
docker-compose 拉云端镜像),配置项速查表按新作用域重排
|
||||
- `docs/USER-GUIDE.md` 重写:新增「权限与数据边界」「信息安全与隐私安全」两章,
|
||||
截图重出为普通账号视角
|
||||
- `README.md` / `SECURITY.md` / `CONTRIBUTING.md` / `docs/ARCHITECTURE.md` /
|
||||
`docs/API.md` / `docs/FAQ.md` 同步权限模型、配置作用域与验证层变化;
|
||||
订正了 CONTRIBUTING / ARCHITECTURE / FAQ 里残留的旧断言数(165/83 → 215/122)
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] — 2026-09-15
|
||||
|
||||
**主题:多用户化 · Cookie 加密 · 开放注册与验证码**
|
||||
@@ -159,7 +247,7 @@
|
||||
| 容器跑着跑着页面全 500,日志里 `sqlite3.OperationalError: unable to open database file` | 数据原本用**绑定挂载**;Windows + Docker Desktop 走 **9p**,宿主的 Windows 进程只要访问过这个 WAL 库(**纯读也会触发**),容器侧下一次连接就重建不了 `-shm`,且**不会自愈** | 改为 **Docker 命名卷**(容器独占数据目录);需要宿主目录时叠加 `docker-compose.hostdir.yml`(仅建议 Linux) |
|
||||
|
||||
最小复现:容器正常 → 宿主跑一次 `manage.py stats` → 容器立刻打不开库、且重启前不再恢复。
|
||||
已写进 [DEPLOYMENT.md 第九节](DEPLOYMENT.md#windows-绑定挂载的坑容器打不开数据库)。
|
||||
已写进 [DEPLOYMENT.md 第十一节](DEPLOYMENT.md#115-windows-绑定挂载的坑容器打不开数据库)。
|
||||
|
||||
### 安全
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户