明确谁可以读取和修改一条记录

使用 AI 辅助编写与复核。

本文目录

“只允许用户编辑自己的记录”仍留下了几个未决问题。Reviewer 能否读取他人的记录?Maintainer 能否删除它?成员资格被撤回后,旧浏览器标签页仍显示 Edit 按钮时,应该怎样处理?

在让编程 Agent 选择授权库之前,先给它一张针对明确动作和记录的决策表。表格应说明请求处理器可以披露或修改什么,并与界面显示哪些控件分开。

身份认证确立身份;授权决定该身份是否可以对这个资源执行这项动作。OWASP 建议明确采用默认拒绝,并对每个请求检查权限。知道对象标识符,不代表有权访问它。OWASP 授权速查表

把一个小型场景交代完整

考虑一个名为 Lumen 的虚构项目笔记本。每条记录都属于一个工作区和一个所有者。以下身份、数据与策略均为本示例虚构,不是 CueSpec 的账户角色。

身份标签 在 demo 中的当前成员角色 拥有的记录
Mira Editor R41
Sol Editor R42
Tess Reviewer 两条记录都不拥有
Jae Maintainer 两条记录都不拥有
记录 工作区 所有者 标题 正文
R41 demo Mira Outline parser Try a short example.
R42 demo Sol Review export Check the column order.

范围内恰好有三项动作:read、updateTitle 和 delete。read 返回完整记录。updateTitle 只修改 title,不能改变所有权、成员资格、工作区或正文。delete 从这个虚构笔记本中移除记录。新增动作必须先作出新的策略决定。

服务器从可信的应用上下文确定身份和当前成员资格。调用方提交的 role、owner 或身份标签,不能用于取得这种上下文。这些标签只是为了让示例易读,不是一套身份认证协议。

把每一格都写出来,包括拒绝项

对于成员所在工作区中的一条已有记录,采用以下自选策略:

当前角色 与记录的关系 read updateTitle delete
Editor 自己的记录 允许 允许 拒绝
Editor 他人的记录 拒绝 拒绝 拒绝
Reviewer 自己的记录 允许 拒绝 拒绝
Reviewer 他人的记录 允许 拒绝 拒绝
Maintainer 自己的记录 允许 允许 允许
Maintainer 他人的记录 允许 允许 允许

“自己的记录”是一种关系,不是第四种角色,也不能覆盖 Reviewer 的限制。Tess 和 Jae 目前没有记录,但“自己的记录”这两行定义了日后所有权变化时应有的行为。

应用表格之前,先要求当前成员资格可识别、动作可识别,而且记录属于同一工作区。缺少任何一项条件,都拒绝该动作。Guest 这样的标签、transferOwnership 这样的自造动作,或其他工作区的记录,都不会自动带来权限。

本练习有意给予 Maintainer 较广的权限。真实应用是否需要删除、单独批准或更窄的角色,是产品决定。不要把这张小表默默扩展到计费、导出、附件或管理操作。

把界面和请求决定分开

Mira 打开 R41,输入新标题,并提交以下示意性应用请求:

{
  "action": "updateTitle",
  "recordId": "R41",
  "changes": {"title": "Outline parser examples"}
}

按照 Mira 当前的 Editor 成员角色,处理器允许这项请求。R41.title 变成 Outline parser examples;所有者、工作区和正文仍与上表一致。收到成功结果后,客户端可以显示已确认的标题。

现在从同一份初始数据出发,考虑彼此独立的请求。每行都是手工推导的预期结果,不是发送到服务的实际请求。

