简体   繁体   中英

How to access information_schema foreign key constraints with read-only user in Postgres?

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, eg 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 REFERENCES is 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM