Права доступа

Модуль Генератор документов documentgenerator проверяет разрешения на настройки, шаблоны и документы отдельно от доступа к данным исходного объекта. Используйте класс Bitrix\DocumentGenerator\UserPermissions для разрешений модуля, а методы Document::hasAccess() и DataProvider::hasAccess() — для данных провайдера.

Для безопасной генерации нужны обе проверки:

разрешение модуля
    -> доступ пользователя к шаблону
        -> доступ провайдера к исходному объекту
            -> обработка и сохранение документа

Разрешение на создание документа по шаблону не дает доступ к CRM-элементу, заказу или другому исходному объекту. И наоборот, доступ к исходным данным не дает право изменять настройки и шаблоны генератора документов.

Модель разрешений модуля

Класс UserPermissions сопоставляет объект прав, действие и уровень доступа. Внутренняя карта класса содержит следующие сочетания.

Объект прав

Действие

Доступные уровни

Что контролирует

SETTINGS

MODIFY

Нет доступа, есть доступ

Изменение настроек модуля

TEMPLATES

MODIFY

Нет доступа, свои, свои и своего отдела, любые

Изменение шаблонов с учетом автора шаблона

DOCUMENTS

VIEW

Нет доступа, есть доступ

Просмотр документов

DOCUMENTS

MODIFY

Нет доступа, есть доступ

Создание и изменение документов

Константы объектов, действий и уровней находятся в классе UserPermissions:

  • ENTITY_SETTINGS, ENTITY_TEMPLATES, ENTITY_DOCUMENTS — объекты прав,

  • ACTION_VIEW, ACTION_MODIFY — действия из карты разрешений,

  • PERMISSION_NONE, PERMISSION_SELF, PERMISSION_DEPARTMENT — запрет, собственные шаблоны и шаблоны своего отдела,

  • PERMISSION_ALLOW — разрешение «Есть доступ» для настроек и документов,

  • PERMISSION_ANY — разрешение изменять любые шаблоны.

Входные данные для проверок

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

Параметр

Тип

Описание

$userId

int

Положительный идентификатор пользователя, от имени которого выполняется действие

$templateId

int

Положительный идентификатор существующего шаблона

$documentId

int

Положительный идентификатор существующего документа

$providerClass

class-string<DataProvider>

Полное имя зарегистрированного класса провайдера данных

В конкретном сценарии передавайте только нужные значения. Например, для проверки настроек достаточно $userId, а для выбора шаблона нужны $userId и $providerClass.

Получить права пользователя

Метод Driver::getUserPermissions() возвращает объект прав. Без аргумента метод использует идентификатор текущего пользователя. Передайте идентификатор, если проверяете права другого пользователя.

Пример. Получить объект прав для заданного пользователя и проверить основные операции:

use Bitrix\DocumentGenerator\Driver;
use Bitrix\Main\Loader;

// $userId — идентификатор пользователя, чьи права нужно проверить

if (!Loader::includeModule('documentgenerator'))
{
    throw new \RuntimeException('Модуль documentgenerator не установлен');
}

$permissions = Driver::getInstance()->getUserPermissions($userId);

$canModifySettings = $permissions->canModifySettings();
$canModifyTemplates = $permissions->canModifyTemplates();
$canViewDocuments = $permissions->canViewDocuments();
$canModifyDocuments = $permissions->canModifyDocuments();

Метод canViewDocuments() также возвращает true, если пользователь может изменять документы. Методы возвращают логическое значение и не добавляют ошибки в объект Result. Код приложения должен сам остановить операцию или вернуть сообщение об отказе.

Проверка административного статуса действует только для текущего пользователя. Если передать идентификатор другого пользователя, класс рассчитает его права по ролям, даже если этот пользователь является администратором.

Проверить изменение настроек

Проверяйте право не только при отображении элементов управления, но и на сервере непосредственно перед изменением настроек модуля.

// $permissions — права пользователя из Driver::getUserPermissions()
if (!$permissions->canModifySettings())
{
    throw new \RuntimeException('Нет права изменять настройки модуля');
}

// Измените настройки только после проверки

После операции проверьте результат API, который сохраняет настройку. Метод canModifySettings() подтверждает разрешение, но не успешное сохранение.

Проверить доступ к шаблону

Для шаблона действуют два разных ограничения:

  • область изменения определяет, какие шаблоны пользователь может редактировать,

  • привязанные к шаблону коды доступа определяют, может ли пользователь создать по нему документ.

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

Проверить изменение шаблона

Метод canModifyTemplate() учитывает общий уровень изменения шаблонов и автора конкретного шаблона.

Уровни доступа определяют область изменения шаблонов:

  • PERMISSION_SELF — собственные шаблоны,

  • PERMISSION_DEPARTMENT — шаблоны пользователя и сотрудников его подразделений,

  • PERMISSION_ANY — любые шаблоны.

// $permissions — права пользователя из Driver::getUserPermissions()
// $templateId — идентификатор существующего шаблона
if (!$permissions->canModifyTemplate($templateId))
{
    throw new \RuntimeException('Нет права изменять шаблон');
}

