В торговых конфигурациях 1С часто возникает задача разграничения доступа к прикрепленным файлам и сертификатам товаров. Например, когда рядовым сотрудникам разрешено просматривать сертификаты только на базовую номенклатуру, а доступ к отслеживаемым позициям (учитываемым в специальном регистре сведений) должен быть открыт только пользователям с определенной ролью (например, ПечатьВсехСертификатов).
Разберем, как правильно реализовать такой отбор в коде формы обработки или отчета, выясним причину возможных проблем с производительностью и рассмотрим альтернативные варианты через механизм ограничения прав доступа (RLS).
Проанализируем базовый подход, когда список доступных сертификатов формируется с помощью запроса к базе данных. Пользователь проверяется на наличие определенной роли с помощью системной функции РольДоступна().
В исходной постановке задачи часто пытаются использовать подзапрос с конструкцией НЕ ... В (ВЫБРАТЬ ...) и передачей параметра-флага:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Сертификат КАК Сертификат
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| (&БезОтбора
| ИЛИ НЕ Номенклатура.Сертификат В
| (ВЫБРАТЬ
| ЗБ_СписокОтслеживаемыхSKU.Сертификат
| ИЗ
| РегистрСведений.ЗБ_СписокОтслеживаемыхSKU КАК ЗБ_СписокОтслеживаемыхSKU))";
Запрос.УстановитьПараметр("БезОтбора", РольДоступна("ПечатьВсехСертификатов"));
Результат = Запрос.Выполнить();
Однако такая реализация имеет существенные архитектурные недостатки:
&БезОтбора ИЛИ ... заставляет оптимизатор базы данных выполнять полное сканирование таблицы (Index Scan или Table Scan), так как он не может заранее применить индексы.НЕ ... В (...) при больших объемах данных в регистре работает крайне медленно по сравнению с соединением таблиц.Сертификат заблокирует показ даже для неотслеживаемых товаров. В регистре присутствуют два измерения — Номенклатура и Сертификат, следовательно, соединять и проверять нужно именно связку этих полей.Посмотрим на оптимизированный вариант с динамическим формированием текста запроса и ЛЕВЫМ СОЕДИНЕНИЕМ:
Запрос = Новый Запрос;
ТекстЗапроса =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Сертификат КАК Сертификат
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Если НЕ РольДоступна("ПечатьВсехСертификатов") Тогда
// Исключаем отслеживаемые SKU через соединение и проверку на NULL
ТекстЗапроса = ТекстЗапроса + "
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СписокОтслеживаемыхSKU КАК Отслеживаемые
| ПО Номенклатура.Ссылка = Отслеживаемые.Номенклатура
| И Номенклатура.Сертификат = Отслеживаемые.Сертификат
|ГДЕ
| Отслеживаемые.Номенклатура ЕСТЬ NULL";
КонецЕсли;
Запрос.Текст = ТекстЗапроса;
Результат = Запрос.Выполнить();
В этом случае, если пользователю доступна роль, соединение с регистром вообще не выполняется, и запрос отрабатывает мгновенно. Если роли нет, СУБД использует соединение по индексам измерений регистра.
В интерфейсных механизмах (например, в формах подбора или просмотра) сертификат часто отображается не общей таблицей, а для одной текущей строки, выбранной пользователем в списке номенклатуры. Выполнять тяжелый запрос ко всей таблице номенклатуры при каждом клике нерационально.
Рассмотрим, как выполнить быструю проверку конкретной позиции:
Если СертификатыХарактеристик = Ложь Тогда
Если ЭлементыФормы.СписокНоменклатур.ТекущиеДанные = Неопределено Тогда
Номенклатура = Справочники.Номенклатура.ПустаяСсылка();
Иначе
Номенклатура = ЭлементыФормы.СписокНоменклатур.ТекущиеДанные.Ссылка;
КонецЕсли;
Если НЕ Номенклатура.ЭтоГруппа Тогда
Сертификат = Номенклатура.Сертификат;
Иначе
Сертификат = Справочники.СертификатыНоменклатуры.ПустаяСсылка();
КонецЕсли;
КонецЕсли;
// Проверяем доступность сертификата к просмотру
РазрешеноКПросмотру = Истина;
Если ЗначениеЗаполнено(Сертификат) И НЕ РольДоступна("ПечатьВсехСертификатов") Тогда
ЗапросПроверки = Новый Запрос;
ЗапросПроверки.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1
|ИЗ
| РегистрСведений.СписокОтслеживаемыхSKU КАК Отслеживаемые
|ГДЕ
| Отслеживаемые.Номенклатура = &Номенклатура
| И Отслеживаемые.Сертификат = &Сертификат";
ЗапросПроверки.УстановитьПараметр("Номенклатура", Номенклатура);
ЗапросПроверки.УстановитьПараметр("Сертификат", Сертификат);
Если НЕ ЗапросПроверки.Выполнить().Пустой() Тогда
РазрешеноКПросмотру = Ложь;
КонецЕсли;
КонецЕсли;
// Управляем отображением данных в интерфейсе
Если РазрешеноКПросмотру Тогда
ЭтаФорма.ТекущийЭлемент = ЭтаФорма.ЭлементыФормы.СписокИзображений;
// Код вывода изображения сертификата
Иначе
// Очищаем область просмотра, чтобы не оставалось изображение предыдущего товара
ЭлементыФормы.СписокИзображений.Значение = Неопределено;
Сообщить("Просмотр сертификата по данной позиции ограничен правами доступа.", СтатусСообщения.Внимание);
КонецЕсли;
При использовании проверки через запрос к регистру сведений СписокОтслеживаемыхSKU важно учесть права рядового пользователя:
ПечатьВсехСертификатов нет права на чтение самого регистра сведений, выполнение запроса вызовет ошибку Нарушение прав доступа.УстановитьПривилегированныйРежим(Истина) в серверном контексте.Если требуется запретить просмотр сертификатов не только в конкретной обработке, но и во всей системе (в общих списках, отчетах, связанных документах), целесообразно использовать механизм RLS (Record Level Security).
Проанализируем особенности настройки RLS для справочника СертификатыНоменклатуры:
ВЫБРАТЬ РАЗРЕШЕННЫЕ. Без этой конструкции запрос, встретивший недоступный сертификат, завершится аварийно с ошибкой доступа.ПечатьВсехСертификатов получают полные права на чтение без ограничений RLS, благодаря чему они видят полный состав данных без накладных расходов на проверки.