CREATE POLICY
CREATE POLICY — 为表定义新的行级安全策略
大纲
CREATE POLICYnameONtable_name[ AS { PERMISSIVE | RESTRICTIVE } ] [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] [ TO {role_name| PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] [ USING (using_expression) ] [ WITH CHECK (check_expression) ]
描述
CREATE POLICY 命令为一个表定义一条新的行级安全策略。请注意,必须先在该表上启用行级安全(使用
ALTER TABLE ... ENABLE ROW LEVEL SECURITY),已创建的策略才会被应用。
策略授予选择、插入、更新或删除符合相应策略表达式的行的权限。使用 USING 中指定的表达式检查现有表行,而使用 WITH CHECK 中指定的表达式检查通过 INSERT 或 UPDATE 创建的新行。当 USING 表达式对给定行返回 true 时,该行对用户可见;如果返回 false 或 null,则该行不可见。当 WITH CHECK 表达式对某行返回 true 时,该行会被插入或更新;如果返回 false 或 null,则发生错误。
对于 INSERT 和 UPDATE 语句,会在触发 BEFORE 触发器之后、进行任何实际数据修改之前强制执行 WITH CHECK 表达式。因此,BEFORE ROW 触发器可能会修改要插入的数据,从而影响安全策略检查的结果。WITH CHECK 表达式会在任何其他约束之前强制执行。
策略名称按表区分。因此,同一个策略名可以用于许多不同的表,并且在每个表上都可以有适合该表的定义。
策略可以针对特定命令或特定角色应用。除非另有指定,新建策略默认适用于所有命令和角色。多个策略可以应用于同一命令;更多细节见下文。表 241 总结了不同类型的策略如何应用于特定命令。
对于既可以具有 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)。条件表达式不能包含聚合函数或窗口函数。如果启用行级安全,该表达式会添加到引用该表的查询中。表达式返回 true 的行将对用户可见。表达式返回 false 或 null 的任何行对用户都不可见(在SELECT中),也不能被修改(在UPDATE或DELETE中)。这类行会被静默抑制,不会报告错误。check_expression任何 SQL 条件表达式(返回
boolean)。条件表达式不能包含聚合函数或窗口函数。如果启用行级安全,该表达式会用于针对该表的INSERT和UPDATE查询。只有表达式计算结果为 true 的行才会被允许。如果对于任何插入的记录或更新产生的任何记录,表达式计算结果为 false 或 null,则会抛出错误。请注意,check_expression是针对行预计的新内容计算的,而不是针对原始内容。
针对每种命令的策略
ALL#对策略使用
ALL意味着无论命令类型如何,该策略都适用于所有命令。如果存在ALL策略且还存在更具体的策略,则ALL策略和更具体的策略(或多个策略)都会被应用。此外,ALL策略会同时应用于查询的选择端和修改端,并在两端使用USING表达式(如果只定义了USING表达式)。例如,如果发出
UPDATE,则ALL策略既适用于UPDATE能够选择哪些行进行更新(应用USING表达式),也适用于更新后的行,以检查它们是否允许被添加回表中(如果定义了WITH CHECK表达式则应用该表达式,否则应用USING表达式)。如果INSERT或UPDATE命令尝试向表中添加不通过ALL策略的WITH CHECK表达式的行,则整个命令都会中止。SELECT#对策略使用
SELECT意味着该策略适用于SELECT查询,以及在策略所定义的关系上需要SELECT权限的任何时候。因此,在SELECT查询期间,只会返回关系中通过SELECT策略的记录;需要SELECT权限的查询(例如UPDATE)也只能看到被SELECT策略允许的记录。SELECT策略不能有WITH CHECK表达式,因为它只适用于从关系中检索记录的情况。INSERT#对策略使用
INSERT,意味着它将适用于INSERT命令。插入的行如果未通过该策略,将导致策略违规错误,并且整个INSERT命令将被中止。INSERT策略不能带有USING表达式,因为它只适用于向关系添加行的情况。请注意,带有
ON CONFLICT DO UPDATE的INSERT只会为通过INSERT路径添加到关系中的行检查INSERT策略的WITH CHECK表达式。UPDATE#对策略使用
UPDATE,意味着它将适用于UPDATE、SELECT FOR UPDATE、SELECT FOR SHARE命令,以及INSERT命令中辅助的ON CONFLICT DO UPDATE子句。由于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路径绝不会被静默跳过)。DELETE#对策略使用
DELETE意味着该策略适用于DELETE命令。DELETE命令只能看到通过该策略的行。如果某些行不通过DELETE策略的USING表达式,则它们可能通过SELECT可见,但不能被删除。在多数情况下,
DELETE命令也需要从其删除所针对的关系中的列读取数据(例如在WHERE子句或RETURNING子句中)。这种情况下,该关系上也需要SELECT权限,并且除了DELETE策略外,还会应用适当的SELECT或ALL策略。这样,用户除了必须通过DELETE或ALL策略获准删除这些行之外,还必须通过SELECT或ALL策略访问正在被删除的行。DELETE策略不能具有WITH CHECK表达式,因为它只适用于正在从关系中删除行的情况,所以没有新行需要检查。
表 241. 按命令类型应用的策略
| 命令 | SELECT/ALL策略 | INSERT/ALL策略 | UPDATE/ALL策略 | DELETE/ALL策略 | |
|---|---|---|---|---|---|
USING 表达式 | WITH CHECK 表达式 | USING 表达式 | WITH CHECK 表达式 | USING 表达式 | |
SELECT | 现有行 | — | — | — | — |
SELECT FOR UPDATE/SHARE | 现有行 | — | 现有行 | — | — |
INSERT | — | 新行 | — | — | — |
INSERT ... RETURNING | 新行[a] | 新行 | — | — | — |
UPDATE | 现有 & 新行[a] | — | 现有行 | 新行 | — |
DELETE | 现有行[a] | — | — | — | 现有行 |
ON CONFLICT DO UPDATE | 现有 & 新行 | — | 现有行 | 新行 | — |
[a] 如果需要读取现有行或新行(例如引用关系中列的 | |||||
多条策略的应用
当不同命令类型的多条策略应用于同一命令时(例如
SELECT 和 UPDATE 策略应用于
UPDATE 命令),用户必须同时具有这两种权限(例如既有从该关系中选取行的权限,也有更新这些行的权限)。因此,一种策略类型的表达式会与另一种策略类型的表达式使用
AND 操作符组合。
当同一命令类型的多条策略应用于同一命令时,必须至少有一条
PERMISSIVE 策略授予对该关系的访问权,并且所有
RESTRICTIVE 策略都必须通过。因此,所有
PERMISSIVE 策略表达式使用 OR
组合,所有 RESTRICTIVE 策略表达式使用
AND 组合,然后再将两者的结果使用
AND 组合。如果没有
PERMISSIVE 策略,则访问被拒绝。
请注意,就组合多条策略而言,ALL 策略会被视为与当前正在应用的其他策略同一类型。
例如,对于一个同时需要 SELECT 和
UPDATE 权限的 UPDATE 命令,如果这两类策略各自都有多个适用项,它们会按以下方式组合:
RESTRICTIVE SELECT/ALL 策略 1 中的表达式AND RESTRICTIVE SELECT/ALL 策略 2 中的表达式AND ... AND ( PERMISSIVE SELECT/ALL 策略 1 中的表达式OR PERMISSIVE SELECT/ALL 策略 2 中的表达式OR ... ) AND RESTRICTIVE UPDATE/ALL 策略 1 中的表达式AND RESTRICTIVE UPDATE/ALL 策略 2 中的表达式AND ... AND ( PERMISSIVE UPDATE/ALL 策略 1 中的表达式OR PERMISSIVE UPDATE/ALL 策略 2 中的表达式OR ... )
注解
要为一个表创建或修改策略,你必须是该表的拥有者。
虽然策略会应用于针对数据库中表的显式查询,但当系统执行内部引用完整性检查或验证约束时,并不会应用这些策略。这意味着仍然存在间接判断某个给定值是否存在的方法。一个例子是,尝试向某个主键列或带有唯一约束的列插入重复值。如果插入失败,用户就能推断该值已经存在。(这个例子假定策略允许该用户插入自己无权看见的行。)另一个例子是,用户被允许向一个引用了另一张表的表中插入数据,而被引用的那张表本身对其是隐藏的。用户可以通过向引用表插入值来判断其存在性;插入成功就表示该值存在于被引用表中。要解决这些问题,可以仔细设计策略,防止用户插入、删除或更新那些可能暗示其本来无权看见的值是否存在的行,或者改用生成的值(例如代理键)来代替具有外部含义的键。
通常,为了防止受保护的数据无意间暴露给可能不可信的用户定义函数,系统会在应用用户查询中出现的条件之前,先强制执行安全策略施加的过滤条件。不过,被系统(或系统管理员)标记为
LEAKPROOF 的函数和操作符由于被假定为可信,可以在策略表达式之前求值。
由于策略表达式会被直接添加到用户查询中,因此它们将以执行整个查询的用户权限运行。因此,使用某条策略的用户必须能够访问表达式中引用的所有表或函数,否则在尝试查询启用了行级安全的表时,只会收到权限被拒绝的错误。不过,这并不改变视图的工作方式。与普通查询和视图一样,被视图引用的表的权限检查和策略将使用视图所有者的权限,以及适用于视图所有者的任何策略。
更多讨论和实际示例见第 5.7 节。
兼容性
CREATE POLICY 是一种 PostgreSQL 扩展。