Альтернативное использование ролей в Interbase

© 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

Rambler's Top100