Система контроля прав доступа При помощи процедур и триггеров в MySQL
Постановка задачи Для каждой пары пользователь - запись должна быть возможность определить уровень доступа : нет, просмотр или редактирование. Доступ определяется сочетанием нескольких правил, которые может сформулировать администратор системы. Из результатов, которые дают несколько правил, выбирается наиболее строгий вариант. Каждое правило может содержать условия, зависящие от свойств сотрудника (его роль, принадлежность к подразделению), и произвольных свойств записи.
Пример Пусть есть следующие правила для доступа пользователей группы « Менеджеры » к справочнику « Клиенты »: 1. Если в поле «Менеджер» в свойствах клиента выбран тот пользователь, для которого мы определяем права, то он получит право редактирования этого клиента. Иначе у него будет право только на просмотр информации о клиенте. 3. Если клиент относится к группе «Новые», то любой менеджер получит право на редактирование информации об этом клиенте. 2. Если клиент закреплен за тем подразделением, к которому принадлежит пользователь, то пользователь сможет редактировать его; иначе, пользователь не сможет даже увидеть клиента.
Пример Клиенты, закрепленные за подразделением Клиенты данного менеджера Клиенты группы «Новые» нет просмотр редактирование
Варианты реализации 1. Создать функцию GetRights (user, record, module) в PHP, которая А) Построит список правил, применимых в данном случае, Б) По каждому правилу сформулирует условие и проверит его выполнение, В) Выберет минимальный результат из всех, которые дают правила. Для определения доступа к одной записи – просто и надежно. Со списком записей будет работать очень медленно, даже если кэшировать результат шага А. Таким образом можно работать только со списками в сотни записей.
Варианты реализации 2. Кэшировать результат расчета прав в БД. Пересчитывать фрагмент кэша каждый раз при изменении правил, свойств пользователя, свойств записи. Выборка из кэша будет работать очень быстро. Кэш будет пересчитываться очень медленно, особенно после выполнения операций с группами записей.
Варианты реализации 3. Создать из PHP хранимую функцию GetRightsModule (user, record) в SQL, которая А) В своем коде уже содержит условия всех правил, Б) Автоматически пересоздается при изменении правил. Намного быстрее, чем определение прав из PHP, но для списка записей – все равно медленно. Таким способом можно эффективно работать с несколькими тысячами записей. Становится неудобно получать права для одной записи из PHP – надо конструировать вызов функции.
Варианты реализации 4. Соединить оба подхода : пусть будет кэш в БД, который пересчитывается при помощи хранимой процедуры. Пересчет будет автоматически происходить после изменения свойств записи – при помощи триггеров. Очень быстрая работа со списками, даже в сотни тысяч записей. Не нужно отслеживать изменение свойств записей из PHP-кода. Нужно автоматически генерировать триггеры при сохранении правил, и хранимые процедуры – при пересчете прав.
Реализация Таблица кэша : Moduleмодуль=> таблица modules Recordid записи=> таблица данных (например, clients) Userпользователь=> таблица users Accessдоступ0, 1 или 2 (нет, чтение или редактирование) Таблица очереди на расчет прав: Moduleмодуль=> таблица modules Userпользователь=> таблица users Recordid записи=> таблица данных (например, clients) Lockedдата блокировки
Реализация Отслеживание изменений прав : При сохранении правил доступа вызывается PHP-функция RebuildTriggerFor(module), которая создает триггеры на INSERT, UPDATE и DELETE на таблице этого модуля. After insert: INSERT INTO rights_queue (module,`user`,record) VALUES(X',NULL,NEW.id); After update: IF _managerNEW.manager OR (_manager IS NULL AND NEW.manager IS NOT NULL) OR (NEW.manager IS NULL AND _manager IS NOT NULL) THEN INSERT INTO rights_queue (module,`user`,record) VALUES(X,NULL,NEW.id); END IF; After delete: DELETE FROM rights_cache WHERE module='4' AND record=OLD.id;
Реализация Перестройка фрагмента кэша : PHP-функция BuildSetFor(module,user) перестраивает часть кэша для данных модуля и пользователя. Для этого она генерирует хранимую функцию tmp_check_[модуль]_[пользователь] (id записи), в теле которой содержится набор условий, соответствующих всем применимым для данной пары правилам. Делается вызов SELECT tmp_check…(id) FROM table при этом функция срабатывает для каждой записи в таблице, и помещает результат расчета прав в кэш. По определенному правилу (через какой-то интервал времени, или при каждой загрузке страницы) вызывается PHP-скрипт, который проверяет наличие очереди на пересчет прав. Он захватывает несколько записей из очереди, и обрабатывает их.
Оптимизация 1. Не храним нулевые значения в кэше 2. Если права доступа к таблице « плоские » ( не зависят от свойств записи ) – не храним кэш. 3. Создаем « обертку » ( программный интерфейс ), который позволяет работать с правами из PHP и SQL, не задумываясь о физической реализации расчета прав.