Как сформировать отчет СКД в привилегированном режиме, обходя ограничения прав доступа?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с системой компоновки данных (СКД) в 1С:Предприятии часто возникает ситуация, когда необходимо сформировать отчет, содержащий данные, к которым у текущего пользователя нет прямых прав доступа. Это может быть связано с ограничениями на уровне записей (RLS) или на уровне объектов — для их выявления поможет отчет для анализа прав и профилей пользователей. Для решения этой задачи мы стремимся запустить формирование отчета в привилегированном режиме. Однако простое использование метода УстановитьПривилегированныйРежим(Истина) не всегда дает ожидаемый результат, особенно при работе с внешними отчетами или при автоматическом отсечении полей СКД на основе прав пользователя. Давайте разберем эту проблему и предложим несколько эффективных решений.

Общая проблема: ограничение прав при получении настроек СКД

Предположим, мы хотим сформировать отчет программно, используя следующий код в процедуре ПриКомпоновкеРезультата:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;
    УстановитьПривилегированныйРежим(Истина); // Устанавливаем привилегированный режим

    Настройки = КомпоновщикНастроек.ПолучитьНастройки(); // Получаем текущие настройки

    // Здесь может быть дополнительная логика по изменению настроек, как у автора
    // Например, отключение колонок, если роль "Снабжение" недоступна

    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);

    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);

    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.НачатьВывод();

    ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    Пока ЭлементРезультата <> Неопределено Цикл
        ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
        ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    КонецЦикла;

    ПроцессорВывода.ЗакончитьВывод();
    УстановитьПривилегированныйРежим(Ложь); // Возвращаем обычный режим
КонецПроцедуры

Казалось бы, мы установили привилегированный режим, и отчет должен сформироваться со всеми данными. Однако на практике часто возникает сообщение об ошибке, например: "Ресурсы и измерения не выбраны", или отчет формируется пустым, даже если в привилегированном режиме данные доступны. Причина кроется в том, что платформа 1С автоматически отбрасывает из настроек СКД поля, на которые у пользователя нет прав, еще на этапе получения этих настроек методом КомпоновщикНастроек.ПолучитьНастройки(). Это происходит до того, как привилегированный режим будет учтен при выполнении запроса к данным. То есть, к моменту получения настроек, платформа уже "усекла" их, исходя из текущих прав пользователя.

Давайте рассмотрим подробнее, как мы можем обойти это поведение, используя быструю проверку прав доступа на объект метаданных, и обеспечить корректное формирование отчета в привилегированном режиме.

Решение 1: Использование общего привилегированного модуля и отключение безопасного режима для внешних отчетов

Один из наиболее структурированных и безопасных подходов к выполнению кода в привилегированном режиме заключается в вынесении всей логики, требующей полных прав, в отдельный общий модуль. Этот общий модуль должен быть помечен свойством "Привилегированный".

Шаг 1: Создаем или используем существующий общий модуль с флагом "Привилегированный".

Откройте конфигурацию, найдите или создайте новый общий модуль (например, ОтчетыСервер или ПривилегированныеВызовы). В свойствах этого модуля обязательно установите флаг "Привилегированный" в значение "Истина" (или просто отметьте его галочкой). Также убедитесь, что модуль работает на сервере (например, флаги "Сервер", "ВызовСервера").

Шаг 2: Реализуем логику формирования СКД в экспортной функции общего модуля.

Перенесите весь код, отвечающий за программное формирование отчета СКД (в этом может помочь СКДБилдер), в новую экспортную функцию этого общего модуля. Эта функция будет принимать необходимые параметры из модуля отчета.


