In PostgreSQL 9.0 I have this PLPGSQL anonymous code block:
DO $$
DECLARE
bigobject integer;
BEGIN
SELECT lo_creat(-1) INTO bigobject;
ALTER LARGE OBJECT bigobject OWNER TO postgres;
INSERT INTO files (id, "mountPoint", data, comment) VALUES (15, '/images/image.png', bigobject, 'image data');
SET search_path = pg_catalog;
SELECT pg_catalog.lo_open(bigobject, 131072);
SELECT pg_catalog.lowrite(0, '\\x000001000100101010000000000028010000160000002800000010000000200000000100040');
SELECT pg_catalog.lo_close(0);
REVOKE ALL ON LARGE OBJECT bigobject FROM PUBLIC;
REVOKE ALL ON LARGE OBJECT bigobject FROM postgres;
GRANT ALL ON LARGE OBJECT bigobject TO postgres;
GRANT ALL ON LARGE OBJECT bigobject TO "com.ektyn.eshops.myuser";
END
$$;
but it fails:
ERROR: syntax error at or near "bigobject"
LINE 6: ALTER LARGE OBJECT bigobject OWNER TO postgres;
^
********** Error **********
ERROR: syntax error at or near "bigobject"
SQL state: 42601
Character: 103
and I can't find mistake in code.
There must be an oid constant in ALTER LARGE OBJECT oid .... Try this workaround:
DO $$
DECLARE
bigobject integer;
BEGIN
SELECT lo_creat(-1) INTO bigobject;
EXECUTE 'ALTER LARGE OBJECT ' || bigobject::text || ' OWNER TO postgres';
...
The same also applies to GRANT and REVOKE, of course.
In addition to what #klin already cleared up, you cannot use SELECT without a target in PL/pgSQL code. Replace it with PERFORM in those calls.
Aside: Using "com.ektyn.eshops.myuser" as name for a role is a terrible idea. Use legal, lower case identifiers that don't have to be double-quoted.
This is an artifact of the fact that PostgreSQL has two completely different kinds of SQL statements internally - plannable (SELECT, INSERT, UPDATE, and DELETE) and unplannable (everything else) statements.
Only plannable statements support query parameters.
PL/pgSQL implements variable substitutions into statements, like your bigobject, using query parameters.
Because they aren't supported for non-plannable statements, no substitution is performed. So PL/pgSQL tries to execute the statement literally, as if you'd typed:
ALTER LARGE OBJECT bigobject OWNER TO postgres;
directly at the psql prompt. It does not detect this as an error.
To work around this, use EXECUTE ... FORMAT, e.g.
EXECUTE format('ALTER LARGE OBJECT %s OWNER TO postgres', bigobject);
See this related answer about COPY.
Related
I'm using a client library that only accepts SQL strings that are compile-time constant, in order to prevent SQL injection attacks. And I wanted to execute some GRANT statements for a set of tables and a user.
I tried
GRANT SELECT ON $1 TO $2
and passing the table and user names as bound parameters. But that fails with
syntax error at or near "$1"
Not being able to pass in a tablename as a bound parameter is understandable (you can't use SELECT columns FROM $1 for instance), and with a bit of work, I can make the tablenames compile-time constants. But changing the command to
GRANT SELECT ON MyTable to $1
and passing just the username as a bound parameter also fails. Which is more of an issue: whereas the tablenames can be hard-coded with a bit of work, the username is only known at runtime.
Is there a way to pass the username as a bound parameter, or do I need to bypass my client library in order to GRANT permissions to a run-time-defined username?
CREATE OR REPLACE FUNCTION test_grant (_role text)
RETURNS void
AS $$
DECLARE
_sql text := '';
BEGIN
_sql := 'GRANT SELECT ON a to ' || quote_ident(_role) || ' GRANTED BY current_user ';
RAISE NOTICE '%', _sql;
EXECUTE _sql;
RAISE NOTICE '% granted table a to %', CURRENT_USER, _role;
END
$$
LANGUAGE plpgsql
STRICT.
You can also make the table as function input argument. quote_ident is used for identifiers quoting. In GRANT SELECT ON MyTable to $1 you hope to make sure $1 is a identifiers rather than some string. Because if $1 string then the whole command can be:
GRANT SELECT ON MyTable to public;
GRANT SELECT ON MyTable to role_a WITH GRANT OPTION;
So the above function can solve these problem.
The only statements that can use parameters are INSERT, UPDATE, DELETE and SELECT. GRANT cannot use parameters; you will have to build a statement dynamically.
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.
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.
I try to ALTER a function with a new user and I get the error:
ERROR: must be owner of function ACases
********** Error **********
ERROR: must be owner of function ACases
SQL state: 42501
What permission do I have to give to a user so he can ALTER that function?
The only way I found was to make the user the OWNER of the function.
But if that is the case, only one user (owner) can ALTER the function. So how would I change the OWNER for all functions?
CREATE OR REPLACE FUNCTION public."ACases"(caseid integer)
RETURNS boolean AS
$BODY$
DECLARE
BEGIN
RETURN FALSE;
END;
$BODY$
LANGUAGE plpgsql;
ALTER FUNCTION public."ACases"(integer) OWNER TO postgres;
GRANT ALL PRIVILEGES ON FUNCTION public."ACases"(integer) TO user_name;
The manual on ALTER FUNCTION is clear on that:
You must own the function to use ALTER FUNCTION. To change a function's
schema, you must also have CREATE privilege on the new schema.
To alter the owner, you must also be a direct or indirect
member of the new owning role, and that role must have CREATE
privilege on the function's schema. (These restrictions enforce that
altering the owner doesn't do anything you couldn't do by dropping and
recreating the function. However, a superuser can alter ownership of
any function anyway.)
Bold emphasis mine.
You also need a couple of basic privileges to create functions. Per documentation:
To be able to define a function, the user must have the USAGEprivilege on the language.
...
To be able to create a function, you must have USAGE privilege on the argument types and the return type.
The simple solution would be make changes to functions as superuser. (Default superuser is postgres, but any user can be made superuser.)
If you really need to change ownership on all functions, this would do the trick:
SELECT string_agg('ALTER FUNCTION '
|| quote_ident(n.nspname) || '.'
|| quote_ident(p.proname) || '('
|| pg_catalog.pg_get_function_identity_arguments(p.oid)
|| ') OWNER TO foo;'
, E'\n') AS _sql
FROM pg_catalog.pg_proc p
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public';
-- AND p.relowner <> (SELECT oid FROM pg_roles WHERE rolname = 'foo')
-- AND p.proname ~~ 'f_%'
Restricted to the public schema.
For more details and explanation refer to this more complete answer on dba.SE.
Also closely related:
DROP FUNCTION without knowing the number/type of parameters?
This query returns the OID of the function whose name and signature is supplied:
select 'myfunc(signature)'::regprocedure::oid;
But is there something in PostgreSQL plpgsql like a myNameAndSignature() function so we could use dynamic sql to build a statement that gets the OID of the function and then creates a temporary table with the OID appended to the name of the temp table?
The statement to execute dynamically is:
create temp table TT17015
I'm new to PostgreSQL, and maybe there's a better way to handle naming of temporary tables so the functions that use temp tables, and call each other, don't get the error that a particular temp table it is trying to delete is in use elsewhere?
Using the OID of a function does not necessarily prevent a naming conflict. The same function could be run multiple times in the same session.
If you are in need of a unique name, use a SEQUENCE. Run once in your database:
CREATE SEQUENCE tt_seq;
Then, in your plpgsql function or DO statement:
DO
$$
DECLARE
_tbl text := 'tt' || nextval('tt_seq');
BEGIN
EXECUTE 'CREATE TEMP TABLE ' || _tbl || '(id int)';
END
$$
Drawback is that you have to use dynamic SQL for dynamic identifiers. Plain SQL commands do not accept parameters for identifiers.