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