Отфильтровать шаблоны для изменения

Метод getFilterForTemplateList() возвращает ORM-фильтр по полю CREATED_BY. Применяйте его к списку шаблонов, который пользователь сможет изменять.

use Bitrix\DocumentGenerator\Model\TemplateTable;

// $permissions — права пользователя из Driver::getUserPermissions()
$filter = array_merge(
    $permissions->getFilterForTemplateList(),
    ['=IS_DELETED' => 'N']
);

$templates = TemplateTable::getList([
    'select' => ['ID', 'NAME', 'CREATED_BY'],
    'filter' => $filter,
    'order' => ['SORT' => 'ASC', 'ID' => 'ASC'],
]);

Если пользователь не может изменять шаблоны, фильтр не должен возвращать доступные записи. Всегда добавляйте результат getFilterForTemplateList() в запрос списка, даже после отдельной проверки canModifyTemplates(). Общая проверка не учитывает область «свои» или «своего отдела».

Проверить доступность шаблона для генерации

Шаблон связывается с пользователями, группами и подразделениями через коды доступа. Константа TemplateUserTable::ALL_USERS содержит системный код доступа UA. Запись с таким кодом делает шаблон доступным всем пользователям. Для остальных вариантов модуль сравнивает коды шаблона с кодами доступа пользователя.

Метод canCreateDocumentOnTemplate() объединяет право изменять документы и доступ пользователя к шаблону:

// $permissions — права пользователя из Driver::getUserPermissions()
// $templateId — идентификатор существующего шаблона
if (!$permissions->canCreateDocumentOnTemplate($templateId))
{
    throw new \RuntimeException('Нет права создать документ по шаблону');
}

Для текущего администратора метод возвращает true без проверки кодов доступа шаблона.

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

Чтобы получить активные шаблоны для определенного провайдера, передайте идентификатор пользователя в TemplateTable::getListByClassName(). Метод добавит фильтр по кодам доступа пользователя.

use Bitrix\DocumentGenerator\Model\TemplateTable;

// $providerClass — полное имя класса провайдера данных
$templates = TemplateTable::getListByClassName($providerClass, $userId);

Без положительного идентификатора пользователя метод не добавляет ограничение по кодам доступа. Передавайте $userId, если список формируется для пользовательского действия.

Сформировать документ безопасно

Проверяйте доступ в том же запросе, в котором запускаете генерацию. Безопасный порядок состоит из следующих шагов:

  1. Получите положительный идентификатор пользователя, который инициировал действие.

  2. Загрузите шаблон и проверьте, что объект найден.

  3. Вызовите canCreateDocumentOnTemplate() для идентификатора шаблона.

  4. Создайте объект Document по шаблону и исходному значению.

  5. Передайте пользователя через setUserId() и включите setIsCheckAccess(true).

  6. Вызовите hasAccess() до чтения полей и создания файла.

  7. Вызовите getFile() и проверьте ошибки объекта Result.

Создание объекта, передача значений и обработка результата подробно разобраны в статье Документы. Не сохраняйте положительный результат проверки доступа для следующих запросов или фоновых задач. Права пользователя и исходного объекта могут измениться.

Проверить доступ к документу

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

Просмотр документа

Сначала проверьте canViewDocuments(), затем установите пользователя документа и вызовите Document::hasAccess().

use Bitrix\DocumentGenerator\Document;

// Загрузите документ
$document = Document::loadById($documentId);
if ($document === null)
{
    throw new \RuntimeException('Документ не найден');
}

// Проверьте разрешение модуля
if (!$permissions->canViewDocuments())
{
    throw new \RuntimeException('Нет права просматривать документы');
}

// Проверьте доступ провайдера к исходным данным
$document->setUserId($userId);
if (!$document->hasAccess())
{
    throw new \RuntimeException('Нет доступа к исходным данным документа');
}

Метод hasAccess() получает корневой провайдер документа и вызывает его проверку доступа через DataProviderManager. Если корневой провайдер не создан, метод возвращает true, поэтому перед просмотром также проверяйте, что документ загружен и содержит ожидаемый источник данных.

Изменение документа

Для пользователя без административного обхода метод canModifyDocument() выполняет составную проверку. Пользователь должен:

  1. Иметь право изменять документы.

  2. Иметь доступ к исходным данным через провайдер.

  3. Иметь доступ к шаблону, по которому создан документ.

// $permissions — права пользователя из Driver::getUserPermissions()
// $document — загруженный объект Document
if (!$permissions->canModifyDocument($document))
{
    throw new \RuntimeException('Нет права изменять документ');
}

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

Для текущего администратора и при отключенной возможности разграничения прав метод сразу возвращает true. Если правила приложения требуют проверять доступ к исходному объекту и для администратора, дополнительно вызовите $document->hasAccess($userId).

Сформировать список документов

