Baserow 企业版 RBAC 权限系统技术指南:角色、团队与作用域详解
【免费下载链接】baserowBuild databases, automations, apps & agents with AI — no code. Open source platform available on cloud and self-hosted. GDPR, HIPAA, SOC 2 compliant. Best Airtable alternative.项目地址: https://gitcode.com/GitHub_Trending/ba/baserow
本篇技术指南面向希望深入理解 Baserow 企业版基于角色的访问控制(Role Based Access Control,RBAC)内部原理的开发者。文章以 enterprise/docs/RBAC-guide.md 为主体骨架,结合仓库内 RBAC 模块的源码、模型与测试实现,系统讲解 RBAC 的术语体系、默认角色、作用域继承规则、角色判定优先级、数据模型以及权限分配与校验的底层调用链,帮助读者掌握在企业级权限架构中正确使用与二次开发 RBAC 的能力。
前置知识:Baserow 权限系统基础
RBAC 是 Baserow 权限系统(Permission System)之上的一层具体实现。在阅读本文之前,建议先阅读 docs/technical/permissions-guide.md,以理解如下核心概念:
- Object:Baserow 中的任意数据对象,如 Field、Row、Table、Database、Workspace、User、Team、Role、Webhook 等。对象之间存在父子层级关系,例如
Table是Database的子对象。 - Actor:可以执行操作的主体,可以是
User、Personal API Token甚至AnonymousUser。 - Operation:Actor 对 Object 执行的动作,例如
database.list_tables(列出某 Database 下的 Tables)、database_table.create_row(在某 Table 中创建 Row)。 - Context:Operation 所作用的对象实例。
- Permission request:由(Actor, Operation, Context)组成的三元组,是权限系统判定的基本单位。
- Permission manager:权限系统中可插拔的判定组件,每个 Permission manager 可以对请求做出允许(Allow)、拒绝(Disallow)或放行(Passthrough)三种决定。Baserow 按固定顺序依次让各 manager 处理请求,若全部放行则默认拒绝。
- Subject:包含所有 Actor,也包含 Actor 的集合(如 Teams)。
- Workspace:操作发生的空间范围(旧称 Group)。
权限系统在设计上刻意保持可扩展性(官方文档明确以"支持 enterprise 目录中的 RBAC"为约束),RBAC 正是通过注册一个新的PermissionManagerType(即RolePermissionManagerType)接入这一体系的。在默认设置中,BasicPermissionManagerType只区分ADMIN与MEMBER两种"角色",角色名存储在WorkspaceUser.permissions字段中;RBAC 则复用该字段作为 Workspace 级别的角色存储,从而保证两种权限体系可以平滑切换、不产生数据重复或同步问题。这一点在RolePermissionManagerType的兼容逻辑中有直接体现。
术语表:RBAC 专属概念
RBAC 在权限系统通用术语之上,进一步定义了以下专属术语(见 enterprise/docs/RBAC-guide.md):
- Role(角色):一组 Operation 的集合,可以被赋予某个 Subject。当角色被赋予 Subject 后,该 Subject 即获得该角色下全部 Operation 的权限。
- Custom Role(自定义角色):由 Actor(通常是管理员)创建的角色,区别于 Baserow 默认内置的角色集合。从源码看,
Role模型带有workspace外键与default布尔字段(见 enterprise/backend/src/baserow_enterprise/role/models.py),default=True表示默认角色,workspace非空则表示该角色属于某个 Workspace,这正是自定义角色的数据基础。 - Team(团队):一组 Subject 记录的集合,便于统一管理,让高权限角色可以批量管理 Subject,而无需逐个分配角色。一个 Team 仅关联一个 Workspace。
- Role assignment(角色分配):一个三元组,表示"在某个 Scope 上、将某个 Role 分配给某个 Subject"。
- Scope(作用域):默认情况下,一个 Role assignment 应用于 Workspace 的全部对象;但角色的应用范围可以被限制到某个特定对象实例上,这个对象实例即该角色的 Scope。此时该角色授予的权限只作用于这个对象及其全部子对象。Role assignment 的默认 Scope 是 Workspace;对于
Database应用,额外允许的 Scope 还有Database和Table。
从源码常量定义看(见 enterprise/backend/src/baserow_enterprise/role/constants.py),可分配角色的对象映射ROLE_ASSIGNABLE_OBJECT_MAP实际包含四级:
| Scope 类型 | READ 操作 | UPDATE(分配角色)操作 |
|---|---|---|
workspace | workspace.read_role | workspace.assign_role |
application | application.read_role | application.update_role |
database_table | database.table.read_role | database.table.update_role |
database_view | database.table.view.read_role | database.table.view.update_role |
即在当前实现中,Scope 除了文档提到的Workspace、Database、Table外,还支持到View层级。
主要原则:角色、优先级与继承规则
结构性角色与默认角色
RBAC 中存在若干结构性角色与默认角色(见 enterprise/backend/src/baserow_enterprise/role/default_roles.py),它们以 UID 字符串标识:
- NO_ROLE / NO_ACCESS:移除给定 Scope(及其所有子对象,通过继承)上的全部权限。在代码中该角色的 UID 常量为
NO_ACCESS_ROLE_UID,可通过 Django 设置NO_ACCESS_ROLE_UID覆盖(默认"NO_ACCESS",见 constants.py)。 - NO_ROLE_LOW_PRIORITY:与 NO_ROLE 类似,但如果某个 Team 在同一 Scope 上有角色,则 Team 角色会被采纳。文档特别指出,这一角色的存在主要是因为无法在 Group(即 Workspace)级别移除某个
User的角色——Workspace 级别用户角色存储在WorkspaceUser.permissions字段中,无法用"无角色"表达,因此用低优先级占位符来区分。 - VIEWER:只读操作集合。一个操作只有当它不修改数据库中的任何数据时才被认为是只读的。
- READ_ONLY:这是源码中的一个隐藏角色(
hidden_roles),不可被用户直接设置。当用户在某个子级作用域(如 Table)上拥有角色、但未拥有其祖先作用域(如 Database)的访问权限时,系统会自动为其祖先补上READ_ONLY角色,使中间对象可被看到以便访问子对象(见 default_roles.py)。
除上述结构角色外,默认存在以下角色(每个默认角色都是前一个角色的超集,见 default_roles.py):
- ADMIN:可对 Workspace 做任何事情,包括权限管理(
workspace.assign_role、workspace.read_role、UpdateWorkspaceUserOperationType、团队与邀请管理等)。 - BUILDER:除权限管理外可以做任何事情,包括建表、建视图、创建字段、Webhook、自动化、页面构建等。
- EDITOR:可以在表中创建/更新/删除行。
- COMMENTER:只能评论行。
值得注意的实现细节:默认角色在代码中按层级叠加构建——COMMENTER继承VIEWER的读写权限,EDITOR继承COMMENTER,BUILDER继承EDITOR,ADMIN继承BUILDER。VIEWER又是在READ_ONLY+NO_ACCESS基础上叠加而来。角色与操作之间通过Role.operations多对多关系关联(见 models.py)。
角色判定规则
当一个 Permission request 到来时,系统按以下规则计算应使用的角色(见 enterprise/docs/RBAC-guide.md):
- 最近祖先角色规则(Rule of closest ancestor Role):对某个 Context 上的操作,取该 Context 最近祖先(包含其自身)上的 Role assignment,无论它是 Team 角色还是 Actor 角色。
- Actor 角色优先规则(Rule of Actor Role precedence):在同一 Scope 上,如果同时存在 Actor 自身的 Role assignment 和该 Actor 所属 Team 的 Role assignment,则 Actor 角色总是优先于 Team 角色,除非 Actor 角色是
NO_ROLE_LOW_PRIORITY。 - Team 角色继承规则(Rule of Team Roles inheritance):如果某个 Scope 上不存在 Actor 角色,或者 Actor 角色是
NO_ROLE_LOW_PRIORITY,则将该 Actor 所属全部 Team 的角色"取并集",即最宽松的 Team 角色生效。 - 反向 Scope 继承规则(Rule of reverse Scope inheritance):当 Actor 在某个 Scope 上拥有至少一个
VIEWER操作的角色分配时,该 Scope 的全部祖先对象自动获得VIEWER角色——即使按照前面的规则它们应被赋予NO_ROLE。这使得 Actor 能够看到所有中间对象并访问子对象。这一自动角色在应用"最近祖先角色规则"时被忽略。
对以上规则的简化概括:
- 层级优先:Tables 角色 > Database 角色 > Workspace 角色
- 主体优先:Actor 角色 > Team 角色
NO_ROLE_LOW_PRIORITY⇒ 使用 Team A 角色 + Team B 角色 + …- 任意 n 级角色 ⇒ n-1 级 VIEWER 角色(反向继承)
上述规则在 enterprise/backend/src/baserow_enterprise/role/handler.py 的get_computed_roles方法中有直接实现:该方法遍历按层级排序的roles_per_scopes,若某 Scope 包含当前 Context,则保留该更精确的角色集合(最近祖先规则);若某子级 Scope 被 Context 包含且持有非 NO_ACCESS 角色,则追加READ_ONLY角色(反向继承),并在代码注释中给出了"对某个 Table 拥有 BUILDER 角色时,其父 Database 必须可读"的典型例子。
实战示例:规则如何组合生效
以下示例均来自原文档,结合上述规则逐一解读:
示例一:仅 Actor 分配
Actor A 的分配:
- Workspace 1 上为 BUILDER
- Table 10(Database 5 的子对象)上为 VIEWER
结果:该 Actor 在除 Table 10 及其子对象外的所有地方是 BUILDER;对 Table 10 及其子对象是 VIEWER(最近祖先角色规则)。
示例二:Actor 与 Team 在子级冲突
Actor A 的分配:
- Workspace 1 上为 BUILDER
- Table 10 上为 VIEWER
Actor A 属于 Team T,Team T 的分配:
- Table 10 上为 COMMENTER
- Table 20(同为 Database 5 子对象)上为 NO_ROLE
结果:
- 对 Table 10 及其子对象为 VIEWER(Actor 角色优先规则——Actor 的 VIEWER 压过 Team 的 COMMENTER);
- 对 Table 20 及其子对象为 NO_ROLE(最近祖先角色规则);
- 其余地方为 BUILDER(最近祖先角色规则)。
示例三:多个 Team 取并集
Actor A 的分配:
- Workspace 1 上为 VIEWER
Actor A 属于 Team T1(Table 10 上为 COMMENTER)和 Team T2(Table 10 上为 BUILDER)。
结果:对 Table 10 及其子对象为 BUILDER(Team 角色继承规则——取最宽松者);其余地方为 VIEWER(最精确角色规则)。
示例四:NO_ROLE 压制全部 Team 角色
Actor A 的分配:
- Workspace 1 上为 NO_ROLE
Team T1(Workspace 1 上为 COMMENTER)、Team T2(Workspace 1 上为 BUILDER)。
结果:该 Actor 处处为 NO_ROLE(Actor 角色优先规则——NO_ROLE 是普通 Actor 角色,直接生效)。
示例五:NO_ROLE_LOW_PRIORITY 让位给 Team
Actor A 的分配:
- Workspace 1 上为 NO_ROLE_LOW_PRIORITY
Team T1(Workspace 1 上为 COMMENTER)、Team T2(Workspace 1 上为 BUILDER)。
结果:该 Actor 同时拥有 COMMENTER 与 BUILDER,即处处为 BUILDER(Team 角色继承规则——NO_ROLE_LOW_PRIORITY 不生效,取并集后最宽松者胜出)。
示例六:反向 Scope 继承
Actor A 的分配:
- Workspace 1 上为 NO_ROLE
- Table 10(Database 5 的子对象)上为 EDITOR
结果:
- 对 Table 10 及其子对象为 EDITOR(最近祖先角色规则);
- 对 Database 5、Application 5 和 Workspace 1 为 VIEWER(反向 Scope 继承规则);
- 其余地方为 NO_ROLE(最近祖先角色规则)。
技术细节:RoleAssignment 数据模型
要赋予某个 Actor 在给定 Scope 上的角色,需要创建RoleAssignment。它是 RBAC 系统的核心对象,权限请求所需的全部信息都存储在该对象中。
数据模型如下(原图见 enterprise/docs/RBACDataModel.jpg):
对应代码实现见 enterprise/backend/src/baserow_enterprise/role/models.py:
RoleAssignment继承CreatedAndUpdatedOnMixin,记录创建与更新时间。- Subject通过一对 GenericForeignKey(
subject_type+subject_id)引用——因为 Subject 可以是不同类型的对象(User、Team等)。 - Scope同样通过 GenericForeignKey(
scope_type+scope_id)引用——因为 Scope 可以是Workspace、Application(Database)、Table、View等不同类型。 - role是
Role的外键,删除角色会级联删除相关分配。 - workspace指明该分配所属的 Workspace。
- 模型层还定义了
UniqueConstraint(字段组合scope_id, scope_type, subject_id, subject_type),保证同一 Subject 在同一 Scope 上最多只有一条分配记录。测试 enterprise/backend/tests/baserow_enterprise_tests/role/test_role_handler.py 验证了重复创建会抛出IntegrityError。
Role模型本身(见 models.py)包含:
uid:唯一标识符(默认 UUID,但默认角色使用ADMIN、BUILDER等固定 UID);name:人类可读名称;operations:与Operation的多对多关系,即该角色允许的操作列表;default:是否为默认角色;hidden:是否隐藏(隐藏角色用户不可见、不可设置,仅用于内部,如READ_ONLY与FIELD_PERMISSION_EDITOR);workspace:可空的 Workspace 外键,非空即自定义角色所属的 Workspace。
从源码常量看,FIELD_PERMISSION_EDITOR是一个特殊隐藏角色,仅作为字段级RoleAssignment上的标记使用,由字段权限管理器独占处理,绝不会参与常规 RBAC 角色计算(见 constants.py)。
分配角色的两种存储路径
在 enterprise/backend/src/baserow_enterprise/role/handler.py 的assign_role方法中,分配角色时根据条件走两条路径:
- Workspace 级 + User 主体:直接设置
WorkspaceUser.permissions属性为角色 UID。其中 BUILDER 会被翻译为"MEMBER"以保持与BasicPermissionManagerType的兼容;其余角色直接写入 UID。这一步保证了RolePermissionManagerType与BasicPermissionManagerType之间的兼容切换,不丢失也不重复存储信息。 - 其他情况:创建(或
update_or_create更新)一条RoleAssignment记录,然后发送role_assignment_created/role_assignment_updated信号。判断逻辑见is_workspace_level_assignment(handler.py):当scope == workspace且 Subject 是User时为 Workspace 级分配。
移除角色(remove_role,见 handler.py)也遵循同样的分叉:Workspace 级 + User 则把WorkspaceUser.permissions置为NO_ACCESS_ROLE_UID,否则删除对应的RoleAssignment记录并发送role_assignment_deleted信号。
校验链路与权限判定
- 批量分配入口:
assign_role_batch_for_user(handler.py)先校验 License(LicenseHandler.raise_if_user_doesnt_have_feature(RBAC, ...)),再校验 Subject 是否支持(ALLOWED_SUBJECT_TYPE_BY_PRIORITY,默认["auth.User", "baserow_enterprise.Team"])、是否在 Workspace 内、Scope 是否为 Workspace 子对象,最后通过ROLE_ASSIGNABLE_OBJECT_MAP中对应的 UPDATE 操作做权限检查,全部通过后才执行批量分配。 - 权限判定入口:
RolePermissionManagerType(enterprise/backend/src/baserow_enterprise/role/permission_manager.py)实现了PermissionManagerType的三个核心方法:check_multiple_permissions:对一批 (actor, operation, context) 检查,先调用RoleAssignmentHandler().get_roles_per_scope_for_actors获取每个 Actor 按层级排序的角色集合,再用get_computed_roles计算 Context 上的实际角色,最后把角色允许的操作名集合与请求的操作名比对,允许则返回True,否则返回PermissionDenied()(permission_manager.py)。get_permissions_object:为前端生成"每个操作默认是否允许 + 例外对象 ID 列表"的权限对象(形如{"operation_name": {"default": bool, "exceptions": [id, ...]}}),前端据此无需再向后端请求即可本地判断(permission_manager.py)。前端对应实现位于 enterprise/web-frontend/modules/baserow_enterprise/permissionManagerTypes.js,并在 enterprise/web-frontend/modules/baserow_enterprise/plugin.js 中注册。filter_queryset:依据权限对象对 Django QuerySet 做过滤——若默认允许则exclude(id__in=exceptions),否则filter(id__in=exceptions),无例外且默认拒绝时直接返回空集(permission_manager.py)。
- 启用条件:
is_enabled通过LicenseHandler.workspace_has_feature(RBAC, workspace)判断该 Workspace 是否拥有 RBAC 企业特性,未启用时该 manager 直接返回(permission_manager.py)。
对应地,RBAC 定义了自己的 Operation 类型(见 enterprise/backend/src/baserow_enterprise/role/operations.py):workspace.assign_role、workspace.read_role、application.read_role/application.update_role、database.table.read_role/database.table.update_role、database.table.view.read_role/database.table.view.update_role。这些操作正是ROLE_ASSIGNABLE_OBJECT_MAP中校验分配权限时使用的操作名。
缓存与实时更新
为提升性能,角色分配结果通过local_cache缓存:get_roles_per_scope、团队-用户映射、Workspace 用户权限等均以role_assignments_*为前缀做本地缓存;所有写操作(assign_role、remove_role)都会通过clear_roles_from_local_cache装饰器清除相关缓存(见 handler.py)。
角色分配变更后,enterprise/backend/src/baserow_enterprise/role/receivers.py 中的信号处理器会转发permissions_updated信号,进而通过 WebSocket 向受影响用户广播permissions_updated消息,提示其刷新页面以获取最新权限。
删除级联与注意事项
原文档指出:RoleAssignment的两个 GenericForeignKey 没有自动删除级联,因此需要手动注册删除信号处理器。这一实现位于 enterprise/backend/src/baserow_enterprise/role/receivers.py:
cascade_subject_delete:当任意 Subject 类型(注册在subject_type_registry中的模型)被删除时,删除其作为 Subject 的全部 RoleAssignment;cascade_workspace_user_delete:当WorkspaceUser被删除时,清理该用户在该 Workspace 下的角色分配;cascade_scope_delete:当 Scope 对象(ROLE_ASSIGNABLE_OBJECT_MAP中列出的对象类型,以及字段权限内部使用的Field)被删除时,清理以其为 Scope 的全部 RoleAssignment。
这些信号在 enterprise/backend/src/baserow_enterprise/apps.py 的应用就绪阶段通过connect_to_post_delete_signals_to_cascade_deletion_to_role_assignments()统一注册。
已知限制与待办事项
原文档明确列出以下未完成事项,编写代码时需注意规避:
- RBAC 领域的角色端点(roles endpoint)尚缺失;
- 部分命名欠佳,有待改进;
- 计划使用
HierarchicalMixin替代ObjectScopeType; - 系统已接近支持自定义角色创建(
Role.workspace与Role.default字段已就位),但尚未完全开放; - 计划新增一个"为 Workspace 中每个对象展示各 Actor 角色"的调试页面,帮助用户排查角色分配;
- 权限的实时更新目前只是部分实现——用户只会收到一条"请刷新页面以获取新权限"的消息,尚做不到免刷新即时生效。
配置项速查
RBAC 模块支持通过 Django settings 覆盖以下常量(见 constants.py 与 default_roles.py):
| 配置项 | 默认值 | 说明 |
|---|---|---|
NO_ACCESS_ROLE_UID | "NO_ACCESS" | 无权限角色的 UID |
READ_ONLY_ROLE_UID | "READ_ONLY" | 只读(隐藏)角色的 UID |
NO_ROLE_LOW_PRIORITY_UID | "NO_ROLE_LOW_PRIORITY" | 低优先级占位角色的 UID |
GET_ROLE_ASSIGNABLE_OBJECT_MAP | 内置四级映射(workspace / application / database_table / database_view) | 可分配角色的对象类型及对应读写操作名 |
ALLOWED_SUBJECT_TYPE_BY_PRIORITY | ["auth.User", "baserow_enterprise.Team"] | 支持分配角色的 Subject 类型及优先级排序 |
BASEROW_PERSONAL_VIEW_LOWEST_ROLE_ALLOWED | 无默认值(必须显式设置) | 允许使用个人视图的最低角色;若设置为非默认角色 UID,启动时会抛出ImproperlyConfigured |
BASEROW_PERSONAL_VIEW_LOWEST_ROLE_ALLOWED的校验逻辑在 default_roles.py:该值必须是user_role_uids(即除内部角色外的全部默认角色 UID)之一,否则拒绝启动;选定角色会被自动追加CreateAndUsePersonalViewOperationType操作。
测试验证
RBAC 模块在仓库中拥有较完整的测试覆盖,可作为理解与二次开发的参考:
- enterprise/backend/tests/baserow_enterprise_tests/role/test_role_handler.py:覆盖角色分配的创建、唯一约束、查询、移除、批量分配与校验逻辑。其中
test_create_role_assignment验证了 Workspace 级分配写入WorkspaceUser.permissions而表级分配写入RoleAssignment的双路径行为。 - enterprise/backend/tests/baserow_enterprise_tests/role/test_role_permission_manager.py:覆盖
RolePermissionManagerType的权限检查、权限对象生成与 queryset 过滤。 - enterprise/backend/tests/baserow_enterprise_tests/role/test_role_receivers.py 与 enterprise/backend/tests/baserow_enterprise_tests/ws/test_role_signals.py:覆盖删除级联与实时信号广播。
- enterprise/backend/tests/baserow_enterprise_tests/api/role/test_role_views.py 与
test_application_views_with_roles.py、test_views_filtered_by_roles.py:覆盖角色相关 API 及角色过滤视图的行为。
结语
Baserow 的 RBAC 是一套建立在可插拔权限系统之上的细粒度授权方案:通过RoleAssignment把"角色 × 主体 × 作用域"三元组落库,以"最近祖先优先、Actor 优先、Team 取并集、反向只读继承"四条规则完成角色计算,再交由RolePermissionManagerType统一裁决权限请求。理解这套规则与数据模型,是在企业部署中正确配置团队权限、排查"为什么用户能看到/看不到某张表"等问题的关键;而源码中预留的Role.workspace、default字段与明确的 TODO 清单,也勾勒出了未来自定义角色等能力的演进方向。
【免费下载链接】baserowBuild databases, automations, apps & agents with AI — no code. Open source platform available on cloud and self-hosted. GDPR, HIPAA, SOC 2 compliant. Best Airtable alternative.项目地址: https://gitcode.com/GitHub_Trending/ba/baserow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考