powered by simpleCommunicator - 2.0.49     © 2025 Programmizd 02
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Ограничение доступа к записям
9 сообщений из 9, страница 1 из 1
Ограничение доступа к записям
    #40127874
Быдло__кодер
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В базе есть некая таблица, скажем user_departments которая хранит к каким подразделением имеет доступ юзер
С подразделениями связана еще тонна других таблиц например employee_departments, employee и т.п.
Хочется чтобы логика доступа к подразделениям была инкапсулирована, т.е чтоб был такой объект к которому можно обратиться и получить только лишь данные к которым есть доступ у пользователя
Есть вариант написать функции типа
Код: plsql
1.
2.
3.
4.
5.
6.
7.
create function f_employee_departments(userName..) as select * from employee_departments ed
join user_departments ud on..
where  ud.userName  = userName 

create function f_employee(userName..) as select * from employee e
join f_employee_departments(userName) ed on e.employee_id = ed.employee_id
  



и т.д. и т.п

Однако это плохо будет работать в запросах вида
Код: plsql
1.
select * from f_employee('UserName') where employee_id = 123


Поскольку вначале будут выбраны все сотрудники, а потом только применен фильтрующее условие в where
Вот если б можно было как-то заставить предикат пропихнуться внутрь функции

Вообще по специфике работы есть либо юзеры с доступом ко всем подразделениям (типа "админы") либо есть юзеры с доступом к 1-2 подразделениям.

Раньше база была на оракле и для такого ограничения использовался такой кунгфу прием:
Админам даем доступ на прямую к таблицам, ограниченным пользователям делаем вьюхи в которых ставим условия отсечения where userName = current_user. И для ограниченных юзеров делаем сининомы на вью с именами таблиц. Тогда подключившийся юзер (не важно "админ" это или ограниченный) просто всегда выбирает данные из обычных таблиц, а "под ка

Это решение переносится на постгри и во-первых прям также сделать нельзя потому что в постгри нету синонимов,а во вторых по некоторым соображениям хочется избавиться от подключения физически под разными пользователями
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40127952
delphinotes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Быдло__кодер,

а) представления никто не отменял (имя текущего пользователя можно хранить в контексте сессии )
б) почитайте о RLS
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40127969
Быдло__кодер
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Про RLS я читал, но меня отпугнул этот момент в доке
докаTo specify which rows are visible or modifiable according to a policy, an expression is required that returns a Boolean result. This expression will be evaluated for each row prior to any conditions or functions coming from the user's query
Т.е если верить этому то это еще хуже, в запросе
Код: plsql
1.
select * from employee where employee_id = 123 


cначала для каждой строчки проверится условие RLS а потом только наложится отсечение, это тогда еще печальнее чем функция
Есть еще одна фигня с RLS, она не работает с foreign tables

С вьюхами да, можно так сделать (и я пока не вижу других способов), но тогда "админы" (у которых в старых таблицах было фактически отсутствие записей в user_deparments) тоже будут ходить через вьюхи, т.е придется писать нечто типа такого

Код: plsql
1.
2.
3.
4.
create or replace view vw_employee_deparment as 
select ed.* 
from employee_departemnt ed
join user_departments ud on ed.department_id = ud.department_id and (ed.userName = current_setting('custom.user', true) or current_setting('custom.is_admin')=1)



Это не особо хорошо скажется на производительности небось, особенно OR в условиях + надо будет после создания сессии устанавливать все эти параметры сесии + тогда только один юзер на сессию.. Т.е это вариант решения но с недостатками
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40128000
delphinotes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Быдло__кодер
Про RLS я читал, но меня отпугнул этот момент в доке
докаTo specify which rows are visible or modifiable according to a policy, an expression is required that returns a Boolean result. This expression will be evaluated for each row prior to any conditions or functions coming from the user's query

Т.е если верить этому то это еще хуже, в запросе
Код: plsql
1.
select * from employee where employee_id = 123 



cначала для каждой строчки проверится условие RLS а потом только наложится отсечение, это тогда еще печальнее чем функция

Смелый вывод... вообще там дальше написано про "герметичные функции", а условие "employee_id = 123" - скорее всего будет трактоваться как "фильтр", и если по этому столбцу есть индекс, то наверняка оптимизатор это увидит.
Но я не проверял, хотя проверку соорудить несложно.

Вместо RLS мы решили использовать представления: у пользователей нет прямого доступа к таблицам вообще, гранты выдаются только вьюхам, а во вью уже накладываем ограничения - так тупо проще управлять всем этим хозяйством.

Т.е это вариант решения но с недостатками

