Skip to Content

IaC 權限授予

grant 把表格權限當成 IaC resource 管理。它用 principal 的穩定種類與 ID 表達「誰」,以 spec 表達「能做什麼」,並以 table/column refs 表達「在哪張表、哪些欄位與資料列」。撤銷同樣是可審查的 state: "absent",不是把整行偷偷移除。

Grant Line 心智模型

{"kind":"grant","table":"orders","principal":{"type":"department","id":"support"},"spec":{"can_read":"filtered","can_insert":false,"can_edit":"none","visible_columns":["order_no","status"],"read_filter":{"and":[{"column":"status","op":"eq","value":"open"}]}}}

table 是本文件中的 table ref。Grant 不另有作者指定的 ref;principal identity 會形成穩定 grant identity。state 預設為 presentspec 在 Pydantic model 中可選,absent 不得帶 spec。新 grant 若省略 spec,寫入預設是 can_read: "none"can_insert: falsecan_edit: "none";既有 grant 的未宣告欄位保留 live 值。實務上應明寫安全意圖,避免讀者猜測預設。

Principal 種類

principal.typeprincipal.idaudience 規則
user同公司 user 的穩定 ID禁止提供
departmentdepartment ID禁止提供
client由同公司鏈結可解析的 client ID禁止提供
chatroomchatroom ID;table 必須具備可用的 department scope必填,只能是 internalexternal

只有 chatroom principal 可以帶 audience;「audience iff chatroom」是結構契約,不是稍後的授權建議。

principal.id:原始 ID 或可攜 token

principal.id 現在是 1 到 160 個字元,且符合:

^([a-z0-9][a-z0-9_.-]{0,35}|\$(user|dept|smc|room):[^\r\n]{1,150})$

這是兩個並列選項,不是把單一 pattern 放寬:

  • 原始 ID 仍遵守 token 出現前的契約——最多 36 個字元、不含冒號與空白,這樣衍生的 {table}.grant:{type}:{id} ref 才會合法。原始 ID 綁定環境。
  • 可攜身分 token 以公司內唯一的自然鍵指名 principal。它的主體是 1 到 150 個字元的自由文字,因此含空白與冒號的部門、聊天室名稱都寫得出來。

前綴必須與 type 相符:user$user:department$dept:client$smc:chatroom$room:。前綴交錯是 per-line 錯誤,而不是去解析成另一種 principal。

{"kind":"grant","table":"orders","principal":{"type":"department","id":"$dept:Front Desk"},"spec":{"can_read":"all","can_insert":false,"can_edit":"none"}}

解析以公司為錨點並 fail-closed:找不到、有歧義、已軟刪除、跨租戶都會讓該行失敗。它發生在衍生 grant:{type}:{id} ref 建立之前,因此指向同一 principal 的 token 文件與原始 ID 文件會落在同一列 state——把文件從 ID 改寫成 token 會收斂為 noop,而不是新增一筆重複 grant。

解析後的 token 綁定會折入 plan_hash。有釘住 hash 時,審查與 apply 之間的改名會得到 409 plan_stale;沒有釘住時,apply 會綁到當下持有該名稱的人。完整語法與錯誤目錄請見可攜身分 token

精確 Spec 欄位

Grant spec 只接受以下欄位:

欄位型別與值意義
can_read"none""own""all""filtered"null資料列讀取範圍;明確 null 會正規化為 none
can_insertboolean 或 null是否可新增;明確 null 會正規化為 false
can_edit"none""own""all""filtered"null資料列編輯範圍;明確 null 會正規化為 none
visible_columnscolumn refs 陣列或 null可見欄位;每個 token 會翻成 live internal key
read_filterrow policy object 或 nullcan_read: "filtered" 時必填
edit_filterrow policy object 或 nullcan_edit: "filtered" 時必填
scope_values字串陣列或 nullchannel/chatroom scope governance;明確 null 表示 deny-all scope

省略欄位與明確 null 不同。省略代表本次宣告不接管該 live grant 欄位;明確 null 依上表正規化或清除。

Filtered Grant 與 Row Policy

filtered 必須與對應 filter 成對;非 filtered 模式禁止保留該 filter:

can_read "filtered" requires a read_filter read_filter is only valid when can_read is "filtered" can_edit "filtered" requires an edit_filter edit_filter is only valid when can_edit is "filtered"

