Как ограничить доступ к строкам табличной части документа в 1С?

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

В процессе разработки в среде 1С перед программистом часто возникает задача разграничения прав доступа. Традиционным и наиболее надежным инструментом для этого является механизм RLS (Row-Level Security) — ограничение доступа на уровне записей. Однако при попытке настроить RLS так, чтобы пользователь видел в табличной части документа только определенные строки (например, относящиеся к доступным ему складам или подразделениям), разработчики неизбежно сталкиваются с жесткими платформенными ограничениями.

В этой статье мы подробно проанализируем, почему механизм RLS не работает для строк табличных частей, выясним архитектурные особенности платформы 1С и разберем практические способы решения этой задачи с помощью программных методов на управляемых формах.

1. Почему RLS не работает на уровне строк табличной части?

Рассмотрим архитектурную причину этого ограничения. Механизм RLS в платформе 1С спроектирован для работы исключительно с таблицами верхнего уровня (это сами документы, справочники, регистры сведений, регистры накопления и т.д.).

Табличная часть документа (например, Товары в документе ЗаказИМ) физически и логически не является самостоятельной независимой таблицей с точки зрения системы прав. Она представляет собой неотъемлемую часть своего объекта-владельца. Когда платформа считывает документ из базы данных или записывает его, она оперирует объектом целиком. Невозможно стандартными средствами СУБД или платформы разрешить доступ к документу в целом, но «отрезать» при чтении конкретные строки его табличной части.

Если попытаться обойти это ограничение «в лоб» — например, наложить RLS на справочник Склады, который выбирается в строках табличной части, — мы получим крайне нежелательный результат:

  1. Строка табличной части никуда не исчезнет. Пользователь по-прежнему будет видеть её в документе.
  2. Вместо наименования склада в соответствующей колонке пользователь увидит системную надпись «<Объект не найден>».
  3. Вся сопутствующая конфиденциальная информация в этой строке (номенклатура, количество, цена, сумма) останется полностью доступной для просмотра.

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

2. Способ А: Программная фильтрация на управляемой форме через «ОтборСтрок»

Это самый популярный и относительно простой способ скрыть ненужные строки от пользователя на уровне интерфейса. Он отлично подходит для ситуаций, когда жестких требований к абсолютной безопасности данных нет (то есть если пользователь не является злоумышленником, способным достать данные через отладчик или консоль запросов), а фильтрация нужна для удобства работы.

Разберем по шагам, как реализовать этот механизм:

  1. Откроем управляемую форму документа и в окне элементов формы перейдем к свойствам нашей таблицы (например, таблицы Товары).
  2. Добавим в реквизиты формы новый не сохраняемый в базе данных реквизит. Назовем его, к примеру, ДоступноДляПросмотра с типом Булево. Обратите внимание: этот реквизит должен быть добавлен именно как реквизит формы, а не реквизит объекта (документа).
  3. Свяжем этот реквизит с колонкой таблицы формы. При необходимости колонку можно сделать невидимой для пользователя.
  4. В обработчике события формы ПриСозданииНаСервере или в специализированной процедуре обновления формы напишем код, который обойдет строки табличной части и определит доступность каждой строки для текущего пользователя.

Рассмотрим пример кода для заполнения признака доступности:


&НаСервере
Процедура ЗаполнитьДоступностьСтрокНаСервере()
    
    // Получаем массив доступных текущему пользователю складов
    ДоступныеСклады = ПолучитьДоступныеСкладыПользователя(ПараметрыСеанса.ТекущийПользователь);
    
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        // Проверяем, входит ли склад строки в список разрешенных
        Если ДоступныеСклады.Найти(СтрокаТЧ.Склад) <> Неопределено Тогда
            СтрокаТЧ.ДоступноДляПросмотра = Истина;
        Иначе
            СтрокаТЧ.ДоступноДляПросмотра = Ложь;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

После того как признак заполнен, нам необходимо применить фильтрацию к табличному полю формы. Для этого используется свойство таблицы формы ОтборСтрок. Пропишем применение отбора:


&НаКлиентеНаСервереБезКонтекста
Процедура УстановитьФильтрТабличнойЧасти(Форма)
    
    НовыйОтбор = Новый ФиксированнаяСтруктура("ДоступноДляПросмотра", Истина);
    Форма.Элементы.Товары.ОтборСтрок = НовыйОтбор;
    
КонецПроцедуры

Важное предупреждение: Не пытайтесь использовать для скрытия строк механизм Условного оформления (задавая правило Видимость = Ложь по условию). Согласно стандартам разработки фирмы «1С», использование условного оформления для управления видимостью строк в динамических списках и табличных частях крайне не рекомендуется. Это приводит к существенному падению производительности интерфейса (особенно в веб-клиенте), появлению визуальных артефактов, пустых строк-фантомов и ломает корректную работу прокрутки (скроллинга).

3. Способ Б: Использование буферной таблицы значений (Метод повышенной безопасности)

Если бизнес-требования к безопасности данных очень строгие и пользователь ни при каких обстоятельствах (даже через чтение XML-схемы формы или контекстное меню) не должен получить доступ к «чужим» строкам, программного отбора строк будет недостаточно. В этом случае мы должны физически исключить запрещенные данные из контекста формы.

Для этого применяется метод разделения реальной табличной части документа и таблицы на форме через «буфер»:

  1. На управляемой форме документа создается реквизит формы типа ТаблицаЗначений (назовем его ТаблицаТоварыФормы), который в точности повторяет структуру нашей табличной части Товары. Именно эту таблицу мы выводим на экран пользователю.
  2. При открытии формы в обработчике ПриСозданииНаСервере мы считываем данные из реальной табличной части документа, но загружаем в ТаблицаТоварыФормы только те строки, которые соответствуют разрешенным складам.

