Calling function which has no permission from another function - postgresql

Say we have two functions. First should check permissions and if all goes right update table. Here it is:
CREATE OR REPLACE FUNCTION public.clients_test(
_clientid int
,_comments varchar
)
RETURNS void AS
$BODY$
declare _result varchar;
BEGIN
if now()::time>'17:00'::time then
select public.clients_check_17_00() into _result;
end if;
update clients set comments=_comments where clientid=_clientid;
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 1;
ALTER FUNCTION public.clients_test(int, varchar) OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_test(int, varchar) TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_test(int, varchar) TO "RestrictedAccess";
REVOKE ALL ON FUNCTION public.clients_test(int, varchar) FROM public;
Second function doing nothing with database and exists only for security reasons. I was going to call it from the fist one. Here it is:
CREATE OR REPLACE FUNCTION public.clients_check_17_00()
RETURNS void AS
$BODY$
BEGIN
END
$BODY$
LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
COST 1;
ALTER FUNCTION public.clients_check_17_00()OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_check_17_00() TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_check_17_00() TO "FullAccess";
REVOKE ALL ON FUNCTION public.clients_check_17_00() FROM public;
Some users should have to update comments only before 17:00. So they have permissions on
public.clients_test
and have no permissions on
public.clients_check_17_00
I'd like to have the error 'You do not have permissions to execute public.clients_check_17_00', but this did not work.

This is an interesting problem, but your approach is all wrong.
Rather than checking for the time inside the function, you should manipulate permissions outside of the function. In your approach, the time is checked on every call, also at 08:02, 14:23, 16:34, etc, which is very inefficient for the obvious reasons. Instead, make two simple SQL script files, one to disable execute permissions on the function and another to enable those permissions again and have a scheduled job run those scripts at 17:00 and then presumably at 08:00 or so to re-enable execute permission. It could be as simple as:
psql -h localhost -d your_db -U postgres \
-c 'REVOKE EXECUTE ON FUNCTION clients_test(int, varchar) FROM "RestrictedAccess"'
And vice-versa for enabling the permission again. But see the documentation for parameters specific to accessing your server.
How exactly that works depends on your OS; in Linux you would use a cron job, on Windows you use Task Scheduler.
Incidentally, returning a message from a function to the session is done with RAISE NOTICE.
Another important point from your code: NEVER USE THE postgres ROLE AS OWNER OF OBJECTS OR TO RUN CODE. Apologies for EMPHASIS but this point can not be stressed enough. You should instead make a non-privileged role, preferably without login privilege, as owner of all objects (tables and functions, etc) and then explicitly set privileges for specific user roles.

Related

CREATE SUBSCRIPTION ... WITH (create_slot = true) cannot be executed from a function"}

I almost never use superuser. In rare cases that I use a function that executes the command with SECURITY DEFINER permissions. However, a subscriber cannot be executed inside a function. What is a workaround for this without create_slot= false?
CREATE OR REPLACE FUNCTION public.exec_su(text)
RETURNS void
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
BEGIN
EXECUTE $1;
END
$BODY$;
select public.exec_su('CREATE SUBSCRIPTION n1_subscription CONNECTION 'host=pg12x_aaa port=5432 user=rep password=aaa dbname=aaa' PUBLICATION n1_publication;');
Those are the two work arounds. Either do it outside of a function (and outside of a transaction block), or do it with create_slot=false. There are no extra choices.

PostrgreSQL trying to perform a plsh function

