Top.Mail.Ru
25 июля в 17:00 по МСК
Регистрируйся!
Онлайн-митап по 1С «Типовой механизм многопоточности БСП»

Внутренний механизм работы шаблонов ролей в производительном методе RLS

Продолжаем цикл статей про производительный метод RLS.

Первая статья цикла рассказывает о методике подключения новых объектов к механизму RLS.

Вторая статья цикла подробно описывает работу функции «ПриЗаполненииОграниченияДоступа».

Теперь рассмотрим шаблоны ограничений доступа для ссылочных типов и регистров. Шаблоны производительного метода в отличие от шаблонов стандартного метода редактировать не рекомендуется, т.к. ограничения прописываются в методе «ПриЗаполненииОграниченияДоступа» в модуле менеджера объекта, на который накладываем ограничения.

Статья предназначена для разработчиков любого уровня и поможет пониманию механизма взаимодействия объектов, в которых прописаны ограничения, — «Ключей». Тех самых, о которых мы говорим: «Ключи не пересчитались» или «Надо бы пересчитать ключи», когда случаются какие-то ошибки в отборах ограничений.

Шаблоны возьмем из роли «ИзменениеУчастниковГруппДоступа», как рекомендуемой в ИТС для копирования шаблонов в новые роли.

Начнем с рассмотрения объектов метаданных, участвующих в данных шаблонах.

Параметры сеанса

  • ОграничениеДоступаНаУровнеЗаписейУниверсально — тип «Булево». Основной параметр, определяет какой метод применяется: классический или производительный.
  • СпискиСОтключеннымОграничениемЧтения — Тип «Строка». Строка, формируемая по списку объектов, к которым не подключено ограничение. Заполняется на основании ролевой модели. В момент блокировки базы или при отключенном производительном методе равна «Неопределено».
  • СпискиСОграничениемЧерезКлючиДоступаГруппДоступа — Тип «Строка». Строка со списком объектов, на который накладываются ограничения доступа, формируемая на основании функции.
  • «ПриЗаполненииСписковСОграничениемДоступа» в ОМ «УправлениеДоступомПереопределяемый». Существуют уточнения при применении шаблона:
    • если у пользователя вообще нет доступа к таблице, то она вставляется в данный список, но с уточнением «;1» для внешнего пользователя, или «;0» для обычного;
    • для каждого реквизита, используемого в функции «ПриЗаполненииОграниченияДоступа» в модуле менеджера объекта с условием «ЧтениеОбъектаРазрешено» или «ИзменениеОбъектаРазрешено», будет добавлена запись в строке вида «ПолноеИмя:ИмяРеквизита;*». Если же такого условия нет, то запись объекта будет вида «ПолноеИмя:;*»
  • СпискиСОграничениемЧерезКлючиДоступаПользователей — тип «Строка». Строка, формируемая на основании функции «ПриЗаполненииСписковСОграничениемДоступа» в ОМ «УправлениеДоступомПереопределяемый». Если в процедуре «ПриЗаполненииОграниченияДоступа» в модуле менеджера есть проверка на ограничение по пользователю, то объект в список добавляется, иначе нет.
  • РазрешенныйНаборГруппДоступа — справочник «НаборыГруппДоступа». Набор групп доступа, разрешенный текущему пользователю. В ТЧ хранит группы доступа, в которых он состоит.
  • РазрешенныйПустойНаборГруппДоступа — справочник «НаборыГруппДоступа». Набор групп доступа с незаполненными реквизитами.
  • ТекущийВнешнийПользователь — справочник «ВнешниеПользователи». Внешний пользователь сеанса.
  • РазрешенныйПользователь – справочник «НаборыГруппДоступа». Набор групп пользователя, разрешенных текущему пользователю. В ТЧ хранит группы пользователей, в которых он состоит.

