Permify 实现 Google Docs 风格文档权限系统:组织、嵌套群组与文档级授权建模实战
【免费下载链接】permifyAn open-source authorization as a service inspired by Google Zanzibar, designed to build and manage fine-grained and scalable authorization systems for any application. — Permify is now part of FusionAuth 🎉项目地址: https://gitcode.com/GitHub_Trending/pe/permify
本指南基于 Permify(受 Google Zanzibar 启发的开源授权即服务项目)官方示例文档,完整演示如何用 Permify Schema(DSL)为"Google Docs 风格"的文档管理系统建模:用户既可以获得文档的直接访问权限,也可以通过所在组织(organization)与嵌套群组(group)间接获得权限。读完本文你将掌握实体/关系/权限(action/permission)三要素建模方法、关系元组(relationship tuples)的写法,以及如何用permify validate命令对授权模型做本地断言验证。
场景概述:组织 — 群组 — 文档的三层授权结构
Google Docs 风格的文档系统有一个典型特征:权限来源多样且可叠加。一个用户能访问某份文档,可能是因为:
- 他是文档的直接 viewer/manager;
- 他所属的群组被授予了文档的查看或管理权限;
- 他是文档所属组织的管理员(admin),从而对组织下所有文档拥有编辑/查看权限;
- 群组之间还可以嵌套(group 的成员是另一个 group),形成多级继承。
这正是 Permify 最擅长的场景。官方文档在 docs/getting-started/examples/google-docs.mdx(对应docs/docs/getting-started/examples/下的同名 markdown 镜像)中给出了完整的建模方案,本文将以它为骨架展开讲解。
完整的授权模型 Schema
下面是示例的核心 Schema,它定义了user、organization、group、document四个实体及其关系与权限:
entity user {} entity organization { relation group @group relation document @document relation administrator @user @group#direct_member @group#manager relation direct_member @user permission admin = administrator permission member = direct_member or administrator or group.member } entity group { relation manager @user @group#direct_member @group#manager relation direct_member @user @group#direct_member @group#manager permission member = direct_member or manager } entity document { relation org @organization relation viewer @user @group#direct_member @group#manager relation manager @user @group#direct_member @group#manager action edit = manager or org.admin action view = viewer or manager or org.admin }模型分解:四个实体的职责与关系设计
user:授权的最终主体
entity user {}user是空实体,代表可以被授予文档访问权限的人。它是关系元组中最常见的 subject(主体),用户既可以作为"直接授权对象"出现,也可以通过群组或组织成员关系间接获得权限。
document:被授权访问的资源
entity document { relation org @organization relation viewer @user @group#direct_member @group#manager relation manager @user @group#direct_member @group#manager action edit = manager or org.admin action view = viewer or manager or org.admin }document实体定义了两个关系(relation)和两个动作(action):
关系(Relations)
- org:文档所属的组织。通过
@organization注解限定该关系的 subject 只能是organization实体,这是文档级权限"上溯"到组织级权限的桥梁(org.admin的语法即通过该关系访问组织的admin权限)。 - manager:被授权管理文档的用户。subject 类型包括
@user(直接指定个人),以及@group#direct_member、@group#manager(指定某个群组的成员或管理员集合)。 - viewer:被授权查看文档的用户,subject 约束与
manager一致。
动作(Actions)
- edit:
manager or org.admin。可编辑文档的用户 = 文档的直接 manager,或文档所属组织的管理员。 - view:
viewer or manager or org.admin。可查看文档的用户 = 文档的 viewer、manager,或所属组织的管理员。
注意edit与view存在隐式包含关系:凡是能编辑的必然能查看,这正是权限模型用action组合relation与其它权限的典型写法。
group:可嵌套的中间授权层
entity group { relation manager @user @group#direct_member @group#manager relation direct_member @user @group#direct_member @group#manager permission member = direct_member or manager }group实体有两个关系:
- manager:群组的管理员,subject 可以是具体用户,也可以是其它群组的成员/管理员集合。
- direct_member:群组的直接成员,subject 类型同样支持
@user与@group#direct_member、@group#manager。"direct"(直接)一词非常关键:它强调该关系不经过任何推导,为群组嵌套提供了精确的成员边界。
group还定义了一个权限member:direct_member or manager。注意在示例文档的建模中,document的viewer/manager关系引用的是@group#direct_member与@group#manager这两种"直接关系",而不是group.member这个权限——这意味着只有群组的直接成员/直接管理员才能继承文档权限,群组的嵌套成员(通过direct_member@group:...引入的间接成员)不会自动获得外层群组挂载的文档权限。这种"显式控制继承深度"的设计正是细粒度授权建模的精髓,需要在实际建模中根据业务语义仔细取舍。
organization:组织级权限的汇聚点
entity organization { relation group @group relation document @document relation administrator @user @group#direct_member @group#manager relation direct_member @user permission admin = administrator permission member = direct_member or administrator or group.member }organization实体聚合了群组、文档与用户:
- group:组织下属的群组(subject 为
@group)。 - document:组织下属的文档(subject 为
@document)。 - administrator:组织管理员,subject 可以是用户或群组的成员/管理员集合。
- direct_member:组织直接成员(subject 为
@user)。
权限:
- admin:
administrator,即组织管理员集合。 - member:
direct_member or administrator or group.member,组织成员 = 直接成员 ∪ 管理员 ∪ 组织下所有群组的成员。这里group.member通过relation group @group实现"跨实体权限引用",是嵌套继承的核心语法。
关系元组:注入示例授权数据
有了 Schema 后,需要写入关系元组(tuples)来表达"谁对什么拥有什么关系"。下面是示例数据,按业务语义分组:
// 为用户分配群组角色 group:tech#manager@user:ashley group:tech#direct_member@user:david group:marketing#manager@user:john group:marketing#direct_member@user:jenny group:hr#manager@user:josh group:hr#direct_member@user:joe // 群组嵌套:marketing、hr 群组的成员同时也是 tech 群组的成员 group:tech#direct_member@group:marketing#direct_member group:tech#direct_member@group:hr#direct_member // 将群组挂载到组织 organization:acme#group@group:tech organization:acme#group@group:marketing organization:acme#group@group:hr // 将文档挂载到组织 organization:acme#document@document:product_database organization:acme#document@document:marketing_materials organization:acme#document@document:hr_documents // 指定组织管理员:tech 群组的管理员 + 用户 jenny organization:acme#administrator@group:tech#manager organization:acme#administrator@user:jenny // 为文档设置权限 document:product_database#manager@group:tech#manager document:product_database#viewer@group:tech#direct_member document:marketing_materials#viewer@group:marketing#direct_member document:hr_documents#manager@group:hr#manager document:hr_documents#viewer@group:hr#direct_member元组(tuple)的语法为entity:id#relation@subject,其中 subject 可以是entity:id(如user:ashley),也可以是带关系的entity:id#relation(如group:tech#manager)。这 21 条元组共同构建了一棵授权关系图:
- tech 群组:ashley 是 manager,david 是 direct_member;marketing 与 hr 的 direct_member 也嵌套进 tech 群组;
- acme 组织:挂载了 tech、marketing、hr 三个群组和三份文档;管理员为 tech 群组管理员集合与 jenny;
- 三份文档:product_database 由 tech 群组管理,marketing_materials 由 marketing 群组查看,hr_documents 由 hr 群组管理与查看。
权限断言验证:三种典型授权路径
基于上述数据,示例给出了三个访问检查(access check)用例,用来验证授权逻辑是否符合预期。
用例一:user:ashley能编辑document:product_database吗?
根据edit = manager or org.admin,检查引擎会沿着两条路径推导:
- 检查 ashley 是否与
document:product_database存在直接或间接的manager关系——是的,元组document:product_database#manager@group:tech#manager将 tech 群组的 manager 集合(包含 ashley)授予了该文档的 manager; - 检查 ashley 是否拥有组织 admin 权限——她不是 acme 组织的管理员。
由于路径 1 命中,检查结果为 true(允许)。这演示了"通过群组角色间接获得文档权限"的授权路径。
用例二:user:joe能查看document:hr_documents吗?
根据view = viewer or manager or org.admin:
- joe 不是文档的直接 viewer/manager,也不是组织 admin;
- 但 joe 是 hr 群组的 direct_member(
group:hr#direct_member@user:joe),而元组document:hr_documents#viewer@group:hr#direct_member把 hr 群组的直接成员集合授予了文档的 viewer。
因此检查结果为 true(允许)。这演示了"群组成员身份传递到文档查看权限"的路径。
用例三:user:david能查看document:marketing_materials吗?
- david 是 tech 群组的 direct_member,但 marketing_materials 文档的 viewer 只授予了 marketing 群组的直接成员(
document:marketing_materials#viewer@group:marketing#direct_member),david 不在其中; - david 也不是文档 manager,更不是组织 admin。
检查结果为 false(拒绝)。这个用例非常有教学价值:即使 david 通过群组嵌套(marketing 是 tech 的子群组)与 tech 群组相关,但由于文档关系引用的是@group#direct_member(直接成员)而非group.member(含嵌套),跨群组继承被精确阻断。
说明:示例文档在叙述用例二时提到
group:hr#member与@group#member,但实际 Schema 与元组中采用的是direct_member关系(permission member = direct_member or manager),本文以仓库中真实 Schema 与元组数据为准。
本地验证:用permify validate运行断言
上述访问检查可以通过 Permify 官方提供的validator(验证器)在本地一键运行。验证文件是一个 YAML,结构由 pkg/development/file/shape.go 中的Shape结构定义,包含三大块:
- schema:待测试的授权模型;
- relationships:样本授权数据(元组列表);
- scenarios:测试场景,每个场景包含
checks(访问检查断言,assertions中的true/false表示期望的允许/拒绝结果),还可选entity_filters与subject_filters(分别对应 LookupEntity 与 LookupSubject 的断言,用于验证反向查询)。
完整的验证 YAML 文件
schema: >- entity user {} entity organization { relation group @group relation document @document relation administrator @user @group#direct_member @group#manager relation direct_member @user permission admin = administrator permission member = direct_member or administrator or group.member } entity group { relation manager @user @group#direct_member @group#manager relation direct_member @user @group#direct_member @group#manager permission member = direct_member or manager } entity document { relation org @organization relation viewer @user @group#direct_member @group#manager relation manager @user @group#direct_member @group#manager action edit = manager or org.admin action view = viewer or manager or org.admin } relationships: - group:tech#manager@user:ashley - group:tech#direct_member@user:david - group:marketing#manager@user:john - group:marketing#direct_member@user:jenny - group:hr#manager@user:josh - group:hr#direct_member@user:joe - group:tech#direct_member@group:marketing#direct_member - group:tech#direct_member@group:hr#direct_member - organization:acme#group@group:tech - organization:acme#group@group:marketing - organization:acme#group@group:hr - organization:acme#document@document:product_database - organization:acme#document@document:marketing_materials - organization:acme#document@document:hr_documents - organization:acme#administrator@group:tech#manager - organization:acme#administrator@user:jenny - document:product_database#manager@group:tech#manager - document:product_database#viewer@group:tech#direct_member - document:marketing_materials#viewer@group:marketing#direct_member - document:hr_documents#manager@group:hr#manager - document:hr_documents#viewer@group:hr#direct_member scenarios: - name: "scenario 1" description: "test description" checks: - entity: "document:product_database" subject: "user:ashley" assertions: edit: true - entity: "document:hr_documents" subject: "user:joe" assertions: view: true - entity: "document:marketing_materials" subject: "user:david" assertions: view: false运行验证的步骤
- 克隆 Permify 仓库后,新建一个 YAML 文件,将上述内容粘贴进去;
- 构建并启动 Permify 服务,执行
make serve(相关命令定义见 Makefile); - 另开终端,运行
permify validate {你的验证文件路径}开始测试。
permify validate命令由 pkg/cmd/validate.go 实现,其底层执行流程清晰可见:
- 通过
file.NewDecoderFromURL解析 YAML(支持本地文件路径与外部 URL),解码为Shape; - 使用
schema.NewSchemaLoader+ DSLparser+compiler加载、解析并编译 Schema(pkg/schema/loader.go、pkg/dsl/parser/parser.go、pkg/dsl/compiler/compiler.go); - 将每条 relationship 经
tuple.Tuple解析后,通过dev.Container.DW.Write写入开发容器(内存存储),写入前用实体定义校验元组合法性; - 对每个 scenario 的 checks 逐条调用
dev.Container.Invoker.Check执行访问检查,将结果与assertions中的期望值比对; - 全部通过时输出
SUCCESS,否则打印具体失败的断言(ALLOWED/DENIED 与期望不符)并退出。
将验证接入 CI 流水线
如果希望把这类断言纳入持续集成,仓库文档 docs/docs/getting-started/testing.md 介绍了permify-validate-actionGitHub Action 的用法:只需在 workflow 中通过validationFile指定验证 YAML(支持本地文件或外部 URL)即可自动执行同样的校验。
源码佐证:Google Docs 示例的集成测试
该场景不仅是文档示例,还以代码形式固化在仓库的集成测试中,可作为最可信的"可复现依据":
- 场景 Shape 定义在 integration-test/usecases/shapes/google_docs.go:包含 Schema(此处使用
resource实体名,语义与document一致)、21 条关系元组,以及三个断言场景(user:ashley edit product_database → true、user:joe view hr_documents → true、user:david view marketing_materials → false),与本文讲解完全对应; - 测试用例定义在 integration-test/usecases/google_docs_test.go:通过 ginkgo 驱动,覆盖四类 API:
PermissionCheckRequest单条检查;PermissionBulkCheckRequest批量检查(BulkCheck);PermissionLookupEntityRequest实体过滤(给定主体反查可访问的实体 ID 列表);PermissionLookupSubjectRequest主体过滤(给定实体与权限反查可访问的主体 ID 列表)。
这组测试印证了文档中"断言 → 引擎推导 → 允许/拒绝"的完整闭环,也说明同一条关系数据可同时支撑正向检查(Check)与反向查询(Lookup)两类典型授权 API。
小结
通过 Google Docs 风格示例,我们完整走通了 Permify 建模到验证的闭环:
- 建模:用
entity/relation/permission/action表达文档、群组、组织的授权结构,并通过@group#direct_member这类"关系级引用"精确控制权限继承深度; - 数据:用关系元组注入样本授权数据,组织、群组嵌套、文档授权三类元组各司其职;
- 验证:借助
permify validate与验证 YAML,把"Ashley 可编辑产品数据库、Joe 可查看 HR 文档、David 不可查看营销材料"这类业务断言固化为可重复运行的测试,并可在 CI 中持续执行。
如果想继续深入,可参考仓库中的相关文档:安装与启动指南、授权建模指南、同步授权数据,以及同类场景示例(Facebook Groups、Notion、Instagram、Mercury),从而覆盖 RBAC、ReBAC、属性/条件等更多建模范式。
【免费下载链接】permifyAn open-source authorization as a service inspired by Google Zanzibar, designed to build and manage fine-grained and scalable authorization systems for any application. — Permify is now part of FusionAuth 🎉项目地址: https://gitcode.com/GitHub_Trending/pe/permify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考