How to change password of a newly created user using variable in postgresql 9.5 SP - postgresql

I have created a stored procedure (function) for my postgresql 9.5 database to create users. code below:
CREATE FUNCTION add_user (name text, cred text) RETURNS void AS $$
DECLARE
name text := 'abc123';
BEGIN
CREATE USER name WITH LOGIN NOSUPERUSER INHERIT NOCREATEDB
NOCREATEROLE NOREPLICATION;
GRANT CONNECT ON DATABASE test TO name;
GRANT USAGE ON SCHEMA test_schema TO name;
GRANT SELECT ON test_schema.test_table TO name;
ALTER USER name WITH PASSWORD cred;
END;
$$ LANGUAGE plpgsql;
This fails for altering password for the user. The moment I replace "cred" with hardcoded password, it works.
So, ALTER USER name WITH PASSWORD 'userpassword' works,
but NOT with any variable (cred).
Is there anyway we can use variable instead of hard coding the password?
Tried running another function, just for password

it does not work because the user is called "name", it does not take parameter value
As I mentioned before, you must do it dynamically all the code to create and grant
the function should look like this:
CREATE or REPLACE FUNCTION add_user (name text, cred text) RETURNS void AS $$
DECLARE
name_temp text := 'abc123';
BEGIN
EXECUTE ' CREATE USER '|| $1 ||' WITH LOGIN NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION';
EXECUTE ' GRANT CONNECT ON DATABASE test TO ' || $1;
EXECUTE ' GRANT USAGE ON SCHEMA test_schema TO '|| $1;
EXECUTE 'GRANT SELECT ON test_schema.test_table TO '|| $1;
EXECUTE 'ALTER USER ' || $1 || ' WITH PASSWORD '''|| $2||'''';
END;
$$ LANGUAGE plpgsql;
I recommend using the format function to form the texts of CREATE, GRANT and ALTER, for safety issues, example:
EXECUTE format(' GRANT CONNECT ON DATABASE test TO %I ', $1);
other point, consider variable name diferent of parameter name
thist function work for me

You must execute the ALTER USER with dynamic SQL, for example:
EXECUTE 'ALTER USER || name || ' WITH PASSWORD '|| cred;
I believe that you must execute dynamic SQL in whole code of CREATE and GRANT commands, because I think always will create the user called "name"

Related

GRANT statements with bound parameters

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.

Postgres: how to declare user type variable?

I want to write a small script to grant permissions. The script works if I type in the user directly into each query but it's more efficient to use a variable but I cannot find what type to declare it as.
DO $$
DECLARE
usr ??? := myuser;
BEGIN
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public
TO usr;
GRANT ALL PRIVILEGES ON SCHEMA public to usr;
END $$
You need dynamic SQL for that:
DO
$$DECLARE
usr text := 'myuser' ;
BEGIN
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE '
'ON ALL TABLES IN SCHEMA public '
'TO %I',
usr);
END;$$;
The second statement works similarly.

Postgres function using DDL statement

I am trying to create function in Postgres which can automate user creation process but it doesn;t accept parameter in DDL statement.
CREATE OR REPLACE FUNCTION AUTOUSER (uname varchar(20))
RETURNS TEXT AS $$
DECLARE
nm varchar(20);
BEGIN
nm=$1;
CREATE USER nm WITH PASSWORD 'Iash12';
GRANT ALL ON DATABASE iashdb TO nm;
GRANT ALL ON ALL TABLES IN SCHEMA public TO nm;
RETURN CONCAT(nm,' Created');
END;
$$
LANGUAGE plpgsql;
Above function create user as 'nm' instead of passed parameter name however RETURN statement showing correct result. Thanks in advance,
You need to use dynamic SQL and you need to quote the parameters properly. The easiest way is to use the format() function with the appropriate placeholders:
CREATE OR REPLACE FUNCTION AUTOUSER (uname varchar(20))
RETURNS TEXT AS $$
BEGIN
execute format('CREATE USER %I WITH PASSWORD %L', uname, 'Iash12');
execute format('GRANT ALL ON DATABASE iashdb TO %I', uname);
execute format('GRANT ALL ON ALL TABLES IN SCHEMA public TO %I', uname);
RETURN CONCAT(uname,' Created');
END;
$$
LANGUAGE plpgsql;
The placeholder %I properly quotes SQL identifiers. The placeholder %L properly deals with string literals.

How can a set PostgreSQL schema on the fly using Doctrine and Symfony?

I'm trying to create a multi tenent app using Symfony 2.6 and PostgreSQL schemas (namespaces). I would like to know how can I change some entity schema on the pre persist event?
I know that it's possible to set the schema as annotation #Table(schema="schema") but this is static solution I need something more dynamic!
The purpose using PostgreSQL is take advantage of schemas feature like:
CREATE TABLE tenant_1.users (
# table schema
);
CREATE TABLE tenant_2.users (
# table schema
);
So, if I want only users from tenant_2 my query will be something like SELECT * FROM tenant_2.users;
This way my data will be separated and I will have only one database to connect and maintain.
$schema = sprintf('tenant_%d', $id);
$em->getConnection()->exec('SET search_path TO ' . $schema);
You might also want to involve PostgreSQL's row level security instead - that way you can actually prevent the tenant from accessing the data, not just hiding it by prefixing a schema path.
Check this one out: https://www.tangramvision.com/blog/hands-on-with-postgresql-authorization-part-2-row-level-security. I just set a working tenant separation with the information on that page and I'm quite excited about it.
In my case, my tenants are called organisations, and some (not all) tables have an organisation_id that permanently binds a row to it.
Here is a version of my script I run during a schema update, which finds all tables with column organisation_id and enables the row level security with a policy that only shows rows that an org owns, if the org role is set:
CREATE ROLE "org";
-- Find all tables with organisation_id and enable the row level security
DO $$ DECLARE
r RECORD;
BEGIN
FOR r IN (
SELECT
t.table_name, t.table_schema, c.column_name
FROM
information_schema.tables t
INNER JOIN
information_schema.columns c ON
c.table_name = t.table_name
AND c.table_schema = t.table_schema
AND c.column_name = 'organisation_id'
WHERE
t.table_type = 'BASE TABLE'
AND t.table_schema != 'information_schema'
AND t.table_schema NOT LIKE 'pg_%'
) LOOP
EXECUTE 'ALTER TABLE ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' ENABLE ROW LEVEL SECURITY';
EXECUTE 'DROP POLICY IF EXISTS org_separation ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name);
EXECUTE 'CREATE POLICY org_separation ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || 'FOR ALL to org USING (organisation_id = substr(current_user, 5)::int)';
END LOOP;
END $$;
-- Grant usage on all tables in all schemas to the org role
DO $do$
DECLARE
sch text;
BEGIN
FOR sch IN (
SELECT
schema_name
FROM
information_schema.schemata
WHERE
schema_name != 'information_schema'
AND schema_name NOT LIKE 'pg_%'
) LOOP
EXECUTE format($$ GRANT USAGE ON SCHEMA %I TO org $$, sch);
EXECUTE format($$ GRANT SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA %I TO org $$, sch);
EXECUTE format($$ GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA %I TO org $$, sch);
EXECUTE format($$ ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT SELECT, UPDATE ON SEQUENCES TO org $$, sch);
EXECUTE format($$ ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT INSERT, SELECT, UPDATE, DELETE ON TABLES TO org $$, sch);
END LOOP;
END;
$do$;
Step two, when I create a new organisation, I also create a role for it:
CREATE ROLE "org:86" LOGIN;
GRANT org TO "org:86";
Step three, at the beginning of every request that should be scoped to a particular organisation, I call SET ROLE "org:86"; to enable the restrictions.
There is much more happening around what we do with all of this, but the code above should be complete enough to help people get started.
Good luck!

Give a user permission to ALTER a function

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?