NOTE: plsh is a PostgreSQL language that executes shell commands.
I'm trying to call a plsh function from within a plpgsql function.
First there's the bigger plpgsql function:
CREATE OR REPLACE FUNCTION my_schema.big_function(my_arg character varying)
RETURNS text AS
$BODY$
BEGIN
DROP TABLE IF EXISTS backend.table_B;
CREATE TABLE backend.table_B AS
--Create a table with a SELECT with some joins,
--right now the resulting table is empty
RAISE NOTICE 'table_B created';
PERFORM my_schema.insert_from_shell(my_arg);
RETURN 'Function Ended';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Then there's the plsh function:
CREATE OR REPLACE FUNCTION my_schema.insert_from_shell(my_arg character varying)
RETURNS text AS
$BODY$
#!/bin/sh
PGPASSWORD=mypassword psql -d mydatabase -h myhost -p myport -U myuser -c "INSERT INTO myschema.table_A(mycolumn) VALUES('$1')"
$BODY$
LANGUAGE plsh VOLATILE
COST 100;
When I try to execute the following:
SELECT my_schema.big_function('test');
it never ends. On the other hand, if I comment the PERFORM line from the first function, and call both functions separately, they each end in less than a second:
SELECT my_schema.big_function('test');
SELECT my_schema.insert_from_shell('test');
There might be a problem with executing plsh functions from within plpgsql functions, but I'm not sure.
I could work around it calling both functions separately using a script outside the database, but I'd rather solve it from within the database, and call a single function.
Any pointer or help will be greatly appreciated.
Assuming that a trigger in the transaction of big_function puts a conflicting lock on table_a, as suggested in the comment, you could consider the following solution:
Make the trigger a CONSTRAINT TRIGGER (it has to be an AFTER trigger then) and declare it as DEFERRABLE INITIALLY DEFERRED. Then it will not run right when the triggering statement does, but will be deferred to the COMMIT at the end of the transaction.
By that time, insert_from_shell has finished and committed, so there will be no deadlock.

GRANT EXECUTE not granting access to related tables in function