ну, после Oracle, это меньшая из проблем, с которыми вам ещё придётся столкнуться.
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40128025
Misha111
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Быдло__кодер,

аналогичную задачу решали через вьюхи.
но не логическим или, а через кейсы:
WHERE
CASE
WHEN админ THEN 1::numeric
WHEN доступ разрешен THEN 1::numeric
ELSE 0
END > 0::numeric;

пгшный рлс очень сильно влияет на планы и заметно снижает скорость запросов во многих наших ситуациях (об этом упоминается и на стэке), поэтому от него отказались.

в любом случае я бы потесткейсил оба варианта - может у вас все будет наоборот
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40128256
Быдло__кодер
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Пощупал RLS
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
create table departments(depId int primary key );

do $$
begin
  for i in 1..30 loop
    insert into departments(depId) values(i);
  end loop;
end $$;

create table user_privs
(
	user_name varchar(30),
	dep_id int
		constraint user_privs_departments_depid_fk
			references departments,
	constraint user_privs_pk
		primary key (user_name, dep_id)
);

do $$
begin
  for i in 1..30 loop
    insert into user_privs(user_name, dep_id)
    values ('user'||(i%15)::text, i);
  end loop;
end;
$$

create table employee
(
	employee_id int not null
		constraint employee_pk
			primary key,
	dep_id int not null
		constraint employee_fk
			references departments
);


do $$
begin
  for i in 1..1000000 loop
    insert into employee(employee_id, dep_id)
    values (i, i%30+1);
  end loop;
end;
$$

alter table employee enable row level security;

create policy pol_emp on employee
using (dep_id in (select up.dep_id from user_privs up where up.user_name = current_user));


create user user1 with login password '1';
grant all privileges  on all tables in schema dk to user1;



Под юзером user1
Код: plsql
1.
explain analyze verbose select * from dk.employee where employee_id = 14505;


выдает
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
QUERY PLAN
Index Scan using employee_pk on dk.employee  (cost=21.54..29.56 rows=1 width=8) (actual time=0.061..0.062 rows=1 loops=1)
  Output: employee.employee_id, employee.dep_id
  Index Cond: (employee.employee_id = 14505)
  Filter: (hashed SubPlan 1)
  SubPlan 1
    ->  Seq Scan on dk.user_privs up  (cost=0.00..21.10 rows=4 width=4) (actual time=0.010..0.016 rows=2 loops=1)
          Output: up.dep_id
          Filter: ((up.user_name)::text = CURRENT_USER)
          Rows Removed by Filter: 28
Planning Time: 0.150 ms
Execution Time: 0.099 ms


Судя по плану с одной стороны не так все печально, он все же не выполняет "в тупую" условие в using строчка за строчкой, а по сути выполнил select up.dep_id from user_privs up where up.user_name = current_user а потом поискал в индексе по тому что получилось.
Но с другой стороны он все же не догадался вначале сделать поиск по ключу а потом проверить фильтрацию из using

Остановлюсь пока на вьюхах наверное
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40128260
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Быдло__кодер
Пощупал RLS
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
create table departments(depId int primary key );

do $$
begin
  for i in 1..30 loop
    insert into departments(depId) values(i);
  end loop;
end $$;

create table user_privs
(
	user_name varchar(30),
	dep_id int
		constraint user_privs_departments_depid_fk
			references departments,
	constraint user_privs_pk
		primary key (user_name, dep_id)
);

do $$
begin
  for i in 1..30 loop
    insert into user_privs(user_name, dep_id)
    values ('user'||(i%15)::text, i);
  end loop;
end;
$$

create table employee
(
	employee_id int not null
		constraint employee_pk
			primary key,
	dep_id int not null
		constraint employee_fk
			references departments
);


do $$
begin
  for i in 1..1000000 loop
    insert into employee(employee_id, dep_id)
    values (i, i%30+1);
  end loop;
end;
$$

alter table employee enable row level security;

create policy pol_emp on employee
using (dep_id in (select up.dep_id from user_privs up where up.user_name = current_user));


create user user1 with login password '1';
grant all privileges  on all tables in schema dk to user1;



Под юзером user1
Код: plsql
1.
explain analyze verbose select * from dk.employee where employee_id = 14505;


выдает
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
QUERY PLAN
Index Scan using employee_pk on dk.employee  (cost=21.54..29.56 rows=1 width=8) (actual time=0.061..0.062 rows=1 loops=1)
  Output: employee.employee_id, employee.dep_id
  Index Cond: (employee.employee_id = 14505)
  Filter: (hashed SubPlan 1)
  SubPlan 1
    ->  Seq Scan on dk.user_privs up  (cost=0.00..21.10 rows=4 width=4) (actual time=0.010..0.016 rows=2 loops=1)
          Output: up.dep_id
          Filter: ((up.user_name)::text = CURRENT_USER)
          Rows Removed by Filter: 28
