PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

CREATE POLICY

CREATE POLICY — 为一个表定义新的行级安全策略

Synopsis

CREATE POLICY name ON table_name
    [ AS { PERMISSIVE | RESTRICTIVE } ]
    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
    [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( using_expression ) ]
    [ WITH CHECK ( check_expression ) ]

描述

CREATE POLICY 命令为一个表定义一条新的行级安全策略。注意,必须在该表上启用行级安全(使用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY),已创建的策略才能被应用。

策略授予对匹配相关策略表达式的行进行选择、插入、更新或删除的权限。现有的表行会根据 USING 中指定的表达式进行检查,而将通过 INSERT 或 UPDATE 创建的新行则会根据 WITH CHECK 中指定的表达式进行检查。当 USING 表达式对某个给定的行返回真时,该行对用户可见;如果返回假或空,则该行不可见。通常,当行不可见时不会发生错误,但例外情况请参见 Table 297。当 WITH CHECK 表达式对某一行返回真时,该行会被插入或更新;如果返回假或空,则会发生错误。

对于 INSERT、UPDATE 和 MERGE 语句,WITH CHECK 表达式会在 BEFORE 触发器被引发之后、任何实际数据修改发生之前被强制执行。因此,BEFORE ROW 触发器可能会修改要插入的数据,从而影响安全策略检查的结果。WITH CHECK 表达式会在任何其他约束之前被强制执行。

策略名称是针对每个表的。因此,同一个策略名称可以用于许多不同的表,并且对每个表都有一个适合该表的定义。

策略可以应用于特定的命令或特定的角色。除非另有说明,新创建的策略的默认行为是应用于所有命令和所有角色。多条策略可以应用于单个命令;更多细节请参见下文。Table 297 总结了不同类型的策略如何应用于特定的命令。

对于可以同时拥有 USING 和 WITH CHECK 表达式的策略(ALL 和 UPDATE),如果没有定义 WITH CHECK 表达式,那么 USING 表达式将既用于确定哪些行可见(普通的 USING 情形),也用于确定允许添加哪些新行(WITH CHECK 情形)。

如果一个表启用了行级安全,但不存在任何适用的策略,则会假定存在一条 “默认拒绝” 策略,这样任何行都不可见也不可更新。

参数

name

要创建的策略的名称。它必须与该表上任何其他策略的名称不同。

table_name

该策略所应用于的表的名称(可以选择带模式限定)。

PERMISSIVE

指定将策略创建为宽容策略。适用于给定查询的所有宽容策略将使用布尔 “OR” 操作符组合在一起。通过创建宽容策略,管理员可以扩大可访问记录的集合。策略默认是宽容的。

RESTRICTIVE

指定将策略创建为限制策略。适用于给定查询的所有限制策略将使用布尔 “AND” 操作符组合在一起。通过创建限制策略,管理员可以缩小可访问记录的集合,因为每条记录都必须通过所有限制策略。

注意,必须至少存在一条宽容策略来授予对记录的访问,限制策略才能有效地用来缩减这种访问。如果只存在限制策略,那么将无法访问任何记录。当宽容策略和限制策略同时存在时,只有在至少一条宽容策略通过、并且所有限制策略都通过的情况下,记录才是可访问的。

command

该策略所应用的命令。有效选项为 ALL、SELECT、INSERT、UPDATE 和 DELETE。默认为 ALL。有关如何应用这些选项的具体信息,请参见下文。

role_name

该策略要应用到的角色。默认为 PUBLIC,即把该策略应用于所有角色。

using_expression

任意的 SQL 条件表达式(返回 boolean)。条件表达式中不能包含任何聚集或窗口函数。如果启用了行级安全,该表达式将被加入到引用该表的查询中。表达式对其返回真的行将是可见的。表达式对其返回假或空的任何行都将对用户不可见(在 SELECT 中),并且不能被修改(在 UPDATE 或 DELETE 中)。通常,这样的行会被静默地排除;不会报告错误(例外情况请参见 Table 297)。

check_expression

