【问题标题】:Have attribute set to specific value on every query在每个查询中将属性设置为特定值
【发布时间】:2022-10-05 04:31:19
【问题描述】:

设置:

我使用psycopg2 访问我用SQL 构建的(PostgreSQL)数据库。 我希望有不同的用户,它们只能访问表中自己的行。因此,我为方案中的每个表添加了 UserId 属性。 UserId 的值可作为变量(让我们将其命名为pyUserId)在程序执行时提供。 我对curs.execute() 的所有调用都通过我定义的一个函数进行。

我想要达到的目标:

是否可以为每个查询集中设置属性值,例如UserId=pyUserId? 否则,我必须将 pyUserId 作为我已经构建的每个查询的数据传递,我怀疑这也违反了 DRY。

例如:
SELECT UserName FROM Users WHERE Age < 30 变为:
SELECT UserName FROM Users WHERE Age < 30 AND UserId = pyUserId
或者
INSERT INTO Profiles (Name, Bio, SoAccount) VALUES ('jon', 'blah...', '22656') 变为
INSERT INTO Profiles (Name, Bio, SoAccount, UserId) VALUES ('jon', 'blah...', '22656', pyUserId)

DELETE 等也一样。

我尝试了什么:

  • 对于INSERT it is possible 以动态更改 PostgreSQL DEFAULT 约束。但这仅适用于插入。
  • 根据 psycopg2 文档中的string composition,我可以操作 SQL 查询字符串。但我想有很多陷阱,对 SQL 字符串进行操作。
  • 但是,也许有一种方法可以通过操作准备好的语句或分析树来做到这一点。
  • 我不确定这个想法是否可行。如果不是,请解释原因。

【问题讨论】:

  • 有关信息,PostgreSQL 实现了row level security,它可以做任何想做的事(我认为),但是您需要以用户身份进行连接才能使其生效(尽管this answer 中链接的文章显示了一种可以完成的方法) .
  • 行级安全性似乎是我想要的。尽管所有答案都为不同的用户推荐或期望不同的角色。对我来说,似乎有两种方法: 1. 为不同的用户实现角色。然后在官方文档中使用 RLS。 2. 使用签名的会话变量跟随答案。

标签: python postgresql psycopg2


【解决方案1】:

Snakecharmer 的评论帮助我实现了以下内容。

在特定行属于特定用户的所有表中都有一个属性UserId

CREATE TABLE Users (
    UserId INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    user_attributes ...

CREATE TABLE some_table (
    UserId INTEGER REFERENCES Users(UserId) 
    DEFAULT current_setting('my_app.CurrentUserId')::INTEGER NOT NULL,
    some_attribute INTEGER, ...

基于this tutorial:定义一个会话变量,当用户通过身份验证时设置为DEFAULT

SQL = "SET my_app.CurrentUserId = %s"
data = (self.__userId, )
cur.execute(SQL, data)

实施Row-Level Security,创建一个只允许访问UserId 等于会话变量的策略:

DO -- Create a loop over all tables to create a policy and enable it for them
$$
DECLARE
    row record;
BEGIN
    FOR row IN SELECT tablename FROM pg_tables AS t
        WHERE t.schemaname = 'public' 
        AND NOT t.tablename ~* 'exclude|tables|regex'
    LOOP
        EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY;', row.tablename); 
        EXECUTE format('CREATE POLICY isolateUserId ON %I TO some_user 
                            USING (UserId = current_setting(''my_app.CurrentUserId'')::INTEGER);', row.tablename); -- Escape ' !
    END LOOP;
END;
$$;

这个怎么运作:

  • 这很好用,因为只有少数表没有获取 RLS,因此可以在正则表达式中轻松删除它们。
  • some_user 是您在 psycopg2 中用来连接数据库的那个。
  • 我为循环复制了this answer
  • 链接的文章中很好地解释了这种工作方式。 TL;DR:通过会话变量和默认值的组合,确保用户始终使用自己的 UserId 创建项目。会话变量和策略确保用户只看到/更改/删除他们自己的 UserId 存在的行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 1970-01-01
    • 2010-11-24
    • 2018-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多