Общая концепция
С версии 20.0.1200 Главного модуля (main) добавлен API для работы с новыми правами доступа.
Библиотека предназначена для упрощения и стандартизации работы с правами доступа пользователей в модулях.
Терминология
Термин | Описание |
---|---|
Пользователь | Авторизованный пользователь системы. Обязательно имеет свой идентификатор (id). |
Группа пользователей | Объединение пользователей, предоставляемое главным модулем: администраторы, сотрудники и т.д. |
Access code | Объединение пользователей по какому-либо критерию. Например: пользователи, состоящие в конкретной рабочей группе; сотрудники отдела и т.д. Предоставляется модулем main и хранится в таблице b_user_access. |
Действие | Действие (операция), совершаемое пользователем. Например: сохранение записи. |
Разрешение доступа | Конкретное право, предоставляемое пользователю. Например: редактирование своих записей или редактирование всех записей. |
Правило доступа | Набор логики, учитывающий предоставленные пользователю права и другие факторы, и разрешающий или нет выполнение выбранного действия. |
Роль | Связь между access кодами и выбранными правами доступа. |
Общая концепция
В качестве основы для реализации контроля доступа выбрана rule based модель:
- Cоставляется список действий, которые может совершить пользователь;
- Cоставляется список разрешений, которые могут быть включены для пользователя;
- Для каждого действия описывается правило доступа (на основе разрешений или каких-то других факторов);
- Контроллер на основе входных данных (действие, пользователь или любая дополнительная информация) находит нужное правило, выполняет его и возвращает результат;
- Клиентский код определяет, что делать с результатом.
Система прав доступа предполагается независимой в каждом модуле, со своим набором действий, прав и правил, и со своими таблицами в БД для хранения настроек. Общее API лишь предоставляет единый подход, необходимые классы и методы, но их использование не обязательно.
Работа с БД
Предполагается, что каждый модуль будет хранить роли и права доступа в своих таблицах. API предоставляет базовые классы для упрощения работы с ORM:
Класс | Описание |
---|---|
Bitrix\Main\Access\Permission\AccessPermissionTable | Класс для работы с таблицей для хранения разрешений. Поддерживает иерархию разрешений и содержит необходимые для этого проверки. |
Bitrix\Main\Access\Role\AccessRoleTable | Класс для работы с таблицей для хранения ролей. |
Bitrix\Main\Access\Role\AccessRoleRelationTable | Класс для работы с таблицей для хранения связей между ролями и пользователями (access кодами пользователей). |
Роли
Роль используется для обеспечения связи между access кодами пользователей и разрешениями. Если какой-то пользователь находится в нескольких ролях, то он получит все права, доступные этим ролям (сумму прав).
Пользователь может не принадлежать никакой роли и все равно иметь возможность совершать какие-то действия, т.к. возможность что-то сделать определяется правилом, описанным для этого действия. Правило же может учитывать выданные разрешения, а может вообще не зависеть от них.
Разрешения
Для работы со списком разрешений реализован базовый класс справочника Bitrix\Main\Access\Permission\PermissionDictionary, предоставляющий методы для:
- получения списка всех разрешений;
- получения названий, подсказок и их переводов;
- работы с иерархией в разрешениях (см. ниже).
При этом предполагается, что как таковые разрешения будут записаны в словарь в виде констант. Аналогичный пример можно посмотреть в модуле tasks: Bitrix\Tasks\Access\Permission\PermissionDictionary
.
Иерархия разрешений
Из коробки поддерживается возможность создания разрешений, зависящих от других разрешений. Для примера возьмем следующий набор прав:
Редактирование записей
- Редактирование записей в своем отделе;
- Редактирование всех записей.
Без включения права Редактирование записей не получится включить остальные два. При выключении Редактирования записей будут автоматически выключены и все зависимые.
С технической точки зрения это реализуется с помощью особого формата значений в PermissionDictionary (используется material path нотация), вложенность при этом не ограничена. Пример:
const PERMISSION_EDIT = '1', PERMISSION_EDIT_DEPARTMENT = '1.1', PERMISSION_EDIT_ALL = '1.2';
Отдельно стоит отметить причины по которым используется именно такая запись:
- константы легко отслеживать по коду с помощью IDE;
- не требуется дополнительная запись о родительском элементе;
- в целом компактный формат, удобный для записи словаря и чтения его человеком.
Контроллер
Единой точкой входа для проверки прав доступа в рамках модуля выступает контроллер, удовлетворяющий интерфейсу AccessibleController и, в общем случае, являющийся наследником BaseAccessController.
В каждом модуле, где предполагается использование, необходимо создавать свой контроллер. В минимальном случае достаточно отнаследоваться от BaseAccessController и реализовать два метода:
protected function loadItem(int $itemId = null): AccessibleItem { //... } protected function loadUser(int $userId): AccessibleUser { //... }
Базовый контроллер предоставляет два способа использования (на примере модуля tasks).
Полный (об используемых моделях описано ниже):
$userModel = UserModel::createFromId($userId); $item = TaskModel::createFromId($taskId); $params = []; $result = (new TaskAccessController($userModel))->check($action, $item, $params);
И упрощенный:
$result = TaskAccessController::can($userId, $action, $itemId, $params);
Кроме этого существует возможность пакетной проверки доступа к набору действий над одной сущностью:
$request = [ $actionId => $params ]; $result = (new TaskAccessController($userModel))->batchCheck($request, $item);
В ответе такой запрос вернет массив $actionId => $result
В контроллере реализован статический кеш по пользователю, позволяющий сократить количество запросов к БД. Что бы получить уже созданный экземпляр, можно воспользоваться методом getInstance($userId)
.
Модели
Проверка прав доступа на базовом уровне работает с двумя моделями:
- AccessibleUser - предоставляет необходимую информацию и методы о пользователе, совершающем действие. Для удобства реализован базовый класс UserModel. UserModel может быть создан из id пользователя или заполнен свойствами вручную.
- AccessibleItem - модель сущности, над которой выполняются действия.
UserModel из коробки предоставляет методы, удобные для использования в правилах:
isAdmin()
- проверка, является ли пользователь администратором;getPermission(string $permissionId)
- значение указанного разрешения для пользователя;getUserDepartments()
- список отделов пользователя;getAccessCodes()
- список access кодов пользователя;getSubordinate($userId)
- отношения в иерархии компании с указанным пользователем (об этом ниже).
Bitrix\Main\Access\User\UserSubordinate
Вспомогательный класс, обеспечивает удобную работу с иерархией в компании. Необходим, т.к. один из часто встречающихся сценариев: начальник пользователя должен иметь не меньше прав, чем сам пользователь.
UserSubordinate::getSubordinate($userId)
- позволяет определить кем является указанный пользователь по отношению к текущему.
Возможные варианты:
UserSubordinate::RELATION_HIMSELF
- один и тот же человек;UserSubordinate::RELATION_DEPARTMENT
- сотрудники одного отдела;UserSubordinate::RELATION_SUBORDINATE
- подчиненный;UserSubordinate::RELATION_DIRECTOR
- начальник;UserSubordinate::RELATION_OTHER_DIRECTOR
- начальник другого отдела;UserSubordinate::RELATION_OTHER
- все прочее (возможно получить, если, к примеру, не установлен модуль intranet).
Перехват управления
Для расширения функционала и перехвата управления реализована работа с событиями:
Событие | Описание |
---|---|
Bitrix\Main\Access\Event\EventDictionary::EVENT_ON_BEFORE_CHECK | Вызывается перед проверкой доступа по конкретному правилу. |
Bitrix\Main\Access\Event\EventDictionary::EVENT_ON_AFTER_CHECK | Вызывается после того как отрабатывает проверка по конкретному правилу. |
В события передается следующий набор параметров:
[ 'user' // AccessibleUser 'item' // AccessibleItem 'action' // string действие, выполняемое пользователем 'params' // misc любые дополнительные параметры 'isAccess' // null|bool результат работы правила ]
Каждый обработчик, висящий на каком-либо событии должен вернуть объект Bitrix\Main\Access\Event\EventResult
. Любые прочие значения будут проигнорированы.
Если хотя бы один из обработчиков вернул запрет, то в доступе будет отказано. Кроме этого, если событие onBeforeCheck возвращает конкретный результат, то выполнение прерывается и контроллер возвращает полученный результат.