ToolJet 权限模型全解:3 层 RBAC + 5 条硬规则 + 源码级校验逻辑
2026/9/12 7:55:52 网站建设 项目流程

ToolJet 权限模型全解:3 层 RBAC + 5 条硬规则 + 源码级校验逻辑

【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet

测试账号刚进 Builder 组,就有人拿它把内部应用推上了生产。ToolJet 的权限模型(Role-Based Access Control, RBAC)靠一条固定的判定链来拦这类事故。下面沿一次真实请求的生命周期走一遍。

核心链路:权限判定请求如何走完 6 步校验

一次"Builder 打开某应用的发布页"请求,在后端会依次穿过 6 个关卡,任何一步不通过就短路返回:

  1. 请求进来:命中带tjModuleId/tjFeatureId反射元数据的控制器,AbilityGuard.canActivate接管;
  2. 拦截:先查WorkspaceBanList,组织被停用直接抛 403(errorType: 'WORKSPACE_BANNED');再按 feature 查许可证,缺许可抛 451;
  3. 查所属组:上游 guard 把请求用户的组信息挂到request.tj_grouprequest.tj_admin_groups(EE 侧填充,CE 恒为空数组);
  4. 读组级开关FeatureAbilityFactory.defineAbilityFor依据userAllPermissionsisAdmin / isBuilder给当前用户装配一组FEATURE_KEY
  5. 查细粒度资源授权ability.can(feature, GroupPermissions, resourceId)判定该资源 ID 是否在授权范围内;
  6. 放行 / 抛错:只要有一个 feature 不满足,forbiddenFeature命中即抛ForbiddenException(403)。

第 4 步的分支逻辑是整条链的"开关总闸",源码在 ability/index.ts:

const { superAdmin, isAdmin, isBuilder } = userAllPermissions; if (superAdmin || isAdmin) { can([FEATURE_KEY.ADD_GROUP_USER, FEATURE_KEY.CREATE /* …省略 30+ 个 feature */], GroupPermissions); return; } if (!isBuilder) return; const adminGroups: GroupPermissions[] = request?.tj_admin_groups || [];

这里有个坑:if (!isBuilder) return;意味着 End-user 在"配置权限"这一侧直接出局,拿不到任何FEATURE_KEY。而 Admin 分支return前一次性放通 30 多个 feature,等于把细粒度授权的增删改查全交给管理员。真正决定"能操作哪些具体应用 / 环境"的,是第 5 步的resourceId——它对应的就是下一节的细粒度授权表。

ToolJet RBAC 配置的分层拆解:决策、存储与执行

决策层:谁有权定规则

规则来源分三类,边界由"能否被运行时改写"划清。下表按操作维度列出可配置性:

操作维度系统内置(代码写死)管理员可配(运行时)
Admin 组组级开关18 个布尔位出厂全true不可配——建 / 改细粒度授权时直接抛错
Builder 组级开关出厂trueappCreate等 18 位)仅自定义组可调,默认组改不动
canAccessProduction等 4 个环境位MULTI_ENVIRONMENT许可证门控持许可证才可放开
End-user 构建级位出厂全false任何构建级位 → 400 拒绝
canEdit/canView互斥约束写死二者只能置其一

组级开关的出厂值集中在 constants/index.ts 的DEFAULT_GROUP_PERMISSIONS,Builder 与 Admin 在这一层 18 个位完全等价,差别全部落在资源级(细粒度)授权上。

存储层:数据落在哪几张表

核心是 3 张主表 + 3 张动作子表。permission_groups(group_permissions.entity.ts)挂 18 个布尔位 +organization_id,成员经group_users关联(删组级联删成员)。资源级授权的主记录在granular_permissions(granular_permissions.entity.ts):

@Column({ name: 'group_id' }) groupId: string; @Column({ name: 'type', type: 'enum', enum: ResourceType }) type: ResourceType; // 7 类资源 @Column({ name: 'is_all', nullable: false, default: true }) isAll: boolean;

