Skip to content

澄清:viewAllRecords / modifyAllRecords 在 public_read_write 下失效,是否为开源版有意设计? #3005

Description

@baozhoutao

背景

框架 15.0.0,对象采用默认 OWD public_read_write。

我们观察到(真机,11 用户 × 51 对象):

  • 读跨属主全开——任意已认证用户可读每个对象的每条记录,包括机密行;权限集是否授予 viewAllRecords 没有任何区别。
  • 无法授予跨属主写——即使权限集带 modifyAllRecords: true,行级属主门仍拦非属主的 update/delete。因此在 public_read_write 下,「属主可编辑 + 主管可编辑任意记录」通过 RBAC 无法实现。

问题

这在开源版里是有意设计吗?我们把 #2920 理解为:开源版fail-closed 到 owner-only,而层级 / all-records 提权是企业版能力、不随开源框架发布。若如此,则 viewAllRecords / modifyAllRecords 在 public_read_write 下失效属于有意设计,我们会把「主管看 / 改全部」当作企业版 / admin / Hook 层面的事,而不是 bug。

能否确认?如果确属设计,建议在 sharing 文档里加一行说明(「*AllRecords 为企业版能力;开源版在 public_read_write 下为 owner-only」),可以省掉后来者的同样排查。

如果这些开关本应在开源版生效,我们再另开 bug 附复现。

Activity

  1. self-assigned this
    on Jul 16, 2026
  2. os-zhuang commented on Jul 16, 2026

    @os-zhuang
    Contributor

    已按 15.0.0 的实际执行链逐层核实。结论:两个现象都是有意行为,但成因和你们的推断不同——*AllRecords 不是企业版能力,#2920 的「fail-closed 到 owner-only」指的也不是它。逐条说明:

    1. 读全开、viewAllRecords 无区别 —— 是 OWD 语义本身

    public_read_write 在 sharing 层的含义就是「组织内人人可读写」(plugin-sharing/src/sharing-service.ts effectiveSharingModel → public,不注入任何行级过滤)。viewAllRecords 的作用是把 sharing 轴的深度拉到 org——在 private / public_read 下它会解除属主过滤;在 public_read_write 下基线已经是 org 全开,没有可再放宽的东西,所以「授不授没区别」是这个 OWD 的预期结果,不是开关失效。机密行不应放在 public_read_write 对象上;把 OWD 设为 private(ADR-0090 D1 的 fail-closed 默认)或 public_read,viewAllRecords 立即有意义(实测路径:getEffectiveScope → __readScope: 'org' → buildReadFilter 返回 null,纯开源可用)。

    2. modifyAllRecords 拦不住跨属主写 —— 拦的不是 OWD,是基线 RLS + posture 门

    public_read_write 下 sharing 层对写是放行的(canEdit → public → true)。真正拦住非属主 update/delete 的是平台基线权限集 member_default 自带的两条通配 RLS 策略 owner_only_writes / owner_only_deletes(created_by == current_user.id,适用域 positions: ['org_member'],plugin-security/src/objects/default-permission-sets.ts),经由写前 pre-image 检查强制(#1985 的修复)。每个 membership role 为 member 的用户都自动持有 org_member 岗位,所以它对全体普通成员生效——与 OWD 是什么无关。

    而 modifyAllRecords 之所以解不开它:超级用户位对 RLS 的短路是 posture-gated 的(ADR-0066 D2 修订①原文:"only when the object's posture permits it (platform-global or private)"),即仅限 access: { default: 'private' }、tenancy.enabled: false 或 better-auth 托管对象;普通租户业务对象不满足,computeLayeredRlsFilter(security-plugin.ts)里 Layer 1 不短路。这是有 ADR 记录的设计,动机是共享库多租户下不让超级位静默跨租户(现在 Layer 0 拆出后该动机部分由 ADR-0095 结构性承接,但 15.0.0 的行为就是如此)。

    3. 对 #2920 的理解需要修正

    #2920 留在企业侧的只有层级深度作用域(own_and_reports / unit / unit_and_below),它们依赖 IHierarchyScopeResolver(@objectstack/security-enterprise),开源版对这三个值 fail-closed 到 owner-only(sharing-service.ts resolveOwnerScopeIds)。viewAllRecords / modifyAllRecords 和 org 深度完全是开源功能,由开源 plugin-security 强制执行。

    4. 「属主可编辑 + 主管可改任意记录」在开源版 RBAC 可以实现

    • 方案 A(推荐):给主管岗位绑一条 OR 放宽的 RLS 策略——{ object: '*', operation: 'update', using: 'organization_id == current_user.organization_id', positions: ['supervisor'] }(delete 同理)。同对象 RLS 策略 OR 合并(ADR-0066 ②-3),普通成员保持属主门、主管放宽到全组织。
    • 方案 B:主管的 membership role 设为 admin(→ org_admin 岗位,不在 org_member 适用域内,天然不受属主门约束)——但附带整套 org admin 权限,面较宽。
    • 方案 C:机密对象改用 access: { default: 'private' } posture + 显式授权,此时 modifyAllRecords 按 ADR-0066 设计正常旁路 RLS。

    文档

    按建议补了文档,但没有采用「*AllRecords 为企业版能力」的表述(那不准确):PR #3008 在 sharing 文档(OWD 表下)新增了「View/Modify All 与 OWD 的交互」小节 + supervisor 配方,并给 permissions-matrix.mdx 里那句无条件的 "both bypass ownership and sharing"(正是误导预期的句子)加上了 posture 门限定。

    一句话总结:不用另开 bug;行为符合 ADR-0066/0090/0095 的设计。需要跨属主写请走上面 A/B/C 三条 RBAC 路径,层级(汇报链/BU 子树)提权才是企业版能力。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions