# Дискретный доступ Дискретный доступ позволяет выдавать привилегии в рамках объектов классов или записей в администрируемой выборке. ## Настройка привилегии ### Административный объект 1. Открыть карточку `Адм. объекта` 2. Установить признак `Дискретный доступ` 3. Перейти на закладку `Дискретные ограничения доступа` 4. Создать `Дискретное ограничение`. #### Дискретное ограничение доступа 1. Создать новую запись или встать на запись для редактирования 2. Указать уникальное в рамка адм. объекта имя и наименование 3. Настроить тип правила 4. Настроить параметры правила. 5. На закладке `Скрипт для фильтрации объектных привилегий` написать sql-запрос, который будет фильтровать списки объектов 6. На закладке `Скрипт проверки строк по объектному кешу` написать jexl-скрипт, который будет по rop-у объекта или строке выборки возвращать признак, что условие выполнилось для этого объекта. #### Тип правила Доступны 3 варианта типа правила: - Примитивное - Составное - Без параметров ##### Примитивное правило Состоит из одного параметра. При анализе прав пользователя со всех его ролей будут собраны значения параметров и собраны в один массив примитивных значений. Например для числового правила будет собран массив вида: `[10, 20, 30]` ##### Составное правило Состоит из нескольких параметров. При анализе прав пользователя используется следующий алгоритм: 1. Для каждого набора значений, указанных на роли, формируется json-объект вида: ``` { <ИД параметра 1>: [<Массив указанных значений>], <ИД параметра 2>: [<Массив указанных значений>], ... <ИД параметра n>: [<Массив указанных значений>] } ``` 2. Каждая роль может обладать несколькими наборами значений на одно правило. По этому результатом агрегации настроенных значений на роли будет массив, содержащий объекты, указанные в п.1: ``` [ {json-объект для набора 1}, {json-объект для набора 2}, ... {json-объект для набора n}, ] ``` 3. При агрегации прав пользователя: - со всех ролей анализируются массивы, описанные в п.2 - в json-е заменяются ИД параметров на их имена - каждый элемент массива приводится к строке - собирается результирующий массив, содержащий уникальные строки. Таким образом исключаются дублирующие настройки на ролях. Пример агрегации прав пользователя: ``` [ { "paramName1": ["a", "b", "c"], "paramName2": [1, 2, 3] }, { "paramName1": ["c", "d", "e"], "paramName2": [3, 4, 5] } ] ``` ##### Правило без параметров Не содержит параметров. При анализе прав пользователя, если хотя на одной роли будет правило без параметров, оно будет проверяться. #### Параметры правила Позволяют настроить тип данных и ссылочность для значений, которые будут указаны на ролях. Если правило примитивное, то у него доступен для настройки только один параметр, для составного правила доступны несколько параметров. #### Скрипт для фильтрации объектных привилегий Используется для фильтрации списков объектов, через наложение макроса универсального фильтра `&DefUniFltMacros`. Доступные макросы внутри текста скрипта: - `(&<Имя атрибута>)` \ Будет заменено на указанное имя атрибута. Например, `t.id`\ Для фильтрации доступны все атрибуты дата-сета выборки шапки БО. - `(¶ms)` \ Массив строк, в котором будут параметры, собранные со всех ролей пользователя. - `(&CurrentUserID)` \ Будет заменено на ИД текущего пользователя. - `(&CurrentUserName)` \ Будет заменено на имя текущего пользователя. Запрос чаще всего представляет из себя `exist`, в котором джойнится таблица класса, и через массив значений накладываются условия фильтрации. #### Скрипт проверки строк по объектному кешу Используется для проверки ограничения по объектам, используя объектный кеш. Представляет из себя `jexl-скрипт`, который возвращает `true`, если объект проходит условие, и `false` - если не проходит. Доступные переменные: - `row` \ - Если адм. объект создан по классу, то в этой переменной будет `rop` проверяемого объекта. Это не исходный `rop`, а его обертка `JexlRop`, которая позволяет обращаться к полям объекта. ```{tip} Для получения исходного `rop` воспользуйтесь следующим примером: ~~~ //в переменной jexlRop находится объект класса JexlRop var rop = jexlRop.data() ~~~ ``` - Если адм. объект создан не по классу, то здесь будет json-представление строки дата-сета выборки. - `params` \ массив значений параметров, собранных со всех ролей пользователя. - если правило примитивное - то будет содержать примитивы (строка или число) - если правило составное - то будет содержать строки, каждая из которых - json ### Роль 1. Открыть карточку роли или выбрать ее в списке; 2. перейти на закладку `Дискретный доступ`; 3. создать новую запись, выбрав адм. объект; 4. указать значение параметров для ограничений; 5. сопоставить объектные и элементарные привилегии с нужным значением параметров ограничений, заполнив поле `Ограничение дискретного доступа` #### Ограничение доступа к объектам класса. Выдается через объектные привилегии с системными именами `edit#` и `view#` ```{tip} Если таких привилегий нет у адм. объекта, то выполните его синхронизацию. ``` Дискретные ограничения применяются только, если: - это главная выборка адм. объекта (шапка БО); - это не супер-пользователь; - к адм. объекту приминаются настройки администрирования; - адм. объект имеет признак `Дискретный доступ`. #### Определение объектов для проверки дискретных прав При проверке дискретных привилегий строки, в контексте которых требуется проверить наличие прав, определяются методом `acRows` Алгоритм работы метода: 1. Если выборка принадлежит классу (`thisApi() != null`), то для всех выделенных записей через поле первичного ключа загружаются `провайдеры строк`, из них через поле `gidRoot_dz` определяются шапки БО 2. Если выборка не принадлежит классу (`thisApi() == null`), то по всем выделенным строкам собирается json-массив, содержащий на каждую строку json-объект, в котором ключ - имя атрибута, значение - значение атрибута. ##### Ограничение списков Используя значения параметров всех ролей и настроенные "Скрипты для фильтрации объектных привилегий" формируется условие ограничения и накладывается фильтрация через макрос `&DefUniFltMacros`. ##### Ограничение открытия карточек На открытие карточки используя значения параметров всех ролей выполняется `jexl-скрипт`, настроенный в `Скрипт проверки строк по объектному кешу`. Если у пользователя нет прав на объект, то будет выдана ошибка. ##### Ограничение на редактирование объектов На `beforeEdit` или `delete` проверяется наличие привилегии `edit#` (выполняется `jexl-скрипт`, настроенный в `Скрипт проверки строк по объектному кешу`). Если у пользователя нет прав на объект, то будет выдана ошибка. #### Ограничение на объектные привилегии Если для объектной привилегии указано `Ограничение дискретного доступа`, то такая привилегия может быть проверена только в контексте какого-либо объекта. Для проверки объектных привилегий используется метод `Btk_AdminPkg().hasObjPriv`. Пример: ```scala if (Btk_AdminPkg().hasObjPriv("SomeObjectName", "SomePrivName", Seq(rop))) { //код, выполняемый при наличии привилегии } ``` Если для привилегии, имеющей ограничение, выполнить проверку без передачи объектов, то будет выдана ошибка. #### Ограничение на элементарные привилегии Для элементарной привилегии может быть указано ограничение дискретного доступа. Если ограничение указано, то при выполнении операции будут проверены права в контексте записей в выборке. На загрузку выборки или перезагрузку `ClassLoader`-а происходит подписка на событие выполнения операции. При выполнении операции проверяются дискретные права на эту элементарную привилегию: 1. Получение `acRows` 2. Для полученных строк выполняется `jexl-скрипт`, настроенный в `Скрипт проверки строк по объектному кешу` 3. Если прав нет, то выдается ошибка. ## Примеры скриптов ### Примитивное правило по атрибуту класса Правило значимое, тип данных строка. Фильтрация по like атрибута `sCode` Скрипт для фильтрации объектных привилегий: ``` select 1 from RplTst_ClassTree tt join ( select cast(json_array_elements_text(cast((¶ms) as json)) as varchar) as sCode ) as codes on tt.sCode like codes.sCode where tt.id = (&id) ``` Скрипт проверки строк по объектному кешу: ``` for (p: params) { if (row.sCode != null && p != null) { if (like(row.sCode, p)) { return true; } } } ``` ### Примитивное правило по атрибуту коллекции Правило ссылочное на `Btk_Group`. Фильтрация по прямому вхождению объекта в группу (фильтрация коллекции `Btk_ObjectGroup`). Скрипт для фильтрации объектных привилегий: ``` select 1 from Rpltst_TestGroup tt join Btk_ObjectGroup og on tt.gid = og.gidSrcObject join ( select cast(json_array_elements_text(cast((¶ms) as json)) as int8) as id ) as params on og.idGroup = params.id where tt.id = (&id) ``` Скрипт проверки строк по объектному кешу: ``` for (p: params) { var rops = toJRops(Btk_ObjectGroupApi.byParent(row.data())).asList(); if (p != null) { for (r: rops) { if (r.idGroup == p) { return true; } } } } ``` ### Примитивное правило по адм. объекту, созданному не по классу (произвольная выборка) Правило значимое, тип данных строка. Фильтрация регистронезависимая по like поля `sClass` Скрипт для фильтрации объектных привилегий: ``` select 1 from json_array_elements_text(cast((¶ms) as json)) as p where upper((&sClass)) like upper(p) ``` Скрипт проверки строк по объектному кешу: ``` for (p: params) { if (row.sClass != null && p != null) { if (like(row.sClass.toUpperCase(), p.toUpperCase())) { return true; } } } ``` ### Составное правило по 3 атрибутам класса Параметры правила: 1. `dDate` - Значимый, тип данных дата. 2. `nNumber` - Значимый, тип данных число 3. `sCaption` - Значимый, тип данных строка Фильтрация отбирает записи у которых дата меньше указанной, число равно указанному, и Наименование по like регистронезависимо совпадает с указанной Скрипт для фильтрации объектных привилегий: ``` select 1 from RplTst_AllDbTypes tt where tt.id = (&id) and exists ( select 1 from ( select v -> 'dDate' as dDate ,v -> 'nNumber' as nNumber ,v -> 'sCaption' as sCaption from json_array_elements(cast((¶ms) as json) ) as v ) p where exists ( select 1 from json_array_elements_text(p.dDate) pp where tt.dDate <= to_timestamp(pp, 'DD.MM.YYYY HH24:MI:SS') ) and exists ( select 1 from json_array_elements_text(p.nNumber) pp where tt.nNumber = cast(pp as numeric) ) and exists ( select 1 from json_array_elements_text(p.sCaption) pp where upper(tt.sCaption) like upper(pp) ) ) ``` Скрипт проверки строк по объектному кешу: ``` for (p: params) { var jObj = toJObject(p); var res = true //проверка даты if (res) { res = false var aValues = jObj.childJArray("dDate"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getDate(i); if (row.dDate <= value) { res = true; } i = i + 1; } } //проверка числа if (res) { res = false var aValues = jObj.childJArray("nNumber"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getNumber(i); if (row.nNumber == value) { res = true; } i = i + 1; } } //проверка наименования if (res) { res = false var aValues = jObj.childJArray("sCaption"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getString(i); if (row.sCaption != null && value != null) { if (like(row.sCaption.toUpperCase(), value.toUpperCase())) { res = true; } } i = i + 1; } } if (res) { return true; } } ``` ### Составное правило по атрибуту класса и коллекции Параметры правила: 1. `sCaption` - Значимый, тип данных строка 2. `idGroup` - ссылочный на `Btk_Group` Фильтрация отбирает записи, у которых Наименование по like регистронезависимо совпадает с указанным, и есть прямое вхождение в группу (фильтрация коллекции `Btk_ObjectGroup`). Скрипт для фильтрации объектных привилегий: ``` select 1 from RplTst_TestGroup tt where tt.id = (&id) and exists ( select 1 from ( select v -> 'sCaption' as sCaption ,v -> 'idGroup' as idGroup from json_array_elements(cast((¶ms) as json)) as v ) p where exists ( select 1 from json_array_elements_text(p.sCaption) pp where upper(tt.sCaption) like upper(pp) ) and exists ( select 1 from Btk_ObjectGroup og join json_array_elements_text(p.idGroup) pp on og.idGroup = cast(pp as int8) where og.gidSrcObject = tt.gid ) ) ``` Скрипт проверки строк по объектному кешу: ``` for (p: params) { var jObj = toJObject(p); var res = true //проверка наименования if (res) { res = false var aValues = jObj.childJArray("sCaption"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getString(i); if (row.sCaption != null && value != null) { if (like(row.sCaption.toUpperCase(), value.toUpperCase())) { res = true; } } i = i + 1; } } //проверка группы if (res) { res = false var rops = toJRops(Btk_ObjectGroupApi.byParent(row.data())).asList(); var aValues = jObj.childJArray("idGroup"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getLong(i); for (r: rops) { if (r.idGroup == value) { res = true; } } i = i + 1; } } if (res) { return true; } } ``` ### Составное правило по 2 атрибутам адм. объекта, созданного не по классу (произвольная выборка) Параметры правила: 1. `sClass` - Значимый, тип данных строка 2. `idRefClass` - ссылочный на `Btk_Class` Фильтрация отбирает записи, у которых поле `sClass` по like регистронезависимо совпадает с указанным, и поле `idRefClass` совпадает с указанным Скрипт для фильтрации объектных привилегий: ``` select 1 from ( select v -> 'sClass' as sClass ,v -> 'idRefClass' as idRefClass from json_array_elements(cast((¶ms) as json)) as v ) p where exists ( select 1 from json_array_elements_text(p.sClass) pp where upper((&sClass)) like upper(pp) ) and exists ( select 1 from json_array_elements_text(p.idRefClass) pp where (&idRefClass) = cast(pp as int8) ) ``` Скрипт проверки строк по объектному кешу: ``` for (p: params) { var jObj = toJObject(p); var res = true //проверка sClass if (res) { res = false var aValues = jObj.childJArray("sClass"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getString(i); if (row.sClass != null && value != null) { if (like(row.sClass.toUpperCase(), value.toUpperCase())) { res = true; } } i = i + 1; } } //проверка idRefClass if (res) { res = false var aValues = jObj.childJArray("idRefClass"); var i = 0 while(i < aValues.size() && !res) { var value = aValues.getLong(i); if (row.idRefClass == value) { res = true; } i = i + 1; } } if (res) { return true; } } ```