Почему при проведении документов в 1С возникает ошибка прав доступа на корректных запросах и как решить проблему «сломанной» транзакции?

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

Настройка прав доступа и механизмов ограничения на уровне записей (RLS) в платформе «1С:Предприятие 8» — одна из самых нетривиальных задач для разработчика. Часто возникают ситуации, когда система ведёт себя непредсказуемо: выдает ошибки нарушения прав на тех участках кода, к которым у пользователя гарантированно есть полный доступ. Особенно ярко это проявляется при проведении документов в транзакции.

В этой статье мы подробно разберем классический сценарий такого поведения на примере конфигурации «Управление торговлей 10.3» (работающей в режиме обычного приложения), выясним истинные причины «призрачных» ошибок СУБД и рассмотрим надежные способы их устранения.

Анализ проблемы: почему сбоит «правильный» код?

Рассмотрим ситуацию. При проведении документа «Реализация товаров и услуг» всё проходит штатно. Однако следующее за ним проведение связанного документа «Счет-фактура выданный» аварийно завершается с ошибкой:

Ошибка при вызове метода контекста (Выполнить): Ошибка выполнения запроса: У пользователя недостаточно прав на исполнение операции над базой данных.

При этом отладчик указывает на абсолютно безобидный и корректный запрос к регистру сведений Переменные:


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

Разработчик проверяет права роли текущего пользователя и видит, что на регистр Переменные предоставлены полные права (на чтение, изменение и просмотр). Тип данных измерения — простая строка, а значение ресурса — ссылка на справочник Организации, к которому также есть полный доступ. Более того, при использовании альтернативного кода через выборку набора записей ошибка полностью повторяется:


Набор = РегистрыСведений.Переменные.СоздатьНаборЗаписей();
Набор.Отбор.НаименованиеПеременной.Установить(ИмяПеременной);
Набор.Прочитать();
Если Набор.Количество() > 0 Тогда
    Возврат Набор[0].ЗначениеПеременной;
КонецЕсли;

Почему же система ругается на недостаток прав доступа там, где они точно есть?

Причина №1: Эффект «сломанной» транзакции (Doomed Transaction)

Главный секрет кроется в поведении СУБД внутри транзакции. Проведение любого документа в 1С всегда выполняется строго в транзакции базы данных. Если в процессе проведения происходит критическая ошибка работы с базой (включая нарушение прав RLS при чтении или записи), СУБД переводит текущую транзакцию в особое состояние — «транзакция помечена для отмены» (doomed transaction).

Проанализируем цепочку событий, которая приводит к ошибке:

  1. В процессе проведения «Счета-фактуры» выполняется скрытый от глаз разработчика запрос (назовем его Запросом №3) к какому-то другому регистру или справочнику.
  2. У текущего пользователя действительно отсутствуют права на чтение некоторых записей, попадающих в этот скрытый Запрос №3.
  3. Этот запрос разработчики типовой конфигурации обернули в конструкцию Попытка ... Исключение. С точки зрения кода 1С, ошибка «заглушается» в блоке Исключение, и программа продолжает работу дальше.
  4. Но для СУБД это не проходит бесследно! Как только СУБД зафиксировала попытку несанкционированного доступа внутри транзакции, она заблокировала любые дальнейшие операции чтения и записи в рамках этой транзакции.
  5. Когда выполнение доходит до нашего «правильного» запроса к регистру Переменные, СУБД мгновенно отвергает его, так как транзакция уже «сломана».
  6. Платформа 1С прерывает работу программы и выводит стек ошибки, указывая именно на ту строчку кода, на которой споткнулась уже заблокированная транзакция (то есть на наш запрос), хотя реальный «виновник» находится выше по коду и скрыт в блоке Попытка.

Причина №2: Не сработал Привилегированный режим

Попытка разработчика временно обойти ограничение прав с помощью вызова УстановитьПривилегированныйРежим(ИСТИНА) не дает результатов по двум причинам:

---

Варианты решения проблемы

Давайте подробно разберем, как исправить эту ситуацию и как правильно диагностировать аналогичные проблемы в будущем.