Регистры сведений

  • КлючиДоступаКОбъектам — связь между объектом и ключами доступа: существующими уникальными сочетаниями реквизитов отбора, определяемыми функцией менеджера объекта «ПриЗаполненииОграниченияДоступа», нужен для формирования отбора запроса шаблона ролей. В шаблоне из него берутся все ключи текущего объекта и связываются с нижеприведенными РС. Используется для объектов ссылочного типа.
  • КлючиДоступаКРегистрам — связь между объектом и ключами доступа: существующими уникальными сочетаниями реквизитов отбора, определяемыми функцией менеджера объекта «ПриЗаполненииОграниченияДоступа», нужен для формирования отбора запроса шаблона ролей. В шаблоне из него берутся все ключи текущего объекта и связываются с нижеприведенными РС. Используется для регистров сведений.
  • КлючиДоступаНаборовГруппДоступа – связь между ключами и наборами групп доступа, формируется только для тех ключей, которым разрешено чтение для конкретного набора групп. Т.е. если в ТЧ набора групп входит группа, у которой есть права на чтение, с учетом RLS, на этот ключ, то запись в регистр будет записана. Если в наборе нет ни одной такой группы, то запись в регистре мы не увидим. Нужен для формирования отбора запроса шаблона ролей через ПС РазрешенныйНаборГруппДоступа.
  • КлючиДоступаПользователей — связь между пользователем и ключами. Нужен для формирования отбора запроса шаблона ролей через ПС РазрешенныйПользователь.
  • КлючиДоступаВнешнихПользователей — связь между пользователем и ключами. Нужен для формирования отбора запроса шаблона ролей через ПС ТекущийВнешнийПользователь.
  • ГруппыДоступа — список групп, в которых настраиваются как роли (через назначенный группе профиль), так и виды доступа и их ограничения. ТЧ «Пользователи» содержит пользователей входящих в группу
  • ГруппыПользователей — список для консолидации пользователей.

Справочники

  • КлючиДоступа — уникальные комбинации значений доступа, формируются на основании ограничений доступа, процедура «ПриЗаполненииОграниченияДоступа» в модуле менеджера. Например, если в процедуре используется ограничение на два реквизита: организация и слад, то для этого вида объектов метаданных сформируются все пары «организация, склад», которые встречаются у существующих объектов данных. Есть ограничение на количество ключевых полей.

При разработке нужно стараться спроектировать архитектуру так, чтобы количество ключей было минимальным.

  • НаборыГруппДоступа — содержит комбинацию групп или связь пользователя и разрешенной комбинацией групп. Комбинации групп содержится в ТЧ «группы доступа». Т.е. на каждого пользователя создается два элемента справочника: элемент, в котором в ТЧ содержится сочетание всех групп куда входит пользователь и элемент, в котором записан пользователь и его разрешенная группа. Нужен для формирования отбора запроса шаблона ролей.

Разбор шаблона

Для понимания работы шаблона: из шаблона, посредством условий препроцессора, формируется запрос, который возвращает ИСТИНА или ЛОЖЬ. Если ИСТИНА — объект доступен, если ЛОЖЬ — нет. Команды препроцессора в данном случае используются: #Если, #Тогда, #ИначеЕсли, #Иначе и #КонецЕсли. Если условие выполняется, то строки алгоритма внутри берутся в формирование текста запроса, иначе игнорируются, например:

#Если &ТекущийВнешнийПользователь = Значение(Справочник.ВнешниеПользователи.ПустаяСсылка) #Тогда
    КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
#Иначе
    КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаВнешнихПользователей
#КонецЕсли
  И КлючиДоступаРазрешенногоНабораГруппДоступа.НаборГруппДоступа = &РазрешенныйПустойНаборГруппДоступа
И КлючиДоступаРазрешенногоНабораГруппДоступа.НаборГруппДоступа = &РазрешенныйПустойНаборГруппДоступа

Если параметр сеанса «ТекущийВнешнийПользователь» равен пустой ссылке на справочник «ВнешниеПользователи», тогда в текст запроса попадет код:

КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей

иначе:

КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаВнешнихПользователей

а код:

И КлючиДоступаРазрешенногоНабораГруппДоступа.НаборГруппДоступа = &РазрешенныйПустойНаборГруппДоступа

будет в запросе всегда, т.к. он не вне условий препроцессора.

