如何在Postgres中使用只读用户访问information_schema外键约束?

Pau*_*aul 2 postgresql privileges information-schema

介绍

我一直在开发一个向导,为没有任何编程/SQL 背景的用户创建复杂的数据库 Postgres 查询。由于存储在 information_schema 视图中的外键约束,用户可以选择任意数量的表,并且该工具将找到正确的联接设置(因此,用户不必添加ON table_a.field_1 = table_b.field_2)。

在开发过程中,我一直使用管理数据库用户,现在想将其更改为只读用户以使其更安全。但是,这个只读用户似乎无法访问外键约束。

现在的情况

当选择了多个表时,该工具会尝试获取各个表之间的连接,以便了解如何连接它们。在此过程中,将执行以下查询:

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');
Run Code Online (Sandbox Code Playgroud)

WHERE(注意:使用最后一个子句IN是因为可以有多个可用基表。TableA 是基表,每个成功连接/联接的表将可用于其他联接,例如可以使用第三个表,AND ccu.table_name = 'TableC' AND tc.table_name IN ('TableA', 'TableB');依此类推。)

当使用 admin db 用户(具有最常见的权限,如 GRANT、SELECT、INSERT、UPDATE、DELETE、TRUNCATE...)执行查询时,结果如下所示:

constraint_name | table_name | column_name | foreign_table_name | foreign_column_name
----------------+------------+-------------+--------------------+---------------------
constraint1     | TableA     | field_1     | TableB             | field_2
(1 row) 
Run Code Online (Sandbox Code Playgroud)

但是当只读数据库用户运行该查询时,它会返回:

constraint_name | table_name | column_name | foreign_table_name | foreign_column_name
----------------+------------+-------------+--------------------+---------------------
(0 rows)
Run Code Online (Sandbox Code Playgroud)

由于存在但未返回的外键约束条目,连接无法正确编写为 SQL,并且用户生成的查询(通过使用向导)失败。

我尝试过的

首先,我当然认为只读用户(ro_user)可能没有访问数据库中的表和视图的权限information_schema。所以我跑了

GRANT SELECT ON ALL TABLES IN SCHEMA information_schema TO ro_user;
Run Code Online (Sandbox Code Playgroud)

作为管理员但无济于事。更深入地了解文档,我发现information_schema在 postgres 中默认情况下任何用户都可以使用和访问其中的所有表和视图。因此,授予选择权限甚至不应该改变任何东西。

为了确定一下,我也跑了

GRANT REFERENCES ON ALL TABLES IN SCHEMA actual_database TO ro_user;
Run Code Online (Sandbox Code Playgroud)

但当然,这也没有改变任何东西,因为REFERENCES只需要创建新的外键,我只需要阅读它们。

接下来,我想,也许该工具的 sql 由于某些信息不可用而失败,因此我通过运行分别查询三个视图:

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;
Run Code Online (Sandbox Code Playgroud)

果然,最后一个不会为 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)
Run Code Online (Sandbox Code Playgroud)

而管理员用户得到了很多结果。所以,一切都归结为这一观点information_schema.constraint_column_usage

Pau*_*aul 6

当我在一个小时的时间里打出这个问题时,回忆并总结了我在过去几天尝试过的所有想法,我终于找到了原因。

视图constraint_column_usage标识当前数据库中某些约束使用的所有列。仅显示当前启用的角色所拥有的表中包含的那些列。

通过此SO 答案文档

通过这个我找到了一个解决方案

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;
Run Code Online (Sandbox Code Playgroud)

它不会输出与旧查询相同的格式,但它包含相同的信息,并且最重要的是可供 ro_user 使用。