В правах доступа к объектам Firebird довольно легко запутаться: например, пользователь входит под определённой ролью, запускает хранимую процедуру, которая вносит изменения в таблицу, при этом вызывается триггер на изменение, и этот триггер пишет что-то в другую таблицу, или вызывает другую процедуру и т. д. Каждый пользователь, роль, процедура или триггер должен иметь соответствующие права, в противном случае пользователь получит сообщение об ошибке. А ошибка эта может быть из-за того, что какому-то триггеру не разрешено работать с какой-то таблицей в длинной цепочке связей.
Interbase Expert умеет показывать только права доступа «первого уровня», то есть можно узнать например, с какими объектами базы может работать пользователь. Для получения прав «второго уровня» нужно извлечь список подпрограмм, проверить все триггеры ко всем таблицам… в общем, если таблиц больше десятка, то это тяжёлая работа.
В очередной раз столкнувшись с необходимостью отслеживать права доступа к объектам, я написал рекурсивный скрипт. Скрипт получает на вход имя пользователя, роли, процедуры или триггера и выводит список объектов базы данных, к которым этот пользователь (роль и т. д.) имеет доступ, а также объектов, которые участвуют в описанной выше цепочке. Здесь представлены два варианта скрипта: один просто выводит список объектов и прав доступа, второй ещё и показывает саму цепочку связей.
Поскольку скрипт рекурсивный, на него распространяется традиционное для Firebird ограничение: не более 1024 звеньев в цепочке ссылок. Впрочем, если ваша база данных содержит такое количество взаимосвязей, то вы наверняка используете какие-нибудь специализированные программы, и вам такой скрипт не нужен.
В очередной раз столкнувшись с необходимостью отслеживать права доступа к объектам, я написал рекурсивный скрипт. Скрипт получает на вход имя пользователя, роли, процедуры или триггера и выводит список объектов базы данных, к которым этот пользователь (роль и т. д.) имеет доступ, а также объектов, которые участвуют в описанной выше цепочке. Здесь представлены два варианта скрипта: один просто выводит список объектов и прав доступа, второй ещё и показывает саму цепочку связей.
Поскольку скрипт рекурсивный, на него распространяется традиционное для Firebird ограничение: не более 1024 звеньев в цепочке ссылок. Впрочем, если ваша база данных содержит такое количество взаимосвязей, то вы наверняка используете какие-нибудь специализированные программы, и вам такой скрипт не нужен.
Простой список
/* получить по имени пользователя (процедуры, триггера) все его права доступа
и права доступа всех связанных процедур и триггеров по цепочке. Позволяет
избежать бесконечной рекурсии в ситуации, когда процедуры имеют право на вызов
друг друга, в том числе по цепочке из нескольких объектов
Входной параметр:
USER_NAME — имя пользователя, роли, процедуры или триггера, чьи права
проверяются
Выходные параметры:
OBJECT_TYPE — тип объекта (процедура или таблица, триггеры не перечисляются)
OBJECT_NAME — имя объекта
PRIVILEGE — список непосредственно полученных прав через запятую:
S — чтение,
R — ссылка,
I — вставка,
D — удаление,
U — изменение данных,
X — выполнение (только для процедур),
M — членство (для пользователя по отношению к роли)
PRIVILEGE_EX — список косвенных (по цепочке) прав, расшифровывается так же
*/
execute block (
USER_NAME varchar(31) = :USER_NAME)
returns (
OBJECT_TYPE varchar(9),
OBJECT_NAME varchar(31),
PRIVILEGE varchar(9),
PRIVILEGE_EX varchar(9))
as
declare variable MAX_LEVEL smallint;
declare variable RECORD_CNT integer;
declare variable RECORD_CNT_OLD integer;
begin
MAX_LEVEL = 2;
RECORD_CNT_OLD = -1;
RECORD_CNT = 0;
-- получение максимального уровня вложенности
while (RECORD_CNT > RECORD_CNT_OLD) do
for with recursive data
as (select
1 as LVL,
RDB$OBJECT_TYPE as OBJ_TYPE,
RDB$RELATION_NAME as OBJECT_NAME,
RDB$OBJECT_TYPE || trim(RDB$PRIVILEGE) || RDB$RELATION_NAME as OBJECT_DATA
from
RDB$USER_PRIVILEGES
where
RDB$USER = :USER_NAME
union all
select
data.LVL + 1 as LVL,
R.RDB$OBJECT_TYPE as OBJ_TYPE,
R.RDB$RELATION_NAME as OBJECT_NAME,
R.RDB$OBJECT_TYPE || trim(R.RDB$PRIVILEGE) || R.RDB$RELATION_NAME as OBJECT_DATA
from
RDB$USER_PRIVILEGES R
inner join
data on data.LVL < :MAX_LEVEL and data.OBJECT_NAME = R.RDB$USER and data.OBJECT_NAME <> R.RDB$RELATION_NAME
where
RDB$USER <> :USER_NAME and R.RDB$RELATION_NAME <> :USER_NAME
union all
select
data.LVL + 1 as LVL,
2 as OBJ_TYPE,
T.RDB$TRIGGER_NAME as OBJECT_NAME,
'2X' || T.RDB$TRIGGER_NAME as OBJECT_DATA
from
RDB$TRIGGERS T
inner join
data on data.OBJ_TYPE = 0 and data.OBJECT_NAME = T.RDB$RELATION_NAME
where
T.RDB$TRIGGER_INACTIVE = 0)
select
count(distinct OBJECT_DATA)
from
data
into
:RECORD_CNT
do
begin
RECORD_CNT_OLD = RECORD_CNT;
MAX_LEVEL = MAX_LEVEL + 1;
end
-- извлечение прав доступа
for with recursive data
as (-- права, явно заданные через GRANT
select
1 as LVL,
RDB$OBJECT_TYPE as OBJ_TYPE,
decode(RDB$OBJECT_TYPE, 1, 'представление', 5, 'процедура', 13, 'роль', 'таблица') as OBJECT_TYPE,
RDB$RELATION_NAME as OBJECT_NAME,
trim(RDB$PRIVILEGE) as PRIVILEGE,
null as PRIVILEGE_EX
from
RDB$USER_PRIVILEGES
where
RDB$USER = :USER_NAME
union all
-- права, косвенно полученные по ссылке из процедуры или триггера
select
data.LVL + 1 as LVL,
R.RDB$OBJECT_TYPE as OBJ_TYPE,
decode(R.RDB$OBJECT_TYPE, 1, 'представление', 5, 'процедура', 13, 'роль', 'таблица') as OBJECT_TYPE,
R.RDB$RELATION_NAME as OBJECT_NAME,
null as PRIVILEGE,
trim(R.RDB$PRIVILEGE) as PRIVILEGE_EX
from
RDB$USER_PRIVILEGES R
inner join
data on data.LVL < :MAX_LEVEL and data.OBJECT_NAME = R.RDB$USER and data.OBJECT_NAME <> R.RDB$RELATION_NAME
where
RDB$USER <> :USER_NAME and R.RDB$RELATION_NAME <> :USER_NAME
union all
-- триггеры — на их запуск не нужны отдельные права, но сами они могут иметь право на работу с другими объектами БД
select
data.LVL + 1 as LVL,
2 as OBJ_TYPE,
null as OBJECT_TYPE,
T.RDB$TRIGGER_NAME as OBJECT_NAME,
null as PRIVILEGE,
null as PRIVILEGE_EX
from
RDB$TRIGGERS T
inner join
data on data.OBJ_TYPE = 0 and data.OBJECT_NAME = T.RDB$RELATION_NAME
where
T.RDB$TRIGGER_INACTIVE = 0
order by
2, 4, 5)
select
OBJECT_TYPE,
OBJECT_NAME,
list(distinct PRIVILEGE) as PRIVILEGE,
list(distinct PRIVILEGE_EX) as PRIVILEGE_EX
from
data
where
OBJ_TYPE <> 2
group by
1, 2
order by
1 desc, 2
into
:OBJECT_TYPE,
:OBJECT_NAME,
:PRIVILEGE,
:PRIVILEGE_EX
do
suspend;
end
Список с указанием вызывающих подпрограмм
/* получить по имени пользователя (процедуры, триггера) все его права доступа
и права доступа всех связанных процедур и триггеров по цепочке. Позволяет
избежать бесконечной рекурсии в ситуации, когда процедуры имеют право на вызов
друг друга, в том числе по цепочке из нескольких объектов
Входной параметр:
USER_NAME — имя пользователя, роли, процедуры или триггера, чьи права
проверяются
Выходные параметры:
OBJECT_TYPE — тип объекта (процедура, таблица и т.п. триггеры не перечисляются)
OBJECT_NAME — имя объекта
PRIVILEGE — список непосредственно полученных прав через запятую:
S — чтение,
R — ссылка,
I — вставка,
D — удаление,
U — изменение данных,
X — выполнение (только для процедур),
M — членство (для пользователя по отношению к роли)
CALLER — имя объекта, который имеет права на данный
CALLER_TYPE — тип объекта CALLER (процедура, триггер)
PRIVILEGE_EX — список косвенных (по цепочке) прав, расшифровывается так же
LVL — уровень вложенности (1 — непосредсвенный доступ, 2 — по цепочке через
одну подпрограмму и пр.)
*/
execute block (
USER_NAME varchar(31) = :USER_NAME)
returns (
OBJECT_TYPE varchar(13),
OBJECT_NAME varchar(31),
PRIVILEGE varchar(9),
CALLER_TYPE varchar(13),
CALLER varchar(31),
LVL smallint)
as
declare variable MAX_LEVEL smallint;
declare variable RECORD_CNT integer;
declare variable RECORD_CNT_OLD integer;
begin
MAX_LEVEL = 2;
RECORD_CNT_OLD = -1;
RECORD_CNT = 0;
-- получение максимального уровня вложенности
while (RECORD_CNT > RECORD_CNT_OLD) do
for with recursive data
as (select
1 as LVL,
RDB$OBJECT_TYPE as OBJ_TYPE,
RDB$RELATION_NAME as OBJECT_NAME,
RDB$OBJECT_TYPE || trim(RDB$PRIVILEGE) || RDB$RELATION_NAME as OBJECT_DATA
from
RDB$USER_PRIVILEGES
where
RDB$USER = :USER_NAME
union all
select
data.LVL + 1 as LVL,
R.RDB$OBJECT_TYPE as OBJ_TYPE,
R.RDB$RELATION_NAME as OBJECT_NAME,
R.RDB$OBJECT_TYPE || trim(R.RDB$PRIVILEGE) || R.RDB$RELATION_NAME as OBJECT_DATA
from
RDB$USER_PRIVILEGES R
inner join
data on data.LVL < :MAX_LEVEL and data.OBJECT_NAME = R.RDB$USER and data.OBJECT_NAME <> R.RDB$RELATION_NAME
where
RDB$USER <> :USER_NAME and R.RDB$RELATION_NAME <> :USER_NAME
union all
select
data.LVL + 1 as LVL,
2 as OBJ_TYPE,
T.RDB$TRIGGER_NAME as OBJECT_NAME,
'2X' || T.RDB$TRIGGER_NAME as OBJECT_DATA
from
RDB$TRIGGERS T
inner join
data on data.OBJ_TYPE = 0 and data.OBJECT_NAME = T.RDB$RELATION_NAME
where
T.RDB$TRIGGER_INACTIVE = 0)
select
count(distinct OBJECT_DATA)
from
data
into
:RECORD_CNT
do
begin
RECORD_CNT_OLD = RECORD_CNT;
MAX_LEVEL = MAX_LEVEL + 1;
end
-- извлечение прав доступа
for with recursive data
as (-- права, явно заданные через GRANT
select
1 as LVL,
null as CALLER,
null as CALLER_TYPE,
RDB$OBJECT_TYPE as OBJ_TYPE,
decode(RDB$OBJECT_TYPE, 1, 'представление', 5, 'процедура', 13, 'роль', 'таблица') as OBJECT_TYPE,
RDB$RELATION_NAME as OBJECT_NAME,
trim(RDB$PRIVILEGE) as PRIVILEGE
from
RDB$USER_PRIVILEGES
where
RDB$USER = :USER_NAME
union all
-- права, косвенно полученные по ссылке из процедуры или триггера
select
data.LVL + 1 as LVL,
R.RDB$USER as CALLER,
data.OBJECT_TYPE as CALLER_TYPE,
R.RDB$OBJECT_TYPE as OBJ_TYPE,
decode(R.RDB$OBJECT_TYPE, 1, 'представление', 5, 'процедура', 13, 'роль', 'таблица') as OBJECT_TYPE,
R.RDB$RELATION_NAME as OBJECT_NAME,
trim(R.RDB$PRIVILEGE) as PRIVILEGE
from
RDB$USER_PRIVILEGES R
inner join
data on data.LVL < :MAX_LEVEL and data.OBJECT_NAME = R.RDB$USER and data.OBJECT_NAME <> R.RDB$RELATION_NAME
where
RDB$USER <> :USER_NAME and R.RDB$RELATION_NAME <> :USER_NAME
union all
-- триггеры — на их запуск не нужны отдельные права, но сами они могут иметь право на работу с другими объектами БД
select
data.LVL + 1 as LVL,
T.RDB$RELATION_NAME as CALLER,
data.OBJECT_TYPE as CALLER_TYPE,
2 as OBJ_TYPE,
'триггер' as OBJECT_TYPE,
T.RDB$TRIGGER_NAME as OBJECT_NAME,
null as PRIVILEGE
from
RDB$TRIGGERS T
inner join
data on data.OBJ_TYPE = 0 and data.OBJECT_NAME = T.RDB$RELATION_NAME
where
T.RDB$TRIGGER_INACTIVE = 0
order by
2, 4, 5)
select
OBJECT_TYPE,
OBJECT_NAME,
list(distinct PRIVILEGE) as PRIVILEGE,
CALLER,
CALLER_TYPE,
min(LVL)
from
data
where
OBJ_TYPE <> 2
group by
1, 2, 4, 5
order by
1 desc, 2, 4, 5, 6
into
:OBJECT_TYPE,
:OBJECT_NAME,
:PRIVILEGE,
:CALLER,
:CALLER_TYPE,
:LVL
do
suspend;
end
Комментариев нет:
Отправить комментарий
Пожалуйста, не используйте в сообщениях ненормативную лексику и нарушающие закон темы