I have user that should have access only for 2 functions, so I granted them to him:
REVOKE ALL ON FUNCTION some_func(firstid INT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION some_func(firstid INT) TO some_user;
But I'm getting weird error:
42501: permission denied for relation some_table
when trying to access my function with:
SELECT * FROM offers_get(0);
My function looks like:
CREATE OR REPLACE FUNCTION public.some_func(firstid integer)
RETURNS TABLE(/* something */)
LANGUAGE plpgsql
AS $function$
DECLARE
/* something */
BEGIN
/* some other logic */
RETURN QUERY SELECT * FROM some_table;
END;
$function$
So, access to related tables was not granted? How to grant access only for function execute?
You want to define the function using SECURITY DEFINER (see the documentation).
By default, Postgres uses the permissions of the caller, not the definer.
CREATE OR REPLACE FUNCTION public.some_func(firstid integer)
RETURNS TABLE(/* something */)
LANGUAGE plpgsql
SECURITY DEFINER
. . .

PL/pgSQL function name resolution with nested functions

I have two similar schemas in a single database with the same function names.
Each schema is owned by a role that matches the schema name.
I have issues about function name resolution with nested functions.
I was expecting that the outer function would call inner functions within the same schema, but it does not!
The name is resolved dynamically based on the search_path at run time which make some sens, but not as I would.
Here is a test case. Let say for example that the schemas and roles are named test and prod as follow.
Test schema:
CREATE ROLE test NOLOGIN;
CREATE SCHEMA test AUTHORIZATION test;
CREATE OR REPLACE FUNCTION test.inner_func() RETURNS TEXT
AS $BODY$
BEGIN
RETURN 'test function';
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION test.inner_func() OWNER TO test;
CREATE OR REPLACE FUNCTION test.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
RETURN QUERY SELECT inner_func();
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION test.outer_func() OWNER TO test;
Prod schema:
CREATE ROLE prod NOLOGIN;
CREATE SCHEMA prod AUTHORIZATION prod;
CREATE OR REPLACE FUNCTION prod.inner_func() RETURNS TEXT
AS $BODY$
BEGIN
RETURN 'prod function';
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION prod.inner_func() OWNER TO prod;
CREATE OR REPLACE FUNCTION prod.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
RETURN QUERY SELECT inner_func();
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION prod.outer_func() OWNER TO prod;
Test cases:
SET search_path=test,public;
SELECT outer_func();
> test function
SELECT prod.outer_func();
> test function <<<---- was expecting prod function
SET search_path=prod,public;
SELECT prod.outer_func();
> prod function
The test shows that function names are resolved dynamically based on the search_path at run time. Is there a way to bind inner function within the scope of a schema?
I can get such a behavior by using SECURITY DEFINER functions with dynamic SQL and CURRENT_USER, but I am looking for something more straightforward.
The clean solution is to either schema-qualify the function:
CREATE OR REPLACE FUNCTION test.outer_func()
RETURNS SETOF text AS
$func$
BEGIN
RETURN QUERY SELECT test.inner_func();
END
$func$ LANGUAGE plpgsql; -- no quotes!
Or you explicitly set the search_path per function. You can set configuration parameters this way:
CREATE OR REPLACE FUNCTION test.outer_func()
RETURNS SETOF text AS
$func$
BEGIN
RETURN QUERY SELECT inner_func();
END
$func$ LANGUAGE plpgsql SET search_path = test, pg_temp;
Customize the search_path to your needs, possibly add public to the list. I put pg_temp at the end, so objects in the temporary schema cannot hide persisted objects. (But that's not applicable for functions.) Similar to what's explained in the manual for SECURITY DEFINER functions.
I would not advise to rely on the user setting the proper search_path. That would only make sense for "public" functions, it wouldn't be consistent with your design. Why create separate functions and then still have to rely on user settings after all? You could have a single function in the public schema to begin with, but I would not got that route in any case. Very confusing and error prone.
Also, PL/pgSQL executes statements like prepared statements internally. every time you change the search_path, all "prepared" statements from plpgsql functions have to be de-allocated, which is not helping to optimize performance.
Actually, your test case in the question only works if you set the search_path first:
SET search_path=test,public;
Else you get an error when trying to create
CREATE OR REPLACE FUNCTION test.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
RETURN QUERY SELECT inner_func();
...
ERROR: function inner_func() does not exist
Syntax checks are run against the current search_path at creation time - unless you provide the search_path as suggested. That was fixed 2010 after I reported a bug.
Details for search_path:
How does the search_path influence identifier resolution and the "current schema"
How can I fake inet_client_addr() for unit tests in PostgreSQL?
And don't quote the language name. It's an identifier.

Execute triggers function of another schema on the actual chema

my problem is easy to explain with an example: I have a 'common' schema (the public one?) where I store common data between a clustered application.
For every instance of my application, I have a role (used as the application user).
And i have a common role, app_users, with read-only privileges on the common schema, and every application role is a member of app_users.
Now my problem is: how can i set a trigger on the app_a scheme that execute a function (procedure) in the common scheme, but affect the (and only the) app_a tables?
I mean:
// common_scheme, dummy function to emulate the mysql on update = now()
CREATE OR REPLACEFUNCTION update_etime() RETURNS TRIGGER AS $$
BEGIN
NEW.etime = date_part('epoch'::text, now())::int;
RETURN NEW;
END;
$$ language plpgsql;
// now, in the app_foo scheme, i have the table:
CREATE TABLE foo_table (fid serial not null primary key unique, label char(25));
// and the trigger:
CREATE TRIGGER foo_table_update_etime BEFORE UPDATE ON foo_talbe FOR EACH ROW EXECUTE PROCEDURE update_etime();
// ERROR: function update_etime() does not exist
CREATE TRIGGER foo_table_update_etime BEFORE UPDATE ON foo_talbe FOR EACH ROW EXECUTE PROCEDURE common_scheme.update_etime();
// ERROR: function common_scheme.update_etime() does not exist
The user that will access app_foo has the execute privilege on update_etime() function in common_schema.
Any idea?
I've googled around but the only solution I fount to call functions from other schemas is something like execute 'select * from ' || schema_name || '.table_name'; but i dont think this will do the trick in my case, becose the function must work with the 'local' scheme.
Your second set of syntax should work... the one with "EXECUTE PROCEDURE common_scheme.update_etime();"
If it isn't finding the function, I'd guess that you either have created it in a different schema than you think it is in, or you haven't created it at all (and note, your example create syntax has a bug, no space between "replace" and "function", which would cause an error when trying to create the function. Try doing a:
\df *.update_etime
As superuser to verify the function exists and is in the location you think it is in. HTH.