Planning Time: 0.150 ms
Execution Time: 0.099 ms


Судя по плану с одной стороны не так все печально, он все же не выполняет "в тупую" условие в using строчка за строчкой, а по сути выполнил select up.dep_id from user_privs up where up.user_name = current_user а потом поискал в индексе по тому что получилось.
Но с другой стороны он все же не догадался вначале сделать поиск по ключу а потом проверить фильтрацию из using

Остановлюсь пока на вьюхах наверное


"Но с другой стороны он все же не догадался вначале сделать поиск по ключу а потом проверить фильтрацию из using "
- потому что этого делать нельзя
иначе выполнив запрос вида ... where (1/(employee_id-14505))>0 можно будет узнать есть такой employee_id или нет а это уже утечка данных и так по любому полю фактически.
И обычные view такую утечку будут создавать всегда.
А если вы сделаете view c
security_barrier (boolean)
This should be used if the view is intended to provide row-level security. See Section 41.5 for full details.
то его поведение будет ровно тоже что у RLS
https://www.postgresql.org/docs/14/sql-createview.html
+
https://www.postgresql.org/docs/14/rules-privileges.html
там как раз разобрано как такие views ломаются.

--
Maxim Boguk
лучшая поддержка PostgreSQL: dataegret.ru
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40128261
Melkij
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Быдло__кодер
по сути выполнил select up.dep_id from user_privs up where up.user_name = current_user а потом поискал в индексе по тому что получилось.
Но с другой стороны он все же не догадался вначале сделать поиск по ключу а потом проверить фильтрацию из using

Ваш explain не согласен с такой трактовкой.
Index Cond указан как раз именно (employee.employee_id = 14505), а подзапрос указан в Filter
...
Рейтинг: 0 / 0
Ограничение доступа к записям
    #40128274
Быдло__кодер
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ваш explain не согласен с такой трактовкой.
Index Cond указан как раз именно (employee.employee_id = 14505), а подзапрос указан в Filter
Хм, да, вы правы, выходит что здесь как раз он поискал по ключу а потом только наложил фильтрацию

Но вот например такой запрос
Код: plsql
1.
explain analyze verbose select * from dk.employee where dep_id = 16



Выдает план
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
QUERY PLAN
Gather  (cost=1021.11..13277.81 rows=15817 width=8) (actual time=0.516..177.470 rows=33333 loops=1)
  Output: employee.employee_id, employee.dep_id
  Workers Planned: 2
  Workers Launched: 2
  ->  Parallel Seq Scan on dk.employee  (cost=21.11..10696.11 rows=6590 width=8) (actual time=0.017..46.692 rows=11111 loops=3)
        Output: employee.employee_id, employee.dep_id
        Filter: ((hashed SubPlan 1) AND (employee.dep_id = 16))
        Rows Removed by Filter: 322222
        Worker 0:  actual time=0.002..0.002 rows=0 loops=1
        Worker 1:  actual time=0.002..0.002 rows=0 loops=1
        SubPlan 1
          ->  Seq Scan on dk.user_privs up  (cost=0.00..21.10 rows=4 width=4) (actual time=0.010..0.017 rows=2 loops=1)
                Output: up.dep_id
                Filter: ((up.user_name)::text = CURRENT_USER)
                Rows Removed by Filter: 28
Planning Time: 0.202 ms
Execution Time: 178.211 ms


Т.е он в subPlan 1 не протолкнул таки условие на dep_id=16. В общем не все так однозначно, надо смотреть на реальных примерах будет, сложнее чем эти простые тесты


- потому что этого делать нельзя
иначе выполнив запрос вида ... where (1/(employee_id-14505))>0 можно будет узнать есть такой employee_id или нет а это уже утечка данных и так по любому полю фактически.
И обычные view такую утечку будут создавать всегда.
А если вы сделаете view c
security_barrier (boolean)
This should be used if the view is intended to provide row-level security. See Section 41.5 for full details.
то его поведение будет ровно тоже что у RLS
https://www.postgresql.org/docs/14/sql-createview.html
+
https://www.postgresql.org/docs/14/rules-privileges.html
там как раз разобрано как такие views ломаются.

Спасибо! Почитаю про это
...
Рейтинг: 0 / 0
9 сообщений из 9, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Ограничение доступа к записям
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]