// В общем модуле, например, КФ_СерверВызов (с флагами: Сервер, ВызовСервера, Привилегированный)
Функция СКДПриКомпоновкеРезультата(ОтчетОбъект, ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка, ВПривилегированномРежиме = Ложь, Параметры = Неопределено, ССообщениями = Ложь, ОриентСтр = Неопределено, ИспользоватьКФМакетОформления = Истина) Экспорт

    Если ВПривилегированномРежиме Тогда
        // Для внешних отчетов, если эта функция вызывается из модуля внешнего отчета,
        // убедитесь, что в СведенияОВнешнейОбработке установлено:
        // ПараметрыРегистрации.Вставить("БезопасныйРежим",Ложь).
        // Иначе установка привилегированного режима может не сработать.
        УстановитьПривилегированныйРежим(Истина);
        Если ССообщениями И Не ПривилегированныйРежим() Тогда // Проверяем, удалось ли установить режим
            Сообщить("Ошибка в программе. Не установился привилегированный режим.");
        КонецЕсли;
    КонецЕсли;

    СтандартнаяОбработка = Ложь;

    // Получаем текущие настройки отчета. Важно: настройки получаются из объекта отчета,
    // который передается сюда, но их содержимое уже может быть "урезано"
    // платформой, если не принять дополнительные меры (см. Решения 2 и 3).
    Настройки = ОтчетОбъект.КомпоновщикНастроек.ПолучитьНастройки();

    // Применяем параметры, если они были переданы
    Если Параметры <> Неопределено Тогда
        Для Каждого КлючЗначение Из Параметры Цикл
            ПараметрСКД = Настройки.ПараметрыДанных.Элементы.Найти(КлючЗначение.Ключ);
            Если ПараметрСКД <> Неопределено Тогда
                ПараметрСКД.Значение = КлючЗначение.Значение;
                ПараметрСКД.Использование = Истина;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;

    // Здесь может быть дополнительная логика по изменению настроек,
    // например, отключение колонок, как в исходном сообщении
    // Если Не РольДоступна("Снабжение") Тогда ... КонецЕсли;
    // Однако, если поля уже были отброшены, эта логика может не иметь смысла.

    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(ОтчетОбъект.СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);

    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);

    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.НачатьВывод();

    Пока Истина Цикл
        ЭлементРезультата = ПроцессорКомпоновки.Следующий();
        Если ЭлементРезультата = Неопределено Тогда
            Прервать;
        КонецЕсли;
        ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
    КонецЦикла;

    ПроцессорВывода.ЗакончитьВывод();

    Если ВПривилегированномРежиме Тогда
        УстановитьПривилегированныйРежим(Ложь); // Возвращаем обычный режим
    КонецЕсли;

КонецФункции

Шаг 3: Вызываем экспортную функцию из модуля отчета.

В модуле вашего отчета (например, в процедуре ПриКомпоновкеРезультата) мы вызываем созданную экспортную функцию общего модуля, передавая ей все необходимые параметры. Перед этим полезно провести анализ конфигурации на наличие ошибок в вызовах процедур.


// В модуле объекта отчета
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;

    // Примеры параметров, которые можно передать в общий модуль
    Организации = КФ_СерверВызов.ДоступныеОрганизацииПоДокументам("ЗаявкаНаРасходованиеДенежныхСредств");
    Параметры = Новый Структура("Организации", Организации);

    ВПривилегированномРежиме = Истина;
    ССообщениями = Истина;

    // Вызываем функцию в общем модуле
    КФ_СерверВызов.СКДПриКомпоновкеРезультата(ЭтотОбъект, ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка, ВПривилегированномРежиме, Параметры, ССообщениями);
КонецПроцедуры

Важно для внешних отчетов: отключение безопасного режима.

Если вы разрабатываете внешний отчет, который будет подключаться в базу, то для его выполнения в привилегированном режиме критически важно отключить безопасный режим для этой внешней обработки. Это делается в функции СведенияОВнешнейОбработке вашего внешнего отчета. Добавьте в возвращаемую структуру параметр БезопасныйРежим со значением Ложь:


// В функции СведенияОВнешнейОбработке() внешнего отчета
Функция СведенияОВнешнейОбработке() Экспорт
    ПараметрыРегистрации = Новый Структура;
    ПараметрыРегистрации.Вставить("Вид", "Отчет");
    ПараметрыРегистрации.Вставить("Наименование", НСтр("ru = 'Мой привилегированный отчет'"));
    // ... другие параметры
    ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); // КРИТИЧЕСКИ ВАЖНО ДЛЯ ВНЕШНИХ ОТЧЕТОВ
    Возврат ПараметрыРегистрации;