Ниже будет приведен код шаблона ограничений, который мы разберем. Шаблон немного упрощен, я постараюсь разобрать основные связи. Шаблон буду комментировать по логическим частям, чтобы было удобно читать. Полный текст шаблона можно посмотреть в роли «ИзменениеУчастниковГруппДоступа».

#Если &СпискиСОтключеннымОграничениемЧтения = "Неопределено" #Тогда
  // Информационная база заблокирована для обновления.
  ГДЕ ЛОЖЬ

Проверка на блокировку ИБ или метод RLS (производительный, стандартный). По идее, если равно «Неопределено», то непонятно как мы сюда попали. В общем все заблокировано — ничего не доступно.

#ИначеЕсли &СпискиСОтключеннымОграничениемЧтения = "Все" #Тогда
  // У пользователя отключены все виды доступа или
  // ограничение на уровне записей не используется.
  ГДЕ ИСТИНА

Ограничение на уровне записей отключено и объекты доступны все.

#ИначеЕсли СтрСодержит(&СпискиСОтключеннымОграничениемЧтения, #ИмяТекущейТаблицы + ";") #Тогда
  // У пользователя отключено ограничение для текущей таблицы.
  ГДЕ ИСТИНА

Все понятно из комментария, текущая таблица не ограничена, все доступно.

#ИначеЕсли СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа, #ИмяТекущейТаблицы + ";0") #Тогда
  ГДЕ ЛОЖЬ

Как описывал выше, таблица полностью недоступна, смысла проверять на ключи нет, сразу возвращает ЛОЖЬ

#ИначеЕсли Не СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа,  #ИмяТекущейТаблицы + ":#ПолеОбъекта;")
         И Не СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;") #Тогда

  Ошибка: Требуется актуализировать ограничение доступа по причине: Не удалось определить вариант ограничения доступа в параметрах сеанса для шаблона ДляОбъекта со значением параметра "#ПолеОбъекта". Объект: #ИмяТекущейТаблицы, Право: #ИмяТекущегоПраваДоступа.
#Иначе

Проверка на корректность заполнения ПС «СпискиСОграничениемЧерезКлючиДоступаГруппДоступа» и «СпискиСОграничениемЧерезКлючиДоступаПользователей». Т.е. наша таблица должна находится хотя бы в одном из этих списков.