Посмотрим на пример заполнения буферной таблицы при открытии:


&НаСервере
Процедура ЗаполнитьБуфернуюТаблицуФормы()
    
    ДоступныеСклады = ПолучитьДоступныеСкладыПользователя(ПараметрыСеанса.ТекущийПользователь);
    
    // Очищаем таблицу формы перед заполнением
    ТаблицаТоварыФормы.Очистить();
    
    Для Каждого СтрокаОригинала Из Объект.Товары Цикл
        Если ДоступныеСклады.Найти(СтрокаОригинала.Склад) <> Неопределено Тогда
            ЗаполнитьЗначенияСвойств(ТаблицаТоварыФормы.Добавить(), СтрокаОригинала);
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Пользователь работает с экранной таблицей ТаблицаТоварыФормы: добавляет новые позиции, редактирует количество или удаляет строки. Реальная табличная часть документа Объект.Товары при этом остается неизменной в памяти до момента сохранения документа.

При записи документа нам необходимо выполнить обратную операцию — перенести изменения из экранной таблицы обратно в объект базы данных. Здесь кроется главная сложность: мы не должны затереть те строки, которые пользователь не видел, потому что у него не было к ним прав!

Рассмотрим алгоритм синхронизации в обработчике формы ПередЗаписьюНаСервере:


&НаСервере
Процедура СинхронизироватьТабличнуюЧастьПередЗаписью()
    
    ДоступныеСклады = ПолучитьДоступныеСкладыПользователя(ПараметрыСеанса.ТекущийПользователь);
    
    // Создаем временную таблицу для сборки итогового результата
    ИтоговаяТаблица = Объект.Товары.ВыгрузитьКолонки();
    
    // Шаг 1. Переносим из оригинального объекта те строки, к которым у пользователя НЕТ доступа
    Для Каждого СтрокаОригинала Из Объект.Товары Цикл
        Если ДоступныеСклады.Найти(СтрокаОригинала.Склад) = Неопределено Тогда
            ЗаполнитьЗначенияСвойств(ИтоговаяТаблица.Добавить(), СтрокаОригинала);
        КонецЕсли;
    КонецЦикла;
    
    // Шаг 2. Добавляем измененные пользователем строки из экранной формы
    Для Каждого СтрокаЭкранная Из ТаблицаТоварыФормы Цикл
        ЗаполнитьЗначенияСвойств(ИтоговаяТаблица.Добавить(), СтрокаЭкранная);
    КонецЦикла;
    
    // Шаг 3. Загружаем итоговый массив обратно в объект документа
    Объект.Товары.Загрузить(ИтоговаяТаблица);
    
КонецПроцедуры

Этот метод гарантирует абсолютную безопасность данных на уровне клиентского сеанса, однако требует от разработчика повышенного внимания при написании кода синхронизации, особенно при обработке удаления строк и контроле уникальности номеров строк табличной части.

4. Способ В: Ограничение изменения данных (Запрет редактирования)

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

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


&НаКлиенте
Процедура ТоварыПередНачаломИзменения(Элемент, Отказ)
    
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    Если ТекущаяСтрока <> Неопределено Тогда
        // Проверяем доступность склада в интерактивном режиме
        Если Не ПроверитьДоступностьСклада(ТекущаяСтрока.Склад) Тогда
            ПоказатьПредупреждение(, "У вас нет прав на редактирование строк по складу: " + ТекущаяСтрока.Склад);
            Отказ = Истина;
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

Аналогичную логику следует заложить в обработчик события ПриНачалеРедактирования, чтобы предотвратить изменение ячеек при добавлении новых строк.

Важное правило безопасности: Любые клиентские проверки можно обойти (например, через внешнее подключение, групповое изменение реквизитов или выполнение произвольного кода на клиенте). Поэтому интерактивный запрет обязательно должен дублироваться жесткой проверкой на сервере в модуле объекта в предопределенной процедуре ПередЗаписью:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Проверяем измененные строки
    Для Каждого СтрокаТЧ Из Товары Цикл
        // Сравниваем со старой версией документа из БД, если требуется проверить модификацию
        Если Не ПроверитьДоступностьСкладаДляПользователя(СтрокаТЧ.Склад, ПараметрыСеанса.ТекущийПользователь) Тогда
            ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
                "Обнаружены недопустимые строки по складу: " + СтрокаТЧ.Склад,
                ЭтотОбъект,
                "Товары");
            Отказ = Истина;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

5. Способ Г: Архитектурные альтернативы

Если задача разграничения прав по строкам возникает регулярно и приводит к переусложнению кода форм, имеет смысл пересмотреть саму архитектуру прикладного решения. Проанализируем два альтернативных варианта:

  1. Разделение документов по принципу «Один склад — один документ». Если бизнес-процесс позволяет, откажитесь от практики занесения в один документ позиций по разным складам. Если у каждого склада будет свой отдельный документ, стандартный механизм RLS отработает идеально и без какого-либо дополнительного программирования.
  2. Использование связанных регистров сведений. Вместо хранения разнородных данных внутри табличной части одного документа, можно при его проведении или записи транслировать эти данные в подчиненный Регистр сведений (в разрезе складов и ссылок на документ). На данный регистр сведений уже можно без проблем наложить классический RLS.

Подводя итог, можно сказать: несмотря на то, что платформа 1С не поддерживает наложение RLS на строки табличной части "из коробки", разработчик всегда имеет в своем арсенале набор гибких инструментов для реализации этой логики на уровне управляемых форм и модулей объектов.

← На главную