Почему программно не добавляются роли пользователю в 1С без перезапуска сеанса и как это исправить?

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

В практике разработчиков 1С часто возникает классическая задача: временно предоставить пользователю повышенные права для выполнения определенной операции (например, для проведения документов по регламентированному учету в конфигурации «Управление производственным предприятием 1.3»), выполнить действие, а затем оперативно отобрать эти права. Однако при попытке реализовать этот алгоритм «в лоб» через программное манипулирование коллекцией ролей пользователя информационной базы разработчики неизбежно сталкиваются со странным поведением системы: роль в коде добавляется, но проверка на доступность этой роли всё равно возвращает Ложь.

В рамках этой статьи мы подробно разберем, почему платформа «1С:Предприятие 8» ведет себя подобным образом, проанализируем внутренние механизмы кэширования прав доступа и рассмотрим корректные архитектурные способы решения этой задачи без необходимости принудительного перезапуска сеанса пользователя.

Почему динамическое изменение ролей не работает в текущем сеансе?

Для того чтобы понять суть проблемы, нам необходимо обратиться к архитектуре безопасности платформы «1С:Предприятие». Проанализируем, что происходит, когда мы пишем следующий код:


ПользовательБД = ПользователиИнформационнойБазы.ТекущийПользователь();
ПользовательБД.Роли.Добавить(Метаданные.Роли.ПолныеПрава);
ПользовательБД.Записать();

На первый взгляд, все сделано правильно. База данных пользователей обновилась, и новая роль была успешно записана. Но если мы сразу же выполним проверку Если РольДоступна("ПолныеПрава") Тогда, мы получим отрицательный результат. Более того, если в конце этого же блока кода мы попытаемся удалить эту роль обратно, программа поведет себя неадекватно, либо роль вовсе не применится.

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

Когда мы вызываем метод Записать() у объекта ПользовательИнформационнойБазы, изменения физически вносятся в таблицу пользователей на уровне СУБД. Однако текущий активный сеанс ничего не знает об этих изменениях, так как обращается к своему локальному, уже сформированному кэшу. Метод РольДоступна() работает именно с локальным кэшем сеанса. Обновление этого кэша происходит только при повторном запуске сеанса (перезапуске программы).

Если мы уберем из кода процедуру программного удаления роли, то при следующем входе пользователя в систему его локальный кэш сформируется заново, уже с учетом записанных изменений, и полные права заработают. Но это нарушает исходное требование безопасности — пользователь останется с полными правами навсегда, пока мы их принудительно не отнимем.

Почему привилегированный режим не всегда спасает?

Казалось бы, наиболее очевидное и правильное решение в среде 1С — это использование встроенного механизма платформы для обхода ограничений прав доступа, а именно метода УстановитьПривилегированныйРежим(ИСТИНА). Но разработчики УПП 1.3 часто жалуются, что даже при включении этого режима документы регл. учета отказываются проводиться под обычным пользователем. Выясним основные причины этого поведения:

  1. Программные проверки прав во встроенном коде конфигурации. Привилегированный режим разработан главным образом для отключения ограничений на уровне записей (RLS) и проверки прав доступа к таблицам базы данных непосредственно на уровне СУБД. Однако в крупных типовых конфигурациях (особенно прошлых поколений, таких как УПП 1.3 или КА 1.1) разработчики очень часто встраивали жесткие проверки доступности ролей прямо во встроенный язык. Если в коде проведения документа прописано условие вида: Если НЕ РольДоступна("ПолныеПрава") Тогда Отказ = Истина; КонецЕсли;, то даже в привилегированном режиме выполнение прервется. Метод РольДоступна() проверяет реальные роли сеанса, игнорируя привилегированный режим. Чтобы решить эту проблему, код проверки внутри конфигурации должен быть доработан с учетом функции ПривилегированныйРежим():

Если НЕ (РольДоступна("ПолныеПрава") Или ПривилегированныйРежим()) Тогда
    Отказ = Истина;
    Возврат;
КонецЕсли;
  1. Выполнение кода на стороне клиента. Привилегированный режим функционирует исключительно на стороне сервера (или в файловом варианте работы в некоторых модулях при определенных условиях). В обычных формах УПП 1.3 логика проведения документов или предварительная подготовка данных часто частично выполняется непосредственно на клиенте. Если мы попытаемся вызвать УстановитьПривилегированныйРежим(ИСТИНА) на стороне клиента, платформа просто проигнорирует эту команду. Соответственно, весь код, требующий повышенных прав, обязательно должен быть вынесен в серверный контекст — например, в общий привилегированный модуль с флагом «Вызов сервера».
  2. Файловый вариант базы данных. В файловой базе данных использование привилегированного режима имеет свои ограничения. В обычных формах на клиенте он работать не будет. Чтобы эмулировать серверный контекст в файловой базе, код должен исполняться внутри фонового задания или в модулях, выполняемых на сервере.

Альтернативные способы решения задачи (без перезапуска сеанса)

Если специфика бизнес-логики или жесткие проверки в коде типовой конфигурации не позволяют обойтись стандартным привилегированным режимом, мы можем прибегнуть к альтернативным архитектурным решениям, которые позволят провести документ с повышенными правами «на лету».

