При настройке ограничений доступа на уровне записей (RLS) в «1С:Предприятии 8» разработчики часто сталкиваются с неочевидным поведением системы: ограничение по определенному реквизиту (например, по ответственному лицу или организации) прекрасно работает, пока соответствующая колонка выведена на форму. Но стоит пользователю скрыть эту колонку через настройки формы, как в динамическом списке внезапно отображаются абсолютно все документы, включая чужие.
В этой статье мы подробно разберем, почему платформа ведет себя подобным образом, изучим механизмы работы динамических списков под управлением RLS и пошагово исправим эту ошибку на конкретном примере.
Давайте проанализируем ситуацию. Предположим, мы создали новый документ в конфигураторе и добавили в него реквизит Ответственный с типом ссылки на справочник пользователей. Для новой роли мы прописали простое ограничение на чтение:
ГДЕ Ответственный = &ТекущийПользователь
Когда пользователь открывает список документов, платформа генерирует SQL-запрос к базе данных. Особенность работы динамических списков заключается в жесткой оптимизации. Если колонка Ответственный выведена на форму, платформа запрашивает это поле из базы данных, видит наложенное на него ограничение RLS и применяет соответствующий отбор. В итоге пользователь видит только свои документы.
Но что происходит, когда пользователь отключает видимость колонки Ответственный через команду «Изменить форму»? Платформа понимает, что выводить это поле на экран больше не нужно. С целью экономии трафика и ресурсов СУБД, поле Ответственный полностью исключается из SQL-запроса.
Поскольку поле больше не запрашивается, условие RLS, жестко привязанное к этому конкретному полю, игнорируется платформой. Ограничение просто перестает накладываться на уровне СУБД, и пользователь получает доступ ко всем строкам таблицы в списке. При этом при попытке открыть «чужой» документ RLS сработает корректно (так как форма объекта всегда запрашивает все реквизиты), но сам факт видимости чужих документов в списке является серьезной брешью в безопасности.
Чтобы ограничение доступа гарантированно отрабатывало независимо от настроек формы и видимости колонок пользователем, нам необходимо правильно перераспределить ограничения по полям в конфигурации.
Разберем по шагам, как решить эту задачу:
Ответственный, то именно это и приводит к ошибке. Нам необходимо применить ограничение ко всей записи в целом.<Прочие поля>. Строка ограничения должна выглядеть следующим образом:
ТекущаяТаблица ИЗ #ТекущаяТаблица КАК ТекущаяТаблица
ГДЕ Ответственный = &ТекущийПользователь
Настройка ограничения по <Прочие поля> полностью решает проблему отображения лишних строк в динамических списках, но на практике она может породить другую неприятность. Если мы полностью закроем чтение документа для чужих пользователей, то во всех связанных отчетах, журналах или других документах, где есть ссылки на эти скрытые документы, пользователи увидят пугающую надпись «<Объект не найден>».
Чтобы избежать появления битых ссылок и сохранить эстетичный вид интерфейса, в профессиональной разработке применяется стандартный паттерн разделения прав на Чтение. Мы настраиваем не одну, а две строки ограничений для права Чтение в роли:
Строка 1 (Доступ к ключевым полям для представления ссылки):
Ссылка, Номер, Дата, ПометкаУдаления, Проведен.ГДЕ ИСТИНА. Это позволит абсолютно всем пользователям видеть корректное представление документа (например, «Расходная накладная №5 от 12.10.2023») в отчетах и связанных реквизитах, не вызывая ошибку удаления объекта.Строка 2 (Ограничение доступа к содержанию документа):
<Прочие поля>.
ГДЕ Ответственный = &ТекущийПользователь
При такой схеме пользователь сможет видеть красивое представление чужих документов в отчетах, но при попытке открыть форму документа или прочитать его детальное содержимое (табличные части, суммы, контрагентов) система выдаст сообщение о нарушении прав доступа.
При работе с механизмами RLS критически важно помнить о производительности СУБД. Когда платформа накладывает условия RLS на лету, она модифицирует исходные SQL-запросы, добавляя в них подзапросы и соединения. Если база данных содержит сотни тысяч документов, неоптимальный RLS может привести к колоссальным задержкам при открытии списков.
Проанализируем ситуацию: в нашем условии фигурирует поле Ответственный. Если по этому полю в базе данных нет индекса, СУБД будет вынуждена выполнять полное сканирование таблицы (Full Table Scan) при каждом обращении к списку документов.
Решение чрезвычайно простое, но обязательное: в свойствах реквизита Ответственный нашего документа в Конфигураторе необходимо установить свойство Индексировать в значение Индексировать или Индексировать с доп. упорядочиванием. Наличие индекса позволит СУБД мгновенно отфильтровывать документы по текущему пользователю.
Если мы ведем разработку в современных конфигурациях (таких как «Бухгалтерия предприятия 3.0», «ERP Управление предприятием» или «Управление торговлей 11»), классический «ручной» RLS в ролях практически не используется. В Библиотеке стандартных подсистем (БСП) реализован так называемый Производительный режим RLS.
В этом режиме платформа не строит тяжелые запросы на лету. Вместо этого права доступа рассчитываются предварительно при записи объектов и кэшируются в специальных служебных регистрах сведений (например, РегистрСведений.КлючиДоступаКДокументам). При открытии списка СУБД просто быстро соединяет таблицу документов с таблицей разрешенных ключей для текущего пользователя.
При разработке в таких конфигурациях логика RLS описывается программно в модуле менеджера объекта. Рассмотрим пример настройки доступа по ответственному через БСП:
Процедура ПриЗаполненииОграниченияДоступа(Ограничение) Экспорт
Ограничение.Текст =
"РазрешитьЧтениеИзменение ГДЕ ЗначениеРазрешено(Ответственный)";
КонецПроцедуры
После добавления данного кода в модуль менеджера документа и обновления служебных данных (через запуск обновления конфигурации или запуск специальной обработки БСП) система автоматически сформирует все нужные шаблоны ограничений в ролях и обеспечит максимальную скорость работы динамических списков без уязвимостей безопасности.