问题描述
在下面,您可以找到一个问题的最小测试用例的代码,该问题在使用RLS来管理对分层数据结构的访问的系统中遇到。我正在使用Postgres v11。
在代码中,我有units
,这是一个顶级对象。 units
与subunits
的1-n关系。
还有users
,其中user
可以通过units
表访问多个unit_owner
。
RLS策略旨在让user
将新的subunits
插入他拥有的units
。
所有这些都可以正常工作,直到代码的第二行为止。
但是,这是我的问题:该数据库通过GraphQL中间件(Postgraphile)公开,该中间件需要借助INSERT ... RETURNING
功能进行回插入的结果。
并且在最后一个insert语句中可以看到,这是行不通的,它会显示“ 错误:新行违反了行级安全策略”。
问题似乎是由以下事实引起的:RETURNING需要选择权限,并且选择策略功能是使用{strong>插入之前而不是可用的subunit
ID集合来评估的>之后。
CREATE SCHEMA insert_returning;
CREATE ROLE users;
GRANT USAGE ON SCHEMA insert_returning TO users;
DROP TABLE IF EXISTS insert_returning.unit;
DROP TABLE IF EXISTS insert_returning.subunit;
DROP TABLE IF EXISTS insert_returning.unit_owner;
CREATE TABLE insert_returning.unit (
id integer NOT NULL,description varchar NULL,CONSTRAINT unit_pk PRIMARY KEY (id)
);
CREATE TABLE insert_returning.subunit (
id integer NOT NULL,unit_id integer NOT NULL,CONSTRAINT subunit_pk PRIMARY KEY (id)
);
CREATE TABLE insert_returning.unit_owner (
user_id integer NOT NULL,unit_id integer NOT NULL
);
GRANT SELECT,INSERT,UPDATE ON TABLE insert_returning.unit TO users;
GRANT SELECT,UPDATE ON TABLE insert_returning.subunit TO users;
GRANT SELECT ON TABLE insert_returning.unit_owner TO users;
CREATE OR REPLACE FUNCTION insert_returning.get_users_units()
RETURNS SetoF integer
LANGUAGE sql VOLATILE Security DEFINER AS
$$
SELECT uo.unit_id FROM insert_returning.unit_owner uo
WHERE uo.user_id = 17;
$$;
CREATE OR REPLACE FUNCTION insert_returning.get_users_subunits()
RETURNS SetoF integer
LANGUAGE sql VOLATILE Security DEFINER AS
$$
SELECT s.id FROM insert_returning.subunit s
JOIN insert_returning.unit_owner uo ON uo.unit_id = s.unit_id
WHERE uo.user_id = 17;
$$;
ALTER TABLE insert_returning.unit ENABLE ROW LEVEL Security;
ALTER TABLE insert_returning.subunit ENABLE ROW LEVEL Security;
DROP POLICY IF EXISTS select_unit ON insert_returning.unit;
DROP POLICY IF EXISTS select_subunit ON insert_returning.subunit;
DROP POLICY IF EXISTS insert_subunit ON insert_returning.subunit;
CREATE POLICY select_unit ON insert_returning.unit FOR SELECT TO PUBLIC USING ((
SELECT (id IN ( SELECT unit_id FROM insert_returning.unit_owner WHERE user_id = 17))
));
CREATE POLICY select_subunit ON insert_returning.subunit FOR SELECT TO PUBLIC USING ((
SELECT (id IN (SELECT insert_returning.get_users_subunits()) )
));
CREATE POLICY insert_subunit ON insert_returning.subunit FOR INSERT TO PUBLIC WITH CHECK ((
SELECT (unit_id IN (SELECT insert_returning.get_users_units()) )
));
INSERT INTO insert_returning.unit (id,description) VALUES (1,'I am visible');
INSERT INTO insert_returning.unit (id,description) VALUES (2,'I am hidden');
INSERT INTO insert_returning.subunit (id,unit_id,1,'I belong to a visible unit');
INSERT INTO insert_returning.subunit (id,2,'I belong to a hidden unit');
INSERT INTO insert_returning.subunit (id,description) VALUES (3,'I too belong to a visible unit');
INSERT INTO insert_returning.unit_owner (user_id,unit_id) VALUES (17,1);
SET ROLE users;
SELECT * FROM insert_returning.subunit; -- works
INSERT INTO insert_returning.subunit VALUES (4,'I am a new subunit'); -- works
INSERT INTO insert_returning.subunit VALUES (5,'I am another new subunit') RETURNING *; -- FAILS
--
解决方法
您已正确分析了问题:FOR SELECT
上的subunit
策略中的子查询无法使用插入的行。
没有办法像这样“使其工作”。您将不得不为该策略找到另一种测试,该测试不会期望在表中找到新行。编写案例的方式,您可以直接使用新行的unit_id
进行更简单的测试,但是您向我们保证,这在您的实际用例中是行不通的...
您不能选择新行,但是可以使用新行的所有属性。因此,请尝试使用不涉及表本身的subselect的SQL表达式编写条件。
,为了使其正常工作(您看不到更改基础RLS的方法),可以创建一个custom mutation function,将其标记为SECURITY DEFINER
。
在此突变功能中,您将必须自己进行检查。
这不能回答您有关RLS的问题-我认为该问题的另一个答案已经正确访问了该问题。另一个Postgraphile用户的提示是可能的。
也:
-
以我的经验,在RLS中使用函数几乎总是会降低性能。特别是当它们不是inlined时。在您的情况下,
VOLATILE
和SECURITY DEFINER
应该已经可以防止内联。 -
在RLS定义中使用
EXISTS
而不是IN
几乎总是更快。您的经验可能有所不同。