Класс UserPermissions не предоставляет общего ORM-фильтра документов по доступу провайдера. Разрешение canViewDocuments() относится ко всему модулю, а Document::hasAccess() — к исходному объекту конкретного документа.

По возможности начинайте список с объектов исходного модуля, доступных пользователю. Ограничивайте документы одновременно по полному имени провайдера PROVIDER и значению исходного объекта VALUE. Одинаковое значение может относиться к разным провайдерам, поэтому одного поля VALUE недостаточно.

Если такой фильтр построить нельзя, проверяйте каждый документ отдельно. Загрузите объект Document, передайте $userId и вызовите hasAccess() до добавления записи, ссылок на файлы и других данных в ответ.

Учитывайте фильтрацию при подсчете общего количества и разбиении на страницы. Проверка только после пагинации может вернуть неполную страницу и раскрыть количество недоступных записей.

Включить проверку данных при генерации

Метод Document::hasAccess() возвращает результат проверки корневого провайдера. Этот же вызов включает проверку доступа к загруженным вложенным провайдерам при дальнейшем получении полей.

Для нового документа задайте пользователя и включите проверку до вызова методов, которые получают поля или создают файл:

// $document — созданный объект Document
// $userId — положительный идентификатор инициатора
$document->setUserId($userId);
$document->setIsCheckAccess(true);

if (!$document->hasAccess())
{
    throw new \RuntimeException('Нет доступа к исходным данным');
}

$fileResult = $document->getFile();
if (!$fileResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $fileResult->getErrorMessages()));
}

Провайдер исходного модуля должен переопределить DataProvider::hasAccess($userId) и проверить доступ к загруженному объекту. Базовая реализация возвращает доступ родительского провайдера только для загруженного вложенного объекта, а в остальных случаях — false.

В пользовательском сценарии передавайте идентификатор авторизованного пользователя больше нуля. Если передать 0, модуль попытается использовать идентификатор текущего пользователя. Метод DataProviderManager::checkDataProviderAccess() разрешит доступ без вызова провайдера, если идентификатор текущего пользователя также равен 0.

Подробнее о реализации проверки в собственном источнике данных читайте в статье Провайдеры данных.

Как роли формируют разрешения

Роль объединяет разрешения, а код доступа связывает роль с пользователями, группами или подразделениями. Модуль хранит эти части раздельно:

Объект модели

Назначение

Role

Содержит роль и нормализованный набор разрешений

RolePermissionTable

Связывает роль с объектом прав, действием и уровнем доступа

RoleAccessTable

Связывает роль с кодом доступа пользователя, группы или подразделения

TemplateUserTable

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

При создании UserPermissions модуль получает коды доступа пользователя и выбирает связанные с ними роли. Если несколько ролей задают одно действие, применяется максимальный уровень. Отсутствующее разрешение равно PERMISSION_NONE.

Настраивайте роли через штатный интерфейс прав или контроллер модуля. Не изменяйте записи разрешений напрямую. При сохранении роли модуль нормализует карту и заполняет отсутствующие действия значением «Нет доступа».

Текущий администратор получает максимальные разрешения. Доступ к исходным данным проверяйте отдельно через провайдер, если правила приложения распространяются на администратора.

Права в фоновых задачах

Агент, очередь или отложенный обработчик должны хранить идентификатор пользователя, который инициировал действие. Перед чтением данных и созданием файла задача заново рассчитывает права, проверяет шаблон и вызывает Document::hasAccess().

Метод Driver::getUserPermissions() кеширует объект прав по идентификатору пользователя. Новый вызов в том же долгоживущем PHP-процессе не перечитывает роли. Выполняйте отложенную проверку в новом запросе или процессе. Если фоновый обработчик работает в одном процессе для нескольких задач, создайте новый объект UserPermissions непосредственно перед проверкой.

Не полагайтесь на проверку, выполненную при постановке задачи в очередь. Между постановкой и выполнением могут измениться роли пользователя, коды доступа шаблона и права исходного объекта. Если процесс должен работать без пользовательского контекста, оформите его как отдельный доверенный системный сценарий и не используйте такой режим для обработки произвольного пользовательского запроса.

Частые ошибки

Ошибка

Решение

Проверена только роль модуля

После canModifyDocuments() вызовите Document::hasAccess() или включите setIsCheckAccess(true) до обработки полей

Шаблон загружен без проверки

Для изменения вызовите canModifyTemplate(), для создания документа — canCreateDocumentOnTemplate()

Список шаблонов не ограничен

Используйте getFilterForTemplateList() для списка редактирования, а TemplateTable::getListByClassName($providerClass, $userId) — для выбора шаблона пользователем

Проверка выполнена после получения данных

Установите пользователя и включите проверку до getFields(), getFile() или другого метода, который читает значения провайдера

Отказ заменен пустым результатом

Во внутреннем журнале различайте отсутствие объекта, разрешения модуля и доступа провайдера. Во внешнем ответе не раскрывайте существование недоступного объекта через разные тексты или коды ошибок

Связанные материалы