Способ А: Использование фонового задания (клиент-серверный вариант)

Фоновые задания запускаются в отдельном, изолированном сеансе на сервере 1С. Поскольку это новый сеанс, для него будет сформирован актуальный кэш ролей из базы данных. Мы можем воспользоваться этим, временно добавив пользователю права в текущем сеансе, запустив фоновое задание, а затем вернув права обратно. Схема выглядит следующим образом:

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

Рассмотрим пример реализации этого метода на встроенном языке:


// Код в общем модуле на клиенте или сервере
Процедура ПровестиДокументЧерезФоновоеЗадание(ДокументСсылка)
    
    // Шаг 1: Добавляем роль в базу данных
    УстановитьПолныеПраваПользователю(Истина);
    
    // Шаг 2: Запуск фонового задания
    ПараметрыЗапуска = Новый Массив;
    ПараметрыЗапуска.Добавить(ДокументСсылка);
    
    Задание = ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьПроведениеНаСервере", ПараметрыЗапуска);
    
    Попытка
        // Шаг 3: Ожидаем завершения
        Задание.ОжидатьЗавершенияВыполнения(60); // таймаут 60 секунд
    Исключение
        ЗаписатьВЖурналРегистрации("Проведение документа", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
    
    // Шаг 4: Обязательно возвращаем права в исходное состояние
    УстановитьПолныеПраваПользователю(Ложь);
    
КонецПроцедуры

Процедура УстановитьПолныеПраваПользователю(ДобавитьПрава) Экспорт
    // Данный код должен выполняться в привилегированном модуле на сервере
    ПользовательБД = ПользователиИнформационнойБазы.ТекущийПользователь();
    РольПолныеПрава = Метаданные.Роли.ПолныеПрава;
    
    Если ДобавитьПрава Тогда
        Если НЕ ПользовательБД.Роли.Содержит(РольПолныеПрава) Тогда
            ПользовательБД.Роли.Добавить(РольПолныеПрава);
            ПользовательБД.Записать();
        КонецЕсли;
    Иначе
        Если ПользовательБД.Роли.Содержит(РольПолныеПрава) Тогда
            ПользовательБД.Роли.Удалить(РольПолныеПрава);
            ПользовательБД.Записать();
        КонецЕсли;
    EndIf;
КонецПроцедуры

Способ Б: Использование COM-соединения или внешнего соединения

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

Алгоритм действий следующий:

  1. Как и в предыдущем методе, мы программно добавляем пользователю необходимую роль и записываем изменения.
  2. Инициализируем внешнее COM-соединение с этой же информационной базой, используя учетные данные текущего пользователя:

COMConnector = Новый COMОбъект("V83.COMConnector");
Соединение = COMConnector.Connect("File=""C:\Base1C"";Usr=""ИмяПользователя"";Pwd=""Пароль"";");
  1. При установке этого COM-соединения платформа создает новый полноценный сеанс работы с базой данных. В этот момент считываются только что записанные нами полные права.
  2. Через полученный COM-объект мы вызываем экспортную функцию общего модуля, которая выполняет проведение нашего документа:

Соединение.МойОбщийМодуль.ПровестиДокументНаСервере(ДокументСсылка.УникальныйИдентификатор());
  1. После успешного проведения мы разрываем COM-соединение (приравниваем переменную к значению Неопределено) и программно удаляем роль из карточки пользователя ИБ в текущем сеансе, зафиксировав изменения методом Записать().

Способ В: Выполнение проведения под специальной служебной учетной записью

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

Гораздо более элегантным и безопасным решением будет создание в справочнике «Пользователи» специального служебного пользователя (например, «РоботРеглУчета»), который изначально и на постоянной основе обладает полными правами или специализированным набором прав, достаточным для проведения любых документов.

Когда обычному пользователю требуется провести документ, требующий повышенных прав:

  1. Основной сеанс пользователя инициирует запуск фонового задания (или COM-соединения).
  2. Но запуск этого задания происходит не под учетными данными текущего пользователя, а жестко под учетной записью нашего служебного робота.
  3. Служебный сеанс благополучно выполняет проведение документа и закрывается.
  4. При этом права реального пользователя никак не затрагиваются, не изменяются в базе данных и не кэшируются. Безопасность системы остается на максимальном уровне.

Рассмотрим пример создания фонового задания от имени другого пользователя. Для этого используется менеджер фоновых заданий:


// Запуск фонового задания от имени конкретного пользователя ИБ
ПользовательРобот = ПользователиИнформационнойБазы.НайтиПоИмени("РоботРеглУчета");

ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ДокументСсылка);

// Выполняем под указанным пользователем
Задание = ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьПроведениеНаСервере", ПараметрыЗапуска, , ПользовательРобот);
Задание.ОжидатьЗавершенияВыполнения();

Подведем итоги

Мы детально разобрали особенности работы с ролями в «1С:Предприятии 8». При проектировании подобных механизмов всегда помните о правиле кэширования прав: любые изменения ролей пользователя вступят в силу только в его следующем сеансе.

Для реализации отложенного или временного повышения прав используйте наиболее подходящий под вашу инфраструктуру метод:

← На главную