使用行级安全性RLS时,如何使INSERT ... RETURNING语句起作用?

问题描述

在下面,您可以找到一个问题的最小测试用例的代码,该问题在使用RLS来管理对分层数据结构的访问的系统中遇到。我正在使用Postgres v11。

代码中,我有units,这是一个顶级对象。 unitssubunits的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时。在您的情况下,VOLATILESECURITY DEFINER 应该已经可以防止内联。

  • 在RLS定义中使用EXISTS而不是IN几乎总是更快。您的经验可能有所不同。