В практике разработчиков 1С часто возникает классическая задача: временно предоставить пользователю повышенные права для выполнения определенной операции (например, для проведения документов по регламентированному учету в конфигурации «Управление производственным предприятием 1.3»), выполнить действие, а затем оперативно отобрать эти права. Однако при попытке реализовать этот алгоритм «в лоб» через программное манипулирование коллекцией ролей пользователя информационной базы разработчики неизбежно сталкиваются со странным поведением системы: роль в коде добавляется, но проверка на доступность этой роли всё равно возвращает Ложь.
В рамках этой статьи мы подробно разберем, почему платформа «1С:Предприятие 8» ведет себя подобным образом, проанализируем внутренние механизмы кэширования прав доступа и рассмотрим корректные архитектурные способы решения этой задачи без необходимости принудительного перезапуска сеанса пользователя.
Для того чтобы понять суть проблемы, нам необходимо обратиться к архитектуре безопасности платформы «1С:Предприятие». Проанализируем, что происходит, когда мы пишем следующий код:
ПользовательБД = ПользователиИнформационнойБазы.ТекущийПользователь();
ПользовательБД.Роли.Добавить(Метаданные.Роли.ПолныеПрава);
ПользовательБД.Записать();
На первый взгляд, все сделано правильно. База данных пользователей обновилась, и новая роль была успешно записана. Но если мы сразу же выполним проверку Если РольДоступна("ПолныеПрава") Тогда, мы получим отрицательный результат. Более того, если в конце этого же блока кода мы попытаемся удалить эту роль обратно, программа поведет себя неадекватно, либо роль вовсе не применится.
Причина кроется в кэшировании прав доступа при инициализации сеанса. Платформа «1С:Предприятие» считывает и фиксирует состав ролей текущего пользователя ровно один раз — в момент авторизации и запуска клиентского приложения (то есть при входе в базу). Этот сформированный набор прав сохраняется в локальном кэше сеанса на протяжении всего времени его работы.
Когда мы вызываем метод Записать() у объекта ПользовательИнформационнойБазы, изменения физически вносятся в таблицу пользователей на уровне СУБД. Однако текущий активный сеанс ничего не знает об этих изменениях, так как обращается к своему локальному, уже сформированному кэшу. Метод РольДоступна() работает именно с локальным кэшем сеанса. Обновление этого кэша происходит только при повторном запуске сеанса (перезапуске программы).
Если мы уберем из кода процедуру программного удаления роли, то при следующем входе пользователя в систему его локальный кэш сформируется заново, уже с учетом записанных изменений, и полные права заработают. Но это нарушает исходное требование безопасности — пользователь останется с полными правами навсегда, пока мы их принудительно не отнимем.
Казалось бы, наиболее очевидное и правильное решение в среде 1С — это использование встроенного механизма платформы для обхода ограничений прав доступа, а именно метода УстановитьПривилегированныйРежим(ИСТИНА). Но разработчики УПП 1.3 часто жалуются, что даже при включении этого режима документы регл. учета отказываются проводиться под обычным пользователем. Выясним основные причины этого поведения:
Если НЕ РольДоступна("ПолныеПрава") Тогда Отказ = Истина; КонецЕсли;, то даже в привилегированном режиме выполнение прервется. Метод РольДоступна() проверяет реальные роли сеанса, игнорируя привилегированный режим. Чтобы решить эту проблему, код проверки внутри конфигурации должен быть доработан с учетом функции ПривилегированныйРежим():
Если НЕ (РольДоступна("ПолныеПрава") Или ПривилегированныйРежим()) Тогда
Отказ = Истина;
Возврат;
КонецЕсли;
УстановитьПривилегированныйРежим(ИСТИНА) на стороне клиента, платформа просто проигнорирует эту команду. Соответственно, весь код, требующий повышенных прав, обязательно должен быть вынесен в серверный контекст — например, в общий привилегированный модуль с флагом «Вызов сервера».Если специфика бизнес-логики или жесткие проверки в коде типовой конфигурации не позволяют обойтись стандартным привилегированным режимом, мы можем прибегнуть к альтернативным архитектурным решениям, которые позволят провести документ с повышенными правами «на лету».
Фоновые задания запускаются в отдельном, изолированном сеансе на сервере 1С. Поскольку это новый сеанс, для него будет сформирован актуальный кэш ролей из базы данных. Мы можем воспользоваться этим, временно добавив пользователю права в текущем сеансе, запустив фоновое задание, а затем вернув права обратно. Схема выглядит следующим образом:
ПолныеПрава и вызываем ПользовательБД.Записать(). Наш текущий сеанс права еще не получил, но в базе данных изменения зафиксированы.ОжидатьЗавершенияВыполнения().ПолныеПрава у пользователя и снова записываем его в базу данных. Таким образом, права повышенной безопасности использовались ровно столько секунд, сколько выполнялось проведение документа.Рассмотрим пример реализации этого метода на встроенном языке:
// Код в общем модуле на клиенте или сервере
Процедура ПровестиДокументЧерезФоновоеЗадание(ДокументСсылка)
// Шаг 1: Добавляем роль в базу данных
УстановитьПолныеПраваПользователю(Истина);
// Шаг 2: Запуск фонового задания
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ДокументСсылка);
Задание = ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьПроведениеНаСервере", ПараметрыЗапуска);
Попытка
// Шаг 3: Ожидаем завершения
Задание.ОжидатьЗавершенияВыполнения(60); // таймаут 60 секунд
Исключение
ЗаписатьВЖурналРегистрации("Проведение документа", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
// Шаг 4: Обязательно возвращаем права в исходное состояние
УстановитьПолныеПраваПользователю(Ложь);
КонецПроцедуры
Процедура УстановитьПолныеПраваПользователю(ДобавитьПрава) Экспорт
// Данный код должен выполняться в привилегированном модуле на сервере
ПользовательБД = ПользователиИнформационнойБазы.ТекущийПользователь();
РольПолныеПрава = Метаданные.Роли.ПолныеПрава;
Если ДобавитьПрава Тогда
Если НЕ ПользовательБД.Роли.Содержит(РольПолныеПрава) Тогда
ПользовательБД.Роли.Добавить(РольПолныеПрава);
ПользовательБД.Записать();
КонецЕсли;
Иначе
Если ПользовательБД.Роли.Содержит(РольПолныеПрава) Тогда
ПользовательБД.Роли.Удалить(РольПолныеПрава);
ПользовательБД.Записать();
КонецЕсли;
EndIf;
КонецПроцедуры
Данный метод концептуально похож на использование фоновых заданий, но он является универсальным и стабильно работает в том числе в файловых версиях баз данных, где функционал фоновых заданий может быть ограничен или неудобен в реализации.
Алгоритм действий следующий:
COMConnector = Новый COMОбъект("V83.COMConnector");
Соединение = COMConnector.Connect("File=""C:\Base1C"";Usr=""ИмяПользователя"";Pwd=""Пароль"";");
Соединение.МойОбщийМодуль.ПровестиДокументНаСервере(ДокументСсылка.УникальныйИдентификатор());
Неопределено) и программно удаляем роль из карточки пользователя ИБ в текущем сеансе, зафиксировав изменения методом Записать().Постоянная перезапись прав реальных пользователей в базе данных (даже на несколько секунд) — не самое лучшее архитектурное решение. Это создает избыточную нагрузку на СУБД, может приводить к взаимным блокировкам таблиц конфигурации и засоряет журнал регистрации бесконечными записями об изменении прав.
Гораздо более элегантным и безопасным решением будет создание в справочнике «Пользователи» специального служебного пользователя (например, «РоботРеглУчета»), который изначально и на постоянной основе обладает полными правами или специализированным набором прав, достаточным для проведения любых документов.
Когда обычному пользователю требуется провести документ, требующий повышенных прав:
Рассмотрим пример создания фонового задания от имени другого пользователя. Для этого используется менеджер фоновых заданий:
// Запуск фонового задания от имени конкретного пользователя ИБ
ПользовательРобот = ПользователиИнформационнойБазы.НайтиПоИмени("РоботРеглУчета");
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ДокументСсылка);
// Выполняем под указанным пользователем
Задание = ФоновыеЗадания.Выполнить("МойОбщийМодуль.ВыполнитьПроведениеНаСервере", ПараметрыЗапуска, , ПользовательРобот);
Задание.ОжидатьЗавершенияВыполнения();
Мы детально разобрали особенности работы с ролями в «1С:Предприятии 8». При проектировании подобных механизмов всегда помните о правиле кэширования прав: любые изменения ролей пользователя вступят в силу только в его следующем сеансе.
Для реализации отложенного или временного повышения прав используйте наиболее подходящий под вашу инфраструктуру метод:
УстановитьПривилегированныйРежим(ИСТИНА) и функции ПривилегированныйРежим().