Skip to content

共享规则 hook 对谓词式(multi)写入不重算:if (!id) returnsys_record_share 授权在批量更新后变陈旧 #4779

Description

@os-zhuang

#4757 的「同形状排查」中发现。未认领,归属 plugin-sharing,不在 #4757 的 PR 范围内。

现象

packages/plugins/plugin-sharing/src/rule-hooks.tsbindRuleHooks 为每个有共享规则的对象绑定 afterInsert / afterUpdate,处理函数第一步就靠单条记录 id 定位要重算的行:

const data = ctx?.result ?? ctx?.input?.data ?? {};
const id = String((data as any)?.id ?? ctx?.input?.id ?? '');
if (!id) return;
await service.evaluateAllForRecord(objectName, id, SYSTEM_CTX as any);

packages/objectql/src/engine.tsupdate() 只在 where.id 是标量时提取 input.id;谓词式(multi: true)更新走 updateMany,input.id 为 undefined,input.data 里通常也没有 id,ctx.result 是受影响行数而非行本身。于是批量更新的每一行都不会重算共享规则

后果与 #4757 同向 —— 是授权侧的 fail open,只是路径更隐蔽:

  • 一条基于 criteria 的共享规则给记录发过 sys_record_share 行;
  • 管理员用 multi: true 批量把这些记录改成不再匹配该规则的状态(改 owner、改 stage、改 region…);
  • 重算没有发生,sys_record_share 行原样留在表里 → 本应失去访问权的用户继续能读/能改这些记录。

反向也一样(批量改成匹配规则却没发出共享),但那一侧是「少给权限」,危害小得多。

复现方向

对某对象定义一条 criteria 共享规则,插入命中的记录(hook 正常发共享),然后:

await ql.update(obj, { where: { region: 'east' }, multi: true, data: { region: 'west' }, context: adminCtx });

再查 sys_record_share:原来的共享行仍在,evaluateAllForRecord 一次都没被调用。

建议修法

把「单条 id」的入口扩成「本次写入匹配的行集合」:在 beforeUpdate 阶段用谓词(带上界,超限告警或失败关闭)解析出受影响的 id 列表并暂存到 hook ctx(primary-bu-projection.tsSTASH_KEY 就是这个模式),afterUpdate 再逐行 evaluateAllForRecord。上界参考 attachment / comment 守卫的 1000 行。

需要维护者裁定的一点:超过上界时该怎么办 —— 是拒绝这次批量写入(fail closed,与 #4757 的姿势一致),还是放行但记一条显式告警并标记重算待办(共享规则重算本身是异步可补偿的)。这决定了它算「守卫」还是「投影」,建议在动手前先定。

相关

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions