
Gitea Version: 1.21.0+rc2-60-g00cd5ba6f
diff --git a/docs/permission_acl_design.md b/docs/permission_acl_design.md new file mode 100644 index 0000000..581872b --- /dev/null +++ b/docs/permission_acl_design.md @@ -0,0 +1,553 @@ +节点权限设计说明(支持两棵树 + 子节点操作 + 全局 tree 规则) + +本说明基于当前的权限表设计约束: + +- 有两棵树:`standards` 和 `projects`。 +- 只有 **Allow**(允许)权限,没有 Deny,也没有继承。 +- 使用一张表 `node_permission` 管理权限,支持 6 类主体: + + - 用户:`user` + - 用户组:`group` + - 角色:`role` + - 所有人:`everyone` + - 父节点的 owner:`parent_owner` + - 子树根节点的 owner:`root_owner` + +--- + +### 一、`node_permission` 表字段定义 + +```sql +CREATE TABLE node_permission ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + + -- 树维度: + -- 'standards' : 只对 standards 生效 + -- 'projects' : 只对 projects 生效 + -- NULL : 两棵树都生效(全局规则) + tree VARCHAR(32) NULL, + + -- 节点维度: + -- NULL : 默认权限(作用范围由 tree 决定) + -- > 0 : 某个具体节点的专属权限 + node_id BIGINT NULL, + + -- 主体类型: + -- user : 用户(user.id) + -- group : 用户组(group.id) + -- role : 角色(如 super/sAdmin/pAdmin) + -- everyone : 所有人 + -- parent_owner : 父节点的 owner + -- root_owner : 当前子树根节点的 owner + principal_type ENUM( + 'user', + 'group', + 'role', + 'everyone', + 'parent_owner', + 'root_owner' + ) NOT NULL, + + -- 主体标识: + -- user/group : 对应 id 的字符串 + -- role : 角色 code + -- everyone / parent_owner / root_owner : 固定为 NULL + principal_key VARCHAR(64) NULL, + + -- 节点自身权限 + can_list_children TINYINT(1) NOT NULL DEFAULT 0, + can_add_child_folder TINYINT(1) NOT NULL DEFAULT 0, + can_add_child_doc TINYINT(1) NOT NULL DEFAULT 0, + can_delete_node TINYINT(1) NOT NULL DEFAULT 0, + can_edit_node TINYINT(1) NOT NULL DEFAULT 0, + + -- 文档内容权限 + can_read_doc TINYINT(1) NOT NULL DEFAULT 0, + can_edit_doc TINYINT(1) NOT NULL DEFAULT 0, + can_delete_doc TINYINT(1) NOT NULL DEFAULT 0, + + -- 子节点操作权限 + can_delete_child_node TINYINT(1) NOT NULL DEFAULT 0, + can_edit_child_node TINYINT(1) NOT NULL DEFAULT 0, + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + -- 同一 (tree, node_id, principal_type, principal_key) 只允许一条 + UNIQUE KEY uk_tree_node_principal (tree, node_id, principal_type, principal_key), + KEY idx_node (node_id), + KEY idx_principal (principal_type, principal_key) +); +``` + +--- + +## 二、`node_permission` 关键字段语义 + +这里只说明与鉴权相关的字段 + +- **节点维度** + + - `node_id`: + - `NULL`:表示“**默认权限**”,作用范围由 `tree` 决定: + - `tree = 'standards'`:只对 standards 全部节点生效; + - `tree = 'projects'`:只对 projects 全部节点生效; + - `tree IS NULL`:对两棵树全部节点生效。 + - `> 0`:表示“**某个具体节点的专属权限**”,此时 `tree` 应等于该节点所属树。 +- **主体(Principal)维度** + + - `principal_type`:`'user' | 'group' | 'role' | 'everyone' | 'parent_owner' | 'root_owner'` + - `principal_key`:根据principal_type确定其含义 + - `user`:对应用户表 `user.id` 的字符串形式(如 `'12'`) + - `group`:对应用户组表 `group.id` 的字符串形式 + - `role`:角色编码(如 `'super'`、`'sAdmin'`、`'pAdmin'`) + - `everyone`:固定为 `NULL` + - `parent_owner`:固定为 `NULL` + - `root_owner`:固定为 `NULL` +- **节点自身权限字段** + + - `can_list_children`:是否可展开节点、列出子节点。 + - `can_add_child_folder`:是否可在该节点下新增文件夹(`node_type='folder'`)。 + - `can_add_child_doc`:是否可在该节点下新增文档(`node_type='doc'`)。 + - `can_delete_node`:是否可删除该节点本身。 + - `can_edit_node`:是否可修改该节点本身的元数据(名称、排序、owner 等)。 +- **文档内容权限字段** + + - `can_read_doc`:是否可读取文档内容。 + - `can_edit_doc`:是否可编辑文档内容。 + - `can_delete_doc`:是否可删除文档节点。 +- **对子节点操作权限字段** + + - `can_delete_child_node`:是否有权删除“该节点的直接子节点”。 + - `can_edit_child_node`:是否有权修改“该节点的直接子节点”的元数据(如 name、owner)。 + +**唯一约束建议:** + +- `(tree, node_id, principal_type, principal_key)` 唯一。这样: + - 对于某棵树上的某个节点 + 某个主体,只会有一条“节点专属”权限记录; + - 对于某棵树上的某个主体,只会有一条“默认”规则; + - `tree IS NULL AND node_id IS NULL` 的记录则是“两棵树共用的唯一全局默认”。 + +--- + +## 三、主体集合(Principals)的构造 + +鉴权的第一步是,把“当前用户在权限系统里是谁”表达成一个**主体集合**,集合中的每个元素是一个 `(principal_type, principal_key)`。 + +设: + +- 当前用户 id 为 `U`; +- 当前操作的节点 id 为 `N`; +- 当前节点所属的树为 `T`(`'standards'` 或 `'projects'`,可以从 `nodes` 表的字段`tree_type`获得 。 + +### 3.1 主体集合的组成 + +主体集合 `P` 至少包含以下几类元素: + +1. **用户本身(user)** + + - 元素:`('user', U.toString())` +2. **用户所属的所有组(group)** + + - 从 `groupUser` 中查询:`WHERE user_id = U` 得到所有 `group_id`。 + - 每个组生成一个主体:`('group', groupId.toString())`。 +3. **用户的角色(role)** + + - `users` 表有一个 `role` 字段(值为 `'super'` / `'sAdmin'` / `'pAdmin'`),可获取到角色。 + - 生成主体:`('role', roleCode)`。 +4. **everyone(所有人)** + + - 固定加入:`('everyone', NULL)`。 +5. **parent_owner(父节点 owner)** + + - 先在 `nodes` 表中拿到当前节点的父节点 id:`parentId`。 + - 若 `parentId` 大于 0,则再从 `node` 表读出该父节点的 `owner` 字段: + - 若 `owner == U`,说明当前用户是父节点的 owner,则加入:`('parent_owner', NULL)`。 + - 否则不加入 `parent_owner`。 +6. **root_owner(子树根节点 owner)** + + - 定义一个函数 `isSubtreeRootOwner(N, U)`: + - 输入:当前节点 `N`、当前用户 `U`; + - 算法:根据节点的`code`(`nodes`表的字段`code`形如`001003002008`,表示该节点的祖先节点的`code`为`001`、`001003`、`001003002`)从数据库select出所有祖先节点的`owner`的`id`,判断当前用户`U`是否其中一员 + - 输出:true或false。 + - 在主体构造过程中: + - 若`isSubtreeRootOwner(N, U)` 返回`true`,说明当前用户是该子树根节点的 `owner`,则加入:`('root_owner', NULL)`; + - 否则不加入 `root_owner`。 + +最终,`P` 是一组主体元素(可能包含 user / group / role / everyone / parent_owner / root_owner)。 + +--- + +## 四、权限记录的分类:默认 vs 节点专属 + +在 `node_permission` 中,根据 `tree` 与 `node_id` 组合,可以区分三类规则: + +1. **全局默认规则(对两棵树都生效)** + + - 条件:`tree IS NULL AND node_id IS NULL`。 + - 语义:对 standards 和 projects 的所有节点都生效。 +2. **按树的默认规则** + + - 条件:`tree = T AND node_id IS NULL`。 + - 语义:只对树 `T` 的所有节点生效。 +3. **节点专属规则** + + - 条件:`tree = T AND node_id = N`。 + - 语义:只对树 `T` 上的节点 `N` 生效,用来覆盖默认设置。 + +> 说明:当存在`node_id > 0` 的规则时,**不使用 `tree IS NULL` 以及`node_id IS NULL` 的记录**,这两个规则仅用于默认规则。 + +**优先级规则(同一主体维度):** + +对给定树 `T`、节点 `N`、主体 `(principal_type, principal_key)`,从最具体到最泛化依次为: + +1. 节点专属规则:`tree = T AND node_id = N`; +2. 树级默认规则:`tree = T AND node_id IS NULL`; +3. 全局默认规则:`tree IS NULL AND node_id IS NULL`; +4. 如果以上三类都不存在,则该主体对该节点“没有任何配置”,视为该主体不给任何权限。 + +--- + +### 五、典型场景下的 `INSERT` 示例 + +#### 1. 全局默认 everyone:所有人只能展开树结构 + +对 **两棵树所有节点** 生效(`tree IS NULL AND node_id IS NULL`): + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + NULL, NULL, 'everyone', NULL, + 1, + 0, 0, + 0, 0, + 0, 0, 0, + 0, 0 +); +``` + +#### 2. 全局默认 parent_owner:父节点 owner 管理直接子节点 + +对两棵树所有节点生效,表达“父节点 owner 默认可以管理直接子节点”和“看子列表”: + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + NULL, NULL, 'parent_owner', NULL, + 1, + 1, 1, + 0, 0, + 0, 0, 0, + 1, 1 -- 修改直接子节点 +); +``` + +#### 3. 全局默认 root_owner:子树根节点 owner 管理整棵子树 + +比如项目负责人(子树根的 owner)对整棵子树有较大权限: + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + NULL, NULL, 'root_owner', NULL, + 1, + 1, 1, + 1, 1, + 1, 1, 1, + 1, 1 +); +``` + +4. standards 专用默认:技术标准管理员(角色 sAdmin)全权管理 standards + +只对 standards 生效(projects 不受影响): + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + 'standards', NULL, 'role', 'sAdmin', + 1, + 1, 1, + 1, 1, + 1, 1, 1, + 1, 1 +); +``` + +#### 5. projects 专用默认:项目管理员(角色 pAdmin)管理 projects + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + 'projects', NULL, 'role', 'pAdmin', + 1, + 1, 1, + 1, 1, + 1, 1, 1, + 1, 1 +); +``` + +#### 6. 超级管理员专用默认: + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + NULL, NULL, 'role', 'super', + 1, + 1, 1, + 1, 1, + 1, 1, 1, + 1, 1 +); +``` + +#### 7. 某个敏感节点对 everyone 做节点专属覆盖 + +假设节点 id = 123,是一个敏感目录,希望 **everyone 在这个节点上完全没有权限(连展开都不行)**,但其它节点仍按默认规则: + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + 'standards', 123, 'everyone', NULL, + 0, + 0, 0, + 0, 0, + 0, 0, 0, + 0, 0 +); +``` + +由于这是 `tree='standards' AND node_id=123 AND principal_type='everyone'` 的节点专属记录,会**覆盖**对全局默认 `everyone` 的配置。 + +#### 8. 针对某个用户在单节点上授予完全权限 + +例如用户 id = 42,在节点 500 上拥有完全权限(不依赖角色/owner): + +```sql +INSERT INTO node_permission ( + tree, node_id, principal_type, principal_key, + can_list_children, + can_add_child_folder, can_add_child_doc, + can_delete_node, can_edit_node, + can_read_doc, can_edit_doc, can_delete_doc, + can_delete_child_node, can_edit_child_node +) VALUES ( + 'projects', 500, 'user', '42', + 1, + 1, 1, + 1, 1, + 1, 1, 1, + 1, 1 +); +``` + +## 六、计算“对某个节点的有效权限”的通用算法 + +我们定义一个抽象的“有效权限对象”,包含所有布尔权限字段: + +- `can_list_children` +- `can_add_child_folder` +- `can_add_child_doc` +- `can_delete_node` +- `can_edit_node` +- `can_read_doc` +- `can_edit_doc` +- `can_delete_doc` +- `can_delete_child_node` +- `can_edit_child_node` + +记这个对象为 `R`,初始时这十个值全部为 `false`。 + +### 6.1 输入 + +计算函数需要的输入信息: + +- 用户 id:`U` +- 节点 id:`N` +- 树标识:`T ∈ {'standards', 'projects'}` + +### 6.2 步骤 1:构造主体集合 `P` + +按照第二节“主体集合的构造”的规则,从 `U`、`N`、`node`/`groupUser`/`user` 表中计算出主体集合 `P`。 + +结果是若干个 `(principal_type, principal_key)` 的集合,例如: + +- `('user', '12')` +- `('group', '3')` +- `('role', 'sAdmin')` +- `('everyone', NULL)`(如果有一个全局everyone的配置以及对N节点的everyone配置,则只保留N节点自己的everyone配置) +- `('parent_owner', NULL)`(如果满足条件) +- `('root_owner', NULL)`(如果满足条件) + +### 6.3 步骤 2:读取节点专属权限记录 + +在 `node_permission` 表中,查询**当前树 + 当前节点**的所有权限记录: + +- 条件:`tree = T AND node_id = N` + +结果是一组行,每行包含: + +- `principal_type` +- `principal_key` +- 十个 `can_*` 布尔字段 + +这组记录可以在内存中按 `(principal_type, principal_key)` 组织成一个映射结构,后面用来快速查找: + +- 键:某个主体 `(principal_type, principal_key)` +- 值:对应的一行权限记录。 + +### 6.4 步骤 3:读取默认权限记录(树级 + 全局) + +在 `node_permission` 表中,查询**当前树相关的所有默认权限记录**: + +- 条件:`(tree = T OR tree IS NULL) AND node_id IS NULL` + +这些记录中: + +- `tree = T` 的记录是“树级默认规则”; +- `tree IS NULL` 的记录是“全局默认规则”。 + +在内存中同样按 `(principal_type, principal_key, tree)` 或类似结构组织,以便后续对同一主体区分“树级默认”和“全局默认”。 + +### 6.5 步骤 4:按主体合并权限(带 tree 优先级) + +对主体集合中的每一个主体 `p = (ptype, pkey)` 执行如下逻辑: + +1. **查找节点专属记录** + + - 在“节点专属映射”中查找键 `(ptype, pkey)` 对应的记录。 + - 若存在,则记为 `row_node`。 +2. **查找树级默认记录** + + - 在“默认映射”中查找满足: + - `principal_type = ptype` + - `principal_key = pkey` + - `tree = T` + - 若存在,则记为 `row_tree_default`。 +3. **查找全局默认记录** + + - 在“默认映射”中查: + - `principal_type = ptype` + - `principal_key = pkey` + - `tree IS NULL` + - 若存在,则记为 `row_global_default`。 +4. **确定权限** + + - 如果 `row_node`存在用户`U` 的记录:使用 `row_node`中用户`U`的特定权限配置,其它配置全部忽略; + - 否则将`row_node`、 `row_tree_default`、`row_global_default`中的所有权限做并集,即只要其中一个赋予了权限,就有该权限,相应地给权限对象R赋值。 + +--- + +## 七、针对具体操作的鉴权规则 + +### 7.1 展开节点 / 查看子节点列表 + +**操作:** 展开节点 `N`,列出其直接子节点。 + +- 判断方法: + - 根据 `U, N, T` 计算一次有效权限 `R`。 + - 若 `R.can_list_children == true`,则允许展开;否则禁止。 + +### 7.2 在节点下创建子文件夹 / 子文档 + +**操作:** 在节点 `N` 下创建子节点: + +- 创建文件夹:新子节点 `node_type = 'folder'`。 +- 创建文档:新子节点 `node_type = 'doc'`。 + +**判断方法:** + +- 先对父节点 `N` 计算有效权限 `R`。 +- 创建文件夹时: + - 允许条件:`R.can_add_child_folder == true`。 +- 创建文档时: + - 允许条件:`R.can_add_child_doc == true`。 + +### 7.3 修改节点自身(重命名、改 owner 等) + +**操作:** 修改节点 `N` 本身的元数据(名称、owner、排序等)。 + +**判断方法:** + +- 对节点 `N` 计算有效权限 `R`。 +- 允许条件:`R.can_edit_node == true`。 + +### 7.4 删除节点自身 + +**操作:** 删除节点 `N` 本身。 + +**判断方法:** + +- 对节点 `N` 计算有效权限 `R`。 +- 允许条件:`R.can_delete_node == true`。 + +### 7.5 文档内容的读取 / 编辑 / 删除 + +假设节点 `N` 是一个文档节点(`node_type = 'doc'`)。 + +- **读取文档内容:** + + - 对节点 `N` 计算有效权限 `R`。 + - 允许条件:`R.can_read_doc == true`。 +- **编辑文档内容:** + + - 对节点 `N` 计算有效权限 `R`。 + - 允许条件:`R.can_edit_doc == true`。 +- **删除文档文档节点:** + + - 对节点 `N` 计算有效权限 `R`。 + - 允许条件:`R.can_delete_doc == true` 。 + +### 7.6 删除子节点 + +**操作:** 在父节点 `P` 下,删除一个直接子节点 `C`(`C.parent = P`)。 + +- 允许条件:父节点 `P` 上的 `can_delete_child_node== true`; + +### 7.7 修改子节点(name / owner 等) + +**操作:** 在父节点 `P` 下,修改直接子节点 `C` 的元数据(名称、owner 等)。 + +- 允许条件:父节点 `P` 上的`can_edit_child_node== true`;

Gitea Version: 1.21.0+rc2-60-g00cd5ba6f