Is there an easy way to enumerate all objects that a specific role has some access privilege to? I know of the set of has_*_privilege functions in pg_catalog but they don't do the job, I want to work the other way around. Effectively I want to have a view that gives oid and access privilege for anything stored in pg_class for a specific role.
Such a view would be extremely handy to check if the security of the database is correctly set up. Typically there are far fewer roles than relations so checking the roles is much less onerous IMHO. Should such an utility not be available in the standard PostgreSQL distribution?
According to the source code (acl.h) the aclitem is a struct:
typedef struct AclItem
{ Oid ai_grantee; /* ID that this item grants privs to */
Oid ai_grantor; /* grantor of privs */
AclMode ai_privs; /* privilege bits */
} AclItem;
Easy to work with. However, pg_type lists this as a user-defined, non-composite type. Why is that? The only way I see right now is to parse the aclitem[] array using string functions. Is there a better way to analyze the aclitem array?
Added information
Trawling through the various PG lists, it is obvious that this issue keeps popping up in various forms at least since 1997 (did we have computers then? was tv around?), most relevant in the discussion thread "Binary in/out for aclitem" on pgsql-hackers in early 2011. As a (technically skilled) user - rather than a hacker - of PG I appreciate the concern of the developers to maintain a stable interface, but some of the concern voiced in the thread goes a little far for my tastes. What is the real reason not to have a pg_acl table in the system catalogs with definition equal to the AclItem struct in the source code? When did that struct last change? I am also aware of SE developments that will likely introduce changes to the way security is handled - when a users opts to, presumably - so I will settle for something that presents acl information in such a way that it is easy to enumerate granted privileges for a specific user, such as:
SELECT * FROM pg_privileges WHERE grantee = 16384;
Like so it can still be an abstraction of the underlying structures so any changes under the hood could then (presumably) still be translated into the exposed interface. Not too different from the information_schema approach, I would say.
Cheers,
Patrick
There's no such view out of the box, but the data needed to create it is in the system catalogs:
http://www.postgresql.org/docs/current/static/catalogs.html
For instance, there's a relacl field in pg_class:
select oid::regclass, relacl from pg_class;
There are similar fields in other catalogs, namely typacl in pg_type and proacl in pg_proc.
You'll presumably want to use two more catalogs, namely pg_authid to know which roles are have superuser privileges, and pg_auth_members to know who has what role.
(The pg_default_acl is only used during object creation, so is not useful.)
There are a couple of aclitem-related internal functions that may come in handy in creating the view. You can list them in psql like so:
\df+ *acl*
In particular aclexplode(). The following example will hopefully be enough to get you started:
select oid::regclass,
(aclexplode(relacl)).grantor,
(aclexplode(relacl)).grantee,
(aclexplode(relacl)).privilege_type,
(aclexplode(relacl)).is_grantable
from pg_class
where relacl is not null;
It can be optimized by expanding the acl rows first, e.g.:
select oid::regclass,
aclitem.grantee
from (select oid, aclexplode(relacl) as aclitem from pg_class) sub
It will lead you straight to the desired result.
Insofar as I'm aware, that's about as good as it'll get using the built-in tools. (Naturally, you could write your own set of operators in C if you'd like to try to optimize this further.)
With respect to your extra questions, I'm afraid they can only be answered by a handful of people in the world, aka the core devs themselves. They hang out on the pg hackers list more often than they do here.
Probably not the best / efficient way, but it helps me a lot! I needed it while having problems dropping roles and having the error.
ERROR: role ROLE_NAME cannot be dropped because some objects depend on it
You can use it as
SELECT * FROM upg_roles_privs WHERE grantee = 'testuser'
The code is below. I'm not including "system" objects (from pg_catalog and information_schema), you can take the conditions out of the query if you wish to enumerate them.
CREATE VIEW upg_roles_privs AS
/* Databases */
select type, objname, r1.rolname grantor, r2.rolname grantee, privilege_type
from
(select
'database'::text as type, datname as objname, datistemplate, datallowconn,
(aclexplode(datacl)).grantor as grantorI,
(aclexplode(datacl)).grantee as granteeI,
(aclexplode(datacl)).privilege_type,
(aclexplode(datacl)).is_grantable
from pg_database) as db
join pg_roles r1 on db.grantorI = r1.oid
join pg_roles r2 on db.granteeI = r2.oid
where r2.rolname not in ('postgres')
union all
/* Schemas / Namespaces */
select type, objname, r1.rolname grantor, r2.rolname grantee, privilege_type from
(select
'schema'::text as type, nspname as objname,
(aclexplode(nspacl)).grantor as grantorI,
(aclexplode(nspacl)).grantee as granteeI,
(aclexplode(nspacl)).privilege_type,
(aclexplode(nspacl)).is_grantable
from pg_catalog.pg_namespace) as ns
join pg_roles r1 on ns.grantorI = r1.oid
join pg_roles r2 on ns.granteeI = r2.oid
where r2.rolname not in ('postgres')
union all
/* Tabelas */
select 'tables'::text as type, table_name||' ('||table_schema||')' as objname, grantor, grantee, privilege_type
from information_schema.role_table_grants
where grantee not in ('postgres')
and table_schema not in ('information_schema', 'pg_catalog')
and grantor <> grantee
union all
/* Colunas (TODO: se o revoke on table from x retirar acesso das colunas, nao precisa desse bloco) */
select
'columns'::text as type, column_name||' ('||table_name||')' as objname,
grantor, grantee, privilege_type
from information_schema.role_column_grants
where
table_schema not in ('information_schema', 'pg_catalog')
and grantor <> grantee
union all
/* Funcoes / Procedures */
select 'routine'::text as type, routine_name as objname, grantor, grantee, privilege_type
from information_schema.role_routine_grants
where grantor <> grantee
and routine_schema not in ('information_schema', 'pg_catalog')
--union all information_schema.role_udt_grants
union all
/* Outros objetos */
select 'object'::text as type, object_name||'( '||object_type||')' as objname, grantor, grantee, privilege_type
from information_schema.role_usage_grants
where object_type <> 'COLLATION' and object_type <> 'DOMAIN'
Related
There is one use case in my project where i want to show the user who has got the access to use that database/schema/table in postgresql. Suppose I have created a database employee. So I want list the users who are accessing this database. Same for schema and tables. I tried this:
SELECT
*
FROM
information_schema.tables
WHERE
table_schema not in ('pg_catalog', 'information_schema') AND
table_schema not like 'pg_toast%'
But it gives information about current user has access to. I want the list of accessing users that are using that database/table/schema/column.
You can use the function has_table_privilege() to achieve what you want.
select has_table_privilege('user_name', 'schema.table', 'select');
More info here.
I suppose you need to be a superuser to get all results. Below shows all the users who have privileges, not necessarily whether they are accessing the tables.
Now you can tweak the query to join all users with list of tables -
SET search_path TO public,
schema1;
SELECT *,
usename,
has_table_privilege(usename, table_schema||'.'||table_name, 'select') as has_privilege
FROM SVV_TABLES
JOIN PG_USER
ON 1=1
WHERE table_schema = 'schema1';
Introduction
I've been developing a wizard to create complex database Postgres queries for users without any programming/SQL background. Thanks to foreign key constraints stored in a view in information_schema, the user may select any number of tables and the tool will find the correct join settings (thus, the user does not have to add ON table_a.field_1 = table_b.field_2).
While developing, I have been using an administration database user and now wanted to change that to a read-only user to make it more secure. However, this read-only user seems not to be able to access the foreign key constraints.
Current situation
When more than one table has been selected, the tool tries to get the connections between the various tables in order to know how to join them. During that process, the following query is executed:
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY'
AND ccu.table_name = 'TableB'
AND tc.table_name IN ('TableA');
(Note: the last WHERE clause uses IN because there can be more than one base table available. TableA is the base table and each successfully connected/joined table will be available for additional joins, e.g. a third table could use AND ccu.table_name = 'TableC' AND tc.table_name IN ('TableA', 'TableB'); and so on.)
When using the admin db user (with most common privileges like GRANT, SELECT, INSERT, UPDATE, DELETE, TRUNCATE, ...) executes the query, the result looks something like this:
constraint_name | table_name | column_name | foreign_table_name | foreign_column_name
----------------+------------+-------------+--------------------+---------------------
constraint1 | TableA | field_1 | TableB | field_2
(1 row)
But when the read-only db user runs that query, it returns:
constraint_name | table_name | column_name | foreign_table_name | foreign_column_name
----------------+------------+-------------+--------------------+---------------------
(0 rows)
Due to the existing but not returned foreign key constraint entry, the joins can not be properly written as SQL and the user generated query (by using the wizard) fails.
What I tried
First of course, I thought the read-only user (ro_user) might not have the permissions to access tables and views in database information_schema. So I ran
GRANT SELECT ON ALL TABLES IN SCHEMA information_schema TO ro_user;
as admin but to no avail. Getting more into the depths of the documentation, I found that all tables and views in information_schema are available and accessible to any user by default in postgres anyways. So granting the select privilege shouldn't even change anything.
Just to make sure, I also ran
GRANT REFERENCES ON ALL TABLES IN SCHEMA actual_database TO ro_user;
but of course, this didn't change anything neither, since REFERENCESis only needed for creating new foreign key, I just need to read them.
Next, I thought, maybe the sql from the tool is failing due to some information not being available, so I queried the three views separately by running:
SELECT * FROM information_schema.table_constraints AS tc WHERE constraint_type = 'FOREIGN KEY';
SELECT * FROM information_schema.key_column_usage AS kcu;
SELECT * FROM information_schema.constraint_column_usage AS ccu;
And sure enough, the last one wouldn't return any single row for the ro_user:
psql=> SELECT * FROM information_schema.constraint_column_usage AS ccu;
table_catalog | table_schema | table_name | column_name | constraint_catalog | constraint_schema | constraint_name
---------------+--------------+------------+-------------+--------------------+-------------------+-----------------
(0 rows)
whereas the admin user got lots of results. So, it was coming down to that one view information_schema.constraint_column_usage.
As I was typing out that question over the course of an hour recollecting and boiling down all the ideas I tried during the last days, I finally found the cause.
The view constraint_column_usage identifies all columns in the current database that are used by some constraint. Only those columns are shown that are contained in a table owned by a currently enabled role.
From documentation via this SO answer
And through that I found a solution
SELECT
conrelid::regclass AS table_from,
conname,
pg_get_constraintdef(c.oid) AS cdef
FROM pg_constraint c
JOIN pg_namespace n
ON n.oid = c.connamespace
WHERE contype IN ('f')
AND n.nspname = 'public'
AND pg_get_constraintdef(c.oid) LIKE '%"TableB"%'
AND conrelid::regclass::text IN ('"TableA"')
ORDER BY conrelid::regclass::text, contype DESC;
It doesn't output the same format as the old query, but it contains the same information and is - most importantly - available to the ro_user.
I have a utility that introspects columns of tables using:
select column_name, data_type from information_schema.columns
where table_name=%s
How can I extend this to introspect columns of materialized views?
Your query carries a few shortcomings / room for improvement:
A table name is not unique inside a database, you would have to narrow down to a specific schema, or could get surprising / misleading / totally incorrect results.
It's much more effective / convenient to cast the (optionally) schema-qualified table name to regclass ... see below.
A cast to regtype gives you generic type names instead of internal ones. But that's still only the base type.
Use the system catalog information functions format_type() instead to get an exact type name including modifiers.
With the above improvements you don't need to join to additional tables. Just pg_attribute.
Dropped columns reside in the catalog until the table is vacuumed (fully). You need to exclude those.
SELECT attname, atttypid::regtype AS base_type
, format_type(atttypid, atttypmod) AS full_type
FROM pg_attribute
WHERE attrelid = 'myschema.mytable'::regclass
AND attnum > 0
AND NOT attisdropped; -- no dead columns
As an aside: the views in the information schema are only good for standard compliance and portability (rarely works anyway). If you don't plan to switch your RDBMS, stick with the catalog tables, which are much faster - and more complete, apparently.
It would seem that postgres 9.3 has left materialized views out of the information_schema. (See http://postgresql.1045698.n5.nabble.com/Re-Materialized-views-WIP-patch-td5740513i40.html for a discussion.)
The following will work for introspection:
select attname, typname
from pg_attribute a
join pg_class c on a.attrelid = c.oid
join pg_type t on a.atttypid = t.oid
where relname = %s and attnum >= 1;
The clause attnum >= 1 suppresses system columns. The type names are pg_specific this way, I guess, but good enough for my purposes.
I am trying to revoke a database user's permissions and it seems that permissions can only be revoked by the user who granted them. There is thread here discussing the issue.
http://archives.postgresql.org/pgsql-bugs/2007-05/msg00234.php
The thread dates back to 2007 and I am not quite sure whether it is viewed as bug and whether that problem is still present in PostgreSQL 8.4 which I am using.
Is there a query or a view that can display that information? That way I can use set session authorization and revoke it.
PostgreSQL 8.4 is outdated. Check out the versioning policy for details. But since it is the standard behavior of SQL (as Tom Lane states in the linked discussion you provided), it's not likely to have changed.
Privileges are stored in the system catalog with the respective object. For instance, for a table:
SELECT n.nspname, c.relname, c.relacl
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.oid = 'myschema.mytbl'::regclass -- your tablename here
Would produce something like:
nspname | relname | relacl
----------+---------+---------------------------------------------
myschema | mytbl | {postgres=arwdDxt/postgres,fuser=r/fadmin}
The rolename after the slash is the grantor. To revoke, as user fadmin (or any superuser):
REVOKE SELECT ON TABLE myschema.mytbl FROM fuser;
There are similar *acl columns in other system tables. pg_namespace for schemas etc. See the list of system tables in the manual.
A simpler way would be to use pgAdmin and select an object in the object browser to the left. The ACL will be displayed in the properties pane, top right.
What is the recommended way to figure out if a user got a certain right (e.g. select or execute) on a certain class (e.g. table or function) in PostgreSQL?
At the moment I got something like
aclcontains(
someColumnWithAclitemArray,
makeaclitem(userOid,grantorOid,someRight,false))
but it's terrible since I have to check for every grantorOid that is possible and for every userOid the user can belong to.
On a related note: what are the possible rights you can test for?
I haven't found any documentation but reading the source code I guess:
INSERT
SELECT
UPDATE
DELETE
TRUNCATE
REFERENCES
TRIGGER
EXECUTE
USAGE
CREATE
CONNECT
There also seems to be a CREATE TEMP right, but I can't figure out the correct text to use in the makeaclitem-function.
I've found that a better approach (and I seem to remember this was taken from some queries built into psql, or maybe the information_schema views) is to use the has_*_privilege functions, and simply apply them to a set of all possible combinations of user and object. This will take account of having access to an object via some group role as well.
For example, this will show which users have which access to non-catalogue tables and views:
select usename, nspname || '.' || relname as relation,
case relkind when 'r' then 'TABLE' when 'v' then 'VIEW' end as relation_type,
priv
from pg_class join pg_namespace on pg_namespace.oid = pg_class.relnamespace,
pg_user,
(values('SELECT', 1),('INSERT', 2),('UPDATE', 3),('DELETE', 4)) privs(priv, privorder)
where relkind in ('r', 'v')
and has_table_privilege(pg_user.usesysid, pg_class.oid, priv)
and not (nspname ~ '^pg_' or nspname = 'information_schema')
order by 2, 1, 3, privorder;
The possible privileges are detailed in the description of the has_*_privilege functions at http://www.postgresql.org/docs/current/static/functions-info.html#FUNCTIONS-INFO-ACCESS-TABLE.
'CREATE TEMP' is a database-level privilege: it permits a user to use a pg_temp_* schema. It can be tested with has_database_privilege(useroid, datoid, 'TEMP').
Take a look at the "Access Privilege Inquiry Functions" and also the "GRANT" reference page.
Because Redshift is supporting values() only in INSERT INTO queries, the below query can be used with the obviously not-so-nice union all select.
select usename, nspname || '.' || relname as relation,
case relkind when 'r' then 'table' when 'v' then 'view' end as relation_type,
priv
from pg_class join pg_namespace on pg_namespace.oid = pg_class.relnamespace,
pg_user,
(select 'select' as priv,1 as privorder union all select 'insert',2 union all select 'update',3 union all select 'delete',4)
where relkind in ('r', 'v')
and has_table_privilege(pg_user.usesysid, pg_class.oid, priv)
and not (nspname ~ '^pg_' or nspname = 'information_schema')
order by 2, 1, 3, privorder;
Edit:
Also, I realized, that in our db Dataiku creates tables that can have CAPITAL letter in them, so, if table not exist error happens, you should use the lower() function