четверг, 1 июня 2017 г.

Ограничение доступа к справочникам


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

 
А также, если пользователь догадается как включить режим "Все функции", то он сможет изменять константы, запускать любые обработки, бизнес-процессы (ирония в том, что если он сможет добраться через "Все функции", например, до константы "Ограничивать доступ на уровне записей", то ничто не помешает ему ее отключить…).
Это повышает требования к пользователям, но зачастую они меняют элементы сами того не подозревая, без злого умысла.
Таким образом, главный вопрос этой части статьи в том - как запретить доступ на редактирование к тем объектам, к которым доступ разрешен в типовой роли "Пользователь", не трогая саму роль, и, не создавая новых ролей, иметь возможность оперативно разрешать/запрещать доступ определенным пользователям (опять же исходя из цели, обозначенной в части 1, т.е. с минимальными изменениями в типовой конфигурации).


Здесь я опишу два способа (хотя их конечно может быть и больше). Рекомендую прочесть оба, чтобы определиться который Вам подходит и подходит ли вообще.
Назовем способы условно так:
1) Способ 1 - "Всё запретить - всё разрешить. Доступ по группам объектов"
2) Способ 2 - "Всё разрешено. Объектно-ориентированный запрет"
Способы будут описаны ниже.
А сейчас ответим на вопрос - как защититься от слишком любознательного пользователя?
Рекомендуется, всё же немного подкорректировать роль "Пользователь" в конфигураторе, а именно в настройках прав убрать "Режим "Все функции". Сильных проблем при последующем обновлении программы вызвать не должно, просто надо это запомнить, чтобы делать после каждого обновления. Так мы защитим от намеренного открытия некоторые важные объекты, вроде констант и справочников.
Для этого сначала разрешаем редактировать роль (с сохранением поддержки):
Затем снимаем режим:

Сохраняем конфигурацию, обновляем базу, проверяем, что у пользователя настройка исчезла.
Но опять же, если дотошный пользователь через настройки интерфейса и панелей включит себе то, что мы отключали через шаблоны, то он сможет получить доступ к некоторым справочникам или другим объектам.
Если же случайный доступ принципиально нужно ограничить, то можно попробовать использовать следующее решение, которое позволит через стандартный административный интерфейс предоставлять доступ к справочникам или другим объектам избранным пользователям.
Суть решения: создать подписки на события в момент записи объектов (справочников, констант и т.п.), создать группы пользователей, которым разрешено/запрещено изменять объекты конфигурации, на основании групп предоставлять доступ к редактированию. Будут созданы только пара новых объектов конфигурации, поэтому проблем при обновлении быть не должно.
Реализация решения:
Способ 1  "Всё запретить - всё разрешить. Доступ по группам объектов"

Т.к. мы не хотим менять типовые объекты конфигурации, то вместо изменения самих объектов для проверки доступа на редактирование к ним будем использовать подписки на события "ПередЗаписью"
Чтобы добавить подписку, необходимо установить следующий режим редактирования: на корневом узле установить "Объект поставщика редактируется с сохранением поддержки" (без наследования на подчиненные объекты):
Скорее всего данный режим у Вас уже установлен (если пользовались внешними обработками выгрузки данных в ФИС).
Начнем со справочников, рассмотрим всё на их примере, а остальные объекты (например, константы) делаются по аналогии.
Для начала, мы вставим небольшой код, который будет запрещать редактирование всех справочников пользователями (по умолчанию редактирование всех справочников разрешено).
Для проверки прав доступа нужно добавить новый модуль:
Даем наименование (в моем случае "п_" - это регламентированный префикс для всех добавляемых объектов):
В самом модуле добавляем код обработчика будущей подписки на события:

// Обработчик события "ПередЗаписью" подписки на события
// Разрешающий редактирование только избранным пользователям
Процедура ПередЗаписьюОбъектовПроверкаПрав(Источник, Отказ) Экспорт
    Если ВспомогательныеФункции.ТекущийПользовательСРольюПолныеПрава() Тогда
        Возврат;
    КонецЕсли;   
    Отказ = Истина;
