# 权限安全加固与统一权限管理 Spec
## Why
当前 `user_update()` 无字段过滤,任何插件/hook 都可直接修改 `password`、`gid` 等敏感字段,存在严重的提权和改密风险。同时权限检查散落在各处,没有统一入口,插件无法安全地注册和检查自定义权限。
## What Changes
- **`user_update()` 加字段白名单保护** — 默认禁止修改 `password`、`password_hash`、`salt`、`gid` 等敏感字段
- **新增 `user_change_password()` 专用函数** — 密码修改必须验证旧密码或管理员身份
- **新增 `user_change_group()` 专用函数** — 用户组变更必须验证管理员权限
- **新增 `PermissionService` 统一权限服务** — 提供 `check()`、`register()`、`getPermissions()` API
- **新增 `group_permission` 关联表** — 权限不再硬编码在 group 表字段,改为 key-value 存储
- **修改后台用户组编辑页** — 支持动态权限项配置(核心+插件注册的权限)
- **修改前台密码修改页** — 使用 `user_change_password()` 替代直接 `user_update()`
- **修改后台用户编辑页** — 使用 `user_change_password()` / `user_change_group()`
- **PluginScanner 增加权限安全扫描规则** — 检测插件是否直接调用 `user_update()` 修改敏感字段
- **UpgradeService 增加权限系统升级步骤** — 创建 `group_permission` 表、迁移旧权限数据
- **新增文档** — `doc/permission-system.md`
### **BREAKING** Changes
- `user_update()` 不再允许传入 `password`、`password_hash`、`salt`、`gid` 字段,传入会被静默忽略并记录警告日志
- 插件如需修改这些字段,必须改用 `user_change_password()` 或 `user_change_group()`
## Impact
- Affected code: `model/user.func.php`, `route/my.php`, `admin/route/user.php`, `admin/route/group.php`, `lib/PluginScanner.php`, `lib/UpgradeService.php`
- Affected plugins: `xn_mobile`(直接 `user_update` 改密码)、`sg_group`/`tt_credits`(`user_update` 改积分,不受影响)、所有通过 hook 调用 `user_update` 的插件
- Affected templates: `admin/view/htm/group_update.htm`, `view/htm/my.htm`(密码修改部分)
---
## ADDED Requirements
### Requirement: user_update 字段白名单保护
系统 SHALL 在 `user_update()` 函数中实现敏感字段过滤机制。
#### Scenario: 插件尝试通过 user_update 修改密码
- **WHEN** 任何代码调用 `user_update($uid, array('password' => 'xxx'))`
- **THEN** `password` 字段被静默移除,不写入数据库,并记录 WARNING 级别日志
#### Scenario: 插件尝试通过 user_update 修改用户组
- **WHEN** 任何代码调用 `user_update($uid, array('gid' => 1))`
- **THEN** `gid` 字段被静默移除,不写入数据库,并记录 WARNING 级别日志
#### Scenario: 正常字段更新不受影响
- **WHEN** 调用 `user_update($uid, array('email' => 'new@example.com', 'signature' => 'hello'))`
- **THEN** 正常更新,不受白名单限制
### Requirement: user_change_password 专用函数
系统 SHALL 提供专门的密码修改函数,包含身份验证。
#### Scenario: 用户修改自己的密码
- **WHEN** 调用 `user_change_password($uid, $new_password, $old_password)`
- **THEN** 验证旧密码正确后,同时更新 `password`(md5+salt)和 `password_hash`(bcrypt)字段
#### Scenario: 管理员重置用户密码
- **WHEN** 调用 `user_change_password($uid, $new_password, '', TRUE)`(第4参数为管理员模式)
- **THEN** 跳过旧密码验证,直接更新密码,但必须当前用户 `gid == 1`
#### Scenario: 旧密码错误
- **WHEN** 非管理员模式下旧密码验证失败
- **THEN** 返回 FALSE,不修改密码
### Requirement: user_change_group 专用函数
系统 SHALL 提供专门的用户组变更函数,包含权限验证。
#### Scenario: 管理员变更用户组
- **WHEN** 管理员(gid==1)调用 `user_change_group($uid, $new_gid)`
- **THEN** 验证管理员身份后更新用户组
#### Scenario: 非管理员尝试变更用户组
- **WHEN** 非管理员调用 `user_change_group($uid, $new_gid)`
- **THEN** 返回 FALSE,不修改用户组
### Requirement: PermissionService 统一权限服务
系统 SHALL 提供统一的权限管理服务类。
#### Scenario: 插件注册自定义权限
- **WHEN** 插件在安装时调用 `PermissionService::register('my_plugin', 'allow_upload', '允许上传文件')`
- **THEN** 权限项被注册到系统中,后台用户组编辑页自动显示该权限配置
#### Scenario: 权限检查
- **WHEN** 代码调用 `PermissionService::check('allow_thread')`
- **THEN** 根据当前用户 gid 查询 group_permission 表,返回 TRUE/FALSE
#### Scenario: 向后兼容旧权限字段
- **WHEN** `group_permission` 表中无对应记录
- **THEN** 回退到 group 表的旧字段(如 `allowread`、`allowthread`)进行判断
### Requirement: group_permission 关联表
系统 SHALL 使用 `group_permission` 表存储权限配置,替代 group 表硬编码字段。
表结构:
```sql
CREATE TABLE `bbs_group_permission` (
`gid` smallint(6) unsigned NOT NULL DEFAULT 0,
`permission_key` varchar(64) NOT NULL DEFAULT '',
`value` tinyint(1) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`gid`, `permission_key`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
```
#### Scenario: 迁移旧权限数据
- **WHEN** 执行升级脚本
- **THEN** 将 group 表的 `allowread`/`allowthread`/`allowpost` 等字段数据迁移到 `group_permission` 表
### Requirement: 后台用户组编辑页改造
系统 SHALL 修改后台用户组编辑页面,支持动态权限项。
#### Scenario: 管理员编辑用户组权限
- **WHEN** 管理员访问用户组编辑页
- **THEN** 页面显示核心权限项 + 所有插件注册的权限项,按分组展示
### Requirement: PluginScanner 增加权限安全扫描规则
系统 SHALL 在插件扫描器中增加权限安全相关检测规则。
#### Scenario: 扫描插件直接修改密码
- **WHEN** 插件代码中包含 `user_update.*password` 或 `user_update.*gid`
- **THEN** 报告为 warning 级别,建议改用 `user_change_password()` / `user_change_group()`
### Requirement: UpgradeService 增加权限系统升级步骤
系统 SHALL 在升级服务中增加权限系统相关升级步骤。
#### Scenario: 执行升级
- **WHEN** 管理员执行一键升级
- **THEN** 新增步骤:创建 `group_permission` 表、迁移旧权限数据、注册核心权限项
### Requirement: 权限系统文档
系统 SHALL 在 `doc/` 目录下提供权限系统文档。
#### Scenario: 开发者查阅权限文档
- **WHEN** 开发者阅读 `doc/permission-system.md`
- **THEN** 了解权限架构、API 用法、插件集成方式、迁移指南
## MODIFIED Requirements
### Requirement: 前台密码修改(route/my.php)
原逻辑:直接 `user_update($uid, array('password' => md5(...)))`
修改为:调用 `user_change_password($uid, $password_new, $password_old)`
### Requirement: 后台用户编辑(admin/route/user.php)
原逻辑:直接 `user_update` 修改 password 和 gid
修改为:密码修改用 `user_change_password($uid, $password, '', TRUE)`,用户组修改用 `user_change_group($uid, $_gid)`
### Requirement: 后台用户组编辑(admin/route/group.php)
原逻辑:权限字段硬编码在 group 表
修改为:同时更新 group 表(兼容)和 group_permission 表