Как настроить RLS: если пользователь есть в регистре — показывать только его клиентов, если нет — показывать всех?

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

В практике разработки на платформе «1С:Предприятие 8» часто возникает классическая задача по ограничению доступа на уровне записей (RLS). Сценарий следующий: в системе есть справочник Партнеры и регистр сведений ОсновныеМенеджерыПартнеров (со связью «Менеджер — Партнер»). Нам необходимо настроить права доступа для роли «Продавец» так, чтобы:

  1. Если текущий пользователь назначен менеджером для каких-либо партнеров в регистре, он должен видеть только этих партнеров.
  2. Если текущего пользователя вообще нет в регистре сведений (за ним не закреплен ни один партнер), он должен видеть абсолютно всех партнеров.

На первый взгляд задача кажется простой, однако стандартные попытки решить её «в лоб» через левые соединения или сложные конструкции ВЫБОР КОГДА часто приводят либо к некорректной работе прав, либо к критическому падению производительности СУБД. В этой статье мы подробно разберем, почему не работают простые соединения, изучим классическое решение для экзамена «1С:Специалист», а также рассмотрим максимально оптимизированный и безопасный вариант для высоконагруженных рабочих баз.

Почему не работает запрос с ЛЕВЫМ СОЕДИНЕНИЕМ?

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


ИЗ Справочник.Партнеры КАК Партнеры
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОсновныеМенеджерыПартнеров КАК ОсновныеМенеджерыПартнеров
ПО Партнеры.Ссылка = ОсновныеМенеджерыПартнеров.Партнер
ГДЕ ВЫБОР
    КОГДА ОсновныеМенеджерыПартнеров.Менеджер = &ТекущийПользователь
        ТОГДА Партнеры.Ссылка В (ВЫБРАТЬ... ГДЕ Менеджер = &ТекущийПользователь)
    ИНАЧЕ 1 = 1
КОНЕЦ

Проанализируем ситуацию и выясним причину, почему при такой настройке пользователи всегда видят весь список партнеров, независимо от переданного параметра &ТекущийПользователь.

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

В этот момент условие ОсновныеМенеджерыПартнеров.Менеджер = &ТекущийПользователь возвращает результат ЛОЖЬ. Согласно логике конструкции ВЫБОР, запрос переходит в ветку ИНАЧЕ и выполняет инструкцию 1 = 1. Поскольку условие 1 = 1 всегда является истиной, СУБД беспрепятственно выводит этих «чужих» партнеров текущему пользователю. В итоге ограничение просто перестает работать.

Для реализации корректной логики нам необходимо полностью отказаться от соединения таблиц в секции ИЗ и использовать вложенные запросы с логическим отрицанием.

Решение 1. Классический RLS-запрос (Учебный вариант)

Если перед нами стоит задача решить проблему исключительно средствами языка запросов RLS (например, при подготовке к экзамену «1С:Специалист по платформе»), мы можем написать элегантное условие без использования соединений. Достаточно использовать проверку на вхождение во вложенных запросах.

Пропишем в ограничении доступа на чтение для справочника Партнеры следующий текст запроса:


ГДЕ Ссылка В (
    ВЫБРАТЬ Рег.Партнер 
    ИЗ РегистрСведений.ОсновныеМенеджерыПартнеров КАК Рег 
    ГДЕ Рег.Менеджер = &ТекущийПользователь
)
ИЛИ НЕ &ТекущийПользователь В (
    ВЫБРАТЬ Рег.Менеджер 
    ИЗ РегистрСведений.ОсновныеМенеджерыПартнеров КАК Рег
)

Разберем по шагам, как работает эта логическая конструкция:

  1. Ситуация А: Пользователь есть в регистре (например, Иванов). При попытке открыть справочник система проверяет вторую часть условия: НЕ &ТекущийПользователь В (ВЫБРАТЬ Рег.Менеджер...). Поскольку Иванов есть в регистре, данное условие возвращает ЛОЖЬ. Далее система вынуждена проверять первую часть условия через оператор ИЛИ. В первой части выполняется поиск: Ссылка В (ВЫБРАТЬ Рег.Партнер... ГДЕ Менеджер = Иванов). В результате Иванов видит исключительно тех партнеров, у которых он назначен менеджером.
  2. Ситуация Б: Пользователя нет в регистре (например, Петров). При входе Петрова вторая часть условия НЕ Петров В (ВЫБРАТЬ...) сразу возвращает ИСТИНА, так как записи по Петрову в таблице отсутствуют. Поскольку части условия соединены оператором ИЛИ, система мгновенно одобряет доступ ко всем записям справочника. Петров успешно видит полный список партнеров.

Данный вариант полностью решает логическую задачу автора. Однако у него есть существенный недостаток — производительность. На больших объемах данных в регистре сведений конструкция НЕ &ТекущийПользователь В (ВЫБРАТЬ...) заставит СУБД выполнять сканирование всей таблицы регистра при каждом обращении к справочнику партнеров. Это может сильно замедлить работу системы.

Решение 2. Оптимальный вариант для рабочих баз (через Параметры сеанса)

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

Рассмотрим подробнее реализацию этого метода по шагам.

Шаг 1. Создание параметра сеанса

В конфигураторе создадим новый параметр сеанса с именем ОграничиватьДоступПоПартнерам и типом данных Булево.

Шаг 2. Настройка инициализации в модуле сеанса

Откроем модуль сеанса и добавим в предопределенную процедуру УстановкаПараметровСеанса код, который будет определять статус текущего пользователя при его входе в систему:


Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
    
    Если ТребуемыеПараметры.Найти("ОграничиватьДоступПоПартнерам") <> Неопределено Тогда
        
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 1 1 
            |ИЗ РегистрСведений.ОсновныеМенеджерыПартнеров КАК ОсновныеМенеджерыПартнеров
            |ГДЕ ОсновныеМенеджерыПартнеров.Менеджер = &ТекущийПользователь";
        
        Запрос.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
        
        РезультатЗапроса = Запрос.Выполнить();
        
        // Если запрос вернул записи, значит менеджер есть в регистре — включаем ограничение
        ПараметрыСеанса.ОграничиватьДоступПоПартнерам = НЕ РезультатЗапроса.Пустой();
        
    КонецЕсли;
    
КонецПроцедуры

Шаг 3. Оптимизированный RLS-запрос в роли

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


ГДЕ НЕ &ОграничиватьДоступПоПартнерам
ИЛИ Ссылка В (
    ВЫБРАТЬ Рег.Партнер 
    ИЗ РегистрСведений.ОсновныеМенеджерыПартнеров КАК Рег 
    ГДЕ Рег.Менеджер = &ТекущийПользователь
)

Посмотрим на логику работы оптимизированного запроса. Если в систему заходит Петров (которого нет в регистре), параметр сеанса &ОграничиватьДоступПоПартнерам принимает значение Ложь. Условие НЕ &ОграничиватьДоступПоПартнерам мгновенно превращается в ИСТИНА. СУБД одобряет доступ ко всей таблице партнеров, даже не пытаясь выполнять вложенный запрос к регистру сведений. Скорость работы такого запроса будет моментальной.

Реализация в конфигурациях на базе БСП (ERP, КА, УТ 11)

Если мы работаем в современных типовых конфигурациях фирмы «1С» (таких как «ERP Управление предприятием», «Комплексная автоматизация» или «Управление торговлей»), ручное написание RLS-запросов в ролях может привести к конфликтам со стандартной подсистемой управления доступом БСП (Библиотеки стандартных подсистем).

В современных конфигурациях используется так называемый производительный режим RLS. Он работает по следующему принципу:

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

← На главную