КонецПроцедуры
Данный код разрешает редактирование только пользователям с полными правами. Дополним его более гибкими правами позже.
Теперь создаем подписку на событие (которая будет срабатывать при попытке записать элемент справочника):
В поле свойств Источник выбираем тип данных "СправочникОбъект":
(Если нужно, можно еще дополнительно сделать такую же подписку для констант, тогда в типе данных нужно выбрать "КонстантаМенеджерЗначения" и также для любых других объектов.)
Задаем наименование подписки и указываем в поле Обработчик созданную ранее процедуру нового модуля:
Теперь можно проверять, как работает новый механизм (сохраняем конфигурацию и обновляем базу). Заходим под администратором и пользователем с полными правами, проверяем, что справочники можно редактировать. Проверяем под пользователем, что доступа к записи элементов в справочниках нет и выдается сообщение об ошибке при сохранении "Не удалось записать <название справочника>" (можно временно вернуть режим всех функций, чтобы было легче проверять):
Теперь нужно сделать так, чтобы один пользователь имел доступ, а другой нет. Используем для этого тот же механизм групп, что и ранее для объединения пользователей в группы.
Идея такова - для каждого объекта конфигурации, к которому нужно предоставить доступ, создается группа и любой пользователь, входящий в эту группу, будет иметь доступ на редактирование этого объекта. Можно делать как "разрешающие" группы, так и "запрещающие", т.е. если Вы хотите не всё запрещать, а оставить всё разрешенным (как по умолчанию) и потом отдельно запрещать редактирование некоторых объектов (тогда в указанном выше коде модуля нужно убрать строку "Отказ = Истина;", которая всё запрещает).
Вернемся к нашему примеру. Для удобства можно сгруппировать все папки (группы) пользователей, где будут назначаться права, например, так: (можно задать любую структуру, важны будут только конечные группы)

В этом примере мы решаем, что константы и справочники будут запрещены на редактирование для всех и будем делать разрешающие права в группе "Разрешено редактировать". А на планы видов характеристик будем делать запрещающие права для некоторых пользователей.
Продолжим со справочниками.
Допустим некому пользователю нужно дать доступ на редактирование справочника "Дисциплины".
Тогда в группе справочники создадим подгруппу с названием справочника: Справочник.Дисциплины
Примечание: наименование должно быть таким, как задано "ПолноеИмя" объекта в конфигураторе, например, "Справочник.Дисциплины", "Справочник.ЕдиницыИзмерения", "Константа.ЗаголовокСистемы" и т.п.
Добавьте в эту группу пользователей, которым нужно разрешить редактировать справочник Дисциплины.
Теперь необходимо добавить код в созданный ранее модуль, чтобы обрабатывать вышеуказанные группы (текст кода для копирования см. ниже):
Можно проверять редактирование справочника под пользователем, у которого раньше была ошибка при сохранении и которого мы включили в группу на разрешение. Теперь ошибки быть не должно, и дисциплина сохранится нормально.
Если нужно сделать тоже самое для констант, то опять же создаем подписку "ПередЗаписью" на объекты констант, ссылаемся на ту же процедуру в нашем модуле. В группах создаем соответствующую группу и добавляем в нее пользователей.
Если нужны запрещающие права, то для этого немного модифицируем код.
В итоге код обеих процедур в нашем модуле может быть следующим:
// Обработчик события "ПередЗаписью" подписки на события п_ПередЗапись...ПроверкаПравНаЗапись
// РАЗРЕШАЮЩИЙ редактирование только избранным пользователям
Процедура ПередЗаписьюОбъектовПроверкаПравРазрешение(Источник, Отказ) Экспорт
 Если ВспомогательныеФункции.ТекущийПользовательСРольюПолныеПрава() Тогда
  Возврат;
 КонецЕсли;
 ИмяОбъекта = Источник.Метаданные().ПолноеИмя();
    ОбъектЗапрета = Справочники.ГруппыПользователей.НайтиПоНаименованию(ИмяОбъекта, Истина);   
    Если НЕ ОбъектЗапрета = Справочники.ГруппыПользователей.ПустаяСсылка() Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ
        |   ГруппыПользователейСостав.Ссылка
        |ИЗ
        |   Справочник.ГруппыПользователей.Состав КАК ГруппыПользователейСостав
        |ГДЕ
        |   ГруппыПользователейСостав.Пользователь = &Пользователь
        |   И ГруппыПользователейСостав.Ссылка = &Ссылка";
        Запрос.УстановитьПараметр("Пользователь", ПараметрыСеанса.ТекущийПользователь);
        Запрос.УстановитьПараметр("Ссылка", ОБъектЗапрета);
        Результат = Запрос.Выполнить();
        Если НЕ Результат.Пустой() Тогда
            //Отказ = Истина  //(раскомментировать, если право запрещающее)
            Возврат  //(закомментировать, если право запрещающее)        
        КонецЕсли;
    КонецЕсли;   
    Отказ = Истина;  //(закомментировать, если право запрещающее)
