|
Альтернативный механизм использования
ролей
В замечательной книге "Мир Interbase" есть шикарная фаза ": начинающие
разработчики часто считают права на объекты "излишеством" и стараются
придумать собственные системы безопасности, не утруждая себя изучением уже
существующей".
Согласен с данной точкой зрения целиком и полностью (в том числе сам
проходил через этот этап - документацию читать нам лень, а изучать
системные таблицы - вдвойне) и хочу показать, что даже стандартные
средства если они по какой-то причине вас не удовлетворяют, можно
использовать по-новому, причем без создания дополнительных структур
данных. Сразу вводим соглашение, что привилегии на объекты БД даются не
пользователям, а ролям, ибо пользователи приходят и уходят, а выполняемые
ими функции остаются.
Стандартный подход использования ролей заключается в том, что
пользователь при подключении к БД указывает конкретную роль (из тех, что
ему даны администратором) и получает привилегии данной роли.
Рассмотрим пример. Предположим, что у гипотетической системы
бухгалтерского учета есть 3 группы пользователей - ролей: "главбух",
"кассир" и "расчетчик зарплаты". Должностные обязанности сотрудников
определяют следующий доступ к БД программы:
- все пользователи должны читать данные справочника "План_Счетов";
- "расчетчик зарплаты" работает с таблицей "Расчеты_По_Зарплате";
- "кассир" работает с таблицей "Операции_По_Кассе";
- редактировать справочник "План_Счетов" может только "главбух";
- "главбух" ведет операции по учету основных средств - таблица
"Операции_ОС";
- "главбух" должен контролировать работу подчиненных - значит иметь
как минимум доступ на чтение к их данным;
- "кассир" и "расчетчик зарплаты" не видят операции по основным
средствам, а также работу друг друга.
Очевидно, что при
стандартном подходе использования ролей необходимо сформировать следующие
скрипты назначения привилегий:
/*1. Роль "Расчетчик":*/ grant select
on "План_Счетов" to "Расчетчик"; grant select, insert, update, delete
on "Расчеты_ По_Зарплате" to "Расчетчик"; /*2. Роль
"Кассир":*/ grant select on "План_Счетов" to "Кассир"; grant select,
insert, update, delete on "Операции_По_Кассе" to "Кассир"; /*3. Роль
"Главбух":*/ grant select, insert, update, delete on "Операции_По_ОС"
to "Главбух"; grant select, insert, update, delete on "План_Счетов" to
"Главбух"; grant select on "Расчеты_По_Зарплате" to "Главбух"; grant
select on "Операции_По_Кассе" to "Главбух".
Пример очень простой, но отражает суть: для каждой роли мы прописываем
доступ на чтение справочника "План_Счетов". А поскольку, как говаривал
один из моих преподавателей, "жизнь шире наших схем", в реальной системе
наберется далеко не одна общая привилегия, которую нужно давать доброму
десятку ролей.
Фатального конечно в этом ничего нет, но некрасиво как-то все роли
прописывать с нуля - логичнее было бы пользователю сразу иметь привилегии
всех своих ролей, а сами роли строить по принципу "базовая роль" -
"специфические привилегии". Кроме того, механизм ролей я предлагаю
использовать еще и для идентификации пользователей приложения (напомню,
что в Interbase список пользователей ведется на уровне сервера) дабы в
своей БД не держать информацию о зарегистрированных пользователях.
Создадим роль "Пользователь", наличие которой будет определять, что
пользователь сервера Interbase - зарегистрированный пользователь
приложения (заодно ее можно дать права на чтение общих справочников).
Впрочем, роли-то как раз определяются на уровне БД, и возможно, можно
обойтись проверкой на существование хотя бы одной роли у пользователя.
Плюсы данной схемы: пользователю не надо указывать роль при подключении
(ему вообще не надо будет знать о существовании ролей), он всегда обладает
всеми своими правами в системе; администратору - создавать роли,
различающиеся двумя привилегиями или "супер-роли" (например, роль
"Главбух" как совокупность прав всех сотрудников бухгалтерии + права
администрирования плана счетов и т.п.) - достаточно создать базовый набор
ролей, а специфические будут иметь только те привилегии, которые расширяют
базовую роль. Впрочем, роли можно настроить и по-старому (прописывать
доступ ко всем объектам) - тут появляется гибкость.
Минусы: реализация невозможна при использовании прямого доступа к
таблицам - поскольку пользователь не указывает роль, бессмысленно ролям
давать привилегии на таблицы.
Доступ к данным будем осуществлять через представления (на чтение) и
хранимые процедуры (вставка, обновление, удаление). Доступ на чтение
конечно тоже можно осуществлять через хранимые процедуры, но при
построении своих запросов для нестандартных выборок данных или при
разработке отчетов у пользователей (да и администратора) вашего приложения
могут возникнуть проблемы - непривычно вместо соединений указывать
параметры для процедуры.
Права на запуск процедур и просмотр представлений дадим всем
пользователям (PUBLIC), а внутри процедур (представлений) будем проверять
наличие у пользователя необходимых ролей, обращаясь к системным таблицам
RDB$USER_PRIVILEGES и RDB$ROLES. Тогда представления будут существовать
для всех пользователей сервера, но данные из них увидят только
пользователи, которым даны роли, имеющие привилегию SELECT - для прочих
представления не будут содержать данных. В процедурах можно при проверке
прав пользователя возбуждать исключение, если не найдена ни одна роль,
имеющая права на запуск процедуры или просто выходить.
Для обеспечения этой функциональности нам понадобятся: во-первых,
список ролей текущего пользователя и во-вторых функция, которая бы
определяла у пользователя наличие конкретной роли. Список ролей удобно
получить опять же в виде представления:
CREATE VIEW CURRENT_USER_ROLES(USER_NAME, ROLE_NAME) AS select
UP.rdb$user as USER_NAME, R.rdb$role_name as ROLE_NAME from
rdb$user_privileges UP, rdb$roles R where UP.rdb$relation_name =
R.rdb$role_name and UP.rdb$user = Current_User and Current_User
<> 'DBOWNER' and Current_User <> 'SYSDBA' union
all select Current_User as USER_NAME, R.rdb$role_name as
ROLE_NAME from rdb$roles R where Current_User = 'SYSDBA' or
Current_User = 'DBOWNER';
Второй запрос нужен для того, чтобы SYSDBA и владельцу БД (в данном
примере это пользователь "DBOWNER") от имени которых в некоторых случаях
необходимо работать администратору БД были доступны все роли, а значит,
запуск всех процедур и просмотр данных во всех представлениях.
Функцию реализуем естественно в виде хранимой процедуры:
CREATE PROCEDURE IS_ROLE(vROLE_NAME
CHAR(31)) RETURNS(FLAG SMALLINT) AS DECLARE VARIABLE I
INTEGER; begin FLAG = 0; if (Current_User = 'SYSDBA' or
Current_User = 'DBOWNER') then FLAG = 1; else begin select
count(*) from rdb$user_privileges UP where UP.rdb$user =
Current_User and UP.rdb$relation_name = :vRole_Name into :i; if
(:i > 0) then FLAG = 1; end suspend; end
Тогда в нашем примере скрипт на создания представления на справочник
"План_Счетов" может выглядеть следующим образом:
Create view "V_План_Счетов" (:) As Select : from
"План_Счетов" where exists (select 1 from CURRENT_USER_ROLES
CUR ,RDB$ROLES R ,RDB$USER_PRIVILEGES UP where
UP.Rdb$Relation_Name = "План_Счетов" and UP.Rdb$User =
R.Rdb$Role_Name and CUR.ROLE_NAME = R.RDB$ROLE_NAME);
А новое определение ролей и привилегий будет выглядеть
так:
/* 1. "Бухгалтер" - базовая
роль для отдела "Бухгалтерия" */ grant select on "План_Счетов" to
"Бухгалтер"; /* 2. "Расчетчик" */ grant insert, update, delete on
"Расчеты_По_Зарплате" to "Расчетчик"; /* 3. "Кассир" */ grant
insert, update, delete on "Операции_По_Кассе" to "Кассир"; /* 4.
"Главбух" */ grant insert, update, delete on "План_Счетов" to
"Главбух"; grant select on "Расчеты_По_Зарплате" to "Главбух"; grant
select on "Операции_По_кассе" to "Главбух"; grant select, insert,
update, delete on "Операции_По_ОС" to "Главбух";
Соответственно пользователям назначается не одна роль, а две:
("Бухгалтер" + "Расчетчик", "Бухгалтер" + "Главбух").
Таким образом, мы построили систему безопасности, основанную на домене
ролей пользователя, используя при этом штатный механизм ролей, сохранив
стандартное назначение привилегий и не вводя дополнительных таблиц для
хранения данных о пользователях. |