明确谁可以读取和修改一条记录
本文目录
“只允许用户编辑自己的记录”仍留下了几个未决问题。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 的情况。
返回决策表、请求处理器边界和预期响应。
在选择框架前,指出尚未解决的身份认证、缓存、并发、
缺失记录信息披露和审计选择。
把这些选择与已给定的业务策略分开。
复核返回的设计时,沿着一项被禁止的请求追踪到最终结果。它应在披露记录或应用修改之前停止。这种具体行为,比“向未授权用户隐藏按钮”更能给开发者提供明确依据。