ТекущаяТаблица ГДЕ ИСТИНА В
 (
  ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА
  ИЗ РегистрСведений.КлючиДоступаКОбъектам КАК КлючиДоступаКОбъектам

Всегда соединяется РС «КлючиДоступаКОбъектам» с текущей таблицей. Описание, как соединяется РС «КлючиДоступаКОбъектам» и текущая таблица будет в конце. Далее рассмотрим соединение с РС в зависимости от ПС.

  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа, #ИмяТекущейТаблицы + ":#ПолеОбъекта;") #Тогда
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаНаборовГруппДоступа КАК КлючиДоступаНаборовГруппДоступа
      ПО
      #Если &ТекущийВнешнийПользователь = Значение(Справочник.ВнешниеПользователи.ПустаяСсылка) #Тогда
          КлючиДоступаНаборовГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
      #Иначе
          КлючиДоступаНаборовГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаВнешнихПользователей
      #КонецЕсли
        И КлючиДоступаНаборовГруппДоступа.НаборГруппДоступа = &РазрешенныйНаборГруппДоступа
  #КонецЕсли

Соединяем (это условие срабатывает практически всегда) с РС «КлючиДоступаНаборовГруппДоступа» по ключам доступа и разрешенному набору групп доступа

  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа, #ИмяТекущейТаблицы + ":#ПолеОбъекта;*") #Тогда
      
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаНаборовГруппДоступа КАК КлючиДоступаРазрешенногоНабораГруппДоступа
      ПО
      #Если &ТекущийВнешнийПользователь = Значение(Справочник.ВнешниеПользователи.ПустаяСсылка) #Тогда
          КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
      #Иначе
          КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаВнешнихПользователей
      #КонецЕсли
        И КлючиДоступаРазрешенногоНабораГруппДоступа.НаборГруппДоступа = &РазрешенныйПустойНаборГруппДоступа
  #КонецЕсли

Снова соединяем с РС «КлючиДоступаНаборовГруппДоступа» по ключам доступа, но в этом случае еще и по пустому набору групп доступа.

  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;") #Тогда
      
    #Если &ТекущийВнешнийПользователь = Значение(Справочник.ВнешниеПользователи.ПустаяСсылка) #Тогда
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаПользователей КАК КлючиДоступаПользователей
      ПО
          КлючиДоступаПользователей.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
        И КлючиДоступаПользователей.Пользователь = &РазрешенныйПользователь
    #Иначе
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаВнешнихПользователей КАК КлючиДоступаПользователей
      ПО
          КлючиДоступаПользователей.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаВнешнихПользователей
        И КлючиДоступаПользователей.ВнешнийПользователь = &РазрешенныйПользователь
    #КонецЕсли
  #КонецЕсли

Соединяем с РС «КлючиДоступаПользователей» по ключам доступа и ограничиваем ПС «РазрешенныйПользователь»

  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;+") #Тогда
      
    #Если &ТекущийВнешнийПользователь = Значение(Справочник.ВнешниеПользователи.ПустаяСсылка) #Тогда
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаПользователей КАК КлючиДоступаНаборовГруппПользователей
      ПО
          КлючиДоступаНаборовГруппПользователей.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
        И КлючиДоступаНаборовГруппПользователей.Пользователь = &РазрешенныйНаборГруппПользователей
    #Иначе
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаВнешнихПользователей КАК КлючиДоступаНаборовГруппПользователей
      ПО
          КлючиДоступаНаборовГруппПользователей.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаВнешнихПользователей
        И КлючиДоступаНаборовГруппПользователей.ВнешнийПользователь = &РазрешенныйНаборГруппПользователей
    #КонецЕсли
  #КонецЕсли

Соединяем с РС «КлючиДоступаПользователей» по ключам доступа и ПС РазрешенныйНаборГруппПользователей

  ГДЕ
  #Если "#ПолеОбъекта"  = ""
    Или СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа,  #ИмяТекущейТаблицы + ":#ПолеОбъекта;;-")
    Или СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа,  #ИмяТекущейТаблицы + ":#ПолеОбъекта;*;-")
    Или СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;;-")
    Или СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;+;-") #Тогда
      КлючиДоступаКОбъектам.Объект = ТекущаяТаблица.Ссылка
  #Иначе
      КлючиДоступаКОбъектам.Объект = ТекущаяТаблица.#ПолеОбъекта
  #КонецЕсли

Тут соединяем текущую таблицу с РС «КлючиДоступаКОбъектам». Соединяется по ссылке — если не указали в параметре ничего, или, если передаем в шаблон параметр «ПолеОбъекта», совпадающий со значением параметра функций «ЧтениеОбъектаРазрешено» или «ИзменениеОбъектаРазрешено», которые мы использовали в функции «ПриЗаполненииОграниченияДоступа» модуля менеджера объекта. Если же мы передаем в шаблон не пустой параметр, но функции «ЧтениеОбъектаРазрешено» или «ИзменениеОбъектаРазрешено» не использовали, то связь будет к реквизиту объекта.

    И (
  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа, #ИмяТекущейТаблицы + ":#ПолеОбъекта;") #Тогда
          НЕ КлючиДоступаНаборовГруппДоступа.КлючДоступа ЕСТЬ NULL
  #Иначе
          ЛОЖЬ
  #КонецЕсли
  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаГруппДоступа, #ИмяТекущейТаблицы + ":#ПолеОбъекта;*") #Тогда
      ИЛИ НЕ КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа ЕСТЬ NULL
  #КонецЕсли
  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;") #Тогда
      ИЛИ НЕ КлючиДоступаПользователей.КлючДоступа ЕСТЬ NULL
  #КонецЕсли
  #Если СтрСодержит(&СпискиСОграничениемЧерезКлючиДоступаПользователей, #ИмяТекущейТаблицы + ":#ПолеОбъекта;+") #Тогда
      ИЛИ НЕ КлючиДоступаНаборовГруппПользователей.КлючДоступа ЕСТЬ NULL
  #КонецЕсли
      )

Обрезание из выборки строк, где ключи доступа отсутствуют.

 )

