1
votes

I'm trying to implement row-level security in Postgres. In reality, I have many roles, but for the sake of this question, there are four roles: executive, director, manager, and junior. I have a table that looks like this:

SELECT * FROM ex_schema.residence; --as superuser

  primary_key  |   residence   | security_level
------------------+---------------+--------------
             5 | Time-Share    | executive
             1 | Single-Family | junior
             2 | Multi-Family  | director
             4 | Condominium   | manager
             6 | Modular       | junior
             3 | Townhouse     | director

I've written a policy to enable row-level security that looks like this:

CREATE POLICY residence_policy
ON ex_schema.residence
FOR ALL
USING (security_level = CURRENT_USER)
WITH CHECK (primary_key IS NOT NULL AND security_level = CURRENT_USER);

As expected, when the executive connects to the database and selects the table, that role only sees rows that have executive in the security_level column. What I'd like to do is enable the row-level security so that higher security roles can see rows that match their security level as well as rows that have lower security privileges. The hierarchy would look like this:

ROW ACCESS PER ROLE
executive: executive, director, manager, junior
director: director, manager, junior
manager: manager, junior
junior: junior 

I'm wondering how to implement this type of row-level policy so that a specific role can access multiple types of security levels. There's flexibility in changing the security_level column structure and data type.

1

1 Answers

1
votes

One thing you can do is define an enum type for your levels:

CREATE TYPE sec_level AS ENUM
   ('junior', 'manager', 'director', 'executive');

Then you can use that type for the security_level column and write your policy as

CREATE POLICY residence_policy ON ex_schema.residence
   FOR ALL
   USING (security_level >= CURRENT_USER::sec_level);

There is no need to check if the primary key is NULL, that would generate an error anyway.

Use an enum type only if you know that these levels won't change, particularly that no level will ever be removed.

Alternatively, you could use a lookup table:

CREATE TABLE sec_level
   name text PRIMARY KEY,
   rank double precision UNIQUE NOT NULL
);

The column security_level would then be a foreign key to sec_level(rank), and you can compare the values in the policy like before. You will need an extra join with the lookup table, but you can remove levels.