PostgreSQL mini cookbook: Controlling Access to your data
Restricting users to certain records or certain fields with views
The accounts team don’t need to see unfilled orders and the sales team don’t need to see bank details. You want to enforce this.
Solution
Create one view for each group of users, and deny them access to the underlying table(s).
Discussion
You can create alternative views of your database with
CREATE VIEW <viewname> AS SELECT....
To a user this appears to be a table,
but you can control what underlying data shows through.
The SELECT
query can be anything you like - it can involve JOINs,
UNION
s or GROUP BY
if you want.
So, if the accounts department only needs to see completed orders:
CREATE VIEW accts_orders
AS SELECT * FROM orders
WHERE ord_delivered IS NOT NULL;
You then need to make sure that accounts has access to this view and not to the underlying table:
GRANT ALL ON accts_orders TO accounts;
REVOKE ALL ON orders FROM accounts;
You will need to be a superuser (postgres) or the owner of the database objects to do this.
Possible permissions are: SELECT
, INSERT
, UPDATE
, DELETE
, RULE
, ALL
.
Letting users update views
You want to allow users to make restricted updates using the view you have defined
Solution
Add rules to the view to redirect changes to the underlying table(s). Remember that Postgres won’t allow updates/inserts unless there is an unqualified rule that catches all changes.
Discussion
To let users update a view you need to provide appropriate rules
to Postgres with the CREATE RULE
construct.
In the example of we want the accounts department to be able to alter orders but not delete them (goods have been shipped but not necessarily paid for).
CREATE RULE accts_orders_upd_rule0
AS ON UPDATE TO accts_orders
DO INSTEAD NOTHING;
CREATE RULE accts_orders_upd_rule
AS ON UPDATE TO accts_orders
WHERE NEW.ord_paid != OLD.ord_paid
OR OLD.ord_paid IS NULL
DO INSTEAD UPDATE orders
SET ord_paid=NEW.ord_paid
WHERE ord_id=NEW.ord_id;
The first rule looks simple enough - instead of
updating accts_orders do nothing instead.
Why do we need it, since you can’t update rules anyway?
Postgres won’t try to update a view unless
there is a rule that covers all eventualities.
Since the second rule has a WHERE
clause we need an empty rule to
cover all possible updates a user can attempt.
This second rule captures updates to the accts_orders view and translates them into update queries onto accts_orders. In addition it only updates the ord_paid field, ignoring others.
The NEW
keyword represents the values the record is going to be updated with.
The OLD
keyword represents the record before the update takes place.
What about permissions? The rule is executed with the permissions of its owner not the person who ends up executing it. So - if accounts don’t have the permissions to access the orders table that’s alright, as long as the user who creates the rule does.
Grant privileges on all tables in one go
You want to do something like GRANT ALL ON 'foo%' TO webuser
.
Solution
You can’t do this directly from psql
- you’ll need to
write a shell-script or plpgsql
function to automate it for you.
Discussion
You can use the following shell script:
###### start of grantall.sh #######
#!/bin/ash
SQL="SELECT relname FROM pg_class WHERE (relkind = 'r' OR relkind = 'S')
AND relname !~ '^pg_' ORDER BY relname"
OBJ=`psql -t -c "${SQL}" $1`
# OBJ=`echo ${OBJ} | sed 's/EOF//g'`
OBJ=`echo ${OBJ} | sed 's/ /, /g'`
# SQL="REVOKE ALL ON ${OBJ} FROM PUBLIC"
SQL="GRANT ALL ON ${OBJ} TO PUBLIC"
echo ${SQL}
psql -c "${SQL}" $1
###### end of grantall.sh #######
Or you can use this plpgsql
function:
SELECT pgx_grant('ALL','comp%','nobody');
-- pgx_grant(PRIVILEGE, TABLES, USER)
-- Grants PRIVILEGE to USER on objects like TABLES%
-- Grants to tables, views and sequences.
--
CREATE FUNCTION pgx_grant(text,text,text) returns int4 as '
DECLARE
priv ALIAS FOR $1;
patt ALIAS FOR $2;
user ALIAS FOR $3;
obj record;
num integer;
BEGIN
num:=0;
FOR obj IN SELECT relname FROM pg_class
WHERE relname LIKE patt || \'%\' AND relkind in (\'r\',\'v\',\'S\') LOOP
EXECUTE \'GRANT \' || priv || \' ON \' || obj.relname || \' TO \' || user;
num := num + 1;
END LOOP;
RETURN num;
END;
' language 'plpgsql';
References
Feedback
Was this page helpful?
Glad to hear it!
Sorry to hear that.