任意的 SQL 条件表达式(返回 boolean)。条件表达式中不能包含任何聚集或窗口函数。如果启用了行级安全,该表达式将被用于针对该表的 INSERT 和 UPDATE 查询。只有表达式求值为真的行才被允许。如果对任何被插入的记录或任何更新所产生的记录,表达式求值为假或空,则会抛出一个错误。注意,check_expression 是针对该行拟采用的新内容求值的,而不是针对原始内容。

针对每种命令的策略

ALL #

对策略使用 ALL 意味着它将应用于所有命令,而不论命令的类型是什么。如果既存在一条 ALL 策略又存在更具体的策略,那么 ALL 策略和更具体的策略(一条或多条)都会被应用。此外,ALL 策略会同时应用于查询的选择一侧和修改一侧,如果只定义了 USING 表达式,则这两种情况都使用该 USING 表达式。

举例来说,如果发出了一条 UPDATE,那么 ALL 策略将既适用于 UPDATE 能选中哪些行来更新(应用 USING 表达式),也适用于更新后得到的行,以检查它们是否被允许加入到表中(如果定义了 WITH CHECK 表达式则应用它,否则应用 USING 表达式)。如果 INSERT 或 UPDATE 命令试图向表中加入未能通过 ALL 策略的 WITH CHECK 表达式(如果该策略没有 WITH CHECK 表达式,则是其 USING 表达式)的行,整个命令都将被中止。

SELECT #

对策略使用 SELECT 意味着它将应用于 SELECT 查询,以及任何需要对该策略所定义的关系具有 SELECT 权限的情形。其结果是,在 SELECT 查询期间只有通过了 SELECT 策略的那些关系记录才会被返回,并且需要 SELECT 权限的查询(例如 UPDATE、DELETE 和 MERGE)也将只能看到 SELECT 策略所允许的那些记录。SELECT 策略不能拥有 WITH CHECK 表达式,因为它只适用于从该关系中检索记录的情形,下文所述的情况除外。

如果数据修改查询带有 RETURNING 子句,则要求对该关系具有 SELECT 权限,并且来自该关系的任何新插入或更新的行都必须满足该关系的 SELECT 策略,才能被 RETURNING 子句使用。如果新插入或更新的行不满足该关系的 SELECT 策略,则会抛出一个错误(要返回的被插入或更新的行 绝不会 被静默忽略)。

如果 INSERT 带有 ON CONFLICT DO UPDATE 子句,或者带有指定了仲裁索引或仲裁者约束的 ON CONFLICT DO NOTHING 子句,则要求对该关系具有 SELECT 权限,并且会使用该关系的 SELECT 策略来检查拟插入的行。如果拟插入的行不满足该关系的 SELECT 策略,则会抛出一个错误(INSERT 绝不会 被静默规避)。此外,如果走了 UPDATE 路径,要更新的行和新更新得到的行都会根据该关系的 SELECT 策略进行检查,如果不满足则抛出一个错误(辅助的 UPDATE 绝不会 被静默规避)。

MERGE 命令要求对源关系和目标关系都拥有 SELECT 权限,因此在它们被连接之前会应用每个关系的 SELECT 策略,并且 MERGE 动作只会看到这些策略所允许的记录。此外,如果执行了一个 UPDATE 动作,目标关系的 SELECT 策略会被应用于被更新的行,就像独立的 UPDATE 一样,区别在于如果不满足则会抛出一个错误。

INSERT #

对策略使用 INSERT 意味着它将应用于 INSERT 命令以及包含 INSERT 动作的 MERGE 命令。未能通过该策略的、正被插入的行会导致策略违反错误,并且整个 INSERT 命令将被中止。INSERT 策略不能拥有 USING 表达式,因为它只适用于向该关系添加记录的情形。

注意,带有 ON CONFLICT DO NOTHING/UPDATE 子句的 INSERT 会针对所有拟插入的行检查 INSERT 策略的 WITH CHECK 表达式,而不管它们最终是否被插入。

UPDATE #

