
用 Permify 构建 Facebook 群组授权模型从 Perm DSL Schema 到验证器测试的完整实战【免费下载链接】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 官方的 Facebook Groups 示例为主线完整演示如何用 Permify 的授权语言Perm DSL为真实的多层级社群场景建模从user、group、post、comment等实体的关系设计到基于关系元组relationship tuples写入授权数据再到用permify validate与集成测试自动化验证权限判断。读完本文你将掌握实体—关系—权限三层建模思路、跨实体的嵌套权限继承ReBAC 核心以及一套可复制、可运行的授权模型验证流程。说明本仓库中与本文主题对应的原文位于 docs/getting-started/examples/facebook-groups.mdx其配套的 YAML 验证文件在 assets/example-shapes/facebook-groups.yamlGo 版模型与集成测试分别在 integration-test/usecases/shapes/facebook_groups.go 与 integration-test/usecases/facebook_groups_test.go。一、示例场景Facebook 群组的授权结构Facebook 群组是一个典型的多层级、多角色授权场景群组内有普通成员member、管理员admin与版主moderator群组成员可以在群内发帖、评论、点赞、创建投票、上传文件、发起活动帖子和评论又归属于具体群组权限需要在评论 → 帖子 → 群组 → 成员这条链路上逐级推导。本示例用 8 个实体刻画这个场景实体职责user表示平台上的用户是授权动作的主体group表示 Facebook 群组承载成员/管理员/版主三种角色关系post群组内的帖子拥有者可以管理群组成员可以浏览comment帖子下的评论通常只有拥有者可编辑/删除like帖子上的点赞拥有者与群组成员可操作poll群组内的投票拥有者与群组管理员可管理file群组内上传的文件成员可上传/查看管理员可删除event群组内的事件成员可查看/RSVP管理员可管理该模型同时覆盖了两类授权模式简单的基于角色的访问控制RBAC群组内的 member/admin/moderator 角色与关系型访问控制ReBAC权限沿着群组 → 帖子 → 评论的实体关系链继承。二、完整 Schema用 Perm DSL 定义授权模型以下是本示例的完整 Perm DSL 模型与 assets/example-shapes/facebook-groups.yaml 中schema字段及 integration-test/usecases/shapes/facebook_groups.go 中InitialFacebookGroupsShape.Schema完全一致// Represents a user entity user {} // Represents a Facebook group entity group { // Relation to represent the members of the group relation member user // Relation to represent the admins of the group relation admin user // Relation to represent the moderators of the group relation moderator user // Permissions for the group entity action create member action join member action leave member action invite_to_group admin action remove_from_group admin or moderator action edit_settings admin or moderator action post_to_group member action comment_on_post member action view_group_insights admin or moderator } // Represents a post in a Facebook group entity post { // Relation to represent the owner of the post relation owner user // Relation to represent the group that the post belongs to relation group group // Permissions for the post entity action view_post owner or group.member action edit_post owner or group.admin action delete_post owner or group.admin permission group_member group.member } // Represents a comment on a post in a Facebook group entity comment { // Relation to represent the owner of the comment relation owner user // Relation to represent the post that the comment belongs to relation post post // Permissions for the comment entity action view_comment owner or post.group_member action edit_comment owner action delete_comment owner } // Represents a comment like on a post in a Facebook group entity like { // Relation to represent the owner of the like relation owner user // Relation to represent the post that the like belongs to relation post post // Permissions for the like entity action like_post owner or post.group_member action unlike_post owner or post.group_member } // Definition of poll entity entity poll { // Relation to represent the owner of the poll relation owner user // Relation to represent the group that the poll belongs to relation group group // Permissions for the poll entity action create_poll owner or group.admin action view_poll owner or group.member action edit_poll owner or group.admin action delete_poll owner or group.admin } // Definition of file entity entity file { // Relation to represent the owner of the file relation owner user // Relation to represent the group that the file belongs to relation group group // Permissions for the file entity action upload_file owner or group.member action view_file owner or group.member action delete_file owner or group.admin } // Definition of event entity entity event { // Relation to represent the owner of the event relation owner user // Relation to represent the group that the event belongs to relation group group // Permissions for the event entity action create_event owner or group.admin action view_event owner or group.member action edit_event owner or group.admin action delete_event owner or group.admin action RSVP_to_event owner or group.member }Perm DSL 的三个核心关键字在这里都有体现entity定义一种资源类型如group、post实体内部声明关系与权限relation定义实体之间的关系语法为relation 名称 主体类型例如relation admin user表示管理员是某个用户。关系也可指向其他实体类型如post中的relation group groupaction/permission定义可执行的操作及其授权规则右侧表达式可组合多个关系例如admin or moderator。三、实体与关系拆解3.1user授权主体user实体不带任何关系与权限只作为授权请求中的**主体subject**存在。这与 Permify 的模型一致——用户是动作的执行者权限都挂在被访问的资源实体上。3.2group三种角色与九个动作群组实体定义了member、admin、moderator三个关系分别表示普通成员、管理员与版主。随后用 9 个action把群组内的操作映射到角色上动作授权规则说明createmember成员可创建此处语义为成员身份即可joinmember成员可加入leavemember成员可退出invite_to_groupadmin仅管理员可邀请入群remove_from_groupadmin or moderator管理员或版主可移出成员edit_settingsadmin or moderator管理员或版主可修改设置post_to_groupmember成员可发帖comment_on_postmember成员可评论view_group_insightsadmin or moderator仅管理员或版主可查看群组数据注意admin or moderator这种多关系取并集的写法只要主体属于其中任意一个关系集合该动作即被允许。3.3post/comment/like内容实体的归属关系帖子拥有两个关系owner帖子拥有者与group帖子所属群组。评论和点赞则拥有owner与post所属帖子。由此群组 → 帖子 → 评论的层级结构被显式建模为关系链。3.4poll/file/event群组附属资源投票、文件、事件三类实体结构相似都有owner与group关系权限规则也遵循同一模式——拥有者或群组管理员可管理拥有者或群组成员可查看。例如file中upload_file owner or group.member、delete_file owner or group.adminevent中RSVP_to_event owner or group.member。四、权限表达两个典型 Action 的深入解读4.1 创建群组权限群组的create动作被限定为只有群组成员可以执行entity group { // Relation to represent the members of the group relation member user // Create group permission action create member }这里action create member的含义是当且仅当请求主体属于该群组的member关系集合时create被允许。这是最基础的关系直接授权。4.2 编辑帖子权限帖子实体的edit_post动作是拥有者优先 群组管理员兜底的经典组合entity post { // Relation to represent the owner of the post relation owner user // Relation to represent the group that the post belongs to relation group group action edit_post owner or group.admin }语义拆解帖子的**拥有者owner**可以随时编辑自己的帖子帖子所属群组中被定义为admin的成员也可以编辑该帖子即使不是帖子的拥有者。同理view_post owner or group.member允许帖子拥有者与群组全体成员浏览而delete_post owner or group.admin将删除权限收紧到拥有者与群组管理员。这种对象级归属 父资源角色的组合正是细粒度授权模型的核心表达力。五、嵌套层级跨实体的权限继承ReBAC 核心由于本示例中多数实体深度嵌套评论属于帖子、帖子属于群组模型里出现了多级权限继承这是本示例最值得学习的地方。以view_comment为例只有当用户是该评论的拥有者或者是该评论所属帖子所在群组的成员时才允许查看评论。模型通过一个中间权限把群组成员这一信息沿实体链向上传递// Represents a post in a Facebook group entity post { // Relation to represent the group that the post belongs to relation group group permission group_member group.member } // Represents a comment on a post in a Facebook group entity comment { // Relation to represent the owner of the comment relation owner user // Relation to represent the post that the comment belongs to relation post post // Permissions action view_comment owner or post.group_member }关键点在于post实体中定义的这一行permission group_member group.member它的作用是把post通过group关系关联到的那个群组的member关系重新暴露为一个名为group_member的权限。而权限permission可以在其他实体中被当作关系来引用——comment中的post.group_member即引用了帖子暴露的group_member权限。于是授权推导链路变成user 查看 comment:1→ 是comment:1的 owner不是 →comment:1属于post:1comment:1#postpost:1post:1属于group:1post:1#groupgroup:1→group:1的member关系是否包含该用户如果是则允许。这种在一个实体中定义权限、在另一个实体中继承引用的机制使得模型可以表达任意深度的层级关系是 Permify 实现 ReBAC关系型访问控制的基石。Go 版模型 integration-test/usecases/shapes/facebook_groups.go 中的 schema 与上述定义完全一致可在源码中逐行对照。六、关系元组Relationship Tuples写入授权数据有了 Schema 之后下一步是写入关系元组——即谁与谁存在什么关系的授权数据。Permify 使用entity:id#relationsubject:id这样的文本格式表达一条关系其字符串拼接实现见 pkg/tuple/tuple.go 中的ToString函数格式化常量为%s:%s、#%s、%s。按本示例的 Schema构造如下样例关系与 assets/example-shapes/facebook-groups.yaml 的relationships一致//group relationships group:1#memberuser:1 group:1#adminuser:2 group:2#moderatoruser:3 group:2#memberuser:4 group:1#memberuser:5 //post relationships post:1#owneruser:1 post:1#groupgroup:1 post:2#owneruser:4 post:2#groupgroup:1 //comment relationships comment:1#owneruser:2 comment:1#postpost:1 comment:2#owneruser:5 comment:2#postpost:2 //like relationships like:1#owneruser:3 like:1#postpost:1 like:2#owneruser:4 like:2#postpost:2 //poll relationships poll:1#owneruser:2 poll:1#groupgroup:1 poll:2#owneruser:5 poll:2#groupgroup:1 //file relationships file:1#owneruser:1 file:1#groupgroup:1 //event relationships event:1#owneruser:3 event:1#groupgroup:1这批数据刻画了如下事实group:1成员为user:1、user:5管理员为user:2group:2成员为user:4版主为user:3post:1属于group:1拥有者是user:1post:2也属于group:1拥有者是user:4comment:1挂在post:1下拥有者user:2comment:2挂在post:2下拥有者user:5event:1属于group:1拥有者是user:3。注意一个细节user:2虽然是group:1的管理员但关系元组里并没有group:1#memberuser:2即管理员身份与成员身份是相互独立的关系——这会在后面的权限断言中体现出来。七、编写验证文件Schema Relationships Scenarios为了让授权逻辑可被自动测试Permify 提供了一套Shape 验证文件格式YAML。其结构定义在 pkg/development/file/shape.go字段类型作用tenant_idstring可选的租户 IDschemastring授权模型的 Perm DSL 源码relationships[]string要写入的关系元组列表attributes[]string可选的属性列表本示例为空用于 ABAC 场景scenarios[]Scenario测试场景列表每个场景包含若干checks断言其中每个Check包含entity被检查的资源、subject发起请求的主体与assertions权限名 → 期望布尔值。以下是根据本示例构造的完整验证文件与官方示例一致schema: - entity user {} entity group { // Relation to represent the members of the group relation member user // Relation to represent the admins of the group relation admin user // Relation to represent the moderators of the group relation moderator user // Permissions for the group entity action create member action join member action leave member action invite_to_group admin action remove_from_group admin or moderator action edit_settings admin or moderator action post_to_group member action comment_on_post member action view_group_insights admin or moderator } entity post { // Relation to represent the owner of the post relation owner user // Relation to represent the group that the post belongs to relation group group // Permissions for the post entity action view_post owner or group.member action edit_post owner or group.admin action delete_post owner or group.admin permission group_member group.member } entity comment { // Relation to represent the owner of the comment relation owner user // Relation to represent the post that the comment belongs to relation post post // Permissions for the comment entity action view_comment owner or post.group_member action edit_comment owner action delete_comment owner } entity like { // Relation to represent the owner of the like relation owner user // Relation to represent the post that the like belongs to relation post post // Permissions for the like entity action like_post owner or post.group_member action unlike_post owner or post.group_member } entity poll { // Relation to represent the owner of the poll relation owner user // Relation to represent the group that the poll belongs to relation group group // Permissions for the poll entity action create_poll owner or group.admin action view_poll owner or group.member action edit_poll owner or group.admin action delete_poll owner or group.admin } entity file { // Relation to represent the owner of the file relation owner user // Relation to represent the group that the file belongs to relation group group // Permissions for the file entity action upload_file owner or group.member action view_file owner or group.member action delete_file owner or group.admin } entity event { // Relation to represent the owner of the event relation owner user // Relation to represent the group that the event belongs to relation group group // Permissions for the event entity action create_event owner or group.admin action view_event owner or group.member action edit_event owner or group.admin action delete_event owner or group.admin action RSVP_to_event owner or group.member } relationships: - group:1#memberuser:1 - group:1#adminuser:2 - group:2#moderatoruser:3 - group:2#memberuser:4 - group:1#memberuser:5 - post:1#owneruser:1 - post:1#groupgroup:1 - post:2#owneruser:4 - post:2#groupgroup:1 - comment:1#owneruser:2 - comment:1#postpost:1 - comment:2#owneruser:5 - comment:2#postpost:2 - like:1#owneruser:3 - like:1#postpost:1 - like:2#owneruser:4 - like:2#postpost:2 - poll:1#owneruser:2 - poll:1#groupgroup:1 - poll:2#owneruser:5 - poll:2#groupgroup:1 - file:1#owneruser:1 - file:1#groupgroup:1 - event:1#owneruser:3 - event:1#groupgroup:1 scenarios: - name: scenario 1 description: test description checks: - entity: event:1 subject: user:4 assertions: RSVP_to_event : false - entity: comment:1 subject: user:5 assertions: view_comment : true7.1 用例一user:4能否对event:1执行RSVP_to_event→ falseevent实体的RSVP_to_event动作定义为owner or group.member即事件拥有者或事件所属群组的成员可以 RSVP。根据关系元组event:1的拥有者是user:3event:1#owneruser:3user:4不是拥有者event:1属于group:1event:1#groupgroup:1而group:1的成员是user:1与user:5user:4是group:2的成员group:2#memberuser:4不在group:1的 member 关系中。两条路径都不满足因此user:4 RSVP_to_event event:1的检查结果应为false拒绝。7.2 用例二user:5能否查看comment:1→ truecomment实体的view_comment定义为owner or post.group_memberuser:5不是comment:1的拥有者拥有者是user:2但user:5是group:1的成员group:1#memberuser:5而comment:1挂在post:1下comment:1#postpost:1post:1又属于group:1post:1#groupgroup:1由permission group_member group.member可知group:1的成员自动成为post:1的group_member进而满足comment:1的view_comment owner or post.group_member。因此user:5 view_comment comment:1的检查结果应为true允许。这个用例完整演示了评论 → 帖子 → 群组 → 成员的多级权限继承推导。八、在本地运行验证器make serve与permify validate8.1 启动 Permify克隆仓库后先构建并启动 Permify 服务对应Makefile中的serve目标make serve8.2 运行验证将上文完整的 YAML 内容保存为一个验证文件例如facebook-groups.yaml然后执行permify validate {path of your schema validation file}validate命令的定义见 pkg/cmd/validate.go其签名是validate file严格接收一个参数。整个验证流程分为四步解码验证文件通过 pkg/development/file/decoder.go 的NewDecoderFromURL按 URL scheme 选择解码器——file://或本地路径走文件解码器http(s)://走 HTTP 解码器对 GitHub Gist 链接还会自动转换为 raw 地址统一以 YAML 反序列化到file.Shape结构加载并编译 Schema用 pkg/schema/loader.go 的LoadSchema读取 schema 源码再经parser解析、compiler编译pkg/dsl/parser/parser.go、pkg/dsl/compiler/compiler.go随后把每个实体的定义写入内存存储并分配一个新的 schema 版本号写入关系元组逐条将relationships解析为 tuple复用 pkg/tuple/tuple.go 的Tuple解析函数先用实体定义校验元组合法性再写入数据存储属性attributes同理执行断言检查遍历每个scenario的checks对每条断言调用Invoker.Check执行真实的权限检查将实际结果与期望值比对——一致输出success: subject permission entity不一致则记录 expected: DENIED actual: ALLOWED或相反并最终以非零退出码结束。几点实用的源码细节检查深度DepthDepth()函数见 pkg/cmd/validate.go规定深度为 0 时默认取100显式指定时必须大于等于 3成功输出全部通过时命令依次打印schema successfully created、relationships successfully created、assertions successfully passed最后输出SUCCESS除checks外scenario还支持entity_filters与subject_filters两类断言分别对应LookupEntity找出主体拥有某权限的全部实体与LookupSubject找出对某实体拥有某权限的全部主体可用于验证批量查询类接口。九、仓库中的自动化佐证集成测试除命令行验证外仓库还把这套 Facebook 群组模型固化到了集成测试中做到文档示例即测试用例Go 版模型integration-test/usecases/shapes/facebook_groups.go 用file.Shape结构在 Go 中复刻了同样的 schema、关系元组与场景断言包含RSVP_to_event: false与view_comment: true两个检查测试执行integration-test/usecases/facebook_groups_test.go 通过 ginkgo 框架把每个场景的每条断言分别用单个 Check与BulkCheck批量检查两种方式发给permissionClient并断言返回的Can结果与期望一致CHECK_RESULT_ALLOWED/CHECK_RESULT_DENIED测试请求中显式指定了Depth: 100与租户facebook-groups。也就是说前面推导的两个用例结论user:4不能 RSVP、user:5可查看评论不只在文档层面成立还由仓库内的自动化测试直接验证。此外仓库在 assets/example-shapes/facebook-groups.yaml 中还附带了一组更丰富的场景断言可作进阶学习user:1group:1成员对group:1的create、join均为 trueuser:2group:1管理员但非成员的invite_to_group、remove_from_group、edit_settings、view_group_insights为 true而create、join、leave、post_to_group、comment_on_post为 false——直观印证了管理员身份 ≠ 成员身份的建模效果帖子权限用例user:1作为post:1拥有者可view/edit/deleteuser:2作为群组管理员可edit/delete post:1但不能view_post因为view_post owner or group.member只认拥有者与成员user:5作为group:1成员可查看post:2但无编辑/删除权。十、总结与延伸通过本示例可以提炼出一套可复用的授权建模方法论实体先行把业务对象群组、帖子、评论、事件……逐一映射为entity关系定义角色与归属用relation表达角色member/admin/moderator与归属post 属于 group权限组合授权规则用action/permission通过or、and组合关系实现拥有者或群组管理员这类细粒度规则嵌套继承表达层级在父实体暴露中间权限如group_member在子实体中引用形成跨实体的权限推导链数据与逻辑分离Schema 描述规则关系元组描述事实验证文件把两者打包并固化断言可随时用permify validate回归测试。想要把本示例接入真实应用可以继续阅读仓库内的相关文档安装与启动、写入关系数据、写入 Schema以及执行权限检查所用的 Check API。模型设计阶段也可以直接使用仓库自带的 WASM 版 playground 在线试玩playground 说明。仓库同时内置了 Perm DSL 语法参考 与语法解析器源码pkg/dsl可作为深入学习授权模型语言的起点。【免费下载链接】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),仅供参考