2

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

在我拥有的代码中units,这是一个顶级对象。unitssubunits1-n 的关系。

还有users,其中一个可以通过表user访问多个。unitsunit_owner

RLS 策略旨在让user新的插入subunitsunits他拥有的。

所有这一切都可以正常工作,直到代码中的第二行。

但这是我的问题:该数据库是通过 GraphQL 中间件(Postgraphile)公开的,该中间件需要通过该INSERT ... RETURNING功能插入返回的结果。

从最后一个插入语句中可以看出,这不起作用,它得到“错误:新行违反行级安全策略”。

问题似乎源于 RETURNING 需要选择权限这一事实,并且选择策略功能是使用插入之前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,
    description varchar 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,INSERT,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, description) VALUES (1, 1, 'I belong to a visible unit');
INSERT INTO insert_returning.subunit (id, unit_id, description) VALUES (2, 2, 'I belong to a hidden unit');
INSERT INTO insert_returning.subunit (id, unit_id, description) VALUES (3, 1, '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, 1, 'I am a new subunit'); -- works

INSERT INTO insert_returning.subunit VALUES (5, 1, 'I am another new subunit') RETURNING *; -- FAILS

--
4

2 回答 2

1

您对问题的分析是正确的:插入的行不适用于FOR SELECT策略中的子查询subunit

没有办法像这样“让它工作”。您将不得不为该策略找到一个不同的测试,该测试不希望在表中找到新行。按照您的案例编写方式,您可以直接使用unit_id新行的 进行更简单的测试,但您向我们保证,这在您的实际用例中不起作用......

您不能选择新行,但可以使用新行的所有属性。因此,请尝试使用不涉及对表本身进行子选择的 SQL 表达式来编写条件。

于 2020-10-07T19:39:22.380 回答
0

为了使其工作(并且您看不到更改底层 RLS 的方法),您可以创建一个自定义突变函数,您可以将其标记为SECURITY DEFINER.

在此突变功能中,您必须自己进行检查。

这不能回答您关于 RLS 的问题——我认为在这个问题的另一个答案中已经正确访问了它。这更有可能是来自 Postgraphile 用户的提示。

还:

  • 根据我在 RLS 中使用函数的经验,几乎总是会降低性能。特别是当它们没有被内联时。在您的情况下VOLATILESECURITY DEFINER 应该已经防止内联。

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

于 2020-10-09T07:01:51.487 回答