При настройке механизмов ограничения прав доступа на уровне записей (RLS) разработчики часто сталкиваются со специфическим поведением платформы «1С:Предприятие»: ограничение по табличным частям отлично работает при создании нового документа, но пропускает запись, если пользователь изменяет существующий документ и добавляет в него строки с запрещенной аналитикой.
В этой статье мы подробно проанализируем, почему возникает эта проблема на уровне СУБД, рассмотрим примеры RLS-запросов и выработаем проверенные архитектурные решения для надежного ограничения прав.
Рассмотрим стандартную ситуацию: пользователю разрешено работать только с определенными подразделениями. На справочник Подразделения задан полный доступ (так как эти подразделения могут выбираться в других документах), но на документ наложено ограничение на право Изменение. Если в табличной части документа указано хотя бы одно запрещенное подразделение, документ не должен записываться или проводиться.
При использовании стандартного шаблона RLS для табличной части запрос ограничения доступа выглядит следующим образом:
ТекущаяТаблица ИЗ #ТекущаяТаблица КАК ТекущаяТаблица
ГДЕ НЕ 1 В
(ВЫБРАТЬ ПЕРВЫЕ 1
1
ИЗ
Документ.ПлатежноеПоручениеИсходящее.РасшифровкаПлатежа КАК ТабличнаяЧастьДокумента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ПраваДоступаПользователейКОбъектам КАК ПраваДоступаПользователейКОбъектам
ПО ПраваДоступаПользователейКОбъектам.ОбъектДоступа = ТабличнаяЧастьДокумента.ЦентрЗатрат
И ПраваДоступаПользователейКОбъектам.ОбластьДанных = ЗНАЧЕНИЕ(Перечисление.ОбластиДанныхОграниченияПравДоступа.ПодразделенияПлатежныеДокументыЗапрет)
И ПраваДоступаПользователейКОбъектам.Пользователь В (&ГруппыТекущегоПользователя)
И ПраваДоступаПользователейКОбъектам.Чтение
ГДЕ
ТекущаяТаблица.Ссылка = ТабличнаяЧастьДокумента.Ссылка)
Для сравнения, ограничение, наложенное на реквизит шапки документа, работает безошибочно:
ТекущаяТаблица ИЗ #ТекущаяТаблица КАК ТекущаяТаблица
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПраваДоступаПользователейКОбъектам КАК ПраваДоступаПользователейКОбъектам
ПО ПраваДоступаПользователейКОбъектам.ОбъектДоступа = ТекущаяТаблица.Подразделение
И ПраваДоступаПользователейКОбъектам.ОбластьДанных = ЗНАЧЕНИЕ(Перечисление.ОбластиДанныхОграниченияПравДоступа.ПодразделенияПлатежныеДокументыЗапрет)
И ПраваДоступаПользователейКОбъектам.Пользователь В (&ГруппыТекущегоПользователя)
И ПраваДоступаПользователейКОбъектам.Чтение
ГДЕ ПраваДоступаПользователейКОбъектам.ОбъектДоступа ЕСТЬ NULL
Выясним, почему логика с изменением реквизита шапки работает, а с табличной частью — пропускает запрещенные данные.
Проанализируем последовательность транзакции записи существующего документа в СУБД через замер профайлера SQL:
UPDATE к основной таблице документа. На этом этапе СУБД встраивает RLS-проверку. При проверке поля шапки (ТекущаяТаблица.Подразделение) используются новые данные, переданные в запросе обновления. Однако при обращении к табличной части через условие ТекущаяТаблица.Ссылка = ТабличнаяЧастьДокумента.Ссылка происходят выборка и соединение с физической таблицей табличной части, которая в этот момент еще содержит старые строки, ранее сохраненные в базе данных!UPDATE шапки платформа выполняет команды DELETE для старых строк табличной части и INSERT для новых строк из памяти.Таким образом, проверка RLS на изменение срабатывает до того, как новые строки табличной части фактически попадают в СУБД. В результате пользователь может открыть документ с разрешенным подразделением, добавить строку с запрещенным подразделением и успешно его записать.
Разберем три практических подхода к реализации ограничения доступа по табличным частям, исключающих данную архитектурную проблему.
Самый надежный и быстродействующий способ для конфигураций без БСП — вынос проверки логики изменений из RLS СУБД в программный код модуля объекта или подписки на событие.
Рассмотрим алгоритм реализации:
ПередЗаписью) выполняем проверку содержимого табличной части объекта, находящегося в памяти (ЭтотОбъект).
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если Отказ Или ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1
|ИЗ
| РегистрСведений.ПраваДоступаПользователейКОбъектам КАК ПраваДоступа
|ГДЕ
| ПраваДоступа.ОбъектДоступа В(&МассивЦентровЗатрат)
| И ПраваДоступа.ОбластьДанных = Значение(Перечисление.ОбластиДанныхОграниченияПравДоступа.ПодразделенияПлатежныеДокументыЗапрет)
| И ПраваДоступа.Пользователь В(&ГруппыТекущегоПользователя)
| И ПраваДоступа.Чтение";
МассивЦентровЗатрат = ЭтотОбъект.РасшифровкаПлатежа.ВыгрузитьКолонку("ЦентрЗатрат");
Запрос.УстановитьПараметр("МассивЦентровЗатрат", МассивЦентровЗатрат);
Запрос.УстановитьПараметр("ГруппыТекущегоПользователя", ПараметрыСеанса.ГруппыТекущегоПользователя);
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() Тогда
ВызватьИсключение "Документ содержит подразделения (центры затрат), запрещенные для использования текущим пользователем!";
КонецЕсли;
КонецПроцедуры
Если разработка ведется в типовой или современной конфигурации на базе Библиотеки Стандартных Подсистем (БСП) с включенным производительным режимом RLS, проверка прав выполняется через регистры сведений КлючиДоступаКОбъектам.
В этом случае проверка накладывается не на физическую табличную часть в SQL, а на набор ключей доступа, рассчитываемых перед записью объекта в памяти:
ЗаполнитьЗначенияИсточниковПолейДоступа или ЗаполнитьНаборыЗначенийДоступа.ДляВсехСтрок или ДляОднойИзСтрок.Если необходимо использовать исключительно штатный RLS платформы без БСП и программных исключений в коде:
ПередЗаписью автоматически заполняйте этот реквизит значениями из табличной части.UPDATE, ограничение RLS сработает корректно и заблокирует изменение документа.Обратите внимание: если на сам справочник Подразделения пользователю предоставлены права на чтение, он сможет выбрать любое подразделение в форме документа. Ограничение RLS СУБД или программная проверка сработают именно в момент сохранения или проведения документа.
Чтобы повысить удобство работы пользователей и предотвратить ошибки на этапе ввода данных, рекомендуется дополнительно ограничить выбор подразделений в самой форме документа через настройки связей параметров выбора или обработчик события НачалоВыбора.