为外键属性的行级安全性创建 Postgres 策略

Create Postgres Policy for Row Level Security on foreign key attribute

我正在尝试在 Postgres 上创建策略以启用行级安全性,但我遇到了一些麻烦。我发现的许多示例在 table 上都有一个直接的 FK,但我试图在外部 table.

上的一个属性上这样做

这里的上下文是我试图让用户只能看到来自同一组织的其他用户。用户在配置参数 organization.current_tenant 上设置,该参数采用 Organization.key

的值

设置:

CREATE TABLE organization (id serial primary key, name text, key text);
CREATE TABLE myuser (id serial primary key, name text, user_organization_id integer);

INSERT INTO organization (name, key) values ('org1', 'org1-key');
INSERT INTO organization (name, key) values ('org2', 'org2-key');

INSERT INTO myuser (name, user_organization_id) values ('org1_agent1', 1);
INSERT INTO myuser (name, user_organization_id) values ('org2_agent1', 2);
INSERT INTO myuser (name, user_organization_id) values ('org2_agent2', 2);

ALTER TABLE myuser ENABLE ROW LEVEL SECURITY;
ALTER TABLE myuser FORCE ROW LEVEL SECURITY;

直接使用时 FK, organization_id:

CREATE POLICY access_tenant_data ON myuser
USING (user_organization_id::TEXT = current_setting('organization.current_tenant'));

这是可行的,因为 user_organization_iduser table 上可用的外键。

但是,我想使用的是 user.organization.key.

CREATE POLICY access_tenant_data ON myuser
USING (
    key IN (
        SELECT key FROM 
        myuser U INNER JOIN organization O
        ON U.organization_id = O.id
    )::TEXT = current_setting('organization.current_tenant')
);

但这显然行不通。我不确定如何从这里开始。

正在测试:

SET organization.current_tenant = "org1-key";
SELECT * from myuser;

您需要定义一个return布尔型函数。 在该函数中,您将确定逻辑和 return TRUE 或 FALSE。 重要的一点是:策略的 USING 子句需要一个 BOOLEAN 值,我们可以根据函数 return 得到的内容 - True 或 False 来决定策略 (allow/disallow) 的行为。

这是要在策略中使用的示例 FUNCTION

create function KeyExist(organization_id int, env_current_tenant text)  returns boolean
language plpgsql
as
$$
declare
    flag boolean;
    keyValue text;
begin
select key into keyValue from organization where id=organization_id;
if not found then
    flag := false;
else
    if keyValue = env_current_tenant then
        flag := true;
    else
        flag := false;
    end if;
end if;
return flag;
end; $$;

政策:

CREATE POLICY access_tenant_data ON myuser for select
USING (KeyExist(user_organization_id,current_setting('organization.current_tenant')));

演示:

postgres=# create function KeyExist(organization_id int, env_current_tenant text)  returns boolean
postgres-# language plpgsql
postgres-# as
postgres-# $$
postgres$# declare
postgres$# flag boolean;
postgres$# keyValue text;
postgres$# begin
postgres$# select key into keyValue from organization where id=organization_id;
postgres$# if not found then
postgres$# flag := false;
postgres$# else
postgres$# if keyValue = env_current_tenant then
postgres$#
postgres$# flag := true;
postgres$# else
postgres$#
postgres$# flag := false;
postgres$# end if;
postgres$# end if;
postgres$# return flag;
postgres$# end; $$;
CREATE FUNCTION

postgres=# CREATE POLICY access_tenant_data ON myuser for select
postgres-# USING (KeyExist(user_organization_id,current_setting('organization.current_tenant')));
CREATE POLICY

验证:

postgres=> SET organization.current_tenant = "org1-key";
SET
postgres=> select * from myuser;
 id |    name     | user_organization_id
----+-------------+----------------------
  1 | org1_agent1 |                    1
(1 row)


postgres=> SET organization.current_tenant = 'org2-key';
SET
postgres=> select * from myuser;
 id |    name     | user_organization_id
----+-------------+----------------------
  2 | org2_agent1 |                    2
  3 | org2_agent2 |                    2
(2 rows)

:

postgres=# \d myuser
                                   Table "public.myuser"
        Column        |  Type   | Collation | Nullable |              Default
----------------------+---------+-----------+----------+------------------------------------
 id                   | integer |           | not null | nextval('myuser_id_seq'::regclass)
 name                 | text    |           |          |
 user_organization_id | integer |           |          |
Indexes:
    "myuser_pkey" PRIMARY KEY, btree (id)
Policies (forced row security enabled):
    POLICY "access_tenant_data" FOR SELECT
      USING (keyexist(user_organization_id, current_setting('organization.current_tenant'::text)))

postgres=# select * from myuser;
 id |    name     | user_organization_id
----+-------------+----------------------
  1 | org1_agent1 |                    1
  2 | org2_agent1 |                    2
  3 | org2_agent2 |                    2
(3 rows)

postgres=# select * from organization;
 id | name |   key
----+------+----------
  1 | org1 | org1-key
  2 | org2 | org2-key
(2 rows)

这应该很简单:

CREATE POLICY access_tenant_data ON myuser
USING (
   EXISTS (SELECT 1 FROM organization AS o
           WHERE myuser.user_organization_id = o.id
             AND o.key = current_setting('organization.current_tenant')
          )
);

这可能相当有效,因为 PostgreSQL 可以使用 semi-join.

计算结果查询