You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
13 KiB

4 weeks ago
# 权限与数据可见性模型 PRD
## 文档目的
本文档定义 CRM 平台统一的权限与数据可见性模型,覆盖线索、商机、客户、项目等业务模块。模型规定:一个用户在任意页面上能看到哪些数据、能执行哪些操作,由三种机制组合决定。本模型为跨模块通用能力,各业务模块一致遵循。
## 背景
现有权限体系包含两层:功能权限(控制按钮与接口)与数据可见范围(按业务模块配置的部门级数据范围)。两层通过"取交集"组合,只能逐层收窄可见范围。
实际业务出现无法表达的场景:商机需支持"将人员加入团队成员即可查看该商机",线索公海池需支持"将人员设为负责人/协作人即可管理该池线索"。这类授权针对的是"某一条具体数据",且需要越过用户所属部门的限制——一名部门数据范围为"本部门"的销售,被拉入一条属于其他部门的商机团队成员后,应当能看到该商机,但现有"取交集"模型会将其挡在部门范围之外。
因此本模型引入第三种机制——行级授权,并明确三者的组合规则。
## 模型总览
4 weeks ago
用户对数据的访问由三种机制组合决定,学名对应 **RBAC + ABAC + ReBAC** 三层:
4 weeks ago
4 weeks ago
1. **功能权限(RBAC)**:控制用户能否执行某个动作(点击按钮、调用接口)。**跟着角色走**——同一角色权限一致。配置后生效,未配置则放行。
2. **数据可见范围(ABAC)**:按数据与用户的属性算可见,不逐条落表。**跟着当前用户代入规则**。包含两块:
- **页面业务语义**(例:`我的线索 = owner_id = 当前用户`、`我的关注 = id IN 关注表`);
- **按角色配的部门数据范围档位**(仅本人 / 本部门 / 本部门及下属 / 全部),作为组织层面的天花板。
3. **行级授权(ReBAC)**:把用户加进团队成员 / 设为协作人时,落一条"用户 ↔ 资源"授权关系。**跟着具体人 + 具体资源走,不跟角色走**——换个同角色的人不会自动继承。它是加法性质的通行证,**只越过 ABAC 里的部门天花板**,不改页面自己的业务语义。
4 weeks ago
组合规则:
```
能否执行某动作 = 功能权限(已配置则校验,未配置则放行)
能否看到某条数据 = 页面业务条件
AND ( 落在数据可见范围内 OR 已被行级授权到该数据 )
```
数据可见性判定的核心是"数据可见范围 OR 行级授权"——满足其一即可见,而非二者同时满足。
4 weeks ago
**边界澄清**:ReBAC 只越过部门天花板,不改页面业务语义。例:李四被拉进某条 B 部门线索的协作人后,他在"相关 / 池视图"里能看到这条;但**不会**因此出现在"我的线索"页——因为那页语义是 `owner_id = 李四`,李四不是 owner,通行证不改这一层。
4 weeks ago
## 机制一:功能权限
功能权限控制"能否执行某个动作",与"能对哪些数据执行"无关,后者由机制二、机制三决定。
- 通过【权限点管理】录入权限点后生效。每个权限点包含两项:
- **前端按钮编码**:控制按钮是否展示、是否可点击。
- **后端接口地址(API URL)**:控制接口是否可被调用。
- 未录入的按钮与接口默认放行,不做控制。需要管控的动作在权限点管理中录入对应权限点即可,无需管控的不录入。
## 机制二:数据可见范围
数据可见范围按业务模块(线索、商机、客户、项目,可扩展)分别配置,定义用户在该模块下组织层面能触达的数据上限。
四档范围:
| 档位 | 含义 |
|------|------|
| 仅本人 | 仅归属本人的数据 |
| 本部门 | 本人所属部门(主部门与兼职部门并集)范围内的数据 |
| 本部门及下属 | 本部门及其全部下级部门的数据 |
| 全部 | 全平台数据,不做部门过滤 |
规则:
- **按模块独立配置**:同一用户在不同模块可配置不同档位,互不影响。
- **多角色取最宽**:用户拥有多个角色时,某模块的最终档位取各角色在该模块配置档位中最宽的一档。未为某角色配置某模块时,该角色在此模块按"仅本人"计,不放宽任何范围。
- **性质为上限**:数据可见范围仅定义组织层面的触达上限;具体页面展示哪一类数据,由页面业务条件在此上限内进一步收窄。
- **无部门时的判定**:档位要求按部门过滤、但用户不属于任何部门时,判定为"一律不可见",不放行任何数据。
## 机制三:行级授权
行级授权针对单条具体数据,将指定用户单独授权,使其能够访问该条数据——即使该数据不在其数据可见范围内。行级授权是加法性质的"通行证",可越过机制二的天花板。
行级授权由业务动作触发产生,随动作撤销而失效:
| 触发动作 | 授权效果 |
|----------|----------|
| 将用户加入商机团队成员 | 该用户可访问此商机 |
| 将用户设为线索公海池负责人 | 该用户可管理此池及池内线索 |
| 将用户设为线索公海池协作人 | 该用户可管理此池及池内线索 |
| 将线索分配/指派给销售 | 被指派销售可访问该线索 |
| 用户关注某条数据 | 该用户可在"我的关注"访问该数据 |
行级授权为平台级通用能力,各业务模块共用同一套授权关系,不逐模块单独实现。授权关系按"用户 + 资源"维度存储与检索。
## 组合规则与示例
页面最终展示的数据由以下条件组合:
```
最终可见数据 =
页面业务条件 -- 页面自身定义展示哪一类数据
AND (
落在数据可见范围内 -- 机制二,组织天花板
OR 已被行级授权到该数据 -- 机制三,通行证
)
```
三段说明:
- **页面业务条件**:由各页面业务语义定义。例:"我的线索"页限定归属本人;"线索公海"页限定状态为待领取且属于用户可触达的公海池;"我的关注"页限定用户已关注;"线索管理"页按筛选条件。
- **数据可见范围 OR 行级授权**:常规数据经数据可见范围放行;被单独授权的数据经行级授权放行,越过天花板。二者取并集。
- **最外层取交集**:既满足页面业务语义,又具备访问该条数据的资格。
### 示例一:我的线索页
销售张三,线索模块数据范围为"本部门",打开"我的线索":
```
归属人 = 张三
AND ( 线索所属部门 ∈ 张三所属部门 OR 张三被行级授权到该线索 )
```
页面业务条件已限定归属本人,天花板不额外收窄。
### 示例二:团队成员查看跨部门商机
销售李四,商机模块数据范围为"本部门",被加入一条属于 B 部门的商机团队成员:
```
(商机列表页业务条件)
AND ( 商机所属部门 ∈ 李四所属部门 -- 该商机属 B 部门,李四属 A 部门,天花板不放行
OR 李四被行级授权到该商机 ) -- 李四在团队成员中,行级授权放行
```
结果:李四可看到该商机。
## 与现有系统的关系
| 机制 | 现状 | 本次改动 |
|------|------|----------|
| 功能权限(权限点管理) | 已有:录入按钮编码与 API URL,配置后生效 | 沿用,不改动 |
| 数据可见范围(按模块四档、多角色取最宽) | 已有:线索、商机、客户、项目已注册为可配置模块 | 沿用,不改动 |
| 行级授权 | 暂无,现有模型仅有部门级天花板 | 本次新增:通用行级授权关系 + 查询时作为并集通道并入 |
补充说明:
- 行级授权为平台级通用能力,商机团队成员、线索公海池负责人/协作人及后续客户、项目的同类需求共用同一套实现。
- 行级授权数据稀疏(团队成员、协作人为少量点名),按"用户 + 资源"维度建立索引,不影响列表查询性能。
- 通行证逻辑集中实现并统一注入查询,各业务页面无需分别处理,避免授权逻辑散落。
4 weeks ago
## 待确认事项(全部已由产品拍板,2026-08-13)
> 三项待确认全部已由产品拍板。以下条目仅保留结论摘要,详细依据见"已定决策"。
1. **(PRD-1)行级授权触发动作清单** —— ✅ 已定:
- **团队成员**(商机 / 客户 / 项目)—— 逐条授权,`resource_type = opportunity / customer / project`。
- **公海池负责人 / 协作人**(线索)—— 池维度授权,`resource_type = pool`。
- **线索侧的关注**不走行级授权(详见 [F])。
2. **(PRD-2)权限边界** —— ✅ 已定:**只读**。通行证只放行"看得到";写权由权限点(能不能点)+ service 层(能不能写在这一条上,owner / 状态机)判定。团队成员 / 协作人因非 owner 而写不动,不分档。
3. **(PRD-3)撤销即失效** —— ✅ 已定:立即失效,授权行硬删。
## 已定决策
### [D] 线索不开"按单条"直接授权通道(2026-08-13,产品确认=场景甲)
线索的可见性只来自两个来源:**负责人(owner)就是本人**(第一层业务过滤 `owner_id = 我`),或**本人够得着该线索所属公海池**(池维度行级授权)。业务上**不存在**"不改负责人、又不设整池协作人的跨部门单条协助"场景。
因此:
- 行级授权表**不设** `resource_type = lead` 的直接授权行;`resource_type` 只记粗粒度授权对象(池 / 商机等)。
- "分配 / 指派"= 改 `owner_id`,走第一层业务过滤,**不额外发通行证**。
- 池维度授权在查询时翻译成"该池下全部线索"(间接授权),无需逐条落表。
### [E] 池配置管理不发通行证(2026-08-13,产品确认=场景甲)
"能不能看 / 改某个池的配置" = **功能权限(角色有没有配这个菜单)× 数据范围(池归属部门 ∈ 用户部门集合)**。行级授权(池负责人 / 协作人身份)**不参与池配置的可见 / 可改判定**。
依据:按 ticket 04 "一部门一池 + 成员 = 部门子树动态推导",用户负责 / 协作的池必是其部门集合内的池,数据范围天花板本来就放行它——无需通行证。不存在"跨部门当池负责人、却要改那个池配置"的场景。
因此:**通行证只用于线索可见(池内线索越过部门天花板)这一件事;池配置管理里不用行级授权。**
### [PRD-3] 撤销授权立即失效、授权行硬删(2026-08-13,产品确认=场景甲)
移出团队成员 / 协作人名单的**那一刻即失效**:下一个请求的 `OR EXISTS` 旁路就命中不到。授权行**硬删**(`DELETE FROM sys_row_grant WHERE ...`),不做软删 + 失效标记。
依据:
- 通行证每请求实时查(`DataScopeInterceptor` 在 `prepare` 阶段注入,`DataVisibilityContext` 为请求级),立即失效是**零成本默认行为**;做“延迟 / 保留”反而要额外加逻辑。
- 授权关系是“当前谁被授权”的事实清单,过期关系无保留价值;“谁何时被加进 / 移出协作人”由操作日志(lead_history)留痕,不靠授权表本身。
### [F] 关注不走行级授权(2026-08-13,产品确认)
线索侧**唯一**产生行级授权的动作 = **设为公海池负责人 / 协作人**(池维度)。**关注不发通行证**:"我的关注" = `id IN 关注表` **AND 部门天花板**(受当前部门实时约束)。
**为何不走(安全场景论证)**:销售 A 被人事误拉进部门 B → 点关注某条 B 部门线索 → 人事改回部门 A。
- 若关注走行级授权:那一刻落了 "A ↔ 这条线索" grant,改回部门不动 grant,A **永久**越部门看到这条 —— 漏洞。
- 实际设计(关注不走 grant):改回部门 A 后,部门天花板变为 A 部门,这条 B 部门线索被天花板挡掉 —— 即使还在关注表里也看不到。可见性每请求按当前部门实时算,不因历史点过关注而永久留权。
4 weeks ago
4 weeks ago
边角:被挡掉的那条关注记录(指向已看不到的线索)**留着不清**——实时判定天然无害(看不到即不越权),清理反而要加逻辑;哪天又调回 B 部门自然又能看到。
4 weeks ago
## 术语
- **数据权限模块**:可独立配置数据范围的业务模块(线索、商机、客户、项目等),存于注册表,以 code 标识。
- **数据范围档位**:仅本人 / 本部门 / 本部门及下属 / 全部。
- **主部门 / 部门集合**:用户唯一正式归属部门 / 主部门与兼职部门并集;数据范围按部门集合计算。
- **一律不可见**:档位要求按部门过滤、但用户无任何部门时的判定结果,不放行任何数据。
- **功能权限点**:权限点管理中录入的按钮编码与 API URL,配置后生效。
- **行级授权**:针对单条数据将用户单独授权的通行证,可越过部门级天花板。
4 weeks ago
- **RBAC / ABAC / ReBAC**:三层模型的学名。RBAC=功能权限(跟角色走,管能不能点);ABAC=数据可见范围(页面业务语义 + 部门档位天花板,按属性算,不逐条落表);ReBAC=行级授权(跟具体人+具体资源走,逐条落表,只越部门天花板、不改页面语义)。ReBAC ≠ ABAC:不得用属性规则伪装逐条关系。