© 2004 Малец В.В.
В замечательной книге "Мир 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 "Главбух".
Соответственно пользователям назначается не одна роль, а две: ("Бухгалтер" + "Расчетчик", "Бухгалтер" + "Главбух").
Таким образом, мы построили систему безопасности, основанную на домене ролей пользователя, используя при этом штатный механизм ролей, сохранив стандартное назначение привилегий и не вводя дополнительных таблиц для хранения данных о пользователях.
Copyright© 2004 Малец В.В. Специально для Delphi Plus