При разработке систем учета часто возникает задача разграничения прав доступа к чувствительным данным, например, к информации о заработной плате или ФОТ (Фонде оплаты труда). Рассмотрим ситуацию, когда у нас есть Регистр сведений с данными о зарплате, и нам необходимо сделать так, чтобы каждый сотрудник видел только свои данные. При этом связь "Пользователь базы — Сотрудник" хранится в реквизитах справочника пользователей.
В этой статье мы подробно разберем, как правильно реализовать ограничение доступа на уровне записей (RLS), почему нельзя просто добавить реквизит и обратиться к нему в запросе, и какие подводные камни нас ожидают при работе с ролями.
Иногда требуется не только ограничить записи, но и обеспечить доступ и видимость реквизитов документов. Имеется регистр сведений (РС), содержащий измерение Сотрудник и ресурс ФОТ.
Требуется настроить права доступа так, чтобы пользователь, заходя в систему, видел записи в этом регистре только по тому сотруднику, который сопоставлен с его учетной записью.
Автор вопроса предлагает следующее решение: добавить в справочник Пользователи реквизит Сотрудник и настроить RLS напрямую по этому реквизиту. Давайте проанализируем, насколько это корректно с точки зрения архитектуры и производительности.
Прежде чем писать код RLS, необходимо разобраться с фундаментом системы прав доступа 1С. Важнейший момент, который часто упускают новички, заключается в том, что права в 1С работают по принципу сложения (аддитивности).
Что это значит?
Если пользователю назначены две роли:
Поэтому, если вы работаете в типовой конфигурации (ЗУП, ERP, УТ) или в самописной с использованием БСП, первым делом нужно убедиться, что у пользователя нет других ролей, предоставляющих безусловный доступ к нашему регистру сведений. Скорее всего, вам придется создать собственную роль "Чтение ФОТ" и не давать пользователю типовые роли, открывающие этот регистр. Для такой проверки подойдёт инструмент анализа прав доступа по ключевым разрезам.
Теперь перейдем к технической реализации. Автор идеи предлагал написать в шаблоне ограничения доступа что-то вроде:
ГДЕ Сотрудник = &ТекущийПользователь.Сотрудник
Давайте разберем, почему так делать не рекомендуется.
Использование обращения через точку (разыменование ссылочных типов) в тексте запроса RLS приводит к серьезным проблемам с производительностью:
Для быстрой и корректной работы RLS используются Параметры сеанса. Это значения, которые вычисляются один раз при входе пользователя в систему (или при первом обращении) и хранятся в памяти сервера. Доступ к ним из RLS происходит мгновенно.
Давайте реализуем правильную схему по шагам.
В дереве конфигурации создадим новый параметр сеанса. Назовем его, например, ТекущийСотрудник. Тип данных должен совпадать с типом измерения вашего регистра (СправочникСсылка.Сотрудники или СправочникСсылка.ФизическиеЛица).
Также полезно создать булевый параметр, например ТекущийПользовательВидитВсе, чтобы администраторы и расчетчики могли видеть все данные без ограничений.
Нам нужно заполнить эти параметры при старте работы пользователя. Для этого используем модуль сеанса.
Откроем модуль сеанса и найдем (или создадим) процедуру УстановкаПараметровСеанса.
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
// Проверяем, нужно ли инициализировать наш параметр
Если ТребуемыеПараметры = Неопределено
Или ТребуемыеПараметры.Найти("ТекущийСотрудник") <> Неопределено Тогда
// Получаем текущего пользователя
ТекущийПользовательСсылка = Пользователи.ТекущийПользователь();
// Получаем сотрудника из реквизита пользователя
// Предположим, у вас есть реквизит "Сотрудник" в справочнике Пользователи
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Пользователи.Сотрудник КАК Сотрудник,
| Пользователи.Администратор КАК Администратор
|ИЗ
| Справочник.Пользователи КАК Пользователи
|ГДЕ
| Пользователи.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ТекущийПользовательСсылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ПараметрыСеанса.ТекущийСотрудник = Выборка.Сотрудник;
// Логика для полных прав
ПараметрыСеанса.ТекущийПользовательВидитВсе = Выборка.Администратор;
Иначе
// Если пользователь не найден или реквизит пуст, записываем пустую ссылку
ПараметрыСеанса.ТекущийСотрудник = Справочники.Сотрудники.ПустаяСсылка();
ПараметрыСеанса.ТекущийПользовательВидитВсе = Ложь;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Теперь перейдем к созданию ограничения.
ЧтениеСвоегоФОТ).В поле текста ограничения напишем запрос. Мы будем использовать конструкцию, которая позволяет видеть свои данные сотруднику, а администратору — все данные.
// Текст ограничения доступа
ГДЕ Сотрудник = &ТекущийСотрудник
ИЛИ &ТекущийПользовательВидитВсе = ИСТИНА
Обратите внимание: мы используем амперсанд & перед именами параметров сеанса. Это сообщает платформе, что значение нужно брать из параметров сеанса, а не искать поле в таблице регистра.
Важно учитывать сценарий, когда у пользователя в настройках не указан сотрудник.
В нашем коде инициализации мы присвоили параметру ПараметрыСеанса.ТекущийСотрудник пустую ссылку.
В этом случае запрос RLS превратится в ГДЕ Сотрудник = ЗНАЧЕНИЕ(Справочник.Сотрудники.ПустаяСсылка). Пользователь увидит только записи, где измерение Сотрудник не заполнено (если такие есть), или не увидит ничего. Это ожидаемое и безопасное поведение.
Если в конфигурации используется Библиотека Стандартных Подсистем (БСП), то часто применяются шаблоны ограничений доступа. Это позволяет не писать код каждый раз заново.
Если вы хотите использовать стандартный механизм БСП "По значениям", то настройка будет выглядеть иначе и требовать создания Групп доступа, Профилей и настройки видов доступа. Однако, автор вопроса хотел избежать создания отдельных Групп доступа для каждого пользователя.
Тем не менее, вы можете создать свой собственный шаблон в роли, чтобы использовать его для нескольких регистров.
На вкладке Шаблоны в роли создадим шаблон #ПоСвоемуСотруднику с текстом:
ГДЕ Сотрудник = &ТекущийСотрудник
ИЛИ &ТекущийПользовательВидитВсе = ИСТИНА
Тогда в поле ограничения доступа самого регистра достаточно написать только имя шаблона:
#ПоСвоемуСотруднику
После применения такой настройки RLS:
Выбрать * Из РегистрСведений...) также вернут только разрешенные данные, если не установлен привилегированный режим.Мы рассмотрели способ настройки RLS "по реквизиту пользователя" через параметры сеанса. Это наиболее производительный и правильный метод. Избегайте прямых обращений к реквизитам через точку в RLS и помните, что для корректной работы ограничений необходимо исключить пересечение с ролями, дающими полные права на чтение этого объекта.