КонецПроцедуры
// Обработчик события "ПередЗаписью" подписки на события п_ПередЗаписью...ПроверкаПравНаЗапись
// ЗАПРЕЩАЮЩИЙ редактирование избранным пользователям
Процедура ПередЗаписьюОбъектовПроверкаПравЗапрет(Источник, Отказ) Экспорт
 ИмяОбъекта = Источник.Метаданные().ПолноеИмя();
    ОбъектЗапрета = Справочники.ГруппыПользователей.НайтиПоНаименованию(ИмяОбъекта, Истина);    
    Если НЕ ОбъектЗапрета = Справочники.ГруппыПользователей.ПустаяСсылка() Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ
        |   ГруппыПользователейСостав.Ссылка
        |ИЗ
        |   Справочник.ГруппыПользователей.Состав КАК ГруппыПользователейСостав
        |ГДЕ
        |   ГруппыПользователейСостав.Пользователь = &Пользователь
        |   И ГруппыПользователейСостав.Ссылка = &Ссылка";
        Запрос.УстановитьПараметр("Пользователь", ПараметрыСеанса.ТекущийПользователь);
        Запрос.УстановитьПараметр("Ссылка", ОБъектЗапрета);
        Результат = Запрос.Выполнить();
        Если НЕ Результат.Пустой() Тогда
            Отказ = Истина
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

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

