При разработке сложных распределенных систем часто возникает задача построения отчетов в одной базе (потребителе) на основе данных, хранящихся в другой базе (источнике). Стандартный механизм получения данных через веб-сервисы под техническим пользователем с полными правами создает серьезную проблему безопасности: данные прилетают «как есть», без учета прав доступа текущего пользователя в основной базе. В этой статье мы разберем, как правильно интегрировать механизм RLS (Record Level Security) в процесс получения внешних данных и как заставить Систему компоновки данных (СКД) фильтровать информацию из таблицы значений.
Это один из самых гибких подходов, который позволяет использовать уже настроенные правила RLS в базе-источнике, не создавая при этом сотни учетных записей. Суть метода заключается в использовании «пула» соединений под одной служебной учетной записью, но с динамической подменой контекста безопасности.
Рассмотрим алгоритм реализации:
ПараметрыСеанса, на которых завязаны правила RLS в этой базе (например, ПараметрыСеанса.РазрешенныеОрганизации или ПараметрыСеанса.ТекущийПользователь).Посмотрим на пример кода в методе веб-сервиса базы-источника:
Функция ПолучитьДанныеПродаж(ИмяПользователяПотребителя, ПараметрыФильтра) Экспорт
// Переходим в привилегированный режим, чтобы установить параметры сеанса
УстановитьПривилегированныйРежим(Истина);
// Находим соответствующего пользователя в текущей базе или определяем его права
ПользовательСсылка = Справочники.Пользователи.НайтиПоНаименованию(ИмяПользователяПотребителя);
// Заполняем параметры сеанса, которые используются в RLS (например, из БСП)
ПараметрыСеанса.ТекущийПользователь = ПользовательСсылка;
// Здесь может быть вызов процедуры инициализации параметров доступа
// ПараметрыДоступа.УстановитьПараметрыСеанса(ПользовательСсылка);
УстановитьПривилегированныйРежим(Ложь);
// Теперь обычный запрос отработает с учетом RLS для установленного пользователя
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ РегистрНакопления.Продажи КАК Продажи";
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Важный нюанс: Чтобы этот метод работал, правила RLS в метаданных должны быть прописаны с использованием параметров сеанса, а не функции ТЕКУЩИЙПОЛЬЗОВАТЕЛЬ(), так как фактически сессия веб-сервиса запущена под служебным аккаунтом.
Если мы не можем или не хотим менять логику во внешней базе, мы можем наложить фильтр уже в основной базе, где формируется отчет. Проанализируем ситуацию: у нас есть набор данных «Объект» (внешняя ТЗ) и нам нужно ограничить его по справочникам (например, Контрагенты), на которые в текущей базе уже действует RLS.
Для реализации этого метода выполним следующие шаги в конструкторе СКД:
ВЫБРАТЬ Ссылка КАК Контрагент ИЗ Справочник.Контрагенты
Благодаря тому, что запрос выполняется в обычном режиме, платформа сама добавит условия RLS к этому запросу.ТаблицуЗначений.Контрагент.Проблема «Левого соединения»: По умолчанию СКД соединяет наборы данных левым соединением. Это значит, что если в ТЗ есть 500 контрагентов, а разрешено только 10, отчет выведет все 500 строк, но данные из первого набора для «запрещенных» будут пустыми. Чтобы имитировать Inner Join (внутреннее соединение) и полностью скрыть строки, выполним следующее:
ПолеСвязиИзПервогоНабора Заполнено. Это заставит систему исключить из результирующего набора все строки, которые не прошли проверку правами доступа.Рассмотрим более продвинутый способ, который позволяет объединить внешние данные и RLS текущей базы в одном SQL-запросе. Это обеспечит максимальную производительность при больших объемах данных.
Проанализируем шаги реализации:
ТаблицуЗначений.МенеджерВременныхТаблиц и помещаем туда эту таблицу.Пример программной подготовки данных:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// 1. Получаем данные извне
ТЗВнешниеДанные = ПолучитьДанныеИзВебСервиса();
// 2. Готовим менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;
ЗапросПодготовки = Новый Запрос("ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ВнешниеДанные ИЗ &ТЗ КАК ТЗ");
ЗапросПодготовки.МенеджерВременныхТаблиц = МенеджерВТ;
ЗапросПодготовки.УстановитьПараметр("ТЗ", ТЗВнешниеДанные);
ЗапросПодготовки.Выполнить();
// 3. Настраиваем компоновщик
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
// 4. Передаем менеджер ВТ в процессор компоновки
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
// В тексте запроса СКД теперь можно писать:
// ВЫБРАТЬ * ИЗ ВТ_ВнешниеДанные КАК ВнешниеДанные
// ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты ПО ВнешниеДанные.КонтрагентСсылка = Контрагенты.Ссылка
// 5. Вывод результата...
КонецПроцедуры
Этот метод хорош тем, что RLS наложится автоматически на Справочник.Контрагенты, а внутреннее соединение отсечет все запрещенные записи из временной таблицы ВТ_ВнешниеДанные.
Выясним причину, по которой иногда стоит фильтровать данные еще до того, как они покинут базу-источник. Если внешняя база содержит миллионы записей, а пользователю разрешено видеть только сотню, гонять всю таблицу через сеть нерационально.
Мы можем программно получить список разрешенных идентификаторов в базе-потребителе и передать их в качестве параметра веб-сервису. В конфигурациях на базе БСП это делается так:
// Получаем массив разрешенных организаций для текущего пользователя
МассивРазрешенныхОрганизаций = УправлениеДоступом.ДоступныеЗначения(Метаданные.Справочники.Организации);
// Передаем этот массив в веб-сервис
Данные = Proxy.ПолучитьДанные(МассивРазрешенныхОрганизаций);
На стороне веб-сервиса этот массив вставляется в условие ГДЕ Ссылка В (&Массив). Таким образом, мы убиваем двух зайцев: соблюдаем безопасность и экономим трафик.
Выбор конкретного метода зависит от архитектуры ваших баз:
Обязательная связь.Помните, что безопасность данных — это комплексная задача. Наложение RLS на внешние данные требует внимательности к типу соединения и способу передачи контекста пользователя, чтобы исключить утечку информации.