Дискретный доступ#

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

Настройка привилегии#

Административный объект#

  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]
  }
]
Правило без параметров#

Не содержит параметров.

При анализе прав пользователя, если хотя на одной роли будет правило без параметров, оно будет проверяться.

Параметры правила#

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

Если правило примитивное, то у него доступен для настройки только один параметр с системным именем «SimpleAcObjectRuleParam». Для составного правила доступны несколько параметров.

Динамическое определение значения параметров#

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

  • В карточке администрируемого объекта, на закладке с параметрами под динамическими параметрами написать jexl-скрипт, который возвращает массив строк Пример:

var idvUser = Btk_UserApi.getCurrentUserID(); //получаем id текущего пользователя
var res =[...];  //объявляем массив для сбора значений параметра
if (idvUser != null){
  var rvUser = Btk_UserApi.load(idvUser);
  var idvObjAttr = Btk_UserApi.getAttrValue(rvUser, "idObjAttr"); // получаем значение атрибута с именем "idObjAttr", хранящийся для текущего пользователя
  if (!empty idvBisObj){
    res.add(idvBisObj.toString()); // добавляем полученное значение в массив
  }
}
res; // возвращаем массив со значениями
  • На роли, на закладке с настройками дискретного доступа для нашего правила включаем флаг «Динамическое определение» у параметра.

Скрипт для фильтрации объектных привилегий#

Используется для фильтрации списков объектов, через наложение макроса универсального фильтра &DefUniFltMacros.

Доступные макросы внутри текста скрипта:

  • (&<Имя атрибута>)
    Будет заменено на указанное имя атрибута. Например, t.id
    Для фильтрации доступны все атрибуты дата-сета выборки шапки БО.

  • (&params)
    Массив строк, в котором будут параметры, собранные со всех ролей пользователя.

  • (&CurrentUserID)
    Будет заменено на ИД текущего пользователя.

  • (&CurrentUserName)
    Будет заменено на имя текущего пользователя.

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

Скрипт проверки строк по объектному кешу#

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

Представляет из себя jexl-скрипт, который возвращает true, если объект проходит условие, и false - если не проходит.

Доступные переменные:

  • row \

    • Если адм. объект создан по классу, то в этой переменной будет rop проверяемого объекта. Это не исходный rop, а его обертка JexlRop, которая позволяет обращаться к полям объекта.

      Совет

      Для получения исходного rop воспользуйтесь следующим примером:

      //в переменной jexlRop находится объект класса JexlRop
      var rop = jexlRop.data() 
      
    • Если адм. объект создан не по классу, то здесь будет json-представление строки дата-сета выборки.

  • params
    массив значений параметров, собранных со всех ролей пользователя.

    • если правило примитивное - то будет содержать примитивы (строка или число)

    • если правило составное - то будет содержать строки, каждая из которых - json

Роль#

  1. Открыть карточку роли или выбрать ее в списке;

  2. перейти на закладку Дискретный доступ;

  3. создать новую запись, выбрав адм. объект;

  4. указать значение параметров для ограничений;

  5. сопоставить объектные и элементарные привилегии с нужным значением параметров ограничений, заполнив поле Ограничение дискретного доступа

Ограничение доступа к объектам класса.#

Выдается через объектные привилегии с системными именами edit# и view#

Совет

Если таких привилегий нет у адм. объекта, то выполните его синхронизацию.

Дискретные ограничения применяются только, если:

  • это главная выборка адм. объекта (шапка БО);

  • это не супер-пользователь;

  • к адм. объекту приминаются настройки администрирования;

  • адм. объект имеет признак Дискретный доступ.

Определение объектов для проверки дискретных прав#

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

Алгоритм работы метода:

  1. Если выборка принадлежит классу (thisApi() != null), то для всех выделенных записей через поле первичного ключа загружаются провайдеры строк, из них через поле gidRoot_dz определяются шапки БО

  2. Если выборка не принадлежит классу (thisApi() == null), то по всем выделенным строкам собирается json-массив, содержащий на каждую строку json-объект, в котором ключ - имя атрибута, значение - значение атрибута.

Ограничение списков#

Используя значения параметров всех ролей и настроенные «Скрипты для фильтрации объектных привилегий» формируется условие ограничения и накладывается фильтрация через макрос &DefUniFltMacros.

Ограничение открытия карточек#

На открытие карточки используя значения параметров всех ролей выполняется jexl-скрипт, настроенный в Скрипт проверки строк по объектному кешу. Если у пользователя нет прав на объект, то будет выдана ошибка.

Ограничение на редактирование объектов#

На beforeEdit или delete проверяется наличие привилегии edit# (выполняется jexl-скрипт, настроенный в Скрипт проверки строк по объектному кешу). Если у пользователя нет прав на объект, то будет выдана ошибка.

Ограничение на объектные привилегии#