Этот способ позволяет сделать также всё наоборот. Т.е. оставить все справочники по умолчанию редактируемыми, и добавить запрещающие редактирование правила. Это уже на усмотрение администратора. 
Однако в этом способе нельзя использовать один и тот же объект как для разрешения, так и для запрета. Поэтому необходимо сразу определиться, что будет по умолчанию включено, а что нет.
Данный механизм можно попытаться расширить, используя, например, механизм "Администрирование объектов метаданных", как было сделано для документов в типовой конфигурации. Тогда можно будет предоставлять доступ сразу на группы объектов (способ 2)
Способ 2 - "Всё разрешено. Объектно-ориентированный запрет"
Предисловие: после описания и обсуждения 1го способа, нами была избрана несколько иная стратегия для справочников - "стратегия запрета".
Её суть: по умолчанию в типовой конфигурации основные объекты разрешены для редактирования (справочники, константы и т.п.). Но есть справочники, к которым доступ необходимо предоставить лишь избранным пользователям или совсем запретить редактирование.
Например, справочники "Типы стандартов" или "Формы обучения" (нам бы не хотелось, чтобы любой из пользователей мог переименовать "очное" на "заочное" и т.п.).
Почему именно так, а не наоборот? Решение было принято из-за сроков (нужно сделать быстро), а "способ разрешения" опасен тем, что на данный момент мы точно не знаем, какие где справочники понадобятся при работе, и, если в разгар приемной кампании всё запретить, может встать работа сотрудников ПК.
Поэтому пока нужно защитить лишь особо важные объекты, а когда с течением времени будут сформированы списки справочников для разных учебных процессов, тогда можно будет перейти к стратегии разрешения.
Однако при детальном обдумывании реализации задачи "стратегии запрета" выяснилось, что первый способ неудобен, т.к. создавая группу объекта нужно будет включить в нее всех пользователей, кому нельзя редактировать объект, которых довольно много (тех, кому разрешено, - единицы). Особенно неудобно, если создается новый пользователь - при создании надо будет не забыть добавить его в группы запрета (если забыть, то он случайно получит доступ на редактирование к защищаемым объектам).
Попытки оптимизировать процесс привели ко второму способу защиты.
Суть способа 2: всё разрешено для всех, но если что-то разрешено только для кого-то, то оно должно быть автоматически запрещено для всех остальных. Т.е. запрет не глобальный (как в способе 1), а объектно-ориентированный.
Например, если на справочник специально не назначено никаких пользователей, то доступ на редактирование по умолчанию разрешен. Если к справочнику привязали хотя бы одну группу пользователей или пользователя, то будем разрешать редактирование только указанным группам, а всем остальным запрещать.
Реализовывать будем немного другим методом, в отличие от способа 1, а именно расширим стандартный механизм "Администрирование прав объекта" (знакомый нам по первой части статьи).
Механизм базируется на справочнике метаданных, в который нужно добавить имена объектов, доступ к которым нужно регулировать, например, "Справочник.ФормаОбучения", "Справочник.ТипыСтандартов" или "Константа.ЗаголовокСистемы" и т.п.
Примечание: в этом справочнике уже будут предопределенные объекты (документы), с которыми мы работали в части 1. Просто добавляем новые объекты.
Также в отличие от первого способы мы ограничены в длине наименования объекта - всего 50 символов, поэтому объекты с длинными названиями включить не получится, либо подправить код модуля так, чтобы вместо префикса "Справочник" использовать "что-нибудь покороче". Или же в справочнике "Объекты метаданных" в конфигураторе установить большее количество символов.
Теперь выбираем группу пользователей, которым будет разрешено редактирование. Например, справочник дисциплин должны редактировать только сотрудники УМУ, т.к. они создают учебные планы, все остальные только чтение.
Для этого в "Администрировании прав группы" добавляем справочник дисциплин из справочника метаданных (кнопка "Подбор объектов") и ставим право "Изменять" или "Добавлять" ("галка Чтение" на данный момент будет проигнорирована).
Это будет означать, что раз появился кто-то, кому даны особые права, то автоматически другим право редактирования будет запрещено.
Теперь нужно сделать обработчик для этих прав и метаданных. Для этого, как уже описывалось в способе 1 выше, создадим подписку "ПередЗаписью" на все справочники и/или другие нужные объекты.
Далее в коде модуля обработчика подписки добавляем следующий код:
Текст кода:
// Обработчик события "ПередЗаписью" подписки на события
Процедура ПередЗаписьюОбъектовПроверкаПрав(Источник, Отказ) Экспорт
 Если ВспомогательныеФункции.ТекущийПользовательСРольюПолныеПрава() Тогда
  Возврат;
 КонецЕсли;
 //Отказ = Истина;
 ИмяОбъекта = Источник.Метаданные().ПолноеИмя();
 РазрешитьРедактирование = ИСТИНА;
 // проверяем, есть ли хотя бы одна ссылка на объект права
 Запрос = Новый Запрос;
 Запрос.Текст = "ВЫБРАТЬ
                 | Истина
                 |ИЗ
                 | РегистрСведений.Роли КАК Роли
                 |ГДЕ
                 | Роли.Объект.Наименование = &Объект";
 Запрос.УстановитьПараметр("Объект", ИмяОбъекта);
 ВыборкаДетальныеЗаписи = Запрос.Выполнить().Выбрать();      
 Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
  РазрешитьРедактирование = ЛОЖЬ; //запрещаем, т.к. есть хотя бы одна запись с таким правом
  Прервать;
 КонецЦикла;
 
 Если РазрешитьРедактирование = ЛОЖЬ Тогда
  // проверяем, есть ли права на редактирование у текущего пользователя
  ЗапросПользователя = Новый Запрос;
  ЗапросПользователя.Текст = "ВЫБРАТЬ
                  | Истина
                  |ИЗ
                  | РегистрСведений.Роли КАК Роли
                  |ГДЕ
                  | Роли.Объект.Наименование = &Объект
                  | И Роли.ЗначениеПрава = ИСТИНА
                  | И (Роли.Право = ЗНАЧЕНИЕ(Справочник.Права.Добавление)
                  |   ИЛИ Роли.Право = ЗНАЧЕНИЕ(Справочник.Права.Изменение))
                  |   И (Роли.Пользователь = &ТекущийПользователь
                  | ИЛИ Роли.Пользователь В (ВЫБРАТЬ Ссылка
                  |  ИЗ         
                  |  Справочник.ГруппыПользователей.Состав
                  |  ГДЕ
                  |   Пользователь = &ТекущийПользователь))";
 
  ЗапросПользователя.УстановитьПараметр("Объект", ИмяОбъекта);
  ЗапросПользователя.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
  ВыборкаДетальныеЗаписиЗапросПользователя = ЗапросПользователя.Выполнить().Выбрать();      
 
  Пока ВыборкаДетальныеЗаписиЗапросПользователя.Следующий() Цикл
   РазрешитьРедактирование = ИСТИНА;
   // разрешаем, т.к. есть хотя бы одна запись с назначением
   // права редактирования текущего объекта текущему пользователю
   Прервать;
  КонецЦикла;
 КонецЕсли;
 Если НЕ РазрешитьРедактирование Тогда
  Сообщение = Новый СообщениеПользователю;
  Сообщение.Текст = "Недостаточно прав для сохранения элемента. Обратитесь к администратору.";
  Сообщение.Сообщить();
 КонецЕсли;
 Отказ = НЕ РазрешитьРедактирование;  