Решение 1. Использование ключевого слова РАЗРЕШЕННЫЕ

Самый быстрый и эффективный способ устранить ошибку «сломанной» транзакции, если она вызвана ограничениями RLS, — добавить ключевое слово РАЗРЕШЕННЫЕ в запросы, которые могут обращаться к закрытым для пользователя данным.

При использовании РАЗРЕШЕННЫЕ платформа 1С не генерирует исключение СУБД при обнаружении недоступных записей. Она просто молча отсекает их из результирующей выборки. Транзакция не помечается как аварийная, и последующий код выполняется без ошибок.

Изменим проблемный запрос, добавив ключевое слово:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
               |	Переменные.ЗначениеПеременной
               |ИЗ
               |	РегистрСведений.Переменные КАК Переменные
               |ГДЕ
               |	Переменные.НаименованиеПеременной = &ИмяПеременной";
Запрос.УстановитьПараметр("ИмяПеременной", ИмяПеременной);
Выборка = Запрос.Выполнить().Выбрать();

Важное примечание: Применяйте РАЗРЕШЕННЫЕ осознанно. Если вам жизненно необходимы данные, к которым у пользователя нет прав (например, константы настройки учета или параметры проведения), маскирование проблемы с помощью этого ключевого слова может привести к получению пустых значений и некорректному расчету себестоимости или налогов. В таких случаях нужно использовать привилегированные модули.

Решение 2. Правильный перенос кода на сервер (Привилегированный режим)

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

Для этого выполним следующие шаги:

  1. Создадим (или найдем существующий) общий модуль, например, ПолныеПраваВызовСервера.
  2. В свойствах модуля обязательно взведем флаги «Сервер» и «Привилегированный». Флаг «Вызов сервера» также должен быть активен, если мы будем обращаться к нему из клиентского кода.
  3. Вынесем логику чтения настроек в экспортную функцию этого модуля:

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

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

---

Методика диагностики «призрачных» ошибок доступа

Чтобы оперативно находить истинного виновника падения транзакции, когда платформа указывает на ложное место в коде, используем три надежных инструмента диагностики.

Метод 1. Анализ Журнала регистрации под Администратором

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

  1. Запустите информационную базу под пользователем с полными правами.
  2. Откройте Журнал регистрации.
  3. Установите фильтр по времени (интервал в несколько секунд вокруг момента возникновения ошибки).
  4. В поле «Событие» выберите «Доступ -> Отказ в доступе».
  5. В деталях найденного события вы увидите точный метаобъект (конкретный регистр сведений, накопления или справочник), к которому действительно пытался обратиться «невидимый» запрос. Достаточно будет добавить права на этот объект в роль пользователя, и проблема решится.

Метод 2. Настройка остановки по ошибке в Конфигураторе

Чтобы поймать ошибку в момент ее зарождения (до того, как управление уйдет в блок Исключение и транзакция заблокируется):

  1. В Конфигураторе откройте меню Отладка -> Настройка генерации исключительных ситуаций (в некоторых версиях — Остановка по ошибке).
  2. Установите флажок «Останавливаться по ошибке».
  3. Критически важно: обязательно установите флаг «Искать обрабатываемые ошибки» (или «Останавливаться на обрабатываемых ошибках»).
  4. Запустите проведение проблемного документа. Отладчик прервет выполнение ровно на том запросе, который вызывает сбой RLS, позволяя вам сразу увидеть проблемное место в коде.

Метод 3. Использование Технологического журнала (ТЖ)

Для расследования сложных случаев на продуктивных базах можно настроить сбор логов технологического журнала. Создайте в каталоге настроек платформы (обычно C:\Program Files\1cv8\conf) файл logcfg.xml следующего содержания:


<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="C:\v8logs\DBErrors" history="24">
        <event>
            <eq property="Name" value="QERR"/>
        </event>
        <property name="all"/>
    </log>
</config>

Событие QERR собирает все ошибки выполнения запросов к СУБД. Проанализировав полученный лог-файл, вы увидите точный текст SQL/SDBL-запроса, выполнение которого привело к отказу в правах доступа, а также контекст вызова всей цепочки процедур и функций.

← На главную