在 INSERT 之前在 SQL 中设置本地配置

Setting local config in SQL before INSERT

来自 JS 世界的 SQL 新手需要一些有关触发器的建议。
我的用户有一个 id 列,我的 GraphQL API 总是调用 INSERT INTO .... RETURNING *,然后在 GraphQL 层上进行转换以 return 我想要的。
目标是允许像 INSERT INTO .... RETURNING * 这样的查询在适当的位置使用 RLS。

政策是:

CREATE POLICY USER_SELECT_RESTRICTIONS 
ON "user" 
FOR SELECT
USING ("id" = current_user_id() OR current_user_role() = 'admin' OR current_user_role() = 'partner');

CREATE POLICY USER_INSERT_RESTRICTIONS
ON "user" 
FOR INSERT
WITH CHECK (true);

这对来宾用户(更多上下文在底部)中断,因为他们被允许 INSERT 但不能 SELECT (因为只有授权用户才能 select 他们的限制自己的行)。

所以我想以某种方式在触发器 before/after 插入中手动设置用户设置(不知道是哪个,因为语法错误我无法前进)。

我已经试过了,但我在 NEW.id? (它只是对我说 "Syntax Error")

CREATE OR REPLACE FUNCTION after_user_insert()
RETURNS TRIGGER AS $$
BEGIN
  SET LOCAL jwt.claims.userId NEW.id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER set_user_id_on_insert
AFTER INSERT ON "user"
FOR EACH ROW
EXECUTE PROCEDURE after_user_insert();

搜索了很多,老实说,我想我可能找不到任何东西,因为我缺少正确的术语来找到我需要找到的东西。

不仅希望就此特定问题提供帮助,还希望为需要特权的来宾用户提供有关政策的任何相关建议。

上下文:

这些是相关的 table 和函数

CREATE TYPE "user_role" AS ENUM (
  'customer',
  'partner',
  'admin'
);
​
CREATE TABLE "user" (
  "id" uuid UNIQUE PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
  "first_name" varchar NOT NULL,
  "last_name" varchar NOT NULL,
  "email" text UNIQUE NOT NULL,
  "role" user_role NOT NULL DEFAULT 'customer',
  "created_at" timestamp NOT NULL DEFAULT NOW(),
  "updated_at" timestamp NOT NULL DEFAULT NOW()
);
​
CREATE FUNCTION current_user_id() RETURNS uuid AS $$
  SELECT nullif(current_setting('jwt.claims.userId', true), '')::uuid;
$$ LANGUAGE SQL stable;
​
CREATE FUNCTION current_user_role() RETURNS user_role AS $$
  SELECT nullif(current_setting('jwt.claims.role', true), '')::user_role;
$$ LANGUAGE SQL stable

RLS 将 SELECT 限制为 user table 的 id 列匹配 current_setting('jwt.claims.userId').
的行 这是之前由 Postgraphile 设置的,如此处所示 (https://www.graphile.org/postgraphile/security/)。

我想到的一个可能的解决方法是这样,但我不知道这是否会因为明显的角色提升而导致某种漏洞:

CREATE OR REPLACE FUNCTION after_user_insert()
RETURNS TRIGGER AS $$
BEGIN
  SET LOCAL jwt.claims.role TO "customer";
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION before_user_insert()
RETURNS TRIGGER AS $$
BEGIN
  SET LOCAL jwt.claims.role TO "admin";
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER before_insert
BEFORE INSERT ON "user"
FOR EACH ROW
EXECUTE PROCEDURE before_user_insert();

CREATE TRIGGER after_insert
AFTER INSERT ON "user"
FOR EACH ROW
EXECUTE PROCEDURE after_user_insert();

你没有说客人是什么,但你的做法似乎不对。

您应该选择以下方法之一,而不是在低级别上禁用您的检查,这会给您带来不好的感觉:

  • 修复您的策略以允许必要的操作(可能通过添加许可策略)

  • 某些操作由属于不受限制的用户的 SECURITY DEFINER 函数执行。

如果您坚持使用基于触发器的解决方案,则必须使用 BEFORE 触发器。另外,请注意您不能将 parameters 与 SET 语句一起使用。您要么必须使用动态 SQL 要么(更好)使用函数:

CREATE OR REPLACE FUNCTION before_user_insert() RETURNS TRIGGER AS
$$BEGIN
   SELECT set_config('jwt.claims.userId', NEW.id::text, TRUE);
   RETURN NEW;
END;$$ LANGUAGE plpgsql;

CREATE TRIGGER set_user_id_on_insert BEFORE INSERT ON "user"
FOR EACH ROW EXECUTE PROCEDURE before_user_insert();