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