对策略使用 UPDATE 意味着它将应用于 UPDATE、SELECT FOR UPDATE 和 SELECT FOR SHARE 命令,以及 INSERT 命令的辅助 ON CONFLICT DO UPDATE 子句和包含 UPDATE 动作的 MERGE 命令。由于 UPDATE 命令涉及取出一条已有记录并用一条修改后的新记录替换它,UPDATE 策略既接受 USING 表达式,也接受 WITH CHECK 表达式。USING 表达式决定 UPDATE 命令将看到哪些记录来对其进行操作,而 WITH CHECK 表达式则定义哪些修改后的行允许被存回该关系。

任何更新后的值未能通过 WITH CHECK 表达式的行都会导致错误,并且整个命令将被中止。如果只指定了 USING 子句,那么该子句将同时用于 USING 和 WITH CHECK 两种情形。

通常,UPDATE 命令还需要从被更新关系的列中读取数据(例如在 WHERE 子句、RETURNING 子句中,或者在 SET 子句右侧的表达式中)。在这种情况下,还需要对被更新的关系拥有 SELECT 权限,并且除了 UPDATE 策略之外,还会应用相应的 SELECT 或 ALL 策略。因此,用户除了必须通过 UPDATE 或 ALL 策略被授予更新行的权限之外,还必须能够通过 SELECT 或 ALL 策略访问被更新的行。

当 INSERT 命令带有辅助的 ON CONFLICT DO UPDATE 子句时,如果走了 UPDATE 路径,要更新的行会首先根据任何 UPDATE 策略的 USING 表达式进行检查,然后新更新得到的行会根据 WITH CHECK 表达式进行检查。但请注意,与独立的 UPDATE 命令不同,如果已有行未能通过 USING 表达式,则会抛出一个错误(UPDATE 路径 绝不会 被静默规避)。UPDATE 动作在 MERGE 命令中也是如此。

DELETE #

对策略使用 DELETE 意味着它将应用于 DELETE 命令以及包含 DELETE 动作的 MERGE 命令。对于 DELETE 命令,只有通过了该策略的行才会被 DELETE 命令看到。可能存在通过 SELECT 策略可见、但因未通过 DELETE 策略的 USING 表达式而不可供删除的行。但请注意,MERGE 命令中的 DELETE 动作会看到通过 SELECT 策略可见的行,如果 DELETE 策略未通过这样的行,则会抛出一个错误。

在大多数情况下,DELETE 命令还需要从它正在删除的关系的列中读取数据(例如在 WHERE 子句或 RETURNING 子句中)。在这种情况下,还需要对该关系拥有 SELECT 权限,并且除了 DELETE 策略之外,还会应用相应的 SELECT 或 ALL 策略。因此,用户除了必须通过 DELETE 或 ALL 策略被授予删除行的权限之外,还必须能够通过 SELECT 或 ALL 策略访问被删除的行。

DELETE 策略不能拥有 WITH CHECK 表达式,因为它只适用于从该关系中删除记录的情形,此时没有需要检查的新行。

Table 297 总结了不同类型的策略如何应用于特定的命令。在该表中,“检查” 表示会检查策略表达式,如果它返回假或空则抛出一个错误;而 “过滤” 表示如果策略表达式返回假或空,则该行会被静默地忽略。

Table 297. 按命令类型应用的策略

CommandSELECT/ALL policyINSERT/ALL policyUPDATE/ALL policyDELETE/ALL policy
USING expressionWITH CHECK expressionUSING expressionWITH CHECK expressionUSING expression
SELECT / COPY ... TOFilter existing row————
SELECT FOR UPDATE/SHAREFilter existing row—Filter existing row——
INSERT 检查新行 [a] Check new row———
UPDATE 过滤现有行 [a] & 检查新行 [a] —Filter existing rowCheck new row—
DELETE Filter existing row [a] ———Filter existing row
INSERT ... ON CONFLICT 检查新行 [b][c] Check new row [c] ———
ON CONFLICT DO UPDATE 检查现有行和新行 [d] —Check existing row Check new row [d] —
MERGE过滤源行和目标行————
MERGE ... THEN INSERT Check new row [a] Check new row———
MERGE ... THEN UPDATECheck new row—Check existing rowCheck new row—
MERGE ... THEN DELETE————Check existing row

[a] 如果需要对现有行或新行进行读取访问(例如,引用了该关系各列的 WHERE 或 RETURNING 子句)。

