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