用途
该技能用于在 Frappe/ERPNext 应用中实现权限系统,提供确定性的实现模式,覆盖 v14、v15、v16 版本。
主要能力
Frappe 权限系统分为五层:
- 角色权限(Role Permissions):控制用户能做什么,通过 DocType 权限表配置,适用于所有版本。
- 用户权限(User Permissions):控制用户能看到哪些文档,通过 User Permission 记录配置,适用于所有版本。
- 权限级别(Perm Levels):控制用户能看到哪些字段,通过字段的 permlevel 属性配置,适用于所有版本。
- 权限钩子(Permission Hooks):自定义逻辑,通过 hooks.py 配置,适用于所有版本。
- 数据脱敏(Data Masking):对字段值进行掩码显示,通过字段的 mask 属性配置,适用于 v16+。
权限类型包括:read、write、create、delete、submit、cancel、select(v14+)、mask(v16+)。
自动角色包括:Guest(所有人,含匿名)、All(所有注册用户)、Administrator(仅 Administrator 用户)、Desk User(System Users,v15+)。
使用方式
核心 API:
- 检查权限:frappe.has_permission("Sales Order", "write"),支持 DocType 级、文档级、指定用户、throw=True 抛出异常;文档实例上可用 doc.has_permission("write") 与 doc.check_permission("write")。
- 获取权限:from frappe.permissions import get_doc_permissions,返回如 {'read': 1, 'write': 1, 'create': 0, 'delete': 0, ...}。
- 用户权限:from frappe.permissions import add_user_permission, remove_user_permission, get_user_permissions。
- 文档共享:from frappe.share import add as add_share,可指定 read、write 等权限。
数据脱敏(v16+):
- 在 DocType(开发者模式)或 Customize Form 中为字段设置 "mask": 1。
- 支持字段类型:Data、Date、Datetime、Currency、Float、Int、Percent、Phone、Password、Link、Dynamic Link、Select、Read Only、Duration。
- 为应查看未脱敏数据的角色添加 mask 权限,例如 {"role": "HR Manager", "permlevel": 0, "read": 1, "mask": 1}。
- 无 mask 权限的用户在表单视图、列表视图、报表视图、API 响应(/api/resource/、/api/method/)中看到脱敏值。
- 注意:数据脱敏不适用于自定义 SQL 查询、使用原始 SQL 的 Query Report、直接 frappe.db.sql() 调用,需手动实现脱敏。
权限钩子:
- has_permission 钩子:添加自定义权限逻辑,只能拒绝不能授予;返回 None 继续标准检查,返回 False 拒绝权限;默认始终返回 None。
- permission_query_conditions 钩子:过滤列表查询,仅影响 get_list(),不影响 get_all();返回 SQL WHERE 片段,必须使用 frappe.db.escape(),禁止字符串拼接。
字段级权限(Perm Levels):
- 为字段设置 permlevel,为角色配置对应 permlevel 的读写权限。
- 规则:必须先授予 Level 0,才能授予更高级别。
决策树:
- 控制整个 DocType 访问 → 角色权限
- 控制特定文档 → 用户权限
- 完全隐藏特定字段 → 权限级别
- 显示脱敏的特定字段 → 数据脱敏(v16+)
- 自定义逻辑 → has_permission 钩子
- 列表查询过滤 → permission_query_conditions 钩子
代码中检查权限:
- 操作前 → frappe.has_permission() 或 doc.has_permission()
- 抛出错误 → doc.check_permission() 或 throw=True
- 需要绕过 → doc.flags.ignore_permissions = True(需记录原因)
常见模式:
- 仅所有者可编辑:{"role": "Sales User", "read": 1, "write": 1, "create": 1, "if_owner": 1}
- 操作前检查:在 @frappe.whitelist() 方法中先 doc.has_permission("write"),无权限则 frappe.throw(_("No permission"), frappe.PermissionError)。
- 角色限制端点:@frappe.whitelist() 方法中使用 frappe.only_for(["Manager", "Administrator"])。
关键规则:
- 始终使用权限 API,而非角色检查
- 始终转义 SQL:frappe.db.escape(user)
- 面向用户的查询始终使用 get_list
- has_permission 钩子中始终返回 None(而非 True)
- 使用 ignore_permissions 时始终记录原因
- 权限变更后始终清除缓存:frappe.clear_cache()
- 自定义 SQL 查询中始终手动脱敏(v16+)
反模式:
- 不要用 if "Role" in frappe.get_roles(),应使用 frappe.has_permission(dt, ptype)
- 不要用 frappe.get_all() 做用户查询,应使用 frappe.get_list()
- 不要在 has_permission 中返回 True,应返回 None
- 不要用 f"owner = '{user}'",应使用 f"owner = {frappe.db.escape(user)}"
- 不要在钩子中 frappe.throw(),应返回 False
- 不要假设自定义 SQL 会自动脱敏,应手动实现
版本差异:
- select 权限:v14、v15、v16 均支持
- Desk User 角色:v14 不支持,v15、v16 支持
- 自定义权限类型:v14、v15 不支持,v16 支持(实验性)
- 数据脱敏:v14、v15 不支持,v16 支持
- mask 权限类型:v14、v15 不支持,v16 支持
调试:
- frappe.has_permission("Sales Order", "read", doc, debug=True)
- print(frappe.local.permission_debug_log)
- from frappe.permissions import get_doc_permissions; get_doc_permissions(doc, user="john@example.com")
参考文件(references/ 文件夹):permission-types-reference.md、permission-api-reference.md、permission-hooks-reference.md、examples.md、anti-patterns.md。
相关技能:erpnext-database、erpnext-syntax-controllers、erpnext-syntax-hooks。
适用场景
适用于在 Frappe/ERPNext 应用中实现或调试权限控制,包括角色与用户权限配置、字段级权限、数据脱敏、权限钩子编写、列表查询过滤、文档共享,以及权限相关的代码审查与反模式排查。
限制
数据脱敏仅 v16+ 支持,且不适用于自定义 SQL 查询、原始 SQL 的 Query Report 和直接 frappe.db.sql() 调用,需手动实现。自定义权限类型为 v16 实验性功能。has_permission 钩子只能拒绝权限,不能授予权限。permission_query_conditions 钩子仅影响 get_list(),不影响 get_all()。