[b] 如果指定了仲裁索引或仲裁者约束。

[c] 无论是否发生冲突,都会对拟插入的行进行检查。

[d] 辅助 UPDATE 命令的新行,它可能与原 INSERT 命令的新行不同。


多条策略的应用

当不同命令类型的多条策略应用于同一条命令时(例如应用于 UPDATE 命令的 SELECT 策略和 UPDATE 策略),用户必须同时拥有这两种类型的权限(例如,从该关系选择行的权限以及更新它们的权限)。因此,一种类型策略的表达式会使用 AND 操作符与另一种类型策略的表达式组合起来。

当同一命令类型的多条策略应用于同一条命令时,必须至少存在一条授予对该关系访问权限的 PERMISSIVE(宽容)策略,并且所有 RESTRICTIVE(限制)策略都必须通过。因此,所有 PERMISSIVE 策略表达式会使用 OR 组合,所有 RESTRICTIVE 策略表达式会使用 AND 组合,而两者的结果再使用 AND 组合。如果不存在 PERMISSIVE 策略,则访问会被拒绝。

注意,在组合多条策略时,ALL 策略被视为与正在应用的任何其他类型的策略具有相同的类型。

例如,在一条同时需要 SELECT 和 UPDATE 权限的 UPDATE 命令中,如果每种类型都有多条适用的策略,它们将按如下方式组合:

expression from RESTRICTIVE SELECT/ALL policy 1
AND
expression from RESTRICTIVE SELECT/ALL policy 2
AND
...
AND
(
  expression from PERMISSIVE SELECT/ALL policy 1
  OR
  expression from PERMISSIVE SELECT/ALL policy 2
  OR
  ...
)
AND
expression from RESTRICTIVE UPDATE/ALL policy 1
AND
expression from RESTRICTIVE UPDATE/ALL policy 2
AND
...
AND
(
  expression from PERMISSIVE UPDATE/ALL policy 1
  OR
  expression from PERMISSIVE UPDATE/ALL policy 2
  OR
  ...
)

注记

要为一个表创建或更改策略,你必须拥有该表。

虽然策略会被应用于数据库中针对表的显式查询,但当系统执行内部引用完整性检查或验证约束时,策略不会被应用。这意味着存在一些间接的方式来确定某个给定的值是否存在。这方面的一个例子是尝试向一个主键列或具有唯一约束的列插入重复值。如果插入失败,用户就可以推断该值已经存在。(这个例子假定策略允许用户插入他们不被允许看到的记录。)另一个例子是用户被允许向一个引用了另一个(否则是隐藏的)表的表中插入。用户可以通过向引用表中插入值来确定存在性,如果插入成功,则表明该值在被引用表中存在。可以通过精心设计策略来解决这些问题,即完全阻止用户插入、删除或更新那些可能暗示他们本来无法看到的值的记录;或者使用生成的值(例如代理键)来代替具有外部含义的键。

通常,系统会先强制执行由安全策略施加的过滤条件,然后再处理用户查询中出现的限定条件,以防止受保护的数据被无意中暴露给可能不可信的用户定义函数。但是,被系统(或系统管理员)标记为 LEAKPROOF 的函数和操作符可以在策略表达式之前求值,因为它们被认为是可信的。

由于策略表达式是直接加入到用户的查询中的,它们将以运行整个查询的用户的权限运行。因此,使用给定策略的用户必须能够访问表达式中引用的任何表或函数,否则他们在尝试查询启用了行级安全的表时只会收到权限被拒绝的错误。不过,这并不会改变视图的工作方式。与普通查询和视图一样,视图所引用的表的权限检查和策略将使用视图拥有者的权限以及适用于视图拥有者的任何策略,除非该视图是使用 security_invoker 选项定义的(参见 CREATE VIEW)。

MERGE 没有单独的策略。相反,在执行 MERGE 时会根据所执行的动作,应用为 SELECT、INSERT、UPDATE 和 DELETE 定义的策略。

更多讨论和实际示例可以在 Section 5.9 中找到。

兼容性

CREATE POLICY 是 PostgreSQL 的扩展。

另请参阅

ALTER POLICY, DROP POLICY, ALTER TABLE