Вы только что добавили новую кнопку в интерфейс. Заказчик просит: "Сделайте, чтобы только начальники смены её видели". Вы находите соответствующую группу в Bitrix и пишете проверку inGroup('НачальникСмены'). Кажется, задача решена.

Но через месяц появляются "Старшие мастера", которым тоже нужен этот доступ. Вы дописываете ||. Потом добавляется очередное условие. И еще, и еще, и еще... В итоге простая кнопка обрастает десятком строк логики, которая дублируется в разных компонентах.

Знакомая картина? Мы сталкивались с этим в каждом крупном проекте. Штатная система прав Bitrix отлично работает для коробочных модулей, но бессильна перед уникальной бизнес-логикой. Проверки групп "просачиваются" в код, делая его хрупким и непрозрачным.

Меня зовут Александр Костиков, я ведущий разработчик в НЛМК ИТ. В этой статье я покажу, как мы перестали "цементировать" бизнес-термины в коде и используем систему, где права меняются без правок в компонентах.

Если вы устали от проверок inGroup('СуперАдминистратор') - этот материал для вас.

Проблема: почему штатные права Bitrix не спасают

В Bitrix есть несколько систем прав, но все они изолированы:

  • Группы - для общих доступов к разделам.

  • Матрицы прав инфоблоков - своя логика.

  • Роли в CRM - своя вселенная.

  • ACL страниц - отдельная история.

Для кастомного функционала (ваши гриды, API-методы, кнопки) остаётся проверять принадлежность к группам в коде. Это ведёт к трём проблемам:

  • Цена изменений. Чтобы дать доступ новой группе, нужно найти и обновить все компоненты, где используется это правило. Пропустите один - получите несогласованное поведение.

  • Чёрный ящик. Нет единого места, где видно, "кто что может". Понимание системы прав требует чтения исходного кода.

  • Дублирование. Одно и то же правило копируется, а со временем его версии в разных файлах могут разойтись.

Мы хотели перейти от модели "правила в коде" к модели "правила в конфигурации", где сама проверка (canDoThis) остаётся в коде, а состав исполнителей (какие роли имеют это право) управляется централизованно.

Наш переломный момент настал в проекте с 40+ кастомными компонентами и самописными модулями. Каждый новый запрос на изменение прав вызывал тихий ужас у разработчиков. Мы поняли: нужно решение, которое отделит бизнес-логику прав от кода приложения.

Решение: Permission-First - права как инструменты

Мы взяли за основу простую метафору: права - это инструменты, роли - наборы инструментов.

Вместо проверки "ты Супервайзер?" код теперь спрашивает: "у тебя есть инструмент 'news.delete'?".
Если завтра Менеджеру понадобится удалять новости - мы просто добавим ему этот "инструмент" в набор, не меняя ни кода, ни его должности.

Архитектура в трёх китах:

  1. Permission (Разрешение) - атомарное действие (news.delete, report.export).

  2. Role (Роль) - именованный набор разрешений, привязанный к группе Bitrix.

  3. Маппинг - таблица связей "Роль → Permissions".

Стандартные группы Bitrix остаются для назначения пользователям, но их бизнес-смысл теперь описывается через permissions.

Как это работает

Определение интерфейсов

interface Permission
{
    public function getCode(): string;
    public function getName(): string;
    public function getDescription(): string;
    public function getCategory(): Category;
}
 
interface Role
{
    public function getCode(): string;
    public function getName(): string;
    public function getDescription(): string;
    public function getCategory(): Category;
    public function isAssignable(): bool;
    public function isVisible(): bool;
}
 
interface Category
{
    public function getCode(): string;
    public function getName(): string;
    public function getDescription(): string;
}

Permission - атомарное действие.
Role - связь с системной группой.
Category - для группировки ролей и прав по смыслу (удобно для UI и аудита).

Работа со связями
Связи между ролями и правами, а также пользователями и ролями, реализуются через ORM-таблицы или кастомные сервисы. В Bitrix удобно назначать роли пользователям через группы (STRING_ID = код роли).

