ld-permissions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePermissions & Authorization Guide
权限与授权指南
This skill helps you work with Lightdash's CASL-based permissions system, including scopes, custom roles, and authorization enforcement.
本指南可帮助你使用Lightdash基于CASL的权限系统,包括作用域(scopes)、自定义角色和授权规则实施。
What do you need help with?
你需要哪方面的帮助?
- Add a new scope/permission - Step-by-step guide to add a new permission
- Debug a permission issue - Troubleshoot why a user can't access something
- Understand the permission flow - Learn how permissions work end-to-end
- Work with custom roles - Create or modify custom roles with specific scopes
- 添加新作用域/权限 - 添加新权限的分步指南
- 调试权限问题 - 排查用户无法访问资源的原因
- 理解权限流转逻辑 - 了解权限的端到端工作机制
- 使用自定义角色 - 创建或修改带有特定作用域的自定义角色
Quick Reference
快速参考
Key Files
关键文件
| Purpose | Location |
|---|---|
| Scope definitions | |
| CASL types | |
| Ability builder (system role vs custom role path) | |
| System role abilities (project level) | |
| System role abilities (org level) | |
| Service account abilities (enterprise, CI/CD) | |
| Role-to-scope mapping | |
| Scope-to-CASL conversion | |
| 用途 | 文件路径 |
|---|---|
| 作用域定义 | |
| CASL类型定义 | |
| 权限能力构建器(系统角色与自定义角色路径) | |
| 系统角色权限能力(项目级) | |
| 系统角色权限能力(组织级) | |
| 服务账号权限能力(企业版、CI/CD) | |
| 角色与作用域映射 | |
| 作用域转CASL规则 | |
Common Patterns
常见模式
Backend permission check (services take and build the ability via — raw is legacy, see ):
account: RegisteredAccountthis.createAuditedAbility(account)user.abilitydocs/account-patterns.mdtypescript
import { subject } from '@casl/ability';
import { ForbiddenError } from '@lightdash/common';
const ability = this.createAuditedAbility(account);
if (ability.cannot('manage', subject('Dashboard', { organizationUuid, projectUuid }))) {
throw new ForbiddenError('You do not have permission');
}后端权限校验(服务接收并通过构建权限能力——原生为旧版实现,请查看):
account: RegisteredAccountthis.createAuditedAbility(account)user.abilitydocs/account-patterns.mdtypescript
import { subject } from '@casl/ability';
import { ForbiddenError } from '@lightdash/common';
const ability = this.createAuditedAbility(account);
if (ability.cannot('manage', subject('Dashboard', { organizationUuid, projectUuid }))) {
throw new ForbiddenError('You do not have permission');
}CASL Subject Scoping: Resource, Not Actor
CASL主体作用域:针对资源,而非操作者
CASL actor is passed before the check:
typescript
getUserAbilityBuilder({
user: lightdashUser, // actor
projectProfiles,
permissionsConfig,
});
const ability = this.createAuditedAbility(accountOrUser); // actorsubject(...)typescript
ability.can(
'manage',
subject('X', {
organizationUuid: target.organizationUuid,
projectUuid: target.projectUuid,
}),
);Never fill from actor fields like . Org-level grants may only check , so actor-sourced subject fields can become cross-org access on multi-org instances. Single-org dev hides it.
subject(...)user.organizationUuidorganizationUuidFrontend permission check:
typescript
const { user } = useApp();
if (user.data?.ability.can('manage', 'Dashboard')) {
return <EditButton />;
}or wrap in a CASL component:
tsx
import { Can } from '../../providers/Ability';
<Can I="manage" a="Dashboard">
<EditButton />
</Can>CASL操作者需在校验前传入:
typescript
getUserAbilityBuilder({
user: lightdashUser, // 操作者
projectProfiles,
permissionsConfig,
});
const ability = this.createAuditedAbility(accountOrUser); // 操作者subject(...)typescript
ability.can(
'manage',
subject('X', {
organizationUuid: target.organizationUuid,
projectUuid: target.projectUuid,
}),
);切勿从操作者字段(如)填充。组织级权限可能仅校验,因此来源于操作者的主体字段会在多组织实例中导致跨组织访问问题。单组织开发环境会隐藏此问题。
user.organizationUuidsubject(...)organizationUuid前端权限校验:
typescript
const { user } = useApp();
if (user.data?.ability.can('manage', 'Dashboard')) {
return <EditButton />;
}或用CASL组件包裹:
tsx
import { Can } from '../../providers/Ability';
<Can I="manage" a="Dashboard">
<EditButton />
</Can>Full Documentation
完整文档
For comprehensive documentation, read:
.context/PERMISSIONS.mdThis includes:
- Architecture diagram showing the complete permission flow
- All scope groups and modifiers (@self, @public, @space, etc.)
- Database schema for custom roles
- Step-by-step guide to add new scopes
- Troubleshooting guide
如需全面文档,请阅读:
.context/PERMISSIONS.md其中包括:
- 展示完整权限流转的架构图
- 所有作用域组及修饰符(@self、@public、@space等)
- 自定义角色的数据库 schema
- 添加新作用域的分步指南
- 故障排查指南
Adding a New Scope (Quick Guide)
添加新作用域(快速指南)
You must update ALL the relevant ability layers:
-
Add subject (if new) toin
CaslSubjectNamespackages/common/src/authorization/types.ts -
Define scope in:
packages/common/src/authorization/scopes.ts
typescript
{
name: 'manage:NewFeature',
description: 'Description for custom role UI',
isEnterprise: false,
group: ScopeGroup.PROJECT_MANAGEMENT,
getConditions: (context) => [addUuidCondition(context)],
}-
Update project-level abilities in— add to the appropriate system role function (e.g.,
packages/common/src/authorization/projectMemberAbility.ts,developer)admin -
Update org-level abilities inif needed — note: org-level abilities are additive and cannot be restricted by project-level custom roles
packages/common/src/authorization/organizationMemberAbility.ts -
Add to system role inin
BASE_ROLE_SCOPES(must stay in sync withpackages/common/src/authorization/roleToScopeMapping.ts— the parity testprojectMemberAbility.tsenforces this)roleToScopeParity.test.ts -
Update service accounts in— add to
packages/common/src/authorization/serviceAccountAbility.ts(or other service account scopes) if service accounts need this permission. Forgetting this breaks CI/CD pipelines.ORG_ADMIN -
Enforce in service via+
this.createAuditedAbility(account)— never rawability.cannot()(legacy pattern, seeuser.ability)docs/account-patterns.md -
Add frontend check with→
useApp()user.data?.ability.can()
你必须更新所有相关的权限能力层:
-
添加主体类型(若为新类型)到中的
packages/common/src/authorization/types.tsCaslSubjectNames -
定义作用域在中:
packages/common/src/authorization/scopes.ts
typescript
{
name: 'manage:NewFeature',
description: 'Description for custom role UI',
isEnterprise: false,
group: ScopeGroup.PROJECT_MANAGEMENT,
getConditions: (context) => [addUuidCondition(context)],
}-
更新项目级权限能力在中——添加到对应的系统角色函数(如
packages/common/src/authorization/projectMemberAbility.ts、developer)admin -
更新组织级权限能力在中(若需要)——注意:组织级权限能力是累加的,无法被项目级自定义角色限制
packages/common/src/authorization/organizationMemberAbility.ts -
添加到系统角色在的
packages/common/src/authorization/roleToScopeMapping.ts中(必须与BASE_ROLE_SCOPES保持同步——projectMemberAbility.ts一致性测试会强制执行此规则)roleToScopeParity.test.ts -
更新服务账号权限在中——若服务账号需要此权限,添加到
packages/common/src/authorization/serviceAccountAbility.ts(或其他服务账号作用域)。遗漏此步骤会导致CI/CD流水线故障。ORG_ADMIN -
在服务中实施校验通过+
this.createAuditedAbility(account)——切勿使用原生ability.cannot()(旧版模式,请查看user.ability)docs/account-patterns.md -
添加前端校验使用→
useApp()user.data?.ability.can()
Changing the Scope Vocabulary (Migrating Custom Roles)
修改作用域术语(自定义角色迁移)
Custom roles persist scope names as strings in the table (, , ). They are decoupled from system roles and do not auto-update when the scope vocabulary changes. Any rename / split / merge / removal must include a Knex migration that reconciles existing rows, otherwise self-hosted instances silently lose or retain permissions.
scoped_rolesrole_uuidscope_namegranted_byBefore merging a scope change, evaluate the impact and write a migration:
| Change | Impact on | Required migration |
|---|---|---|
Rename a scope (e.g. | Old rows reference a name that no longer exists in | |
Split one scope into two (e.g. | Roles with the original scope lose access to whichever capability moved to the new scope. | Backfill the new scope for every role that has the original ( |
| Merge two scopes into one | Roles with only one of the merged scopes may gain or lose capability. | Insert the merged scope where either source exists; then delete the old rows. |
| Remove a scope | Rows reference a non-existent scope name. | Delete the orphaned rows. See |
| Tighten conditions on an existing scope | No row change needed, but the behavioral change is invisible to operators. | None on the table; note in PR description. |
| Add a brand-new scope | No existing rows are affected. Only system roles in | None for custom roles. |
Migration conventions (see for general safe-migration rules):
packages/backend/src/database/CLAUDE.md- Wrap the body in and log a recoverable manual-fix command on failure. These backfills are best-effort cleanup — failing them should never block subsequent migrations.
try/catch - Use for inserts since
ON CONFLICT DO NOTHINGis the natural unique key.(role_uuid, scope_name) - Preserve from the source row when copying a scope, so audit history points back at the original grantor rather than
granted_by.NULL - Provide a sensible — usually deleting the rows the
down()inserted. If the change is irreversible (legacy cleanup), document whyup()is a no-op.down()
Checklist when changing the scope vocabulary:
- Determine which change type applies (rename / split / merge / remove / add / tighten).
- If a migration is required, create it with and follow the patterns above.
pnpm -F backend create-migration <name> - Update so system roles reflect the new vocabulary, and run the parity test.
roleToScopeMapping.ts - Call this out in the PR description so reviewers can verify the data migration matches the code change.
自定义角色的作用域名称以字符串形式存储在表中(字段包括、、)。它们与系统角色解耦,当作用域术语变更时不会自动更新。任何重命名/拆分/合并/删除操作都必须包含Knex迁移脚本以协调现有数据行,否则自托管实例会静默丢失或保留权限。
scoped_rolesrole_uuidscope_namegranted_by合并作用域变更前,请评估影响并编写迁移脚本:
| 变更类型 | 对 | 所需迁移操作 |
|---|---|---|
重命名作用域(例如 | 旧数据行引用的名称已不在 | |
拆分一个作用域为两个(例如 | 拥有原作用域的角色会丢失移至新作用域的权限能力。 | 为所有拥有原作用域的角色回填新作用域( |
| 合并两个作用域为一个 | 仅拥有其中一个原作用域的角色可能会获得或丢失权限能力。 | 在任一原作用域存在的位置插入合并后的作用域;然后删除旧数据行。 |
| 删除作用域 | 数据行引用不存在的作用域名称。 | 删除孤立的数据行。示例请见 |
| 收紧现有作用域的条件 | 无需修改数据行,但行为变更对运维人员不可见。 | 无需修改表;在PR描述中注明。 |
| 添加全新作用域 | 现有数据行不受影响。仅需更新 | 无需针对自定义角色进行迁移。 |
迁移约定(查看获取通用安全迁移规则):
packages/backend/src/database/CLAUDE.md- 将脚本主体包裹在中,失败时记录可手动修复的命令。这些回填操作是尽力而为的清理——失败不应阻止后续迁移。
try/catch - 插入时使用,因为
ON CONFLICT DO NOTHING是天然唯一键。(role_uuid, scope_name) - 复制作用域时保留源数据行的,以便审计历史指向原始授权者而非
granted_by。NULL - 提供合理的方法——通常是删除
down()插入的数据行。若变更不可逆(遗留清理),请说明up()为空操作的原因。down()
修改作用域术语时的检查清单:
- 确定适用的变更类型(重命名/拆分/合并/删除/添加/收紧)。
- 若需要迁移,使用创建迁移脚本并遵循上述模式。
pnpm -F backend create-migration <name> - 更新使系统角色反映新术语,并运行一致性测试。
roleToScopeMapping.ts - 在PR描述中注明此变更,以便评审人员验证数据迁移与代码变更是否匹配。
Debugging Permission Issues
调试权限问题
When a user gets "ForbiddenError":
- Check scope exists - Is the scope defined in ?
scopes.ts - Check role assignment - Does the user's role include this scope?
- Check conditions - Do the CASL conditions match the resource?
- Check enterprise flag - Is but deployment isn't enterprise?
isEnterprise: true - Check subject name - Case-sensitive match in ?
CaslSubjectNames
Use grep to find where the permission is checked:
bash
grep -r "ability.cannot.*'manage'.*'YourSubject'" packages/backend/src/services/Please describe what you're trying to accomplish, or ask me to explain any aspect of the permissions system.
当用户遇到"ForbiddenError"时:
- 检查作用域是否存在 - 该作用域是否在中定义?
scopes.ts - 检查角色分配 - 用户的角色是否包含此作用域?
- 检查条件匹配 - CASL条件是否与资源匹配?
- 检查企业版标记 - 是否设置了但部署的并非企业版?
isEnterprise: true - 检查主体名称 - 是否与中的名称大小写匹配?
CaslSubjectNames
使用grep查找权限校验的位置:
bash
grep -r "ability.cannot.*'manage'.*'YourSubject'" packages/backend/src/services/请描述你想要实现的目标,或让我解释权限系统的任何方面。