CREATE POLICY

为表定义新的行级安全策略。

概要

CREATE POLICY <name> ON <table_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中指定的表达式进行检查,而通过INSERT或UPDATE创建的新行则根据WITH CHECK中指定的表达式进行检查。当USING表达式对某行返回真时,该行对用户可见;若返回假或空值,则该行不可见。当WITH CHECK表达式对某行返回真值时,该行将被插入或更新;若返回假值或空值,则会引发错误。

对于INSERT和UPDATE语句,WITH CHECK表达式将在任何实际数据修改之前强制执行,且优先于其他约束条件。

策略名称是按表设置的。因此,一个策略名称可用于多个不同表,并为每个表提供适合该表的定义。

策略可针对特定命令或特定角色进行应用。新创建策略的默认行为是应用于所有命令和角色,除非另有说明。单个命令可能同时适用多个策略;更多细节请参见下文。在“针对每种命令的策略”中的表格中,总结了不同类型策略如何应用于特定命令。

对于既可包含USING表达式又可包含WITH CHECK表达式的策略(ALL和UPDATE),若未定义WITH CHECK表达式,则USING表达式将同时用于确定可见行(常规USING场景)和允许添加的新行(WITH CHECK场景)。

如果为某张表启用了行级安全,但不存在适用的策略,则默认采用"默认拒绝"策略,因此该表中的所有行都不可见且不可更新。

参数

  • name

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

    • 策略所适用的表的名称(可选带模式限定)。
  • PERMISSIVE

    • 指定该策略应作为允许策略创建。所有适用于特定查询的允许策略将通过布尔或运算符组合在一起。通过创建允许策略,管理员可扩展可访问记录的集合。策略默认采用允许模式。
  • RESTRICTIVE

    • 指定该策略应作为限制性策略创建。所有适用于特定查询的限制性策略将通过布尔AND运算符组合在一起。通过创建限制性策略,管理员可缩小可访问记录的范围,因为每条记录都必须通过所有限制性策略的验证。

    • 必须至少定义一项允许策略以授予对记录的访问权限,之后才能有效使用限制策略来缩小访问范围。若仅存在限制策略,则所有记录均不可访问。当允许策略与限制策略并存时,记录仅在满足以下条件时可被访问:除所有限制策略均通过外,至少有一项允许策略通过。

  • command

    • 该策略适用的命令。有效选项包括ALL、SELECT、INSERT、UPDATE和DELETE。默认值为ALL。具体应用方式详见下文说明。
  • role_name

    • 该策略将应用于的角色。默认值为 PUBLIC,表示将策略应用于所有角色。
  • using_expression

    • 任何 SQL 条件表达式(返回布尔值)。该条件表达式不得包含任何聚合函数或窗口函数。当启用行级安全时,此表达式将添加至引用该表的查询中。表达式返回 true 的行将可见;表达式返回 false 或 null 的行对用户不可见(在 SELECT 语句中),且不可修改(在 UPDATE 或 DELETE 语句中)。此类行将被静默抑制,不会报告任何错误。
  • check_expression

    • 任何返回布尔值的SQL条件表达式。该条件表达式不得包含任何聚合函数或窗口函数。当启用行级安全时,此表达式将用于针对该表的INSERT和UPDATE查询。仅当表达式评估结果为真时,对应行才被允许插入或更新。若表达式对任何插入记录或更新后的记录评估为假或空值,则会抛出错误。请注意,check_expression是对行拟新增内容进行评估,而非原始内容。

针对每种命令的策略

  • ALL

    • 使用 ALL 作为策略意味着该策略将适用于所有命令,无论命令类型如何。若存在 ALL 策略且存在更具体的策略,则 ALL 策略与更具体的策略(或多个策略)将同时生效。此外,ALL 策略将同时应用于查询的筛选侧和修改侧——若仅定义了 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 路径追加到关系中的行检查插入策略的 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权限,系统将同时应用SELECT或ALL策略与UPDATE策略。因此用户必须通过SELECT或ALL策略访问待更新行,同时还需通过UPDATE或ALL策略获得更新权限。

    • 当INSERT命令包含辅助的ON CONFLICT DO UPDATE子句时,若选择UPDATE路径,系统将首先根据任何UPDATE策略的USING表达式检查待更新行,随后再根据WITH CHECK表达式检查新更新行。但需注意,与独立UPDATE命令不同,若现有行未通过USING表达式验证,系统将抛出错误(更新路径绝不会被静默绕过)。

  • DELETE

    • 使用DELETE作为策略意味着该策略将应用于DELETE命令。只有通过此策略验证的行才会被DELETE命令识别。某些通过SELECT可见的行可能无法被删除,若它们未能通过DELETE策略的USING表达式验证。

    • 在大多数情况下,DELETE 命令还需要从被删除关系中的列读取数据(例如在 WHERE 子句或 RETURNING 子句中)。此时,该关系还需具备SELECT权限,除DELETE策略外还将应用相应的SELECT或ALL策略。因此用户必须通过SELECT或ALL策略访问待删除行,同时还需通过DELETE或ALL策略获得删除权限。

    • DELETE策略不能包含WITH CHECK表达式,因为该策略仅适用于从关系中删除记录的情况,此时不存在新行可供检查。

Command SELECT/ALL policyUSING expresson INSERT/ALL policyWITH CHECK expression UPDATE/ALL policy USING expression UPDATE/ALL policyWITH CHECK expression DELETE/ALL policy USING expression
SELECT Existing row -- -- -- --
SELECT FOR UPDATE/SHARE Existing row -- Existing row -- --
INSERT -- New row -- -- --
INSERT ... RETURNING New row[a] New row -- -- --
UPDATE Existing and new rows[a] -- Existing row New row --
DELETE Existing row[a] -- -- -- Existing row
ON CONFLICT DO UPDATE Existing and new rows -- Existing row New row --

[a]若需读取现有或新行(例如,WHERE子句或RETURNING子句引用关系中的列)。

多重策略的应用

当不同命令类型的多个策略同时适用于同一条命令时(例如,对UPDATE命令同时应用SELECT和UPDATE策略),用户必须同时具备两种类型的权限(例如,既需从关系中选择行,又需更新行的权限)。因此,两种策略类型的表达式需通过AND运算符进行组合。

当同一命令类型的多项策略同时适用于同一命令时,必须至少存在一项允许访问该关系的宽容策略,且所有限制策略均需通过验证。因此所有宽容策略表达式通过OR运算符组合,所有限制策略表达式通过AND运算符组合,最终结果再通过AND运算符整合。若不存在任何宽容策略,则访问请求被拒绝。

请注意,在合并多个策略时,所有策略均被视为与正在应用的其他类型策略具有相同类型。

例如,在需要同时具备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的函数和运算符可在策略表达式之前进行评估,因为它们被视为可信。

由于策略表达式直接添加到用户的查询中,它们将以执行整体查询的用户权限运行。因此,使用特定策略的用户必须能够访问表达式中引用的任何表或函数,否则在尝试查询启用行级安全性的表时,将直接收到权限拒绝错误。但这不会改变视图的工作方式。与常规查询和视图相同,对视图所引用的表进行权限检查和策略应用时,将使用视图所有者的权限以及适用于该视图所有者的任何策略。

兼容性

CREATE POLICY 是 SQL 标准的数据库扩展。

另见

ALTER POLICY, DROP POLICY, ALTER TABLE