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

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

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

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

Понимание ограничений RLS для интерактивных прав

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

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

  1. Не увидит документы со "склада А" в списках документов.
  2. Не сможет открыть эти документы, даже если получит на них прямую ссылку.
  3. Не увидит данные из этих документов в любых отчетах, которые формируются от имени этого пользователя.

Последний пункт является критическим. RLS работает на уровне запросов к базе данных. Если запрос выполняется от имени пользователя, для которого установлен RLS, система автоматически добавляет к этому запросу условие ограничения. Это условие фильтрует данные еще до того, как они попадут в отчет или список. Таким образом, если запись не доступна для чтения по RLS, ее просто не существует для данного пользователя в контексте любых операций, включая формирование отчетов. Отчеты будут формироваться на основе неполных данных, что недопустимо, если требуется видеть все данные, но ограничить только их интерактивное изменение.

Основное решение: Программное ограничение доступа к формам

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

1. Использование подписок на события

Одним из наиболее эффективных и централизованных способов реализации интерактивных ограничений являются подписки на события. Мы можем создать подписку на событие, которое возникает при попытке получения или открытия формы объекта. Наиболее подходящей для этой цели является подписка на событие ОбработкаПолученияФормы.

Как это работает:

  1. Мы создаем новую подписку на событие с именем, например, "ОграничитьОткрытиеДокументовПоСкладу".
  2. В качестве Источник указываем ДокументМенеджер (или СправочникМенеджер), а также, при необходимости, конкретные типы документов или справочников. Это позволит подписке срабатывать для всех документов (или конкретных).
  3. В качестве Обработчик указываем процедуру в общем модуле, которая будет содержать нашу логику проверки прав.

Пример процедуры обработчика (в общем модуле):


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

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

В этом примере мы проверяем, имеет ли текущий пользователь право открывать документ, основываясь на его складе. Если права нет, мы устанавливаем Отказ = Истина и СтандартнаяОбработка = Ложь, что предотвращает открытие формы и выводим сообщение пользователю. Важно: для получения реквизита "Склад" из ссылки, если на этот реквизит установлен RLS, нам может понадобиться выполнять запрос или обращение к свойству ссылки в привилегированном режиме, чтобы избежать ошибки "Недостаточно прав для чтения поля".

2. Код в модулях форм

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

Мы можем использовать события форм, такие как ПриСозданииНаСервере или ПередОткрытием. В этих процедурах мы проверяем права пользователя и, при необходимости, выполняем одно из следующих действий:

  1. Закрываем форму: Если пользователю категорически запрещено открывать данный объект, мы можем вызвать метод ЭтаФорма.Закрыть().
  2. Делаем элементы недоступными: Если форма должна открыться, но пользователь не должен иметь возможность редактировать определенные поля, мы можем установить свойство Доступность или ТолькоПросмотр для соответствующих элементов формы.

Пример кода в модуле формы документа (процедура ПриСозданииНаСервере):


// В модуле формы документа "РеализацияТоваровУслуг"
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Получаем текущего пользователя и склад документа
    ТекущийПользователь = Пользователи.ТекущийПользователь();
    СкладДокумента = Объект.Склад; // Предполагается, что Объект уже инициализирован

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

    // Если Отказ не установлен, форма открывается.
    // Дополнительно можно сделать поля только для просмотра:
    // Если Не УсловиеДляРедактирования Тогда
    //     Элементы.ГруппаРеквизитов.ТолькоПросмотр = Истина;
    // КонецЕсли;
КонецПроцедуры

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

3. Централизация логики в общих модулях

Независимо от того, используем ли мы подписки на события или код в модулях форм, мы рекомендуем выносить основную логику проверки прав в общие модули. Это позволяет избежать дублирования кода, упрощает его изменение и тестирование. Например, можно создать общий модуль "ПраваДоступаСервер", который будет содержать функции типа ИмеетПравоОткрыватьДокумент(СсылкаНаДокумент, Пользователь).

Частичные и альтернативные подходы

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

1. RLS на уровне полей (с ограничениями)

В некоторых случаях можно настроить RLS таким образом, чтобы ссылка на документ (поле Ссылка) была видна в отчетах или списках, но при попытке открыть сам документ или получить доступ к его другим реквизитам, система выдавала ошибку "Недостаточно прав".

Как это достигается:

  1. В шаблоне ограничения RLS для таблицы документа мы разрешаем чтение поля Ссылка без условий.
  2. Для всех остальных реквизитов документа (например, Склад, СуммаДокумента, Контрагент и т.д.) мы применяем условие RLS, которое ограничивает их доступность для пользователя.

Результат:

2. Динамические списки с программными отборами

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

Как это реализуется:

  1. В модуле формы списка мы программно устанавливаем отбор для ДинамическийСписок.
  2. Мы можем добавить условие отбора, например, по складу, который разрешен пользователю.

// В модуле формы списка документа
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // ...
    // Получаем список складов, к которым у пользователя есть доступ
    // Это может быть функция из общего модуля, возвращающая массив или список значений складов
    ДоступныеСклады = УправлениеДоступомСервер.ПолучитьДоступныеСкладыДляПользователя(Пользователи.ТекущийПользователь());

    Если ДоступныеСклады.Количество() > 0 Тогда
        Элементы.Список.Отбор.Элементы.Очистить();
        НовыйЭлементОтбора = Элементы.Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        НовыйЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Склад");
        НовыйЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВГруппе;
        НовыйЭлементОтбора.ПравоеЗначение = ДоступныеСклады;
        НовыйЭлементОтбора.Использование = Истина;
    Иначе
        // Если доступных складов нет, можно отобразить пустой список или выдать сообщение
        Элементы.Список.Отбор.Элементы.Очистить();
        НовыйЭлементОтбора = Элементы.Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        НовыйЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
        НовыйЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.НеРавно;
        НовыйЭлементОтбора.ПравоеЗначение = Неопределено; // Отбор, который всегда дает пустой список
        НовыйЭлементОтбора.Использование = Истина;
    КонецЕсли;
    // ...
КонецПроцедуры

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

3. Привилегированный режим для отчетов

Если данные должны быть видны в отчетах, но не доступны интерактивно, мы можем формировать отчеты в привилегированном режиме. Это означает, что при выполнении запросов отчета система временно игнорирует все ограничения RLS, предоставляя полный доступ ко всем данным.

Как это использовать:

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

Для формирования отчетов в привилегированном режиме вы должны либо вызвать процедуру формирования отчета в ВыполнитьВПривилегированномРежиме(), либо убедиться, что код отчета выполняется в контексте общего модуля, у которого свойство Привилегированный установлено в Истина.

Ограничение доступа к печатным формам

Вопрос о печатных формах является логичным продолжением проблемы интерактивных прав. Если пользователь не может открыть документ, то по логике вещей он не должен иметь возможности распечатать его карточку или другие печатные формы, связанные с ним.

Мы можем рассмотреть несколько уровней контроля:

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

Для реализации программного управления печатью, мы можем использовать аналогичную логику, что и для открытия форм, но применительно к обработчику команды печати:


// Пример: В модуле формы документа, в обработчике команды печати
&НаКлиенте
Процедура Печать(Команда)
    Если Не ПроверитьПраваНаПечатьНаСервере() Тогда
        Возврат;
    КонецЕсли;
    // Продолжаем обычную логику печати
КонецПроцедуры

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

Влияние RLS на производительность

Хотя мы пришли к выводу, что RLS не является панацеей для интерактивных ограничений, его использование для базового ограничения чтения/записи остается актуальным. Однако важно понимать, что RLS может существенно влиять на производительность системы.

Основные причины снижения производительности RLS:

  1. Добавление условий к каждому запросу: При активном RLS к каждому запросу, выполняемому от имени пользователя с ограниченными правами, система автоматически добавляет условия, сформированные на основе шаблонов RLS. Это увеличивает сложность запросов и время их выполнения.
  2. Множественные роли: Если одному пользователю назначено несколько ролей, каждая из которых имеет свои ограничения RLS для одной и той же таблицы, то система вынуждена формировать сложное логическое условие ИЛИ для этих ограничений. Для каждой роли проверяется доступ к данным, что значительно увеличивает нагрузку.
  3. Сложные условия в шаблонах RLS: Использование в шаблонах RLS вложенных запросов, функций или обращение к ресурсоемким таблицам (особенно регистрам сведений с большим количеством записей) может критически замедлить выполнение запросов.
  4. Работа с большими объемами данных: Чем больше данных в таблице, тем сильнее будут ощущаться задержки от выполнения сложных условий RLS.

Способы оптимизации производительности RLS:

  1. Минимизация вложенности запросов: Старайтесь использовать максимально простые условия в шаблонах RLS, избегая глубокой вложенности запросов.
  2. Индексирование: Убедитесь, что поля, по которым устанавливаются ограничения в RLS, проиндексированы в базе данных. Это ускорит поиск.
  3. Производительный режим RLS (в БСП): В Библиотеке стандартных подсистем (БСП) реализован производительный режим RLS. Он работает за счет предварительного расчета и хранения "ключей доступа" в специальных регистрах сведений. При формировании запросов система использует эти готовые ключи, что значительно ускоряет выполнение запросов на чтение. Однако стоит учесть, что запись данных при таком режиме может занимать больше времени, а изменения в правах доступа могут применяться с небольшой задержкой (пока не произойдет пересчет ключей).
  4. Оптимизация структуры прав: По возможности, старайтесь проектировать роли таким образом, чтобы пользователю предоставлялся доступ к таблице через одну роль, а не через множество ролей с разными ограничениями.
  5. Анализ и профилирование: Регулярно используйте инструменты профилирования (например, "Замер производительности" в 1С или средства СУБД) для выявления медленных запросов, связанных с RLS, и их дальнейшей оптимизации.

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

← На главную