Реализация сценария
Описываем права и роли через классы/enum:

enum ExamplePermission: string implements Permission
{
    case PAGE_VIEW = 'custom.page.view';
    case EXPORT_DATA = 'grid.export';
}
 
enum ExampleRole: string implements Role
{
    case MANAGER = 'manager';
    case AUDITOR = 'auditor';
}

Связываем роли и права (многие-ко-многим):

$rolePermissionRepo->createRelation(ExampleRole::MANAGER, ExamplePermission::EXPORT_DATA);
$rolePermissionRepo->createRelation(ExampleRole::MANAGER, ExamplePermission::PAGE_VIEW);
$rolePermissionRepo->createRelation(ExampleRole::AUDITOR, ExamplePermission::PAGE_VIEW);

Назначаем роли пользователям (например, через Bitrix-группы):

$userRoleRepo->grantRole($userId, ExampleRole::MANAGER);

Проверяем право:

$context = $contextService->get($userId); // или внутри контекста получать для текущего юзера сразу $contextService->current();
 
if (!$context->can(ExamplePermission::PAGE_VIEW)) {
    // блокируем исполнение
}

Расширяемость через события Bitrix

Для поддержки модульности и масштабирования можно собирать права и роли через события:

class OnCollectPermissionsListener
{
    public static function handle(): \Bitrix\Main\EventResult
    {
        return new \Bitrix\Main\EventResult(
            \Bitrix\Main\EventResult::SUCCESS,
            ['permissions' => ExamplePermission::cases()]
        );
    }
}
 
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler('role', 'OnCollectPermissions', [OnCollectPermissionsListener::class, 'handle']);

Аналогичным образом добавляются обработчики для сбора ролей. Это позволяет любому модулю/сервису в вашем проекте объявить свои права и роли.

Затем в репозиториях Ролей и Прав собираем весь список зарегистрированных сущностей:

$event = new \Bitrix\Main\Event('role', 'OnCollectPermissions');
$event->send();
 
$permissions = [];
foreach ($event->getResults() as $eventResult) {
    if ($eventResult->getType() === \Bitrix\Main\EventResult::SUCCESS) {
        $list = $eventResult->getParameters()['permissions'] ?? [];
        foreach ($list as $permission) {
            $permissions[$permission->getCode()] = $permission;
        }
    }
}

В нашем случае вся функциональность собрана в небольшой модуль.

Наша реализация - один из вариантов подхода Permission-Based Access Control (PBAC), который зарекомендовал себя в современных веб-фреймворках, тот же Битрикс использует подобную модель в модуле CRM храня все данные в таблицах.

Для разработчиков доступны решения такие как spatie/laravel-permission и подобные, мы взяли общую идею и реализовали под нужды наших проектов.

Преимущества подхода

  • Гибкость: любое действие контролируется отдельным permission, исходя из бизнес-логики.

  • Прозрачность и аудит: перечень прав ясен, архитектуру легко ревьюировать, можно строить отчёты по доступу.

  • Масштабируемость: легко добавлять новые сценарии без изменений существующей структуры.

  • Единый код проверки: все кастомные проверки делаются через один фасад.

Что учитывать

  • Требуется поддерживать уникальность кодов и синхронизацию с Bitrix-группами.

  • Необходимо поддерживать дополнительную логику миграций для создания связей и настройки прав между окружениями.

  • Дополнительная дисциплина и контроль - чтобы модель не разрослась и не вступила в конфликт с существующими ролями платформы.

Итог: Order from Chaos

Permission-first подход не заменяет Bitrix ACL, а дополняет его там, где платформа не предоставляет удобных инструментов. Мы получили:

  • Для разработчиков: Чистый код без бизнес-терминов.

  • Для бизнеса: Возможность менять правила доступа без программистов.

  • Для админов: Прозрачную систему, где видно, какая роль что может.

Система прав перестала быть "зашитой в коде" и стала управляемым бизнес-инструментом.

Это рабочий способ построить контролируемый, тестируемый и безопасный слой на уровне всего корпоративного решения.

Комментарии (0)