Row policy 的根可以是任一種布林節點{"and": [node, ...]}{"or": [node, ...]}{"not": node}、predicate leaf,或 link_target leaf。舊的 {"and": [predicates]} 只是其中一棵合法的樹,行為完全不變。群組不可為空;深度上限 5、每條 policy 最多 24 個 predicate、最多 6 個 link_target leaf。

{"or":[{"column":"status","op":"eq","value":"open"},{"and":[{"column":"region","op":"in","value":["tw","jp"]},{"not":{"column":"status","op":"eq","value":"closed"}}]}]}

Predicate 中的 column 使用同一 table 的 column ref。Policy 只應指向可持久化的 scalar columns;computed 或不支援的欄位型別會在伺服器 plan 驗證時被拒絕。Plan 會把 refs 解析成 live internal keys,並對 unresolved token 失敗關閉。

link_target leaf 帶著兩個 ref 命名空間的參照,executor、differ 與 export 都遵守這個區分:

{"kind":"grant","table":"deals","principal":{"type":"department","id":"$dept:Sales"},"spec":{"can_read":"filtered","can_insert":false,"can_edit":"none","read_filter":{"link":"assignee","quantifier":"any","target":{"column":"ts_account","op":"eq","value":"$me"}}}}
  • link本行 table 上的 column ref,且必須指向 link 型別欄位。
  • target 內每個 column 都是被連結那張表的 ref,透過該 link 的 target table 解析——絕不會對到近端表。
  • Export 會把兩者都反解。當被連結的表不在匯出選取範圍內(已刪除或跨公司)時,該節點會降級而不是讓整份匯出失敗:link 仍然反解,target 子樹則原樣輸出被連結表的 internal col_<hex> key,被連結的 table id 會列在 X-IaC-Dangling-Link-Targets response header。重新套用一份匯出之前,先讀那個 header。

Predicate 值中的 row-policy token

read_filteredit_filter 的 predicate 還接受完整的 ACL row-policy token 詞彙;若把 filter 只當成單純的 column/op/value 條件來讀,很容易漏掉這件事:

Token解析為
$me操作主體自己的 ID——在 principal 欄位上,改為它所代表的 tagged cell SET
$me.department操作主體的部門 ID——在 principal 欄位上,改為該部門成員與房間的 SET
$today以 Asia/Taipei 為日界的今天
$today+Nd$today-Nd今天之後或之前 N 天,N 為 1 到 730
$now目前的 Asia/Taipei 分鐘
{"and":[{"column":"owner","op":"eq","value":"$me"},{"column":"due_on","op":"lte","value":"$today+7d"}]}

link_targettarget 內的 token 是對被連結那張表的欄位型別配對,不是本表的。這套語法與 command DSL 互不相干:grant filter 永遠不接受 $input$ctx$row。它也比 public-read 的語法更寬——後者去掉了 $me$me.department,因為公開 token 沒有操作主體。跨介面複製 filter 之前,請先看 public_read

欄位可見性

visible_columns 也是 refs,不是 display names 或 internal keys。例如 "order_no" 必須在 orders table 中解析為一個 column line/state ref。未知 ref 會得到:

unresolved visible_columns ref 'order_no': unknown column ref 'orders.order_no'

同一規則適用於 read_filteredit_filter 內的 column token。不要用看似相同的顯示名稱取代 ref。

Chatroom Grant 與 scope_values

Chatroom principal 必須選定 audience: "internal""external",讓相同 chatroom identity 的內外受眾不混在一起。伺服器還會確認 table 的 department scope 與 chatroom/company 關係。

scope_values 用於 channel-aware 的範圍限制。省略時保留既有治理設定;字串陣列明確列出允許 scopes;null 是 deny-all,不是「全部允許」。把 audience、row policy 與 scope values 一起納入 plan review。

安全撤銷

以相同 tableprincipal 宣告 absent,不帶 spec:

{"kind":"grant","table":"orders","principal":{"type":"department","id":"support"},"state":"absent"}

Plan 應顯示 delete,讓審查者確認被撤銷的 principal。若只是從文件刪掉 grant line,IaC 不會把省略推導為 revoke。

動手試試

IaC 工作台建立一個 department filtered grant,故意移除 read_filter 觀察本機錯誤,再修正並對測試 scope 執行唯讀 plan。完整 grant endpoint 契約請參考權限 API

Last updated on