I have a table named products in my postgresql 9.5 database. And this table fields are like this:
id
name
sales_area
And data is like this:
id name sales_area
1 prod1 A1
2 prod2 A1
3 prod3 A2
4 prod4 A3
And I want to create a database user named user1, and this user should select, update and delete only A1 sales_area datas. Other database user will select, update and delete all datas.
Is this rule possible using policy? And How?
I think that this can be done using row level security as follows:
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
CREATE POLICY for_user1 ON products AS PERMISSIVE
FOR ALL TO PUBLIC
USING (current_user <> 'user1' OR sales_area = 'A1');
Then user1 can only access sales_area A1 and everybody else can access everything.
Some explanations:
FOR ALL means “for all actions”, see the documentation:
Using ALL for a policy means that it will apply to all commands, regardless of the type of command.
The lack of a WITH CHECK clause does not mean that data modifications won't be checked. Again a quote from the documentation:
ALL policies will be applied to both the selection side of a query and the modification side, using the USING expression for both cases if only a USING expression has been defined.
About permission am not sure, but alternatively, you can create a view as below and give them permission to "user1" to a new view.
create table tx1(id int,name varchar(20),sales_area varchar(20));
insert into tx1 values(1,'prod1','A1');
insert into tx1 values(2,'prod2','A1');
insert into tx1 values(3,'prod3','A2');
insert into tx1 values(4,'prod4','A3');
create view tx1_view
as select * from tx1 where sales_area='A1';
insert into tx1_view values(5,'prod5','A1');
select * from tx1_view;
Demo
Related
I have a RLS policy violation on a Postgres function. I believe it's because the policy relies on rows created in the function. A SELECT command is run in the function. New rows are not available because they are still in a transaction.
Here is the function:
CREATE FUNCTION public.create_message(organization_id int, content text, tags Int[])
RETURNS setof public.message
AS $$
-- insert message, return PK
WITH moved_rows AS (
INSERT INTO public.message (organization_id, content)
VALUES($1, $2)
RETURNING *
),
-- many to many relation
moved_tags AS (
INSERT INTO public.message_tag (message_id, tag_id)
SELECT moved_rows.id, tagInput.tag_id
FROM moved_rows, UNNEST($3) as tagInput(tag_id)
RETURNING *
)
SELECT moved_rows.* FROM moved_rows LEFT JOIN moved_tags ON moved_rows.id = moved_tags.message_id
$$ LANGUAGE sql VOLATILE STRICT;
Here is the policy:
CREATE POLICY select_if_organization
on message_tag
for select
USING ( message_id IN (
SELECT message.id
FROM message
INNER JOIN organization_user ON (organization_user.organization_id = message.organization_id)
INNER JOIN sessions ON (sessions.user_id = organization_user.user_id)
WHERE sessions.session_token = current_user_id()));
Ideas:
Add a field to the joining table to simplify the policy, but it violates normal form.
Return user input instead of running the SELECT, but input may be escaped and I should be able to run a SELECT command
Split into two functions. Create the message row, then add the message_tag. I'm running postgraphile, so two mutations. I have foreign key relations setup between the two. I don't know if graphile will do that automatically.
Error message:
ERROR: new row violates row-level security policy for table "message_tag"
CONTEXT: SQL function "create_message" statement 1
I receive the error when I run the function. I want the function to run successfully, insert one row in the message table, and turning the input array into rows for the message_tag table with message_tag.message_id=message.id, the last inserted id. I need a policy in place so users from that join relation only see their own organization's message_tag rows.
Here is another policy on the INSERT command. It allows INSERT if a user is logged in:
create policy insert_message_tag_if_author
on message_tag
for insert
with check (EXISTS (SELECT * FROM sessions WHERE sessions.session_token = current_user_id()));
According to the error message, this part of your SQL statement causes the error:
INSERT INTO public.message_tag (message_id, tag_id)
SELECT moved_rows.id, tagInput.tag_id
FROM moved_rows, UNNEST($3) as tagInput(tag_id)
RETURNING *
You need to add another policy FOR INSERT with an appropriate WITH CHECK clause.
I ended up adding a field to the joining table, and creating a policy with that. That way, RLS validation does not require a row which would be created in a middle of a function.
In relation to my other question "What’s the best way to audit log DELETEs?". What's the PostgreSQL equivalent of CONTEXT_INFO?
I want to log deletes using a trigger, but since I'm not using the database user as my app's logical user, I cannot log the CURRENT_USER from the trigger code as the user who deleted the record. But for INSERT and UPDATE it is possible to log the record changes from trigger since you can just add a user field in the record, say inserted_by and last_updated_by, and use these fields to log to audit table.
http://www.postgres.cz/index.php/PostgreSQL_SQL_Tricks_II#Any_other_session_variables
PostgreSQL provides more variants, please, look to this site.
The accepted answer is outdated.
In recent versions of postgresql (since 9.3 or 9.4 I think), one can set / retrieve config variables are only alive for the current session [or transaction]. Documentation Reference
example of setting a session variable:
hal=# select set_config('myvar.foo', 'bar', false);
set_config
------------
bar
(1 row)
hal=# select current_setting('myvar.foo');
current_setting
-----------------
bar
(1 row)
It is possible to use the function current_setting as one would use other set returning functions.
example:
create table customers (id int);
insert into customers values (1), (2), (3), (4);
select c.*, s.*
from customers c
left join current_setting('myvar.foo') s
on c.id = length(s)
i want to execute a Trigger after every 'DELETE' operation on the table 'table1', because i need to delete the corresponding row in 'table2', too.
So i want to type:
DELETE * FROM table1 WHERE x;
And the Trigger should do the following in addition to the above delete operation:
DELETE * FROM table2 WHERE x;
So how can i access/reuse the 'WHERE' clause inside the trigger?
P.S.: I got the same problem with the UPDATE operation, but i think the solution will be the same...
P.P.S: More context for you: I have one table which stores some encrypted data and another table which stores the encryption-keys. So if someone deletes a row in table1 i want to delete all corresponding key-rows in table2 (there could be 1 for every user who has access to this row).
Thanks for you help :)
I am trying to implement simple table queue in DB2 database. What I need
is to select and delete the row in the table at once so the multiple clients will not get the same row from the queue twice. I was looking for similiar questions but they describes the solution for another database or describes quite complicated solutions. I only need to select and delete the row at once.
UPDATE: I found on the web db2 clause like this, which look exactly like what i need - the select from delete: example:
SELECT * FROM OLD TABLE (DELETE FROM example WHERE example_id = 1)
but I am wondering if this statement is atomic if the two concurent request don't get the same result or delete the same row.
Something like this:
SELECT COL1, COL2, ... FROM TABLE WHERE TABLE_ID = value
WITH RR USE AND KEEP EXCLUSIVE LOCKS;
DELETE FROM TABLE WHERE TABLE_ID = value;
COMMIT;
If you're on DB2 for z/OS (Mainframe DB2) since version 9.1, or Linux/Unix/Windows (since at least 9.5, search for data-change-table-reference), you can use a SELECT FROM DELETE statement:
SELECT *
FROM OLD TABLE
(DELETE FROM TAB1
WHERE COL1 = 'asdf');
That would give you all the rows that were deleted.
Edit: Ooops, just saw your edit about using this type of statement. And as he said in the comment, two separate application getting the same row depends on your Isolation Level.
In relation to my other question "What’s the best way to audit log DELETEs?". What's the PostgreSQL equivalent of CONTEXT_INFO?
I want to log deletes using a trigger, but since I'm not using the database user as my app's logical user, I cannot log the CURRENT_USER from the trigger code as the user who deleted the record. But for INSERT and UPDATE it is possible to log the record changes from trigger since you can just add a user field in the record, say inserted_by and last_updated_by, and use these fields to log to audit table.
http://www.postgres.cz/index.php/PostgreSQL_SQL_Tricks_II#Any_other_session_variables
PostgreSQL provides more variants, please, look to this site.
The accepted answer is outdated.
In recent versions of postgresql (since 9.3 or 9.4 I think), one can set / retrieve config variables are only alive for the current session [or transaction]. Documentation Reference
example of setting a session variable:
hal=# select set_config('myvar.foo', 'bar', false);
set_config
------------
bar
(1 row)
hal=# select current_setting('myvar.foo');
current_setting
-----------------
bar
(1 row)
It is possible to use the function current_setting as one would use other set returning functions.
example:
create table customers (id int);
insert into customers values (1), (2), (3), (4);
select c.*, s.*
from customers c
left join current_setting('myvar.foo') s
on c.id = length(s)