Разработчики отчетов на системе компоновки данных (СКД) часто сталкиваются со следующей архитектурной задачей: необходимо получить данные из первой схемы компоновки, выгрузить их в промежуточную таблицу значений (ТЗ), выполнить дополнительную обработку и передать эту ТЗ во вторую схему в качестве внешнего источника данных. Под полными правами (с ролью «Администратор») такая схема работает безупречно. Однако стоит запустить отчет под пользователем с ограниченными правами, как некоторые поля (например, реквизиты справочников или документов) таинственным образом исчезают из выгруженной таблицы значений, хотя права на чтение этих объектов у пользователя присутствуют.
В этой статье мы подробно проанализируем, почему так происходит, какие скрытые механизмы платформы «1С:Предприятие 8» приводят к «тихому» исключению полей и как гарантированно решить эту проблему с помощью программного кода.
---
Для понимания сути проблемы нам нужно заглянуть под капот платформы 1С и рассмотреть несколько взаимосвязанных механизмов безопасности и оптимизации запросов СКД.
1. Клиентское «вырезание» полей компоновщиком настроек (Client-Side Pruning)
Это одна из самых неочевидных причин поведения СКД. Когда пользователь открывает форму отчета, на стороне клиента инициализируется объект КомпоновщикНастроек. При его инициализации платформа сверяет доступные поля схемы с правами текущего пользователя. Здесь ключевую роль играет не право «Чтение», а интерактивное право «Просмотр» на конкретный реквизит (или объект метаданных в целом).
Если у пользователя отсутствует право «Просмотр» на поле (даже если право «Чтение» предоставлено), или если поле связано с отключенной в данный момент функциональной опцией, платформа автоматически, без вывода каких-либо предупреждений и ошибок, удаляет эти поля из настроек компоновщика — как из списка доступных, так и из состава выбранных полей. В результате, когда управление передается на сервер для формирования отчета, серверный код получает настройки, в которых этих полей уже физически нет. Привилегированный режим на сервере здесь не поможет, так как сервер выполняет компоновку по уже «урезанным» настройкам.
2. Параметр проверки доступности полей в генераторе макета
При программной компоновке отчета разработчики используют метод КомпоновщикМакета.Выполнить(). Мало кто обращает внимание на структуру параметров этого метода. Шестой параметр данного метода называется ПроверятьДоступностьПолей. По умолчанию платформа устанавливает его в значение Истина.
Если этот параметр равен Истина, СКД в процессе генерации макета принудительно проверяет права текущего пользователя на все используемые поля. Даже если вы установили привилегированный режим на сервере, СКД, обнаружив отсутствие прав у пользователя, заблокирует вывод «запрещенных» полей, и они не попадут в результирующую таблицу значений.
3. Влияние функциональных опций (ФО)
Если реквизит (например, поле «Компания» в справочнике «Организации») подчинен определенной функциональной опции (например, «Вести учет по нескольким компаниям»), и эта опция выключена в пользовательском сеансе, СКД полностью исключит это поле из запроса. Платформа считает, что раз функционал выключен, то и данных в этом поле быть не может, поэтому запрашивать их бессмысленно.
4. Специфика работы безопасного режима и БСП
Если ваш отчет разработан как внешний и зарегистрирован в базе через подсистему «Дополнительные отчеты и обработки» Библиотеки Стандартных Подсистем (БСП), по умолчанию он запускается в безопасном режиме. В безопасном режиме вызов метода УстановитьПривилегированныйРежим(Истина) полностью игнорируется платформой. Из-за этого любые попытки обойти ограничения прав «на лету» внутри модуля отчета не дают результата.
5. Разыменование полей «через точку» и ограничения RLS
Когда вы обращаетесь в запросе СКД к реквизиту через точку (например, Организация.Компания), система неявно выполняет левое соединение (LEFT JOIN) с таблицей справочника «Организации». Если на уровне записей (RLS) у пользователя настроены ограничения на чтение таблицы «Организации», то при компиляции запроса СКД под ограниченными правами платформа может некорректно обработать разыменование полей «через точку» и просто вернуть значение NULL или полностью исключить поле из выборки, чтобы предотвратить потенциальную утечку данных.
---
Рассмотрим несколько практических способов обхода данной проблемы, начиная от простых программных доработок компоновки и заканчивая архитектурно правильным выносом логики в привилегированный контекст.
Если вы выполняете компоновку первой схемы программно, то самым простым и быстрым решением будет принудительное отключение проверки прав на поля компоновщиком макета. Для этого при вызове метода Выполнить() необходимо явно передать значение Ложь в шестой параметр.
Рассмотрим пример кода программной выгрузки СКД в таблицу значений:
// 1. Получаем схему компоновки данных
СхемаКомпоновки = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
// 2. Получаем настройки компоновщика
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// 3. Создаем компоновщик макета
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// Важнейший шаг: передаем Ложь в 6-й параметр (ПроверятьДоступностьПолей)
МакетКомпоновки = КомпоновщикМакета.Выполнить(
СхемаКомпоновки,
Настройки,
,
,
,
Ложь // Отключаем проверку доступности полей для пользователя
);
// 4. Инициализируем процессор компоновки
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
// 5. Выводим результат в таблицу значений
ТаблицаРезультата = Новый ТаблицаЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТаблицуЗначений;
ПроцессорВывода.УстановитьОбъект(ТаблицаРезультата);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Этот способ отлично работает в тех случаях, когда у пользователя физически есть права «Чтение» на метаданные, но СКД по какой-то причине (например, из-за отсутствия права «Просмотр») пытается исключить эти поля из итогового макета.
---
Если отключение проверки доступности полей не помогло (например, срабатывают жесткие ограничения RLS или отчет выполняется в безопасном режиме БСП), наилучшим архитектурным решением будет полный перенос построения первой схемы и выгрузки её результатов в таблицу значений на сторону сервера — в специальный привилегированный модуль.
Выполним это по шагам:
Пример кода функции в привилегированном общем модуле:
// Функция выполняется на сервере в привилегированном режиме
Функция ПолучитьДанныеПервойСхемыПривилегированно(ПараметрыОтчета) Экспорт
// Устанавливаем привилегированный режим на всякий случай
УстановитьПривилегированныйРежим(Истина);
Схема = ПолучитьОбщийМакет("СхемаПервогоЭтапа");
Компоновщик = Новый КомпоновщикНастроекКомпоновкиДанных;
Компоновщик.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(Схема));
Компоновщик.ЗагрузитьНастройки(ПараметрыОтчета.Настройки);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// Обязательно отключаем проверку доступности
МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Компоновщик.ПолучитьНастройки(), , , , Ложь);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ТаблицаДанных = Новый ТаблицаЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТаблицуЗначений;
ПроцессорВывода.УстановитьОбъект(ТаблицаДанных);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Возврат ТаблицаДанных;
КонецФункции
Поскольку этот код выполняется на сервере без контроля прав доступа, СКД гарантированно выберет все запрошенные поля и заполнит ими таблицу значений. Полученную ТЗ вы сможете без проблем передать во вторую схему компоновки.
---
Иногда во второй схеме компоновки данных нам не требуется сама ссылка на объект (например, ссылка на перечисление или справочник «Организации»), а нужно лишь текстовое представление этого поля для вывода в отчет или для группировки.
Если это ваш случай, вы можете обойти ограничения прав доступа и RLS на уровне первого запроса СКД. Для этого преобразуйте ссылочное поле в строку непосредственно в тексте запроса первой схемы.
Рассмотрим пример изменения запроса:
// Вместо прямого обращения к реквизиту через точку:
// ВЫБРАТЬ Организация.Компания КАК Компания
// Используйте принудительное приведение к строке или конструкцию ВЫБОР:
ВЫБРАТЬ
ВЫРАЗИТЬ(Организация.Компания КАК СТРОКА) КАК КомпанияПредставление
Или, если поле «Компания» — это перечисление:
ВЫБРАТЬ
ВЫБОР
КОГДА Организация.Компания = ЗНАЧЕНИЕ(Перечисление.Компании.Основная) ТОГДА "Основная компания"
КОГДА Организация.Компания = ЗНАЧЕНИЕ(Перечисление.Компании.Филиал) ТОГДА "Филиал"
ИНАЧЕ ""
КОНЕЦ КАК КомпанияТекст
Строковые типы данных не проверяются системой ограничения прав доступа RLS так строго, как ссылочные типы. Преобразовав значение в строку в первом запросе, вы гарантируете, что данные попадут в ТЗ и будут успешно переданы на второй этап компоновки.
---
Если «вырезание» полей происходит на клиенте еще до момента передачи настроек на сервер, можно применить тактику принудительного программного восстановления выбранных полей перед формированием макета компоновки.
Для этого непосредственно перед вызовом компоновщика макета на сервере мы можем вручную проверить наличие нужных полей в коллекции выбранных полей и, если они были удалены платформой, добавить их программно:
// Получаем текущие настройки компоновщика
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// Проверяем, есть ли поле "Организация.Компания" в выбранных полях
ПолеНайденно = Ложь;
Для Каждого ЭлементОтбора Из Настройки.Выбор.Элементы Цикл
Если ТипЗнч(ЭлементОтбора) = Тип("ВыбранноеПолеКомпоновкиДанных") Тогда
Если Строка(ЭлементОтбора.Поле) = "Организация.Компания" Тогда
ПолеНайденно = Истина;
Прервать;
EndIf;
EndIf;
КонецЦикла;
// Если поле было вырезано платформой на клиенте, добавляем его принудительно на сервере
Если Не ПолеНайденно Тогда
НовоеПоле = Настройки.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
НовоеПоле.Поле = Новый ПолеКомпоновкиДанных("Организация.Компания");
НовоеПоле.Использование = Истина;
КонецЕсли;
После этого, используя Решение 1 (передачу значения Ложь в параметр проверки доступности), вы получите полностью заполненную таблицу значений.
---
Мы детально разобрали, почему СКД может не выводить отдельные поля в таблицу значений под правами обычного пользователя. Для того чтобы ваши многоэтапные отчеты работали стабильно у всех пользователей, придерживайтесь следующих правил:
КомпоновщикМакета.Выполнить().