КонецФункции

Без этого шага платформа может игнорировать вызов УстановитьПривилегированныйРежим(Истина), поскольку внешний отчет по умолчанию выполняется в безопасном режиме, который имеет свои собственные ограничения, блокирующие полный доступ к привилегиям. Серверный вызов в привилегированный общий модуль в этом случае не сработает должным образом, если он инициирован из клиентского кода внешнего отчета, находящегося в безопасном режиме.

Решение 2: Программное формирование отчета с использованием настроек по умолчанию

Как мы уже выяснили, основная проблема заключается в том, что метод КомпоновщикНастроек.ПолучитьНастройки() отфильтровывает поля, на которые у пользователя нет прав, еще до того, как привилегированный режим вступит в полную силу для запросов к данным. Если мы хотим, чтобы отчет сформировался со всеми полями, независимо от текущих настроек пользователя, мы можем использовать настройки по умолчанию, определенные в самой схеме компоновки данных.

Механизм решения:

Вместо того чтобы получать настройки через КомпоновщикНастроек.ПолучитьНастройки(), мы будем передавать в метод КомпоновщикМакета.Выполнить() непосредственно СхемаКомпоновкиДанных.НастройкиПоУмолчанию. Эти настройки, как правило, содержат все поля, определенные в СКД, и не зависят от текущих прав пользователя.


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;
    УстановитьПривилегированныйРежим(Истина); // Устанавливаем привилегированный режим

    // !!! ВАЖНО: Вместо КомпоновщикНастроек.ПолучитьНастройки()
    // используем настройки по умолчанию из схемы
    // Это гарантирует, что все поля будут доступны для компоновки,
    // независимо от прав текущего пользователя.
    НастройкиДляКомпоновки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;

    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиДляКомпоновки, ДанныеРасшифровки);

    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);

    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.НачатьВывод();

    ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    Пока ЭлементРезультата <> Неопределено Цикл
        ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
        ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    КонецЦикла;

    ПроцессорВывода.ЗакончитьВывод();
    УстановитьПривилегированныйРежим(Ложь); // Возвращаем обычный режим
КонецПроцедуры

Ограничения этого подхода:

  1. Потеря пользовательских настроек: Главный недостаток этого метода заключается в том, что все настройки, которые пользователь интерактивно сделал на форме отчета (например, изменил состав полей, добавил отборы, изменил группировки), будут проигнорированы. Отчет всегда будет формироваться так, как он настроен в НастройкиПоУмолчанию схемы СКД.
  2. Необходимость программной адаптации: Если вам все же нужно учитывать часть пользовательских настроек (например, параметры отчета), то после получения НастройкиПоУмолчанию вам придется программно вносить в них необходимые изменения, взяв их из КомпоновщикНастроек.ПолучитьНастройки() или напрямую из элементов формы. Это может усложнить код.

Этот вариант подходит, когда отчет должен всегда формироваться по фиксированным правилам, определенным разработчиком, независимо от действий пользователя, но при этом требовать привилегированного доступа к данным.

Решение 3: Управление доступностью полей через модификацию схемы СКД

Для того чтобы обойти проблему с отсечением полей из-за отсутствия прав, при этом сохраняя возможность использования интерактивных пользовательских настроек, мы можем применить более изощренные методы, которые "обманывают" платформу на этапе анализа прав доступа к полям схемы СКД.

Подход 3.1: Две схемы компоновки данных (Схема для настроек и Схема для формирования)

Идея этого подхода заключается в том, что мы используем две схемы компоновки данных:

  1. Схема для настроек (интерактивная): Это основная схема отчета, которая открывается пользователю. Она может быть адаптирована (например, некоторые поля могут быть скрыты или недоступны, чтобы не смущать пользователя, если на них нет прав). КомпоновщикНастроек работает именно с этой схемой.
  2. Схема для формирования (привилегированная): Это либо копия основной схемы, либо модифицированная версия, в которой гарантированно присутствуют все необходимые поля, даже те, на которые у пользователя нет прав. Мы будем использовать эту схему для непосредственного выполнения компоновки макета.

