В процессе разработки и поддержки конфигураций на платформе 1С:Предприятие разработчики часто сталкиваются с ситуацией, когда под учетной записью с полными правами все работает безупречно, но у рядового пользователя при выполнении тех же действий возникает критическая ошибка: «У пользователя недостаточно прав на исполнение операции над базой данных». Особое коварство этой ошибки заключается в том, что она может возникать не на этапе открытия формы или записи самого объекта, а в процессе интерактивного взаимодействия с элементами формы — например, при изменении строки в дереве значений или табличном поле.
В этой статье мы подробно разберем, почему возникает подобная ошибка, детально проанализируем механизм работы платформы при обращении к реквизитам «через точку», рассмотрим эффективные методы диагностики проблемы и изучим три правильных, безопасных способа её решения, которые соответствуют стандартам разработки фирмы 1С.
Давайте проанализируем конкретную ситуацию. В коде обработчика формы присутствует следующая строка:
ЭлементыФормы.Дерево1.ТекущаяСтрока.ДатаИa = ТекущаяСтрока.ЗаказНаПроизводство.ДатаИa;
На первый взгляд, это обычное присвоение значения одного поля другому. Однако здесь происходит так называемое разыменование ссылки. В правой части выражения мы обращаемся к реквизиту ДатаИa, который принадлежит объекту ЗаказНаПроизводство. Переменная ТекущаяСтрока.ЗаказНаПроизводство хранит лишь ссылку на этот документ (его уникальный идентификатор GUID), но не сам объект и не его реквизиты.
Когда встроенный язык платформы 1С встречает конструкцию обращения «через точку» от ссылки (Ссылка.ИмяРеквизита), он выполняет неявный и автоматический запрос к СУБД для извлечения значения этого реквизита из базы данных.
Здесь в силу вступает механизм RLS (Record Level Security) — ограничение доступа на уровне записей. Если в системе настроена роль, ограничивающая доступ к документам ЗаказНаПроизводство по определенным условиям (например, по организации, подразделению или складу), платформа проверяет, удовлетворяет ли конкретный документ, на который указывает ссылка, условиям RLS для текущего пользователя.
Если документ не удовлетворяет условиям RLS, СУБД полностью блокирует чтение этой записи для данного пользователя. Так как обращение происходит напрямую через встроенный язык, платформа не может просто вернуть пустое значение или Неопределено. Она обязана прервать выполнение операции и инициировать исключение. В результате пользователь видит системное сообщение об ошибке базы данных, а работа алгоритма аварийно завершается.
Прежде чем исправлять код, нам необходимо точно выяснить, на какой именно объект базы данных у пользователя не хватает прав. Для этого применим несколько последовательных шагов диагностики.
Журнал регистрации — это первый инструмент, к которому должен обратиться разработчик. При возникновении ошибок доступа платформа фиксирует событие в ЖР.
Документ.ЗаказНаПроизводство) и вид права, которого не хватило (обычно это право Чтение).Важное примечание: Если журнал регистрации в базе данных временно отключен в целях оптимизации производительности, настоятельно рекомендуется включить его хотя бы на минимальный уровень логирования (фиксировать только ошибки) на время проведения теста, воспроизвести ошибку и затем снова настроить систему под рабочие нужды.
После того как мы выяснили, какой объект вызывает ошибку (в нашем примере это Документ.ЗаказНаПроизводство), необходимо проверить права этого пользователя в Конфигураторе:
Чтение, Просмотр и Использование.В сложных случаях, когда Журнал регистрации не дает подробной информации, мы можем прибегнуть к настройке Технологического журнала сервера 1С. Для отслеживания ошибок компиляции запросов и наложения RLS нам понадобится событие QERR (ошибки выполнения запросов) и событие EXCP (исключительные ситуации).
Пример простейшего файла настройки logcfg.xml для сбора таких событий:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\v8_tech_log" history="2">
<event>
<eq property="Name" value="EXCP"/>
</event>
<event>
<eq property="Name" value="QERR"/>
</event>
<property name="All"/>
</log>
</config>
Анализ полученных логов ТЖ позволит увидеть точный SQL-запрос, который отправляла платформа в СУБД, и понять, на каком именно условии RLS споткнулась система.
В процессе обсуждения проблемы часто выдвигаются простые, но крайне нежелательные варианты решения. Рассмотрим, почему они являются антипаттернами.
ЗначениеЗаполнено()Некоторые разработчики предлагают добавить перед обращением к реквизиту следующую проверку:
Если ЗначениеЗаполнено(ТекущаяСтрока.ЗаказНаПроизводство) Тогда
ТекущаяСтрока.ДатаИa = ТекущаяСтрока.ЗаказНаПроизводство.ДатаИa;
КонецЕсли;
Эта проверка абсолютно бесполезна в контексте борьбы с RLS. Функция ЗначениеЗаполнено() проверяет лишь то, не является ли ссылка пустой (то есть не равна ли она пустому GUID вида 00000000-0000-0000-0000-000000000000). Если в поле содержится реальная ссылка на существующий в базе документ, к которому у текущего пользователя просто нет прав, функция вернет Истина. Код попытается обратиться «через точку» к реквизиту ДатаИa, и система немедленно упадет в ошибку.
Попытка ... ИсключениеВторой популярный, но неверный подход — подавление ошибки:
Попытка
ТекущаяСтрока.ДатаИa = ТекущаяСтрока.ЗаказНаПроизводство.ДатаИa;
Исключение
// Игнорируем ошибку или пишем лог
КонецПопытки;
Использование этого блока для обхода бизнес-логики прав доступа крайне не рекомендуется по следующим причинам:
Попытка может скрыть другие важные проблемы, например, банальную опечатку в имени реквизита или физическое повреждение базы данных.EXCP в ТЖ, что усложнит анализ реальных сбоев в системе администраторами.Теперь рассмотрим профессиональные и безопасные способы получения данных в условиях действующих ограничений RLS.
РАЗРЕШЕННЫЕЭто самый надежный способ, гарантирующий стабильную работу системы без генерации исключений. Вместо прямого обращения к реквизиту «через точку» мы напишем небольшой запрос, в котором явно укажем платформе, что хотим получить только те данные, к которым у пользователя есть доступ.
Рассмотрим пример реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Заказ.ДатаИа КАК ДатаИа
|ИЗ
| Документ.ЗаказНаПроизводство КАК Заказ
|ГДЕ
| Заказ.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ТекущаяСтрока.ЗаказНаПроизводство);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
ТекущаяСтрока.ДатаИa = Выборка.ДатаИа;
Иначе
// Если у пользователя нет прав по RLS, запрос вернет пустой результат,
// и мы можем корректно обработать эту ситуацию без падения системы.
ТекущаяСтрока.ДатаИa = Дата('00010101');
КонецЕсли;
Почему это работает: Ключевое слово РАЗРЕШЕННЫЕ в тексте запроса заставляет платформу автоматически отфильтровать записи, недоступные текущему пользователю согласно его правам RLS. Если права на документ отсутствуют, запрос просто не вернет ни одной строки, не вызывая при этом никаких системных ошибок СУБД.
Если ваша конфигурация разработана на базе современных типовых решений фирмы 1С, она гарантированно содержит в своем составе Библиотеку Стандартных Подсистем (БСП). В составе БСП есть замечательный общий модуль ОбщегоНазначения, который содержит функции для безопасного чтения реквизитов объектов.
Для нашей задачи идеально подходит функция ЗначениеРеквизитаОбъекта. Нам нужно вызвать ее, обязательно передав признак безопасного чтения в условиях RLS. Посмотрим на код:
// Третий параметр "ВыбратьРазрешенные" передаем как Истина
ПараметрыЧтения = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
ТекущаяСтрока.ЗаказНаПроизводство,
"ДатаИа",
Истина
);
Если ЗначениеЗаполнено(ПараметрыЧтения) Тогда
ТекущаяСтрока.ДатаИa = ПараметрыЧтения;
Иначе
ТекущаяСтрока.ДатаИa = Дата('00010101');
КонецЕсли;
Преимущества данного метода:
РАЗРЕШЕННЫЕ.Иногда бизнес-логика программы требует, чтобы определенная служебная информация (например, техническая дата согласования заказа) отображалась в отчете или обработке даже в том случае, если сам документ-источник скрыт от пользователя настройками RLS. То есть, пользователю запрещено видеть сам документ целиком, но конкретное поле в таблице заполнить необходимо.
В этом случае нам на помощь приходит привилегированный режим, в котором проверки RLS полностью отключаются платформой.
Для реализации этого способа нам потребуется вынести логику получения данных на сервер. Выполним следующие шаги:
упПривилегированныйВызов.
// Функция выполняется на сервере без учета ограничений прав RLS текущего пользователя
Функция ПолучитьДатуЗаказаПривилегированно(СсылкаНаЗаказ) Экспорт
// Платформа прочитает значение напрямую из СУБД без ошибок
Возврат СсылкаНаЗаказ.ДатаИа;
КонецФункции
ТекущаяСтрока.ДатаИa = упПривилегированныйВызов.ПолучитьДатуЗаказаПривилегированно(ТекущаяСтрока.ЗаказНаПроизводство);
Важное предупреждение: Использовать привилегированный режим следует с большой осторожностью. Передавайте в привилегированные модули только те операции, которые действительно необходимы для функционирования бизнес-процесса, чтобы не нарушить общую концепцию безопасности и разграничения прав доступа в компании.
Для того чтобы в будущем минимизировать возникновение подобных ошибок, рекомендуется придерживаться хорошего тона при проектировании ролей и ограничений доступа в конфигураторе 1С.
При настройке RLS для ролей на объект метаданных (например, справочник или документ) разделяйте ограничения на две группы:
Мы проанализировали причины возникновения ошибок доступа к данным, научились их локализовать и рассмотрели надежные паттерны программирования в среде 1С:Предприятие. Применяйте эти знания на практике, делайте ваш код отказоустойчивым, и пусть ваши пользователи никогда не увидят сообщений о нехватке прав!