Если для объектной привилегии указано Ограничение дискретного доступа, то такая привилегия может быть проверена только в контексте какого-либо объекта. Для проверки объектных привилегий используется метод Btk_AdminPkg().hasObjPriv.

Пример:

if (Btk_AdminPkg().hasObjPriv("SomeObjectName", "SomePrivName", Seq(rop))) {
   //код, выполняемый при наличии привилегии
}

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

Ограничение на элементарные привилегии#

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

На загрузку выборки или перезагрузку ClassLoader-а происходит подписка на событие выполнения операции.

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

  1. Получение acRows

  2. Для полученных строк выполняется jexl-скрипт, настроенный в Скрипт проверки строк по объектному кешу

  3. Если прав нет, то выдается ошибка.

Принудительное включение и отключение проверки дискретного доступа#

Принудительное включение или отключение проверки дискретного доступа позволяет игнорировать настройки дискретного доступа на административном объекте. Признак устанавливается через параметр UseDiscreteAccess при открытии lookUp-формы через метод-обработчик.

Таким образом, установка параметра UseDiscreteAccess в значение равное 1 позволит принудительно применить настройки дискретного доступа, независимо от настройки на административном объекте. Пример принудительного включения проверки дискретного доступа:

Bs_BisObjAvi.processIdMCEvent(
      event,
      { id => setClientidBisObjFrom(editRef(), id) },
      params = Map(RefEventParam.UseDiscreteAccess -> 1)
)

При установка значения параметра UseDiscreteAccess в значение равное 0 настройки дискретного доступа будут игнорироваться, вне зависимости от настройки дискретного доступа на административном объекте. Пример принудительного выключения проверки дискретного доступа:

Bs_BisObjAvi.processIdMCEvent(
      event,
      { id => setClientidBisObjFrom(editRef(), id) },
      params = Map(RefEventParam.UseDiscreteAccess -> 0)
)

Если параметр не был передан при вызове метода-обработчика то проверка дискретного доступа осуществляется по настройке на административном объекте.

Параметр учитывается на всех методах-обработчиках (processRefEvent, processHLEvent, processIdMCEvent и т.д.).

Проверка прав доступа на объект после его редактирования#

  1. Открыть карточку Адм. объекта

  2. Установить признак Дискретный доступ

  3. Установить признак Проверять изменение прав объектов на сохранении

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

Подправила#

Особенности подправил#

Для каждого правила можно создать подправило. Оно будет использовать те же параметры, что и основное правило, но при этом автоматически нигде не проверяется. Программист должен самостоятельно внедрить соответствующую проверку в коде. После этого администратор, зная имя подправила, сможет задать для него нужные ограничения и проверки.

Для администрируемого объекта можно создать подправило, выполнив следующие шаги:#

  1. Открыть карточку администрируемого объекта.

  2. Перейти в закладку Дискретные ограничения доступа.

  3. Выбрать существующие правило или создать его.

  4. Под выбранным или созданным правилом добавить подправило.

  5. Назвать подправило именем, которое используется в коде для проверки доступа.

  6. Задать необходимые скрипты для подправила.

После выполнения этих шагов подправило будет создано и применено в соответствии с заданными параметрами.

Пример проверки подправила в выборке:

override protected def onRefresh: Recs = {
   lazy val sSubRule = "SomeSubRuleName"
   
   val ropav = SomeApi().refreshByParent(getIdMaster)

   if (needApplyDiscreteAccess) {
      lazy val bHasGroup = Btk_AcLib().hasBtkGroup(acObject, acObject)
      ropav.filter { rv =>
         val rows = AcRows(Seq(rv))

         val viewPriv = if (bHasGroup) Btk_AdminPkg.viewGroupPriv.get else Btk_AdminPkg.viewObjPriv.get
         val editPriv = if (bHasGroup) Btk_AdminPkg.editGroupPriv.get else Btk_AdminPkg.editObjPriv.get

         Btk_AdminPkg().hasObjPrivByRows(acObject.get, viewPriv, rows, spSubRule = sSubRule) ||
                 Btk_AdminPkg().hasObjPrivByRows(acObject.get, editPriv, rows, spSubRule = sSubRule)
      }.map { rop =>
         Row(rop.idJ, SomeApi().getHeadLine(rop.idJ))
      }
   } else {
      ropav.map { rop =>
         Row(rop.idJ, SomeApi().getHeadLine(rop.idJ))
      }
   }
}

Примеры скриптов#

Примитивное правило по атрибуту класса#

Правило значимое, тип данных строка. Фильтрация по like атрибута sCode

Скрипт для фильтрации объектных привилегий:

select 1
  from RplTst_ClassTree tt
  join (
     select cast(json_array_elements_text(cast((&params) 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((&params) 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((&params) 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((&params) 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((&params) 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((&params) 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;
  }

}