ToolJet RBAC 权限模型全解:从两张数据表到细粒度授权的完整拆解【免费下载链接】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自建 ToolJet 部署时,怎么保证财务团队只能看到财务相关的应用、构建者默认碰不到生产环境?这正是 ToolJet 用户组(Groups)与 RBAC(基于角色的访问控制,Role-Based Access Control)权限模型要解决的问题:Admin 把用户拉进工作空间(Workspace)后分配到不同用户组,每个组绑定一组权限——既有组级开关,也有指向具体应用、文件夹、数据源的细粒度权限(Granular Permissions)。本文从源码拆解 ToolJet 权限模型,回答三个问题:权限存在哪、系统如何按组校验资源访问、哪些边界规则是硬性的。权限存在哪:两张表撑起整个模型整个模型的数据链路一句话就能说清:组织 → 组 → 权限,落在两张核心表上。第一张是permission_groups表(实体见 group_permissions.entity.ts)。它的organization_id字段把组钉死在所属组织上——组织就是权限隔离的边界,组与组之间不会跨组织共享。表名本身很直白地反映了它的职责:每行是一个权限组,组名由name保存,身份由type区分(内置默认组还是管理员自建组),后面会展开。这张表上还平铺着一整排布尔权限位:app_create、workflow_create、folder_create、org_constant_crud、tjdb_crud、data_source_create、app_promote、app_release等,默认全部为false。换句话说,组能不能做某类事完全由这些列决定:能否建应用、能否管理工作空间常量、能否操作 ToolJet 内置数据库、能否晋级/发布应用,一列一开关。组与人的关系放在GroupUsers关联表里,且以onDelete: CASCADE挂在该组下——删组即级联清空成员关联。实体里还挂着PageUser、QueryUser、ComponentUser三个一对多关联,说明页面、查询、组件级别的访问授权同样以组为最小授权单位。第二张表granular_permissions负责把授权挂到具体资源上(实体见 granular_permissions.entity.ts)。它只有四个业务字段,却承担了整个资源级授权的路由职责:Entity({ name: granular_permissions }) export class GranularPermissions extends BaseEntity { Column({ name: group_id }) groupId: string; Column({ name: type, nullable: false, type: enum, enum: ResourceType }) type: ResourceType; Column({ name: is_all, nullable: false, default: true }) isAll: boolean; // ... }type决定这条授权面向哪类资源,取值来自ResourceType枚举:app、data_source、workflow、folder、module、workflow_folder、module_folder。isAll是关键开关——默认true,表示覆盖该类资源的全集,不用枚举;设为false时,才通过GroupApps、GroupFolders这类中间表逐个关联具体资源 ID。主记录之外,它再以一对一(同样级联删除)挂着三张动作表:AppsGroupPermissions(应用动作与环境访问)、DataSourcesGroupPermissions(数据源的canConfigure/canUse两档)、FoldersGroupPermissions(文件夹的canEditFolder/canEditApps/canViewApps三档,源码注释说明是单选型,未勾选档位的隐含权限由运行时推导)。这意味着权限数据在库里是一主两翼结构:组级布尔位回答资格问题,granular_permissions主记录加其级联子表回答范围问题。查某个用户对某应用的访问权,路径就是:用户 → 所在组 → 该组的细粒度授权 → 动作表里的具体开关。三个默认角色是怎么被写死的结论先行:Admin、Builder、End-user 不是运行时算出来的,而是直接写死在 constants/index.ts 的常量里,随每个新组织初始化。三者的定位可以先看这张对照表:资源权限AdminBuilderEnd-user应用(Apps)创建 / 更新 / 删除✅可配置❌应用(Apps)查看✅可配置可配置数据源(Data Sources)创建 / 配置 / 删除✅可配置❌文件夹(Folder)创建 / 更新 / 删除✅可配置❌工作空间常量/变量创建 / 更新 / 删除✅可配置❌角色标识本身是一个极简枚举:export enum USER_ROLE { END_USER end-user, ADMIN admin, BUILDER builder, }组级权限位的默认值由DEFAULT_GROUP_PERMISSIONS固化,Admin 段摘录如下(BUILDER 段与它逐位相同,END_USER 段则全为false且isBuilderLevel: false):export const DEFAULT_GROUP_PERMISSIONS { ADMIN: { name: USER_ROLE.ADMIN, type: GROUP_PERMISSIONS_TYPE.DEFAULT, appCreate: true, appDelete: true, folderCreate: true, folderDelete: true, orgConstantCRUD: true, tjdbCRUD: true, dataSourceCreate: true, dataSourceDelete: true, isBuilderLevel: true, appPromote: true, appRelease: true, // ...workflow / module / 各类文件夹权限位同为 true }, // BUILDER: 与 ADMIN 组级权限位等价 // END_USER: 全部 false };换句话说,Builder 与 Admin 在组级开关这一层是完全等价的,真正的分水岭在资源级默认授权(DEFAULT_RESOURCE_PERMISSIONS)上——最典型的就是环境访问:角色DevelopmentStagingProductionReleasedAdmin✅✅✅✅Builder✅✅❌✅End-user❌❌❌✅为什么这样切分?对自建部署来说这是个很实用的安全默认:构建者负责开发和联调,但默认进不了生产环境;最终用户只消费已发布(Released)版本,连开发版都看不到。组织级隔离则天然成立——所有权限记录都挂在organization_id之下,跨组织无从谈起。系统会拦下哪些权限配置 ️前面讲的都是能存什么,这一节讲哪些配置会被直接拒绝。硬校验集中在 granular-permissions.util.service.ts,共四条,每条都值得知道背后的设计动机。其一,Admin 默认组禁止配置细粒度权限。创建或更新细粒度授权时,工具服务入口就有一道前置校验:validateGranularPermissionCreateOperation(group: GroupPermissions) { if (group.name USER_ROLE.ADMIN) { throw new BadRequestException( ERROR_HANDLER.ADMIN_DEFAULT_GROUP_GRANULAR_PERMISSIONS ); } }系统会直接拒绝这个请求,更新路径里的validateGranularPermissionUpdateOperation也有同样判断。为什么?Admin 组是全量权限的系统约定,如果允许用细粒度授权去改它,一次误操作(比如把isAll关掉却漏选资源)就能让管理员集体失权,恢复成本远高于收益。其二,End-user 组禁止获得任何构建级权限。校验逻辑会先查组内是否有 end-user,有则抛错,且不同资源的拒绝线不一样:应用/工作流在canEdit为真时拒绝;模块(Module)更严格——连 Build-with 性质的canView也拒绝,源码注释写明模块永远不会分配给 end-user;数据源是canConfigure或canUse任一为真即拒绝;文件夹拒绝canEditFolder或canEditApps,而module_folder属于任何授权都拒绝,连只读查看也不行。拒绝响应是结构化的(type: USER_ROLE_CHANGE_ADD_PERMISSIONS),data里附带组内所有 end-user 的邮箱。为什么设计成报错并点名?因为解法通常是把相关用户升级为 builder,直接把名单塞进错误体,管理员一步就能定位到人。其三,多环境访问受许可证门控。给组授予canAccessDevelopment/canAccessStaging/canAccessProduction任一环境权限时,服务会先经licenseTermsService.getLicenseTerms查询组织是否持有MULTI_ENVIRONMENT许可证条款;没有该条款时,若组内含 end-user,同样落入上面的拒绝逻辑。换句话说,多环境隔离本身是许可证门控的付费能力,不是开源配置能白嫖的。其四,allowRoleChange可触发角色自动升级。更新授权时若组内既有 end-user 又是构建级更新,validateResourceAction会分两种结局:if (endUsersList.length) { if (!allowRoleChange) { throw new MethodNotAllowedException({ ... type: USER_ROLE_CHANGE }); } // Change end users to builders await this.roleUtilService.changeEndUserToEditor( organizationId, endUsersList.map((user) user.id), ... ); }请求不带allowRoleChange: true时抛 405 错误,前端据此弹出角色变更确认框;确认后则调用changeEndUserToEditor把这些 end-user 批量升级为 builder。为什么不让系统静默升级?因为某个人从查看者变成构建者是权限事实的重大变化,必须由操作者显式拍板。落地:搭一个「财务团队」组 官方概念文档 permissions.md 给的标准场景就是:建一个叫 Finance Team 的自定义组,只授予财务应用与工作空间常量的权限,之后邀请新用户直接入组。要走通这条路,先分清两种组类型(枚举定义在 constants/index.ts):维度default 组custom 组来源系统内置 Admin / Builder / End-userAdmin 自建,任意命名(如 Finance Team)组级权限位系统固化的默认值独立配置,可含构建级权限细粒度授权Admin 组不可改,其余按默认完全可配(选资源、选动作、选环境)典型用途角色基线多团队隔离,如财务、支持、工程按建组 → 配权限 → 加成员的动线走一遍,每一步在后端都有明确留痕(实现见 service.ts)。建组:create委托工具服务落库后,通过RequestContext写入GROUP_PERMISSION_CREATE审计日志;复制组(duplicateGroup)还支持addPermission/addUsers/addApps三个可选项,一次把权限位、成员、应用级授权全搬过去。配权限:细粒度授权的 REST 入口在 granular-permissions.controller.ts,按type分派到 app / />如果目标应用根本不需要登录,还有个更轻的替代方案:直接把应用设为公开(public),未登录用户也能访问,连组都不用建。适合对外展示类页面,不适合含敏感数据的内部工具。日常维护:改角色与查权限的入口最日常的权限操作是换组,它一次性改变用户的全部权限。入口在工作台:点击左下角设置图标(⚙️)→ 进入Workspace settings Users→ 找到目标用户,点行尾 kebab(⋮)菜单 → 选Edit user details,在右侧面板的 User groups 下拉中更新其所属组 → 点Update,阅读弹窗警告后点Continue确认。想深入时,官方文档有三个入口值得按序读:user-roles.md 讲三个默认角色的完整对照与改角色步骤,custom-groups.md 讲自定义组的创建与授权,access-control.md 给出访问控制全景。源码侧的对应关系也已交代清楚:组 CRUD 走 controller.ts,资源级授权走细粒度控制器,规则裁决在工具服务。设计要点整套体系压缩成一句话:组织 → 组(内置三角色 自定义组)→ 组级布尔权限位 细粒度资源授权,permission_groups管资格,granular_permissions及其级联子表管范围,硬边界集中在工具服务里强制执行。给自建部署、多团队协作场景的三条可执行建议:财务类应用只授权 Finance 组,且不要用isAll:细粒度授权设isAllfalse,在GroupApps里精确勾选财务应用清单,新增应用时默认对所有组不可见,想开放再手动加——默认拒绝比默认全量再回收安全得多。把构建者不碰生产当作基线而非例外:Builder 默认授权的canAccessProduction就是false,不要为了方便给 Builder 组批量开生产环境;确需生产访问时,给个人所在的自定义组单独授权,而不是动 Builder 默认组。配多环境前先确认许可证条款:给任何组配 Dev/Staging/Production 访问前,先确认组织持有MULTI_ENVIRONMENT条款,否则含 end-user 的组会被校验直接拦下,返工成本更高。【免费下载链接】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),仅供参考