6

I have a Postgresql DB that I want to enable the Row-Level-Security on one of its tables.

Everything is working fine, except one thing, that is I want to have an error to be thrown when a user try to perform an update on a record that he doesn't have privileges on.

According to the docs:

check_expression: Any SQL conditional expression (returning boolean). The conditional expression cannot contain any aggregate or window functions. This expression will be used in INSERT and UPDATE queries against the table if row level security is enabled. Only rows for which the expression evaluates to true will be allowed. An error will be thrown if the expression evaluates to false or null for any of the records inserted or any of the records that result from the update. Note that the check_expression is evaluated against the proposed new contents of the row, not the original contents.

So I tried the following:

CREATE POLICY update_policy ON my_table FOR UPDATE TO editors
    USING (has_edit_privilege(user_name))
    WITH CHECK (has_edit_privilege(user_name));

I have also another policy for SELECT

CREATE POLICY select_policy ON my_table FOR SELECT TO editors 
    USING (has_select_privilege(user_name));

According to the docs, this should create a policy that would prevent any one from the editors ROLE to perform update on any record of my_table, and would throw an error when an update is performed. This works correctly, but no error is thrown.

What's my problem? Please help.

Faz B
  • 163
  • 4
  • The above policy does not allow "selecting" from my_table. So when you try an `UPDATE` - the rows you are trying to update are already filtered out. If you allow selecting from my_table - then you will get an error on the update. This is just from a quick test. It would be interesting to learn more about the internals. – madflow Oct 23 '19 at 12:43
  • @madflow thanks for your reply, It doesn't look to be the reason. As I have created a policy that allows selecting all records but the same problem still occurs. CREATE POLICY select_policy ON my_table FOR SELECT TO editors USING (true); – Faz B Oct 23 '19 at 13:55
  • Okay - fyi this was my testcase: https://www.db-fiddle.com/f/9eHkDZ8JEursvPaLYnRCir/0 . – madflow Oct 23 '19 at 16:23

1 Answers1

5

First, let me explain how row level security works when reading from the table:

You need not even have to define the policy – if there is no policy for a user on a table with row level security, the default is that the user can see nothing.

No error will be thrown when reading from a table.

If you want an error to be thrown, you could write a function

CREATE FUNCTION test_row(my_table) RETURNS boolean
   LANGUAGE plpgsql COST 10000 AS
$$BEGIN
   IF /* the user is not allowed */
   THEN
      RAISE EXCEPTION ...;
   END IF;
   RETURN TRUE;
$$END;$$;

Then use that function in your policy:

CREATE POLICY update_policy ON my_table FOR UPDATE TO editors
    USING (test_row(my_table));

I used COST 10000 for the function to tell PostgreSQL to test that condition after all other conditions, if possible.

This is not a fool-proof technique, but it will work for simple queries. What could happen in the general case is that some conditions get checked after the condition from the policy, and this could lead to errors with rows that wouldn't even be returned from the query.

But I think it is the best you can get when abusing the concept of row level security.

Now let me explain about writing to the table:

Any attempt to write a row to the table that does not satisfy the CHECK clause will cause an error as documented.

Now let's put it together:

Assuming that you define the policy like in your question:

  • Any INSERT will cause an error.

  • Any SELECT will return an empty result.

  • Any UPDATE or DELETE will be successful, but affect no row. This is because these operations have to read (scan) the table before they modify data, and that scan will return no rows like in the SELECT case. Since no rows are affected by the data modification, no error is thrown.

Laurenz Albe
  • 209,280
  • 17
  • 206
  • 263
  • Thanks for your reply. I have updated the question. I wonder why should I throw an exception myself, while the docs says that it will be automatically thrown? Also the docs says that the argument of USEING and WITH CHECK should be boolean, your function test_row doesn't return boolean it just throw an exception, is that acceptable? – Faz B Oct 23 '19 at 14:08
  • Sorry, I didn't read the question carefully enough. My updated answer should cover it. – Laurenz Albe Oct 23 '19 at 15:10
  • Does throwing an exception instead of returning a boolean may cause problems? – Faz B Oct 24 '19 at 06:02
  • As I wrote, there is the remote possibility that the function is called for values that are later excluded by other conditions, so you get an error for a row that you didn't select. – Laurenz Albe Oct 24 '19 at 06:04