PostgreSQL: trigger function to grant privileges in a loop - postgresql

I have a table users:
CREATE TABLE users (
id serial primary key,
name text,
status text,
tier text,
project text[]);
Column project is an array of schema names. I'd like to create a trigger function that grants a set of privileges to a user when a user is added to the table. A single user can have access to multiple schemas, so my idea is to loop through an array of schema names.
CREATE OR REPLACE FUNCTION common.add_user()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
u_name text = NEW.name;
m text;
project text[] = NEW.project;
begin
FOREACH m IN ARRAY project LOOP
EXECUTE 'GRANT USAGE ON SCHEMA ' || m || ' TO ' || u_name;
END LOOP;
RETURN NEW;
end;
$BODY$;
And the trigger:
CREATE TRIGGER add_user
BEFORE INSERT
ON common.users
FOR EACH ROW
EXECUTE PROCEDURE common.add_user();
When I add an entry to common.users table:
INSERT INTO common.users (name, project) VALUES ('user01', '{"schema01", "schema02"}');
It executes without errors, an entry is added to the table but no privileges are granted. Of course I made sure that the insert query is executed by a user that can actually grant those privileges.

Actually, I was pretty close, maybe an issue with execute syntax inside loop? Anyway, this does exactly what I want:
CREATE OR REPLACE FUNCTION common.add_user()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
u_name text = NEW.name;
m text;
project text[] = NEW.project;
begin
EXECUTE 'GRANT CONNECT ON DATABASE x TO ' ||u_name;
EXECUTE 'GRANT USAGE ON SCHEMA public TO ' ||u_name;
EXECUTE 'GRANT USAGE ON SCHEMA common TO ' ||u_name;
EXECUTE 'GRANT SELECT ON ALL TABLES IN SCHEMA common TO ' ||u_name;
FOREACH m IN ARRAY project LOOP
EXECUTE format('GRANT USAGE ON SCHEMA %1$s to %2$s', m, u_name);
EXECUTE format('GRANT SELECT, UPDATE, DELETE, INSERT ON ALL TABLES IN SCHEMA %1$s to %2$s', m, u_name);
EXECUTE format('GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA %1$s to %2$s', m, u_name);
EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA %1$s to %2$s', m, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s GRANT SELECT, UPDATE, INSERT, DELETE ON TABLES to %2$s', m, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s GRANT EXECUTE ON FUNCTIONS to %2$s', m, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s GRANT USAGE, SELECT ON SEQUENCES to %2$s', m, u_name);
END LOOP;
RETURN NEW;
end;
$BODY$;
I also wanted to be able to easily manage access to projects (schemas) by modifying value in column project. Together with RLS on tables managing privileges is now pretty simple.
CREATE OR REPLACE FUNCTION common.update_user()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
u_name text = OLD.name;
m text;
n text;
deleted text[];
added text[];
begin
deleted := ARRAY(select * from unnest(OLD.project) except select * from unnest(NEW.project));
added := ARRAY(select * from unnest(NEW.project) except select * from unnest(OLD.project));
FOREACH m IN ARRAY deleted LOOP
Raise warning 'REVOKED ACCESS ON PROJECT % FROM %', m, u_name;
EXECUTE format('REVOKE USAGE ON SCHEMA %1$s FROM %2$s', m, u_name);
EXECUTE format('REVOKE ALL ON ALL TABLES IN SCHEMA %1$s FROM %2$s', m, u_name);
EXECUTE format('REVOKE USAGE, SELECT ON ALL SEQUENCES IN SCHEMA %1$s FROM %2$s', m, u_name);
EXECUTE format('REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA %1$s FROM %2$s', m, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s REVOKE ALL ON TABLES FROM %2$s', m, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s REVOKE EXECUTE ON FUNCTIONS FROM %2$s', m, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s REVOKE USAGE, SELECT ON SEQUENCES FROM %2$s', m, u_name);
END LOOP;
FOREACH n IN ARRAY added LOOP
Raise warning 'GRANTED ACCESS ON PROJECT % TO %', n, u_name;
EXECUTE format('GRANT USAGE ON SCHEMA %1$s to %2$s', n, u_name);
EXECUTE format('GRANT SELECT, UPDATE, DELETE, INSERT ON ALL TABLES IN SCHEMA %1$s to %2$s', n, u_name);
EXECUTE format('GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA %1$s to %2$s', n, u_name);
EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA %1$s to %2$s', n, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s GRANT SELECT, UPDATE, INSERT, DELETE ON TABLES to %2$s', n, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s GRANT EXECUTE ON FUNCTIONS to %2$s', n, u_name);
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %1$s GRANT USAGE, SELECT ON SEQUENCES to %2$s', n, u_name);
END LOOP;
RETURN NEW;
end;
$BODY$;

Related

Using a variable on a PostgreSQL function to drop a schema

I'm trying to create a function on PostgreSQL, and I have some problem to use a local variable. Here's my code :
DECLARE query RECORD;
DECLARE schema_name TEXT;
BEGIN
FOR query IN SELECT * FROM context WHERE created_at + make_interval(days => duration) <= CURRENT_TIMESTAMP LOOP
SELECT lower(quote_ident(query.title)) INTO schema_name;
DROP SCHEMA schema_name CASCADE;
DELETE FROM context WHERE id = query.id;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
The select and delete queries work fine, and I've made a test returning the value of schema_name variable, and it's OK.
My problem is with this line :
DROP SCHEMA schema_name CASCADE;
I get an error as "the schema 'schema_name' doesn't exist".
I'd really appreciate any ideas for how to use this variable to do the drop query.
You need dynamic SQL for this:
DECLARE
query RECORD;
BEGIN
FOR query IN SELECT id, lower(title) as title
FROM context
WHERE created_at + make_interval(days => duration) <= CURRENT_TIMESTAMP
LOOP
execute format('DROP SCHEMA %I CASCADE', query.title);
DELETE FROM context WHERE id = query.id;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
I also removed the unnecessary SELECT statement to make the title lower case, this is better done in the query directly.
Also: variable assignment is faster with := then with select, so:
schema_name := lower(quote_ident(query.title));
would be better if the variable was needed.

Why cannot create partitioning table

I'm trying to create simple table with partitions.
this is my command:
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
this is the error I got:
SQL Error [42601]: ERROR: syntax error at or near "PARTITION"
Unable to understand with is the problem..
I am using PostgreSQL 9.6.3
"Declarative table partitioning", that is partitioning as a first-class feature of the DBMS with its own syntax, was added in PostgreSQL 10.
In earlier versions, you can achieve the same effect with a bit more effort using "table inheritance". There is a page in the manual describing how to do this manually, summarised as:
Create the "master" table, from which all of the partitions will inherit.
Create several "child" tables that each inherit from the master table.
Add table constraints to the partition tables to define the allowed key values in each partition.
For each partition, create an index on the key column(s), as well as any other indexes you might want.
Optionally, define a trigger or rule to redirect data inserted into the master table to the appropriate partition.
Ensure that the constraint_exclusion configuration parameter is not disabled in postgresql.conf. If it is, queries will not be optimized as desired.
To make this easier, if you can't upgrade to version 10, you can use an extension such as pg_partman which gives you additional functions for setting up and managing partition sets.
Here is the example of automatically creating monthly partition for 9.6 version, may be compatible with other versions:
`----------Function to create partitions by system------------------
CREATE OR REPLACE FUNCTION schema.insert_function()
RETURNS TRIGGER
AS $$
DECLARE
partition_date TEXT;
partition_schema TEXT;
partition_name TEXT;
start_of_month TEXT;
end_of_month TEXT;
BEGIN
partition_date := to_char(NEW.created_dtm,'YYYY_MM');
partition_schema := 'temp';
partition_name := 'patition_name_' || partition_date;
start_of_month := to_char((NEW.created_dtm),'YYYY-MM'|| '-01');
end_of_month := to_char((NEW.created_dtm + interval '1 month'),'YYYY-MM'|| '-01');
IF NOT EXISTS
(SELECT 1
FROM information_schema.tables
WHERE table_name = partition_name
AND table_schema = partition_schema)
THEN
EXECUTE 'CREATE TABLE '|| partition_schema ||' . '|| partition_name ||' (check (created_dtm >= ''' || start_of_month || ''' and created_dtm < ''' || end_of_month || ''' ), ' || 'LIKE master_schema.master_table INCLUDING ALL) INHERITS (master_schema.master_table)';
EXECUTE format('ALTER TABLE %I.%I OWNER TO role1', partition_schema, partition_name);
EXECUTE format('GRANT SELECT ON TABLE %I.%I TO read_only_role', partition_schema, partition_name);
EXECUTE format('GRANT INSERT, SELECT, UPDATE, DELETE ON TABLE %I.%I TO read_write_role', partition_schema, partition_name);
EXECUTE format('CREATE INDEX ON %I.%I(column1, column2, column3)', partition_schema, partition_name);
EXECUTE format('CREATE UNIQUE INDEX ON %I.%I(column4)', partition_schema, partition_name);
........
RAISE NOTICE 'A partition has been created %.%', partition_schema, partition_name;
RAISE NOTICE 'All necessary indices are created on %.%', partition_schema, partition_name;
END IF;
EXECUTE format('INSERT INTO %I.%I VALUES($1.*)', partition_schema, partition_name) using NEW;
RETURN NULL;
END
$$
LANGUAGE plpgsql;
ALTER FUNCTION schema.insert_function()
OWNER TO super_user;
-----------------------------------------Trigger on master table--------------
CREATE TRIGGER insert_trigger
BEFORE INSERT
ON master_schema.master_table
FOR EACH ROW
EXECUTE PROCEDURE schema.insert_function();`

How to grant permission using FUNCTION in PostgreSQL 9.4.5

I need to grant permissions to database users. i need to use FUNCTION to perform this.
grant all on schema schema1 to user1;
grant all on schema schema1 to user2;
grant all on schema schema1 to user3;
schema names and user names should be picked up from these sql query output:
select nspname from pg_namespace;
schema1
schema2
schema3
select usename from pg_user;
user1
user2
user3
could someone help me how to write a function to achive this ? i am using Postgresql 9.4.5.
Many Thanks,
It could be something like this:
CREATE OR REPLACE FUNCTION grant_all()
RETURNS VOID AS
$$
DECLARE
user TEXT;
schema TEXT;
BEGIN
FOR user IN (SELECT usename FROM pg_user)
LOOP
FOR schema IN (SELECT nspname FROM pg_namespace)
LOOP
EXECUTE format('GRANT ALL ON SCHEMA %s to %s', schema, user);
RAISE NOTICE 'Granted all on % to %', schema, user;
END LOOP;
END LOOP;
END
$$
LANGUAGE plpgsql;

Alter table across all postgresql schemas

If I have several schemas that contain the same table, is there a way for me to make an update to all the tables at once? For example, if I have 3 schemas that each have a user table with columns first_name, last_name, email and I want to add a column for phone_num for each user table in all 3 schemas, is there a way i can do it? I could not find a way in the postgresql docs...
Thanks in advance!
I think you want to alter table and not to update the table. If yes then below code will work for you,
-- Function: alter_table()
-- DROP FUNCTION alter_table();
CREATE OR REPLACE FUNCTION alter_table()
RETURNS integer AS
$BODY$
DECLARE
v_schema text;
BEGIN
FOR v_schema IN
SELECT quote_ident(nspname)
FROM pg_namespace n
WHERE nspname !~~ 'pg_%'
AND nspname <> 'information_schema'
LOOP
EXECUTE 'SET LOCAL search_path = ' || v_schema;
ALTER TABLE "user" ADD COLUMN show_price boolean NOT NULL DEFAULT TRUE;
END LOOP;
return 1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION alter_table()
OWNER TO postgres;

PGsql : Grant every single right to a user on a schema

I can't figure out how to give every single right to a specific user, I want a user to have every single right on a schema:
inserts, deletes, updates, selects, ... on existing tables
I have tried doing :
GRANT ALL PRIVILEGES ON SCHEMA schema to "user";
GRANT ALL ON SCHEMA schema to "local_518561";
GRANT ALL PRIVILEGES ON table schema.table to "user";
GRANT ALL ON table schema.table to "user";
The querys return succesfull, but every time I use the other user I get insuffiecent permissions error.
GRANT ALL PRIVILEGES ON SCHEMA schema_name TO role_name;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA schema_name TO role_name;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA schema_name TO role_name;
If you are using a version of PostgreSQL < 9, you can use the following store procedures to manage permissions of tables and sequences:
CREATE OR REPLACE FUNCTION grantTablesOfSchema (user VARCHAR,
permissions VARCHAR, schema VARCHAR) RETURNS VARCHAR AS
$body$
DECLARE
regActual RECORD;
numTables INTEGER;
BEGIN
numTables := 0;
FOR regActual IN
SELECT tablename FROM pg_tables WHERE schemaname = schema
LOOP
numTables := numTables + 1;
EXECUTE 'GRANT ' || permissions || ' ON ' || schema || '.' || regActual.tablename || ' TO ' || user;
END LOOP;
RETURN 'Tables: ' || numTables::VARCHAR;
END;
$body$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION grantSequencesOfSchema (user VARCHAR,
permissions VARCHAR, database VARCHAR, schema VARCHAR) RETURNS VARCHAR AS
$body$
DECLARE
regActual RECORD;
numSequences INTEGER;
BEGIN
numSequences := 0;
FOR regActual IN
SELECT sequence_catalog, sequence_schema, sequence_name
FROM information_schema.sequences
WHERE sequence_catalog = database AND sequence_schema = schema
LOOP
numSequences := numSequences + 1;
EXECUTE 'GRANT ' || permissions || ' ON ' || schema || '.' || regActual.sequence_name || ' TO ' || user;
END LOOP;
RETURN 'Sequences: ' || numSequences::VARCHAR;
END;
$body$
LANGUAGE 'plpgsql';
And a example of use:
CREATE USER user1 WITH PASSWORD 'user1#user1?user1';
GRANT CONNECT ON DATABASE database1 TO user1;
GRANT USAGE ON SCHEMA schema1 TO user1;
SELECT * FROM grantTablesOfSchema ('user1', 'SELECT, UPDATE, INSERT, DELETE', 'schema1');
SELECT * FROM grantSequencesOfSchema ('user1', 'ALL', 'database1', 'schema1');
If, on the contrary your version of PostgreSQL is >= 9:
GRANT ALL ON ALL SEQUENCES IN SCHEMA schema1 TO user1;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA schema1 TO user1;
The answer lies in the sequences, if you do not give rights to the table AND the sequence (if any) than you cannot insert.