КонецПроцедуры
Возникает вопрос: что делать, если доступ на редактирование справочника надо запретить всем пользователям (кроме администратора и пользователя с полными правами)?
В этом случае можно создать пустую группу пользователей, например, "Защищаемые объекты метаданных", в которую включить право, ссылающее на нужный справочник, но никого в нее не включать:
К этой группе можно привязать все справочники, редактирование которых всем запрещено.
Этот способ удобен тем, что не нужно создавать множество групп для каждого объекта (как в способе 1), а можно делать разрешение уже созданным группам.
На этом, можно сказать, всё. Настройка довольно муторная, однако изменений в типовой конфигурации практически нет, что позволит намного легче проводить обновления.
PS: Также может возникнуть вопрос: а как ограничить доступ к внешним обработкам? Например, выгрузку в ФИС, которую зачастую подключают как дополнительную внешнюю обработку и которую получается может открыть любой пользователь (у которого есть права на приемные кампании).
Так вот, используя способ 2, можно ограничивать доступ к внешним обработкам, включая часть проверочного кода в код открытия их форм (предварительно добавив наименование обработки в справочник метаданных и назначив права на него).
PS2: было замечено, что встроенный механизм назначения прав на документы иногда не отрабатывает на некоторых документах, например, диссертационные советы. Если пользователю давали право на чтение документа, то неожиданно появлялось и право на создание/редактирование, хотя "галка" "Добавлять-Изменять" не была установлена (только чтение). Поэтому возможно имеет смысл продублировать встроенный механизм, создав дополнительно подписку на "ПередЗаписью" для документов (используя способ 2) - да, получается двойная проверка, но зато надежная.
PS3: если есть идеи по оптимизации кода/запросов и т.п., пожалуйста, пишите в комментариях (например, разграничение доступа при праве "Добавление" и "Изменение" отдельно, в статье они не различаются).

(с)PROИТ

Комментариев нет:

Отправить комментарий

Печать чека ККМ из драйвера ККМ

Суть проблемы: Пробиваем чек из 1С: Бухгалтерии. Чек пробит не  правильно. Теперь нужно сделать чек коррекции. Но в программе это сделать н...