案例 界面情况 按当前成员资格评估的请求 要求的结果
A 过时界面向 Tess 显示 Edit 按钮 Tess 修改 R41.title 拒绝;标题保持为 Outline parser;不返回记录正文
B Mira 的界面隐藏 Delete Mira 仍然发送 delete R41 拒绝;R41 仍存在;不返回记录正文
C Mira 知道标识符 R42 Mira 请求 read R42 拒绝;不披露其标题、所有者或正文
D Tess 通过允许的链接打开 R42 Tess 请求 read R42 允许;返回数据表中展示的记录
E Tess 接着尝试修改该标题 Tess 发送 updateTitle R42 拒绝;R42 不变;读取权限没有变成写入权限
F 调用方的成员角色是未识别的 Guest 调用方请求 read R41 拒绝;不返回记录字段
G Jae 请求表中从未定义的动作 Jae 发送 transferOwnership R41 拒绝;不改变所有权,也不返回记录正文
H Mira 额外提交一项所有权变更 updateTitle R41 中包含 owner: Mira 拒绝整个修改;标题和所有权都不改变

案例 H 定义了一条严格的载荷规则:changes 必须恰好只包含 title 一个字段。在本练习中,其值为 1–80 个可打印 ASCII 字符,范围从 U+0020 到 U+007E,而且至少有一个字符不是空格。示例不去除首尾空格,也不做规范化。这个刻意缩小的标题字符范围只是示例策略,不是对国际化文字的建议。不要应用请求中可识别的部分,却忽略额外的未授权修改。

对于被拒绝的案例,本示例选择一条通用应用错误:

{"code":"NOT_ALLOWED","message":"You cannot perform this action."}

错误不携带记录字段。选择 HTTP 状态、决定如何隐藏缺失记录的信息,以及规定审计留存,属于服务契约;权限矩阵不能替这些问题作决定。

说明成员资格撤回后会发生什么

界面可能比当初允许它出现的策略决定存续得更久。在下面的虚构流程中,动作逐个评估;成员资格变化时,没有正在执行的请求。

步骤 评估下一请求时的成员资格 请求 预期结果
1 Mira 是 Editor 读取 R41 允许
2 Mira 是 Editor 更新 R41 的标题 允许
3 Mira 的成员资格被移除 从旧标签页读取 R41 拒绝;不返回新的记录数据
4 成员资格仍然缺失 提交早先准备的编辑 拒绝;没有保存的修改
5 Mira 被明确重新加入为 Reviewer 读取 R41 允许
6 Mira 是 Reviewer,且仍拥有 R41 更新其标题 拒绝;所有权不会恢复 Editor 权限

拒绝请求不会从某人的记忆里抹去此前已显示的文字。这里规定的是下一次服务器响应和效果的边界。对于真实服务,应另外询问:成员资格缓存、并发请求和撤回时机如何满足所选策略;仅靠一张表无法回答这些实现问题。

复制一份授权范围 brief

为虚构的 Lumen 笔记本准备一份授权设计。
不要连接账户、修改真实权限或部署任何内容。

使用本文的身份、两条记录、三项动作和完整六行矩阵。
从可信应用上下文确定身份和当前成员资格;
绝不能根据请求载荷授予角色。

在应用矩阵前,要求成员资格可识别、动作可识别,
且记录位于该成员资格所属工作区,否则默认拒绝。
updateTitle 只能改变 title。changes 中包含其他字段时,
拒绝整个请求。使用规定的 1–80 个可打印 ASCII 字符标题规则。

把 UI 控件当作呈现方式。无论控件可见、隐藏还是已过时,
案例 A-H 都必须得到所列结果。拒绝读取时不返回记录字段;
拒绝修改时不应用任何记录变更。
严格遵循撤回流程,包括重新加入为 Reviewer 的情况。

返回决策表、请求处理器边界和预期响应。
在选择框架前,指出尚未解决的身份认证、缓存、并发、
缺失记录信息披露和审计选择。
把这些选择与已给定的业务策略分开。

复核返回的设计时,沿着一项被禁止的请求追踪到最终结果。它应在披露记录或应用修改之前停止。这种具体行为,比“向未授权用户隐藏按钮”更能给开发者提供明确依据。