2

I am trying to wrap my head around row level security in Postgres. Unfortunately the documentation is not very verbose on the matter. My problem is the following:

I have two tables: locations and locations_owners. There is a TRIGGER set on INSERT for locations, which will automatically add a new row to the locations_owners table including the request.jwt.claim.sub variable.

This works all just fine, however when I want to create a policy for DELETE like this:

CREATE POLICY location_delete ON eventzimmer.locations FOR DELETE TO organizer USING(
    (SELECT EXISTS (SELECT name FROM protected.locations_owners AS owners WHERE owners.name = name AND owners.sub = (SELECT current_setting('request.jwt.claim.sub', true))))
);

It will always evaluate to true, no matter the actual content. I know that I can call a custom procedure with SELECT here, however I ended up with the following questions:

  • what is the scope of a policy? Can I access tables? Can I access procedures? The documentation says "Any SQL conditional expression" so SELECT EXISTS should be fine
  • how are the column names of the rows mapped to the policy. The examples just magically use the column names (which I adopted by using the name variable), however I have not found any documentation about what this actually does
  • what is the magic user_name variable. Where does it come from? I believe it is the current role which is executing the query, but how can I know?
  • why is there no WITH CHECK expression available for DELETE? If I understand correctly, WITH CHECK will fail any row with invalid constraint, which is the behaviour I would prefer (because otherwise PostgREST will always return 204)

I am a little bit confused by the astonishingly missing amount of information in the (otherwise) very good documentation of PostgreSQL. Where is this information? How can I find it?

For the sake of completeness I have also attached the column definitions below:

CREATE TABLE eventzimmer.locations (
  name varchar PRIMARY KEY NOT NULL,
  latitude float NOT NULL,
  longitude float NOT NULL
);

CREATE TABLE IF NOT EXISTS protected.locations_owners (
    name varchar NOT NULL REFERENCES eventzimmer.locations(name) ON DELETE CASCADE,
    sub varchar NOT NULL
);
Fohlen
  • 192
  • 2
  • 18
  • 1
    I had a similar issue in a function recently, and am quite certain you'll need to use `owners.name = locations.name` otherwise `name = name` always comes up as `TRUE`. – Bergi Oct 09 '19 at 09:39

1 Answers1

6

Many of the questions will become clear once you understand how row level security is implemented: the conditions in the policies will automatically be added to the query, just as if you added another WHERE condition.

Use EXPLAIN to see the query plan, and you will see the policy's conditions in there.

  • So you can use any columns from the table on which the policy is defined.

    Essentially, you can use anything in a policy definition that you could use in a WHERE conditions: Function calls, subqueries and so on.

    You can also qualify the column name with the table name if that is required for disambiguation. This can be used in the policy from your example: The unqualified name is interpreted as owners.name, so the test always succeeds. To fix the policy, use locations.name instead of name.

  • There is no magic user_name variable, and I don't know where you get that from. There is, however, the current_user function, which is always available and can of course also be used in a policy definition.

  • WITH CHECK is a condition that the new row added by INSERT or UPDATE must fulfill. Since DELETE doesn't add any data, WITH CHECK doesn't apply to it.

Laurenz Albe
  • 209,280
  • 17
  • 206
  • 263
  • If it is "*just a `WHERE` clause*", does that mean the table name (and alias?) are available to disambiguate a column name? – Bergi Oct 09 '19 at 09:42
  • 1
    Aliases for the table cannot be used, because they depend on the query. You can qualify the column names with the table name, but I can't think of an example where disambiguation might be necessary. – Laurenz Albe Oct 09 '19 at 09:58
  • 1
    See the `owners.name = name` clause in the OP :-) – Bergi Oct 09 '19 at 10:02
  • 1
    Oh, hm, subselects, yes. Anyway, is the case clear now? – Laurenz Albe Oct 09 '19 at 10:07
  • 1
    The comment by @bergi has solved my problem. However, as for _how_ policies work, your reply is the correct response, and now I finally understand why there is such limited documentation. Thanks! – Fohlen Oct 09 '19 at 13:24
  • Oh, I didn't realize you had a manifest problem. I have added that to the answer. – Laurenz Albe Oct 09 '19 at 13:33