在 PostGraphile 中处理列级别 select 赠款的最佳实践

Best practice to handle column level select grants in PostGraphile

PostGraphile 不推荐 column-level SELECT grants,而是推荐

split your concerns into multiple tables and use the one-to-one relationship feature to link them.

现在我希望我的 users table 有一个 role 字段可以被 role_admin 访问但不能被 role_consumer 访问。根据上面的推荐,我创建了两个table。 users table(在 public 架构中)包含两个角色都可以看到的所有字段,而 user_accounts(在私有架构中)包含 role 字段,只有 role_admin一定能看到。 role 字段通过计算列添加到 user GraphQL 类型。

CREATE SCHEMA demo_public;
CREATE SCHEMA demo_private;

/* users table*/
CREATE TABLE demo_public.users (
  user_id SERIAL PRIMARY KEY,
  first_name VARCHAR(50) NOT NULL,
);

/* user_accounts */
CREATE TABLE demo_private.user_accounts (
  user_id INT PRIMARY KEY REFERENCES demo_public.users (user_id) ON DELETE CASCADE,
  role text not null default 'role_consumer',
);

/* role as computed column */
CREATE FUNCTION demo_public.users_role
(
  u demo_public.users
)
RETURNS TEXT as $$
  <code>
$$ LANGUAGE SQL STRICT STABLE;

现在基本上我有两个药水来设置权限。

1) 第一个选项是使用 table 级别的安全性。 IOW 授予 select 对 table user_accounts 的访问权限仅 role_admin.

GRANT SELECT ON TABLE demo_private.user_accounts TO role_admin;
GRANT EXECUTE ON FUNCTION demo_public.users_role(demo_public.users) TO role_admin;
ALTER TABLE demo_private.user_accounts ENABLE ROW LEVEL SECURITY;
CREATE POLICY select_any_user_accounts ON demo_private.user_accounts FOR SELECT TO role_admin using (true);

这种方法的问题在于,当 role_consumer 运行包含 role 字段的查询时

{
  me {
    firstname
    role
  }
}

以上查询returns出错。这不好,因为错误会影响整个结果,从而隐藏其他同级字段的结果。

2) 除了 table 级别之外,另一种选择是使用行级别安全性; table 级别的 IOW,将 table user_accounts 的 select 访问权限授予 role_adminrole_consumer,但在行级别仅允许管理员访问user_accounts.

GRANT USAGE ON SCHEMA demo_private TO role_consumer;
GRANT SELECT ON TABLE demo_private.user_accounts TO role_consumer;
GRANT EXECUTE ON FUNCTION demo_public.users_role(demo_public.users) TO role_consumer;
ALTER TABLE demo_private.user_accounts ENABLE ROW LEVEL SECURITY;
CREATE POLICY select_user_accounts ON demo_private.user_accounts FOR SELECT
  USING ('role_admin' = nullif(current_setting('role', true), ''));

现在,如果 consumer_role 的用户运行上述查询,role 字段将为空,不会影响其兄弟字段。但是有两个问题:

对于选项 1,在查询期间从 PostgreSQL 抛出错误在 PostGraphile 中不是一个好主意,因为我们将整个 GraphQL 树编译成单个 SQL 查询,因此错误中止整个查询。相反,如果不允许用户查看它,我会将权限考虑到函数中并简单地 return null (而不是错误)。一种方法是使用附加的 WHERE 子句:

CREATE FUNCTION demo_public.users_role (
  u demo_public.users
) RETURNS TEXT AS $$
  select role
    from demo_private.user_accounts
    where user_id = u.id
    and current_setting('jwt.claims.role') = 'role_admin';
$$ LANGUAGE SQL STABLE;

对于选项 2:这是一个完全有效的解决方案。

Should we always avoid errors to prevent them affecting their siblings?

在 GraphQL 中查询内容时很少会抛出错误 - 通常您 return null。把它想象成在注销时访问 GitHub 上的私人存储库 - 他们不会 return 显示资源存在的 "forbidden" 错误,而是 return 404提示它不存在的错误 - 除非你知道得更多!

If yes, should we always handle things in Row Level and never only in Table Level?

我个人只在 PostGraphile 中使用一个角色,app_visitor,这对我到目前为止用 PostGraphile 构建的所有应用程序来说已经足够了。