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