Пример получения запроса по шаблону

Теперь рассмотрим, как же можно получить запрос, который нам покажет, как будет обрабатываться конкретный объект. Запрос конечно же будем выполнять уже под пользователем с полными правами.

Запускаем отладку под нужным нам пользователем. Подключаем во внешние обработки обработку чтения параметров сеанса . И начинаем пошагово рассматривать параметры сеанса этого пользователя.

  1. Смотрим «СпискиСОтключеннымОграничениемЧтения». Если равно «Все», то скорее всего у пользователя есть полные права, или вы не включили ограничения по RLS. Если там список строк, ищем нашу таблицу, если есть, значит пользователю назначена роль, дающая полные права на эту таблицу. Если нет, переходим к П.2.
  2. Смотрим «СпискиСОграничениемЧерезКлючиДоступаГруппДоступа». Ищем в списке нашу таблицу, если ее нет, то скорее всего она не подключена данному механизму (как подключить описано в первой статье цикла) и будет выдавать ошибку при открытии данной таблицы объектов. Если есть, смотрим что идет после строки, если «;0» — значит таблица полностью не доступна, скорее всего, у пользователя, нет ни одной роли дающей право чтения на таблицу. Иначе идем П.3.
  3. Смотрим «СпискиСОграничениемЧерезКлючиДоступаПользователей» и начинаем из кода удалять строки, не соответствующие условиям препроцессора.

Допустим у нас остался вот такой код:

ТекущаяТаблица ГДЕ ИСТИНА В
 (
  ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА
  ИЗ РегистрСведений.КлючиДоступаКОбъектам КАК КлючиДоступаКОбъектам
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаНаборовГруппДоступа КАК КлючиДоступаНаборовГруппДоступа
      ПО
          КлючиДоступаНаборовГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
        И КлючиДоступаНаборовГруппДоступа.НаборГруппДоступа = &РазрешенныйНаборГруппДоступа
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаНаборовГруппДоступа КАК КлючиДоступаРазрешенногоНабораГруппДоступа
      ПО
          КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
        И КлючиДоступаРазрешенногоНабораГруппДоступа.НаборГруппДоступа = &РазрешенныйПустойНаборГруппДоступа
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаПользователей КАК КлючиДоступаПользователей
      ПО
          КлючиДоступаПользователей.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
        И КлючиДоступаПользователей.Пользователь = &РазрешенныйПользователь
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаПользователей КАК КлючиДоступаНаборовГруппПользователей
      ПО
          КлючиДоступаНаборовГруппПользователей.КлючДоступа = КлючиДоступаКОбъектам.КлючДоступаПользователей
        И КлючиДоступаНаборовГруппПользователей.Пользователь = &РазрешенныйНаборГруппПользователей
  ГДЕ
      КлючиДоступаКОбъектам.Объект = ТекущаяТаблица.Ссылка
    И (
          НЕ КлючиДоступаНаборовГруппДоступа.КлючДоступа ЕСТЬ NULL
      ИЛИ НЕ КлючиДоступаРазрешенногоНабораГруппДоступа.КлючДоступа ЕСТЬ NULL
      ИЛИ НЕ КлючиДоступаПользователей.КлючДоступа ЕСТЬ NULL
      ИЛИ НЕ КлючиДоступаНаборовГруппПользователей.КлючДоступа ЕСТЬ NULL
      )
)

Меняем первую строку на:

ВЫБРАТЬ ТекущаяТаблица.Ссылка ИЗ <ПолноеИмяОбъекта> КАК ТекущаяТаблица ГДЕ ИСТИНА В

Где «ПолноеИмяОбъекта» — наша таблица, например, Справочник.Контрагенты. После этого спокойно вставляем код запроса в любую консоль запросов, параметры берем из параметров сеанса нужного пользователя.

В данной статье я постарался как можно подробнее описать механизм взаимодействия регистров и справочников в шаблонах при производительном методе. Надеюсь после ее прочтения вам будет понятно куда смотреть при непонятном поведении системы.

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar