При работе с системой компоновки данных (СКД) в 1С:Предприятии часто возникает ситуация, когда необходимо сформировать отчет, содержащий данные, к которым у текущего пользователя нет прямых прав доступа. Это может быть связано с ограничениями на уровне записей (RLS) или на уровне объектов — для их выявления поможет отчет для анализа прав и профилей пользователей. Для решения этой задачи мы стремимся запустить формирование отчета в привилегированном режиме. Однако простое использование метода УстановитьПривилегированныйРежим(Истина) не всегда дает ожидаемый результат, особенно при работе с внешними отчетами или при автоматическом отсечении полей СКД на основе прав пользователя. Давайте разберем эту проблему и предложим несколько эффективных решений.
Предположим, мы хотим сформировать отчет программно, используя следующий код в процедуре ПриКомпоновкеРезультата:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
УстановитьПривилегированныйРежим(Истина); // Устанавливаем привилегированный режим
Настройки = КомпоновщикНастроек.ПолучитьНастройки(); // Получаем текущие настройки
// Здесь может быть дополнительная логика по изменению настроек, как у автора
// Например, отключение колонок, если роль "Снабжение" недоступна
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Пока ЭлементРезультата <> Неопределено Цикл
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
УстановитьПривилегированныйРежим(Ложь); // Возвращаем обычный режим
КонецПроцедуры
Казалось бы, мы установили привилегированный режим, и отчет должен сформироваться со всеми данными. Однако на практике часто возникает сообщение об ошибке, например: "Ресурсы и измерения не выбраны", или отчет формируется пустым, даже если в привилегированном режиме данные доступны. Причина кроется в том, что платформа 1С автоматически отбрасывает из настроек СКД поля, на которые у пользователя нет прав, еще на этапе получения этих настроек методом КомпоновщикНастроек.ПолучитьНастройки(). Это происходит до того, как привилегированный режим будет учтен при выполнении запроса к данным. То есть, к моменту получения настроек, платформа уже "усекла" их, исходя из текущих прав пользователя.
Давайте рассмотрим подробнее, как мы можем обойти это поведение, используя быструю проверку прав доступа на объект метаданных, и обеспечить корректное формирование отчета в привилегированном режиме.
Один из наиболее структурированных и безопасных подходов к выполнению кода в привилегированном режиме заключается в вынесении всей логики, требующей полных прав, в отдельный общий модуль. Этот общий модуль должен быть помечен свойством "Привилегированный".
Шаг 1: Создаем или используем существующий общий модуль с флагом "Привилегированный".
Откройте конфигурацию, найдите или создайте новый общий модуль (например, ОтчетыСервер или ПривилегированныеВызовы). В свойствах этого модуля обязательно установите флаг "Привилегированный" в значение "Истина" (или просто отметьте его галочкой). Также убедитесь, что модуль работает на сервере (например, флаги "Сервер", "ВызовСервера").
Шаг 2: Реализуем логику формирования СКД в экспортной функции общего модуля.
Перенесите весь код, отвечающий за программное формирование отчета СКД (в этом может помочь СКДБилдер), в новую экспортную функцию этого общего модуля. Эта функция будет принимать необходимые параметры из модуля отчета.
// В общем модуле, например, КФ_СерверВызов (с флагами: Сервер, ВызовСервера, Привилегированный)
Функция СКДПриКомпоновкеРезультата(ОтчетОбъект, ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка, ВПривилегированномРежиме = Ложь, Параметры = Неопределено, ССообщениями = Ложь, ОриентСтр = Неопределено, ИспользоватьКФМакетОформления = Истина) Экспорт
Если ВПривилегированномРежиме Тогда
// Для внешних отчетов, если эта функция вызывается из модуля внешнего отчета,
// убедитесь, что в СведенияОВнешнейОбработке установлено:
// ПараметрыРегистрации.Вставить("БезопасныйРежим",Ложь).
// Иначе установка привилегированного режима может не сработать.
УстановитьПривилегированныйРежим(Истина);
Если ССообщениями И Не ПривилегированныйРежим() Тогда // Проверяем, удалось ли установить режим
Сообщить("Ошибка в программе. Не установился привилегированный режим.");
КонецЕсли;
КонецЕсли;
СтандартнаяОбработка = Ложь;
// Получаем текущие настройки отчета. Важно: настройки получаются из объекта отчета,
// который передается сюда, но их содержимое уже может быть "урезано"
// платформой, если не принять дополнительные меры (см. Решения 2 и 3).
Настройки = ОтчетОбъект.КомпоновщикНастроек.ПолучитьНастройки();
// Применяем параметры, если они были переданы
Если Параметры <> Неопределено Тогда
Для Каждого КлючЗначение Из Параметры Цикл
ПараметрСКД = Настройки.ПараметрыДанных.Элементы.Найти(КлючЗначение.Ключ);
Если ПараметрСКД <> Неопределено Тогда
ПараметрСКД.Значение = КлючЗначение.Значение;
ПараметрСКД.Использование = Истина;
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Здесь может быть дополнительная логика по изменению настроек,
// например, отключение колонок, как в исходном сообщении
// Если Не РольДоступна("Снабжение") Тогда ... КонецЕсли;
// Однако, если поля уже были отброшены, эта логика может не иметь смысла.
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(ОтчетОбъект.СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Если ЭлементРезультата = Неопределено Тогда
Прервать;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
Если ВПривилегированномРежиме Тогда
УстановитьПривилегированныйРежим(Ложь); // Возвращаем обычный режим
КонецЕсли;
КонецФункции
Шаг 3: Вызываем экспортную функцию из модуля отчета.
В модуле вашего отчета (например, в процедуре ПриКомпоновкеРезультата) мы вызываем созданную экспортную функцию общего модуля, передавая ей все необходимые параметры. Перед этим полезно провести анализ конфигурации на наличие ошибок в вызовах процедур.
// В модуле объекта отчета
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Примеры параметров, которые можно передать в общий модуль
Организации = КФ_СерверВызов.ДоступныеОрганизацииПоДокументам("ЗаявкаНаРасходованиеДенежныхСредств");
Параметры = Новый Структура("Организации", Организации);
ВПривилегированномРежиме = Истина;
ССообщениями = Истина;
// Вызываем функцию в общем модуле
КФ_СерверВызов.СКДПриКомпоновкеРезультата(ЭтотОбъект, ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка, ВПривилегированномРежиме, Параметры, ССообщениями);
КонецПроцедуры
Важно для внешних отчетов: отключение безопасного режима.
Если вы разрабатываете внешний отчет, который будет подключаться в базу, то для его выполнения в привилегированном режиме критически важно отключить безопасный режим для этой внешней обработки. Это делается в функции СведенияОВнешнейОбработке вашего внешнего отчета. Добавьте в возвращаемую структуру параметр БезопасныйРежим со значением Ложь:
// В функции СведенияОВнешнейОбработке() внешнего отчета
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = Новый Структура;
ПараметрыРегистрации.Вставить("Вид", "Отчет");
ПараметрыРегистрации.Вставить("Наименование", НСтр("ru = 'Мой привилегированный отчет'"));
// ... другие параметры
ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); // КРИТИЧЕСКИ ВАЖНО ДЛЯ ВНЕШНИХ ОТЧЕТОВ
Возврат ПараметрыРегистрации;
КонецФункции
Без этого шага платформа может игнорировать вызов УстановитьПривилегированныйРежим(Истина), поскольку внешний отчет по умолчанию выполняется в безопасном режиме, который имеет свои собственные ограничения, блокирующие полный доступ к привилегиям. Серверный вызов в привилегированный общий модуль в этом случае не сработает должным образом, если он инициирован из клиентского кода внешнего отчета, находящегося в безопасном режиме.
Как мы уже выяснили, основная проблема заключается в том, что метод КомпоновщикНастроек.ПолучитьНастройки() отфильтровывает поля, на которые у пользователя нет прав, еще до того, как привилегированный режим вступит в полную силу для запросов к данным. Если мы хотим, чтобы отчет сформировался со всеми полями, независимо от текущих настроек пользователя, мы можем использовать настройки по умолчанию, определенные в самой схеме компоновки данных.
Механизм решения:
Вместо того чтобы получать настройки через КомпоновщикНастроек.ПолучитьНастройки(), мы будем передавать в метод КомпоновщикМакета.Выполнить() непосредственно СхемаКомпоновкиДанных.НастройкиПоУмолчанию. Эти настройки, как правило, содержат все поля, определенные в СКД, и не зависят от текущих прав пользователя.
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
УстановитьПривилегированныйРежим(Истина); // Устанавливаем привилегированный режим
// !!! ВАЖНО: Вместо КомпоновщикНастроек.ПолучитьНастройки()
// используем настройки по умолчанию из схемы
// Это гарантирует, что все поля будут доступны для компоновки,
// независимо от прав текущего пользователя.
НастройкиДляКомпоновки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиДляКомпоновки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Пока ЭлементРезультата <> Неопределено Цикл
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
УстановитьПривилегированныйРежим(Ложь); // Возвращаем обычный режим
КонецПроцедуры
Ограничения этого подхода:
НастройкиПоУмолчанию схемы СКД.НастройкиПоУмолчанию вам придется программно вносить в них необходимые изменения, взяв их из КомпоновщикНастроек.ПолучитьНастройки() или напрямую из элементов формы. Это может усложнить код.Этот вариант подходит, когда отчет должен всегда формироваться по фиксированным правилам, определенным разработчиком, независимо от действий пользователя, но при этом требовать привилегированного доступа к данным.
Для того чтобы обойти проблему с отсечением полей из-за отсутствия прав, при этом сохраняя возможность использования интерактивных пользовательских настроек, мы можем применить более изощренные методы, которые "обманывают" платформу на этапе анализа прав доступа к полям схемы СКД.
Идея этого подхода заключается в том, что мы используем две схемы компоновки данных:
КомпоновщикНастроек работает именно с этой схемой.Механизм реализации:
СхемаКомпоновкиДанныхПривилегированная), либо программно создать копию основной схемы и модифицировать ее в коде.ПриКомпоновкеРезультата, после получения настроек отчета (которые уже могут быть "урезанными"), мы передаем в метод КомпоновщикМакета.Выполнить() нашу "привилегированную" схему, но используем пользовательские настройки, полученные от КомпоновщикНастроек.
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
УстановитьПривилегированныйРежим(Истина);
// Получаем пользовательские настройки.
// На этом этапе они могут быть урезаны, но мы их будем использовать как основу.
НастройкиПользователя = КомпоновщикНастроек.ПолучитьНастройки();
// Загружаем "привилегированную" схему, которая содержит все поля.
// Это может быть другая схема, сохраненная в макетах отчета,
// или программно созданная/модифицированная схема.
// Например, если у нас есть макет "СхемаСКДПривилегированная"
МакетПривилегированнойСхемы = ЭтотОбъект.ПолучитьМакет("СхемаСКДПривилегированная");
СхемаПривилегированная = МакетПривилегированнойСхемы.ПолучитьТекст(); // Если схема хранится как текст
// Или
// СхемаПривилегированная = ЭтотОбъект.ПолучитьМакет("СхемаСКДПривилегированная").ПолучитьСхемуКомпоновкиДанных();
// Создаем компоновщик макета
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// Выполняем компоновку, используя "полную" схему,
// но с настройками, полученными от пользователя.
// Платформа будет искать поля в СхемаПривилегированная, где они точно есть.
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаПривилегированная, НастройкиПользователя, ДанныеРасшифровки);
// ... далее стандартный код вывода отчета
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Пока ЭлементРезультата <> Неопределено Цикл
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
УстановитьПривилегированныйРежим(Ложь);
КонецПроцедуры
Преимущества: Этот подход позволяет сохранить интерактивность пользовательских настроек, при этом обеспечивая доступность всех полей для формирования отчета.
Недостатки: Требует дублирования или программной модификации схем, что может усложнить поддержку.
Этот метод является одним из наиболее изящных для обхода проблемы отсечения полей. Идея заключается в том, чтобы "обмануть" СКД, заставив ее думать, что все поля всегда доступны, даже если у пользователя нет прав на просмотр данных, из которых они берутся. Мы достигаем этого путем добавления к основному запросу СКД дополнительного "фиктивного" запроса через оператор ОБЪЕДИНИТЬ ВСЕ, который возвращает пустые значения для всех нужных полей, но при этом содержит условие ГДЕ ЛОЖЬ, чтобы эти фиктивные строки никогда не попали в результат.
Механизм реализации:
ОБЪЕДИНИТЬ ВСЕ.ГДЕ ЛОЖЬ, чтобы этот запрос никогда не возвращал реальные данные.Давайте посмотрим на пример. Предположим, у нас есть отчет по справочнику "Валюты", но у пользователя может не быть прав на этот справочник:
// Исходный запрос
ВЫБРАТЬ
Валюты.Наименование КАК Валюта,
Валюты.Код КАК Код
ИЗ
Справочник.Валюты КАК Валюты
Если у пользователя нет доступа к справочнику Справочник.Валюты, то при получении настроек поля Валюта и Код будут отброшены, и отчет не сможет сформироваться. Модифицируем запрос следующим образом:
// Модифицированный запрос с объединением
ВЫБРАТЬ
Валюты.Наименование КАК Валюта,
Валюты.Код КАК Код
ИЗ
Справочник.Валюты КАК Валюты
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"" КАК Валюта, // Пустая строка для наименования
"" КАК Код // Пустая строка для кода
ИЗ
Справочник.Валюты КАК Валюты // Используем тот же источник, но с условием
ГДЕ ЛОЖЬ // Это условие гарантирует, что вторая часть запроса не вернет ни одной строки
Что происходит:
Валюта и Код присутствуют во второй части объединения, которая, казалось бы, "доступна" (по крайней мере, с точки зрения синтаксиса, а не данных).Справочник.Валюты.ГДЕ ЛОЖЬ) не вернет ни одной строки, поэтому в результате не будет лишних пустых строк.Преимущества:
ГДЕ ЛОЖЬ, фиктивные строки не попадают в итоговый отчет.Недостатки:
При работе с привилегированным режимом и СКД мы должны всегда помнить о нескольких важных моментах, чтобы обеспечить надежность, безопасность и корректность работы отчетов:
ИспользоватьОграниченияПравДоступаНаУровнеЗаписей в Ложь перед формированием отчета и его возврата в Истина после. Однако этот метод имеет свои риски и может быть менее безопасным, чем использование привилегированных модулей, так как он отключает RLS для всего сеанса, а не только для конкретного кода.
// Пример временного отключения RLS
ПараметрыСеанса.Установить("ИспользоватьОграниченияПравДоступаНаУровнеЗаписей", Ложь);
Попытка
// ... Ваш код формирования отчета в привилегированном режиме
Исключение
// Обработка ошибок
КонецПопытки;
ПараметрыСеанса.Установить("ИспользоватьОграниченияПравДоступаНаУровнеЗаписей", Истина);
Объект.Наименование или Представление(Объект). Это может позволить отобразить текстовую информацию, даже если доступ к самому объекту ограничен.Выбирая подходящий метод, мы всегда должны исходить из конкретных требований к отчету, уровня необходимой гибкости пользовательских настроек и соображений безопасности. Каждый из представленных подходов имеет свои преимущества и ограничения, и наше мастерство заключается в выборе наиболее оптимального решения для каждой конкретной задачи.