外观
ACL 与继承模式篇
目标:让集成方设置 / 查询 ACL、读懂权限位与预设角色、掌握容器→文档继承(断继承 / 重继承 / 位或聚合)、用
acl/check解析有效权限、用 AclPolicy 做规模化授权与 dry-run 预览。不在本篇范围:身份解析与双通道 granteeId 获取(见 认证与身份双通道模式篇);分享(ShareGrant / ShareLink)授权(分享模式篇);错误码完整码表(见错误码参考)。
本篇是领域模型 §ACL的纵深展开。概念页讲「resource + grantee + role 三段式 + 容器→文档继承」的模型,本篇讲取值枚举、继承规则、有效权限解析与坑。落到具体主体身份怎么来,见认证模式篇。
权限位与预设角色
权限是 6 个可组合的位:
| 位 | 值 | 含义 |
|---|---|---|
READ | 1 | 读元数据 / 列表 |
DOWNLOAD | 2 | 下载内容字节 |
WRITE | 4 | 写 / 上传新版本 |
DELETE | 8 | 删除 |
SHARE | 16 | 创建分享 |
MANAGE | 32 | 管理(含改 ACL) |
预设角色是固定的位组合,按掩码精确匹配:
| 角色 | 掩码 | 等价位 |
|---|---|---|
VIEWER | 3 | READ + DOWNLOAD |
EDITOR | 7 | READ + DOWNLOAD + WRITE |
MANAGER | 63 | 全部 6 位 |
⚠️ 角色是精确匹配:只有掩码恰好等于 3 / 7 / 63 才 resolve 出
VIEWER/EDITOR/MANAGER。自定义组合(如5 = READ + WRITE)resolve 出的resolvedRole为null——这是正常的,集成方应按permissionMask的位判权(mask & READ == READ),不要因 role 为 null 误判「无权限」。传入未定义位返 102004。
grantee 三类主体
granteeType 取值 USER / ROLE / DEPARTMENT,传其它值返 102009。granteeId 是 atkonbase 内部稳定 ID(与认证模式篇 §granteeId 解析同一口径)——不是 IdP 侧 unionId / sub,也不是集成方业务系统的原始用户主键。
一、设置 / 查询 / 删除 ACL
设置一条(POST /v1/acl/set)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/set' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantee": { "granteeType": "USER", "granteeId": "${userId}" },
"role": "VIEWER"
}'resource.resourceType取值DOCUMENT/CONTAINER(其它值返 102007)。role与permissionMask二选一,双传时role优先;走预设角色推荐用role,自定义位组合才用permissionMask。
期望响应:
json
{
"code": 0,
"data": {
"aclId": "A1000001",
"resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantee": { "granteeType": "USER", "granteeId": "${userId}" },
"permissionMask": 3,
"resolvedRole": "VIEWER"
}
}查询资源全部条目(GET /v1/acl/getAcl)
bash
curl -X GET 'https://api.atkonbase.example.com/v1/acl/getAcl?resourceType=DOCUMENT&resourceId=${docId}' \
-H 'Authorization: Bearer ${accessToken}'返回该资源上的 ACL 条目数组(每条含 aclId / grantee / permissionMask / resolvedRole)。
删除一条(GET /v1/acl/delete)
bash
curl -X GET 'https://api.atkonbase.example.com/v1/acl/delete?aclId=${aclId}' \
-H 'Authorization: Bearer ${accessToken}'aclId 来自此前 set / getAcl 响应;条目不存在返 102008(集成方按幂等处理,不存在视为已删除)。
批量设置(POST /v1/acl/batch-set)
entries 是一组 set 请求体,同事务生效:
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/batch-set' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"entries": [
{ "resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantee": { "granteeType": "ROLE", "granteeId": "${roleId}" },
"role": "EDITOR" },
{ "resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantee": { "granteeType": "DEPARTMENT", "granteeId": "${deptId}" },
"role": "VIEWER" }
]
}'二、有效权限检查(POST /v1/acl/check)
check 回答「这组主体对这个资源是否有某权限」,已把继承与多态主体合并解析好:
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/check' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantees": [
{ "granteeType": "USER", "granteeId": "${userId}" },
{ "granteeType": "ROLE", "granteeId": "${roleId}" },
{ "granteeType": "DEPARTMENT", "granteeId": "${deptId}" }
],
"permission": "WRITE"
}'期望响应:
json
{
"code": 0,
"data": {
"allowed": true,
"effectivePermissionMask": 7,
"effectiveDenyMask": 0,
"effectiveRole": "EDITOR"
}
}grantees是多态主体集(一个用户的userId+ 其角色 + 其部门):任一主体命中即可通过,命中行的权限位按「或」聚合。permission(单个PermissionBit)与permissionMask(位组合)二选一,双传时permission优先;判定逻辑是effectivePermissionMask & requiredMask == requiredMask。effectiveDenyMask—— 被grantType=DENY的 Policy 显式拒绝的位(见下「AclPolicy」);无 DENY 命中时为 0。effectiveRole—— 基于effectivePermissionMask精确匹配的角色,自定义组合时为null。- ⚠️ 用户被禁用时会从主体集中过滤掉,其权限按 0 解析;委派链路解析不出主体集返 102003。
批量掩码查询(POST /v1/acl/batchCheck)
一次拿回多个同类型资源的有效掩码(单次 ≤ 200 个):
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/batchCheck' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"resourceType": "DOCUMENT",
"resourceIds": ["${docId1}", "${docId2}"]
}'响应 data.permissions 是 资源ID → 有效掩码 的映射:
json
{ "code": 0, "data": { "permissions": { "${docId1}": 7, "${docId2}": 0 } } }三、容器→文档继承
- 传播源:容器上非 Policy 派生的显式 ACL 条目。在容器授一次权,沿容器树向下传播到后代容器 + 文档。
- 断继承:容器的
aclInheritEnabled=false切断该容器整棵子树的继承(子树不再接收上层传播)。 - 新建 vs 重继承:
- 新建资源时继承父容器的有效 ACL;若落在断继承子树内,则不继承、改为给创建者授全部位(保证创建者能操作自己新建的资源)。
- 对已有资源重继承(如父条目变更触发重算)时,不会自动给操作者授权——只重算继承链,不附加操作者权限。
- 位或聚合、无覆盖:继承与多条授予都按位「或」聚合,没有「覆盖 / 降权」概念——加授权只会叠加、不会相互削减。要降权只能:① 改父容器的条目;② 断继承;③ 用
grantType=DENY的 Policy 显式拒绝(反映在check的effectiveDenyMask)。 - ⚠️ 传播树最大深度 100:继承沿容器层级最多传播 100 层;超过该深度的传播将被截断,组织容器层级时避免病态深度。
四、AclPolicy:规模化自动授权
逐条 acl/set 适合点对点授权;当「某类资源对某批主体统一授权」且资源数量大或持续新增时,用 AclPolicy 声明规则、由系统自动派生 ACL 条目(派生条目带 Policy 来源标记,与手动条目区分)。
创建 Policy(POST /v1/acl/policy/save)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/policy/save' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"name": "合同文档对法务只读",
"matchResourceType": "DOCUMENT",
"matchContainerTypeIds": ["${contractContainerTypeId}"],
"matchConditions": [
{ "key": "status", "operator": "EQ", "value": "signed" }
],
"grantees": [{ "granteeType": "ROLE", "granteeId": "${legalRoleId}" }],
"grantType": "ALLOW",
"grantRole": "VIEWER",
"enabled": true
}'字段说明:
matchResourceType——CONTAINER/DOCUMENT。matchContainerTypeIds—— 按容器类型过滤。⚠️ 仅对文档有效(容器自身无 type 维度);matchResourceType=CONTAINER时此过滤不生效,别指望它能筛容器。matchConditions—— 元数据条件(key+operator+value三元组列表)。⚠️ 空 / 不传 = 全匹配(匹配该资源类型下所有资源),不是「匹配 0 个」——批量授权前务必确认条件,否则可能误授全量。grantType——ALLOW/DENY(其它值返 102011)。grantRole/grantPermissionMask—— 二选一,授予的角色或位组合。enabled—— 是否启用(默认 true)。
启用 / 修改 Policy(含 /v1/acl/policy/enable · /disable · /update)会触发重评估,重新派生匹配资源的 ACL 条目。
dry-run 预览(强烈建议授权前先跑)
Policy 影响面可能很大,落库前用 dry-run 预览。两个端点:
新建预览(POST /v1/acl/policy/dry-run)——传一份 Policy 条件,看会影响多少资源:
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/policy/dry-run' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"matchResourceType": "DOCUMENT",
"matchContainerTypeIds": ["${contractContainerTypeId}"],
"matchConditions": [{ "key": "status", "operator": "EQ", "value": "signed" }],
"grantees": [{ "granteeType": "ROLE", "granteeId": "${legalRoleId}" }],
"grantType": "ALLOW"
}'响应(DryRunResultDTO):
json
{
"code": 0,
"data": {
"totalAffectedResources": 1240,
"sampleResources": [ { "resourceType": "DOCUMENT", "resourceId": "D1000001" } ],
"existingManualAclCount": 12,
"grantType": "ALLOW",
"truncated": false
}
}totalAffectedResources—— 命中资源总数;sampleResources—— 样本(最多 20 条);existingManualAclCount—— 同 grantee 已有的手动 ACL 条目数(提示与现有授权的重叠)。
已有 Policy 改条件预览(POST /v1/acl/policy/dry-run/{policyId})——改条件后影响如何变化,返回 DryRunDiffResultDTO:
json
{
"code": 0,
"data": {
"currentAffectedResources": 1240,
"newAffectedResources": 1310,
"toAdd": 80,
"toRemove": 10,
"sampleToAdd": [ { "resourceType": "DOCUMENT", "resourceId": "D1000500" } ],
"sampleToRemove": [ { "resourceType": "DOCUMENT", "resourceId": "D1000003" } ],
"truncated": false
}
}toAdd/toRemove—— 新条件相对当前新增 / 移除匹配的资源数;sampleToAdd/sampleToRemove—— 各自样本(最多 20 条)。- ⚠️
truncated(截断标记):dry-run 有 30 秒超时上限,超大匹配集会在超时时截断——truncated=true时所有统计值是部分值,不可当精确总数用;缩小条件范围或分批评估后再读。
常见坑
- ⚠️ 继承无覆盖:以为在子节点
set一个更小的角色能「降权」覆盖父条目——实际是位或叠加、权限只增不减。规避:降权改父条目 / 断继承 / 用 DENY Policy。 - ⚠️ granteeId 传外部 ID:把 IdP unionId / 业务系统用户主键当
granteeId——授权落到不存在的主体,检查恒不通过。规避:先解析出 atkonbase 内部 ID(见认证模式篇)。 - ⚠️
matchConditions空 = 全匹配:Policy 不传条件,误以为「没条件就不匹配」,结果对该类型全量资源授权。规避:dry-run 先看totalAffectedResources。 - ⚠️
matchContainerTypeIds对容器失效:matchResourceType=CONTAINER时仍指望容器类型过滤生效。规避:该过滤仅对文档有效。 - ⚠️ 预设角色精确匹配 null:自定义掩码(如 5)的
resolvedRole为 null,被误判为「无权限」。规避:按位判权,不依赖 role 非空。 - ⚠️ dry-run 截断当精确值:
truncated=true的统计当成完整结果。规避:读到 truncated 即缩小范围重跑。 - ⚠️ 跨租户隔离:ACL 与 Policy 都在租户内解析,跨租户主体 / 资源不可见。规避:确保主体与资源同租户。
- ⚠️ 用户禁用权限消失:被禁用用户从主体集过滤、权限按 0 算,表现为「昨天还能访问今天 102001」。规避:排查用户启用状态,非 ACL 配置问题。
下一步
- 拿到主体身份的三种形态 → 认证与身份双通道模式篇
- 权限 / Policy 相关错误码 → 权限 102*
- 上传文件后给文档授权的完整链路 → 文件上传链路模式篇