type取自ResourceType,共 7 类(app/data_source/workflow/folder/module/workflow_folder/module_folder)。isAll=true(默认)表示覆盖该类资源全集,isAll=false时经group_apps/group_folders中间表逐个绑定具体 ID。每条granular_permissions一对一挂 3 个动作子表(应用 / 数据源 / 文件夹),全部onDelete: 'CASCADE'。应用级动作在 apps_group_permissions.entity.ts,其中 4 个环境位(canAccessDevelopment/Staging/Production/Released)默认全true,真正的收紧发生在写入校验时。

执行层:错误码怎么结构化返回

校验失败不返回裸字符串,而是带type的结构化体,供前端识别并弹二次确认。以 granular-permissions.util.service.ts 的创建路径为例:

if (endUsers.length) throw new BadRequestException({ message: { error: ERROR_HANDLER.EDITOR_LEVEL_PERMISSIONS_NOT_ALLOWED, data: endUsers.map((user) => user.email), type: 'USER_ROLE_CHANGE_ADD_PERMISSIONS', }, });

data里附带组内所有 End-user 的邮箱,管理员据此定位需要调整角色的用户。前端读到type后走角色变更确认交互;而更新路径遇到同类冲突且未携带allowRoleChange时,抛的是MethodNotAllowedException(405,type: 'USER_ROLE_CHANGE'),两种状态码对应"新增拒绝"与"需确认后升级"两个不同交互分支。

文档没写但源码写死的 5 条硬规则

  1. Admin 组禁止配细粒度权限validateGranularPermissionCreateOperation/validateGranularPermissionUpdateOperation里只要group.name === USER_ROLE.ADMIN就抛ADMIN_DEFAULT_GROUP_GRANULAR_PERMISSIONS(granular-permissions.util.service.ts)。意图:防止误操作把管理员降权。
  2. End-user 构建级位硬拒绝,且按资源类型粒度不同validateAppResourcePermissionUpdateOperationgroup.name === USER_ROLE.END_USER && (actions.canEdit || isModule)即拒绝——Module 连canView(Build-with)也拒,而应用只拒canEdit。意图:模块永远不会分配给 End-user。
  3. canEditcanView互斥。更新应用授权时if (actions.canEdit) actions.canView = false; else if (actions.canView) actions.canEdit = false;。意图:编辑权隐含查看权,避免二者同时为真产生歧义。
  4. 多环境能力是许可证门控的validateEnvironmentPermissions/validateResourceAction先查MULTI_ENVIRONMENT许可证,未持有时给 End-user 组授予 dev / staging / production 环境会复用 End-user 拒绝逻辑。意图:多环境隔离本身随套餐售卖。
  5. allowRoleChange触发自动升级。更新路径确认是构建级更新、组内存在 End-user 且allowRoleChange=true时,调用roleUtilService.changeEndUserToEditor批量升为 Builder;否则抛 405。意图:把"改组权限 → 弹角色变更确认"的前端交互落在服务端兜底。

按场景选对路径:ToolJet RBAC 配置决策树

  • 如果你要给一个跨职能团队隔离资源,建自定义组type: 'custom')而非动默认三角色:默认组名与开关都改不动,自定义组才能独立配权限位并自由授权应用 / 文件夹。
  • 如果你要让某人能进生产环境调试,先确认组织持有MULTI_ENVIRONMENT许可证,再放开canAccessProduction;否则请求会在写入阶段被 400 拦下,而非界面置灰。
  • 如果你要把 End-user 升级为能编辑,allowRoleChange确认流程而不是手动改权限位:直接授予构建级位会被结构化错误打回,data里返回的邮箱名单就是待升级名单。

延伸阅读:细粒度授权的建 / 改 / 校验全在 group-permissions 模块目录,建议从util-services/granular-permissions.util.service.ts入手对照本文硬规则;角色与权限的产品侧语义可看 user-roles 文档,它把默认三角色与自定义组的边界讲得比源码更直白。

【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询