Механизм реализации:

  1. Создаем дополнительную схему: Вы можете либо создать вторую схему СКД в конфигураторе (например, СхемаКомпоновкиДанныхПривилегированная), либо программно создать копию основной схемы и модифицировать ее в коде.
  2. Присваиваем схему компоновщику макета: В процедуре ПриКомпоновкеРезультата, после получения настроек отчета (которые уже могут быть "урезанными"), мы передаем в метод КомпоновщикМакета.Выполнить() нашу "привилегированную" схему, но используем пользовательские настройки, полученные от КомпоновщикНастроек.

Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;
    УстановитьПривилегированныйРежим(Истина);

    // Получаем пользовательские настройки.
    // На этом этапе они могут быть урезаны, но мы их будем использовать как основу.
    НастройкиПользователя = КомпоновщикНастроек.ПолучитьНастройки();

    // Загружаем "привилегированную" схему, которая содержит все поля.
    // Это может быть другая схема, сохраненная в макетах отчета,
    // или программно созданная/модифицированная схема.
    // Например, если у нас есть макет "СхемаСКДПривилегированная"
    МакетПривилегированнойСхемы = ЭтотОбъект.ПолучитьМакет("СхемаСКДПривилегированная");
    СхемаПривилегированная = МакетПривилегированнойСхемы.ПолучитьТекст(); // Если схема хранится как текст
    // Или
    // СхемаПривилегированная = ЭтотОбъект.ПолучитьМакет("СхемаСКДПривилегированная").ПолучитьСхемуКомпоновкиДанных();

    // Создаем компоновщик макета
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;

    // Выполняем компоновку, используя "полную" схему,
    // но с настройками, полученными от пользователя.
    // Платформа будет искать поля в СхемаПривилегированная, где они точно есть.
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаПривилегированная, НастройкиПользователя, ДанныеРасшифровки);

    // ... далее стандартный код вывода отчета
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);

    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.НачатьВывод();

    ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    Пока ЭлементРезультата <> Неопределено Цикл
        ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
        ЭлементРезультата = ПроцессорКомпоновки.Следующий();
    КонецЦикла;

    ПроцессорВывода.ЗакончитьВывод();
    УстановитьПривилегированныйРежим(Ложь);
КонецПроцедуры

Преимущества: Этот подход позволяет сохранить интерактивность пользовательских настроек, при этом обеспечивая доступность всех полей для формирования отчета.

Недостатки: Требует дублирования или программной модификации схем, что может усложнить поддержку.

Подход 3.2: Использование объединения запросов с фиктивной строкой

Этот метод является одним из наиболее изящных для обхода проблемы отсечения полей. Идея заключается в том, чтобы "обмануть" СКД, заставив ее думать, что все поля всегда доступны, даже если у пользователя нет прав на просмотр данных, из которых они берутся. Мы достигаем этого путем добавления к основному запросу СКД дополнительного "фиктивного" запроса через оператор ОБЪЕДИНИТЬ ВСЕ, который возвращает пустые значения для всех нужных полей, но при этом содержит условие ГДЕ ЛОЖЬ, чтобы эти фиктивные строки никогда не попали в результат.

Механизм реализации:

  1. Модифицируем основной запрос СКД: Для каждого набора данных-запроса, содержащего поля, которые могут быть недоступны из-за прав, мы добавляем вторую секцию запроса через ОБЪЕДИНИТЬ ВСЕ.
  2. Вторая секция запроса: Эта секция должна выбирать пустые значения или значения по умолчанию для всех полей, которые есть в первой секции и которые мы хотим сохранить доступными, даже если на них нет прав. При этом мы добавляем условие ГДЕ ЛОЖЬ, чтобы этот запрос никогда не возвращал реальные данные.

Давайте посмотрим на пример. Предположим, у нас есть отчет по справочнику "Валюты", но у пользователя может не быть прав на этот справочник:


// Исходный запрос
ВЫБРАТЬ
    Валюты.Наименование КАК Валюта,
    Валюты.Код КАК Код
ИЗ
    Справочник.Валюты КАК Валюты

