Механизм RLS (Row-Level Security) в 1С:Предприятии позволяет настраивать детальные ограничения доступа к данным на уровне записей, что критически важно для разграничения прав пользователей. В этом материале мы рассмотрим комплексный подход к доработке RLS в конфигурации 1С:Комплексная автоматизация 2.5, фокусируясь на ограничении доступа по подразделениям. Наша задача — реализовать гибкую систему, которая будет учитывать иерархию подразделений, динамически определять права при пустом регистре сведений и эффективно работать с точки зрения производительности.
Автору требовалось доработать ограничение доступа по подразделениям, используя данные из отдельного регистра сведений. Перед нами встали несколько ключевых вопросов, которые мы поэтапно разберем.
Одной из наиболее частых задач при настройке RLS по иерархическим справочникам является предоставление доступа ко всем дочерним элементам, если пользователь имеет доступ к родительской группе. В стандартном механизме RLS прямое использование конструкции "В Иерархии" в запросах может негативно сказаться на производительности, особенно на больших объемах данных. Поэтому мы рассмотрим более производительные подходы.
Предварительное заполнение регистра сведений: Наиболее распространенный и производительный подход заключается в том, чтобы ваш вспомогательный регистр сведений хранил не только сами группы подразделений, но и все входящие в них дочерние элементы (подгруппы и конечные подразделения). Это позволяет избежать сложных иерархических запросов непосредственно в RLS-шаблоне. При изменении иерархии справочника "Подразделения" этот регистр необходимо актуализировать.
Например, если пользователь имеет доступ к группе "Продажи", то в регистре сведений "ДоступныеПодразделения" для этого пользователя должны быть явно перечислены как сама группа "Продажи", так и все подразделения "Отдел розничных продаж", "Отдел оптовых продаж" и т.д., входящие в нее.
Использование запроса с учетом родительских связей в RLS-шаблоне: Если вы все же хотите хранить только группы в регистре сведений и динамически определять иерархию в RLS, это возможно, но с оговорками относительно производительности. Запрос в RLS должен будет проверять не только прямое соответствие, но и принадлежность к родительской группе. Мы можем использовать условие, аналогичное предложенному в одном из сообщений, адаптировав его под подразделения:
Подразделение ИЗ Справочник.Подразделения КАК Подразделение
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДоступныеПодразделенияПользователя КАК ДоступныеПодразделенияПользователя
ПО (Подразделение.Ссылка = ДоступныеПодразделенияПользователя.Подразделение
ИЛИ Подразделение.Родитель = ДоступныеПодразделенияПользователя.Подразделение
ИЛИ Подразделение.Родитель.Родитель = ДоступныеПодразделенияПользователя.Подразделение // и так далее до нужного уровня вложенности
ИЛИ Подразделение.Владелец = ДоступныеПодразделенияПользователя.Подразделение) // Если Подразделение является подчиненным справочником
ГДЕ ДоступныеПодразделенияПользователя.Пользователь = &ТекущийПользователь
Здесь важно понимать, что прямое перечисление родительских полей (Подразделение.Родитель, Подразделение.Родитель.Родитель и т.д.) работает для фиксированной глубины иерархии. Если иерархия может быть глубокой и переменной, такой подход становится громоздким и может значительно снижать производительность. Именно поэтому рекомендуется предварительно заполнять регистр сведений всеми элементами иерархии.
Пользователи на форуме также подтверждали эту стратегию: "Все родительские папки я писал в регистр что бы не произошло Спр.Ссылка Спр.Родитель. Спр.Родитель.Родитель... если что, на уровень вложенности спр.подразделения". Это подчеркивает важность предварительной подготовки данных для RLS.
Второй важный момент – как обработать ситуацию, когда наш регистр сведений "ДоступныеПодразделенияПользователя" для текущего пользователя пуст. Автор спрашивал: "как в модуле менеджера в процедуре ПриЗаполненииОграниченияДоступа прописать условие 'Если список РС пустой то значения доступа берутся из значений групп доступа.'" Нам нужно, чтобы при отсутствии явных настроек в регистре сведений, действовали стандартные права доступа (например, из обычных групп доступа 1С или полное разрешение).
Мы можем реализовать это с помощью условной логики в RLS-шаблоне. В запросе RLS мы добавим условие, которое проверяет наличие записей для текущего пользователя в нашем регистре. Если записей нет, то ограничение по подразделениям не применяется:
#Если &ИспользоватьОграничениеПоПодразделениям
Тогда
Подразделение ГДЕ Подразделение.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ ДоступныеПодразделения.Подразделение
ИЗ РегистрСведений.ДоступныеПодразделенияПользователя КАК ДоступныеПодразделения
ГДЕ ДоступныеПодразделения.Пользователь = &ТекущийПользователь)
ИЛИ НЕ (ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА ИЗ РегистрСведений.ДоступныеПодразделенияПользователя ГДЕ Пользователь = &ТекущийПользователь)
#КонецЕсли
Разберем это условие:
Подразделение ГДЕ Подразделение.Ссылка В (ВЫБРАТЬ...): Ограничивает доступ только к тем подразделениям, которые явно указаны в нашем регистре сведений для текущего пользователя.ИЛИ НЕ (ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА ИЗ РегистрСведений.ДоступныеПодразделенияПользователя ГДЕ Пользователь = &ТекущийПользователь): Это ключевое условие. Оно истинно, если запрос ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА ИЗ РегистрСведений.ДоступныеПодразделенияПользователя ГДЕ Пользователь = &ТекущийПользователь не возвращает ни одной записи. Другими словами, если для текущего пользователя нет никаких настроек в регистре, то вторая часть условия становится истинной, и все подразделения становятся доступными для просмотра (в рамках других RLS ограничений или полного доступа, если других ограничений нет).Таким образом, мы добиваемся необходимой логики: если есть явные настройки — используем их; если нет — применяются правила по умолчанию, которые могут быть шире.
Ручное поддержание актуальности регистра сведений "ДоступныеПодразделенияПользователя" при изменении иерархии справочника "Подразделения" (добавление новых дочерних элементов, изменение родителя) крайне неудобно. Автор поднимал этот вопрос: "Да согласен что так можно, но если появится дочерний элемент то прийдется вручную это все отрабатывать, либо писать регл задание например которое проверит. Но это не совсем удобно". Мы согласны, поэтому рассмотрим способы автоматизации этого процесса.
Обработчик события справочника "Подразделения": Мы можем использовать обработчики событий объекта справочника "Подразделения". В модуле объекта справочника "Подразделения" в процедурах ПриЗаписи или ПередЗаписью можно реализовать логику, которая при изменении элемента или создании нового будет обновлять наш регистр сведений.
Например, при изменении родителя подразделения или создании нового подразделения, нам нужно проверить, есть ли у его нового родителя (или у него самого, если это группа) записи в регистре "ДоступныеПодразделенияПользователя". Если родительская группа доступна какому-либо пользователю, то новое дочернее подразделение также должно быть добавлено в список доступных для этих пользователей. Это потребует обхода иерархии и обновления записей в нашем регистре сведений.
// Пример логики в модуле объекта справочника "Подразделения" ПриЗаписи
Процедура ПриЗаписи(Отказ)
Если ЭтоНовый Тогда
// Новый элемент - возможно, нужно добавить его в регистры доступа, если его родитель уже доступен
ОбновитьДоступДляПодразделенияИЕгоРодителей(ЭтотОбъект.Ссылка);
Иначе
// Существующий элемент - возможно, изменился родитель
Если Модифицированность() Тогда
// Если изменился родитель, или сам элемент стал группой/перестал быть группой
ОбновитьДоступДляПодразделенияИЕгоРодителей(ЭтотОбъект.Ссылка);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
// Вспомогательная функция (в модуле менеджера или общем модуле)
Процедура ОбновитьДоступДляПодразделенияИЕгоРодителей(ПодразделениеСсылка)
// Эта процедура должна выполнить следующее:
// 1. Получить всю иерархию подразделения, начиная от переданного ПодразделениеСсылка
// 2. Определить, для каких пользователей родительские элементы этого подразделения уже доступны.
// 3. Добавить/обновить записи в РегистрСведений.ДоступныеПодразделенияПользователя
// для текущего подразделения и всех его дочерних элементов
// для тех пользователей, для которых доступны его родительские группы.
// Это достаточно сложная логика, требующая тщательной проработки запросов
// для определения связей и пользователей.
// Для простоты, если регистр хранит только "прямой" доступ,
// и мы хотим, чтобы дочерние элементы "наследовались" из групп:
//
// 1. Получаем всех родителей текущего подразделения
// 2. Для каждого родителя, если он есть в РС "ДоступныеПодразделенияПользователя"
// для каких-то пользователей, то для этих пользователей добавляем текущее подразделение.
// Или, если в РС прописаны группы, то мы должны добавить все элементы этой группы.
// Примерная логика (псевдокод):
ПользователиСДоступомКРодителю = ПолучитьПользователейСДоступомКРодителю(ПодразделениеСсылка);
Для Каждого Пользователь Из ПользователиСДоступомКРодителю Цикл
// Проверяем, есть ли уже запись для этого подразделения и пользователя
// Если нет, то добавляем
// РегистрыСведений.ДоступныеПодразделенияПользователя.ДобавитьЗапись(Пользователь, ПодразделениеСсылка);
КонецЦикла;
// Также необходимо учесть, если само ПодразделениеСсылка является группой,
// и для нее уже настроен доступ - тогда нужно добавить все дочерние элементы
// этой группы в регистр сведений для соответствующих пользователей.
// Эта операция может быть ресурсоемкой, особенно при глубокой иерархии.
КонецПроцедуры
Регламентные задания: Более надежный и менее нагружающий интерактивную работу пользователя подход — это использование регламентных заданий. Мы можем создать регламентное задание, которое будет периодически (например, раз в день, раз в час или по расписанию при изменении справочника) запускаться и пересчитывать права доступа по подразделениям. Это задание будет анализировать изменения в иерархии справочника "Подразделения" и актуализировать записи в регистре сведений "ДоступныеПодразделенияПользователя".
Преимущества регламентного задания:
Такое задание должно включать в себя алгоритм обхода иерархии подразделений и для каждой группы, имеющей настройки доступа, добавлять все ее дочерние элементы в регистр сведений для соответствующих пользователей.
Автор задавался вопросом: "Как в модуле менеджера документа в процедуре ПриЗаполненииОграниченияДоступа получить ТекущегоПользователя который прописан в параметрах сеанса?"
В RLS-шаблонах и в процедуре ПриЗаполненииОграниченияДоступа (если вы используете БСП и генерируете тексты ограничений программно), текущий пользователь всегда доступен через стандартный параметр сеанса &ТекущийПользователь. Платформа 1С автоматически заполняет этот параметр, и вы можете использовать его напрямую в ваших запросах RLS.
Пример использования:
#Если &ИспользоватьОграничениеПоПодразделениям
Тогда
Подразделение ГДЕ Подразделение.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ ДоступныеПодразделения.Подразделение
ИЗ РегистрСведений.ДоступныеПодразделенияПользователя КАК ДоступныеПодразделения
ГДЕ ДоступныеПодразделения.Пользователь = &ТекущийПользователь)
#КонецЕсли
Обратите внимание, что &ТекущийПользователь является параметром запроса, который платформа подставляет динамически во время выполнения RLS. Вам не нужно его явно получать или передавать в модуль менеджера RLS – он доступен "из коробки".
Механизм RLS может существенно влиять на производительность системы, если он настроен некорректно или содержит сложные запросы. Чтобы избежать замедлений, мы рассмотрим основные рекомендации:
Используйте производительный режим RLS (БСП): Если ваша конфигурация основана на Библиотеке стандартных подсистем (БСП) и вы имеете возможность использовать "Производительный режим RLS", активно применяйте его. В этом режиме права доступа предварительно рассчитываются и хранятся в специальных технических регистрах сведений. Запросы RLS тогда становятся максимально простыми, обращаясь к этим предрассчитанным данным, что значительно ускоряет работу.
Если данные в регистрах доступа меняются не слишком часто, настройте регламентное задание для периодического обновления этих предрассчитанных прав.
Минимизируйте вложенность и сложность запросов: Старайтесь, чтобы запросы в шаблонах RLS были максимально простыми. Избегайте множественных вложенных запросов, соединений с большим количеством таблиц или сложных условий ГДЕ. Чем проще запрос, тем быстрее он будет выполняться.
Именно поэтому мы рекомендовали предварительно заполнять регистр сведений всеми элементами иерархии, вместо того чтобы строить иерархический запрос в самом RLS.
Избегайте функций и вычислений в RLS-шаблонах: Запросы RLS должны быть максимально декларативными. Любые функции, агрегатные вычисления или сложные операции с данными в запросе RLS могут значительно снизить производительность, так как они будут выполняться при каждом обращении к защищенным данным.
Используйте индексированные поля: Убедитесь, что поля, по которым происходят соединения и фильтрация в RLS-запросах (например, Подразделение.Ссылка, ДоступныеПодразделения.Подразделение, ДоступныеПодразделения.Пользователь), проиндексированы в базе данных. Это стандартная практика, которая обеспечивает быстрое выполнение запросов.
Кэширование RLS: Платформа 1С, начиная с версии 8.3.18, включает механизм оптимизации RLS, который кэширует результаты подзапросов, не зависящих от защищаемой таблицы. Это может повысить производительность, но всегда необходимо тестировать на реальных данных.
Тестирование на реальных данных: Всегда проводите тщательное тестирование производительности ваших RLS-шаблонов на объемах данных, приближенных к реальным. Это позволит выявить потенциальные "узкие места" и оптимизировать запросы до запуска системы в эксплуатацию.
Следуя этим рекомендациям, мы сможем создать эффективный и производительный механизм RLS для управления доступом по подразделениям в 1С:Комплексной автоматизации 2.5.