Skip to content

列表行内建【编辑】【删除】没接权限门:只与 apiOperations 求交,而它与用户无关 ⇒ 无写权账号恒可见 #4096

Description

@baozhoutao

现象

对象列表页,每一行右侧「更多操作」溢出菜单里的内建【编辑】【删除】,对没有 allowEdit / allowDelete 的账号照常渲染

点【编辑】能打开字段全部预填的编辑对话框,直到点【更新】才由服务端拒掉。服务端门禁是硬的(见下「服务端是对的」一节),所以这不是越权写入,但:

  • 「删除」这种破坏性入口摆在无权账号面前,误点成本高;
  • 用户被引导走进一个注定失败的表单;
  • 同一个页面里另外两处权限门是对的,所以这更像「做漏了一处」而不是「还没做」。

实测于 @objectstack/*@17.0.0-rc.5,zh-CN Console,一个下游业务应用的自建对象上。

根因:这一处的门用了 apiOperations,而它与用户无关

同一份 GET /api/v1/auth/me/permissions 响应里,同一个对象:

账号 allowCreate allowEdit allowDelete apiOperations
A(对该对象零写权) false false false get,list,create,update,delete,upsert,bulk,aggregate,history,search,import
B(对该对象有 update 权) false true false 完全相同的 11 个 verb
C(只读) false false false 完全相同的 11 个 verb

量化判据:把 A 与 B 的响应逐对象比对,30 个共有对象的 apiOperations 100% 相同(差异 0 个),而 allowEdit 明确不同。

这与 spec 对该字段的定义一致:

Server-resolved effective API operations for this object … Present only when the object tightens exposure via apiMethods

apiOperations对象的 API 暴露面(「这个对象开放了哪些 verb」),不是「当前用户能做哪些操作」。拿它与行动作求交 ⇒ 恒 fail-open

同页三处门的对照(两对一错)

界面位置 用的判据 结果
列表工具栏【新建】/【导入】 can(obj, 'create')allowCreate ✅ 正确隐藏
记录详情页头【编辑】/【删除】 逐记录 update/delete 权限判定 ✅ 正确隐藏
列表行「更多操作」内建【编辑】【删除】 只与 apiOperations 求交 ❌ 恒可见

复现

  1. 任一对象,准备两个账号:X 的权限集给该对象 allowEdit/allowDelete = false(只读),Y 给 allowEdit = true;
  2. 两个账号分别打开该对象的列表页;
  3. 展开任意一行右侧的「更多操作」溢出菜单。

实际:X 与 Y 看到的菜单项完全相同,都含【编辑】【删除】。
期望:X 看不到【编辑】【删除】—— 与同页工具栏【新建】按 allowCreate 隐藏的口径一致。

对照观察:同一个 X 账号,列表工具栏的【新建】【导入】确实是隐藏的,详情页头的【编辑】【删除】确实也是隐藏的。三处判据不统一。

服务端是对的(未越权,纯 UI 门缺失)

动作 账号 HTTP 响应 DB
PATCH /api/v1/data/<obj>/{id} X 403 PERMISSION_DENIED: operation 'update' … not permitted 未改写
DELETE /api/v1/data/<obj>/{id} X 403 PERMISSION_DENIED: operation 'delete' … 记录仍在

下游能不能自己绕过去?——实测:不能

在 rc.5 上做过真实探针(验完已回滚):

userActions: {
  edit:   { visibleWhen: P`os.user.email == "someone@example.test"` },
  delete: { visibleWhen: P`record.status == "disabled"` },
}

结论三条:

  1. 内建行 Edit/Delete 确实吃 userActions.edit/delete.visibleWhen —— 行动作声明 visible: false 仍然渲染该菜单项——可见性门用真值判断(#3492 在行菜单上的未收口部分) #3758 的「声明即生效」门(visibleWhen != null)在 rc.5 里是通的,通道存在;

  2. ✅ 谓词上下文里有 os.user,rc.5 实测作用域为 { current_user, user, ctx.user, os.user, app, data, features };

  3. os.user 里没有任何权限信息。三个不同授权的账号,会话 user 对象逐字相同:

    { "role": "user", "positions": ["user", "org_member"], "isPlatformAdmin": false }

    roles 键不存在;positions 恒为 ["user","org_member"](该项目的权限集直绑用户,不走挂岗)。

⇒ CEL 表达不出「当前用户对本对象有无写权」。唯一凑得出来的写法是把账号或角色名硬编码进对象元数据,等于把权限模型抄第二份、必然漂移 —— 不是可接受的解。

期望的修法

列表行内建【编辑】【删除】的可见性,改用与工具栏同一份有效权限判据(allowEdit / allowDelete,以及既有的逐记录权限钩子),而不是 apiOperations

apiOperations 作为「对象暴露面」仍可继续参与判定(暴露面没开 delete 时当然也不该显示),但它不能单独充当用户权限门

相关

若认为 os.user 该带上权限/角色信息(那样下游至少有自救通道),可另开一条;本条只针对「内建行动作没接权限门」。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions