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