Если у пользователя нет доступа к справочнику Справочник.Валюты, то при получении настроек поля Валюта и Код будут отброшены, и отчет не сможет сформироваться. Модифицируем запрос следующим образом:


// Модифицированный запрос с объединением
ВЫБРАТЬ
    Валюты.Наименование КАК Валюта,
    Валюты.Код КАК Код
ИЗ
    Справочник.Валюты КАК Валюты

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    "" КАК Валюта, // Пустая строка для наименования
    "" КАК Код     // Пустая строка для кода
ИЗ
    Справочник.Валюты КАК Валюты // Используем тот же источник, но с условием
ГДЕ ЛОЖЬ // Это условие гарантирует, что вторая часть запроса не вернет ни одной строки

Что происходит:

Преимущества:

Недостатки:

Общие рекомендации и важные аспекты

При работе с привилегированным режимом и СКД мы должны всегда помнить о нескольких важных моментах, чтобы обеспечить надежность, безопасность и корректность работы отчетов:

  1. Всегда тестируйте под разными ролями: После внедрения любого из вышеперечисленных решений, мы должны выполнить анализ прав и ролей (удобно через обработка для детального анализа прав и RLS) и тщательно проверить работу отчета под пользователями с различными наборами прав, включая пользователей без привилегий, чтобы убедиться в корректности формирования отчета и отсутствии утечки конфиденциальных данных.
  2. Временное отключение RLS (альтернатива): В некоторых сценариях, вместо полного перехода в привилегированный режим, можно временно отключить ограничение прав на уровне записей (RLS) для текущего сеанса. Это делается путем установки параметра сеанса ИспользоватьОграниченияПравДоступаНаУровнеЗаписей в Ложь перед формированием отчета и его возврата в Истина после. Однако этот метод имеет свои риски и может быть менее безопасным, чем использование привилегированных модулей, так как он отключает RLS для всего сеанса, а не только для конкретного кода.
    
    // Пример временного отключения RLS
    ПараметрыСеанса.Установить("ИспользоватьОграниченияПравДоступаНаУровнеЗаписей", Ложь);
    Попытка
        // ... Ваш код формирования отчета в привилегированном режиме
    Исключение
        // Обработка ошибок
    КонецПопытки;
    ПараметрыСеанса.Установить("ИспользоватьОграниченияПравДоступаНаУровнеЗаписей", Истина);
    
    Мы рекомендуем использовать этот подход с большой осторожностью и только в тех случаях, когда другие методы не применимы или слишком сложны, и только если вы полностью уверены в отсутствии рисков для безопасности данных.
  3. Работа с недоступными полями ссылочного типа: Если у пользователя нет прав на просмотр самого объекта (например, элемента справочника), то поля ссылочного типа на этот объект будут недоступны в СКД. В таких ситуациях, помимо рассмотренных выше методов, иногда рекомендуется в запросе получать не саму ссылку, а ее строковое представление, например, Объект.Наименование или Представление(Объект). Это может позволить отобразить текстовую информацию, даже если доступ к самому объекту ограничен.
  4. Проблемы с вычисляемыми полями: При программном формировании отчета в привилегированном режиме могут возникать ошибки, связанные с вычисляемыми полями, особенно если в них вызываются функции из общих модулей. Мы должны внимательно проверять доступность этих модулей и их функций в контексте выполнения отчета (клиент, сервер, привилегированный режим). Если вычисляемое поле ссылается на серверную функцию, убедитесь, что она доступна и также может выполняться в привилегированном режиме.
  5. Инкапсуляция логики: Мы настоятельно рекомендуем инкапсулировать всю логику, требующую привилегированного доступа, в общих привилегированных модулях. Это не только улучшает структуру кода, но и повышает безопасность, поскольку область действия привилегированного режима ограничивается только необходимым кодом.

Выбирая подходящий метод, мы всегда должны исходить из конкретных требований к отчету, уровня необходимой гибкости пользовательских настроек и соображений безопасности. Каждый из представленных подходов имеет свои преимущества и ограничения, и наше мастерство заключается в выборе наиболее оптимального решения для каждой конкретной задачи.

← На главную