Реализация дискретного доступа#

В документе описана техническая реализация дискретного доступа: как работают скрипты фильтрации и бинды, как проверять права в коде, как управлять проверкой через параметры.
Материал отвечает на вопросы: какие макросы использовать в SQL-запросах, как получить бинды для выборок и Anorm-запросов, как проверить привилегию в контексте объекта, как принудительно включить или отключить проверку.

Скрипты фильтрации#

В механизме дискретного доступа используются два основных типа проверок:

  • фильтрация объектов в списках — через Скрипт для фильтрации объектных привилегий;

  • проверка доступа к конкретному объекту — через Скрипт проверки строк по объектному кешу.

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

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

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

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

  • (&<Имя атрибута>) — заменяется на имя атрибута (например, t.id);

  • (&params) — массив строк с параметрами со всех ролей пользователя;

  • (&params_<Имя атрибута>) — массив значений указанного атрибута;

  • (&CurrentUserID) — заменяется на ID текущего пользователя;

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

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

Бинды дискретного доступа#

Бинды параметризуют выборки и SQL-запросы. Доступны два вида:

  • Selection — установка параметров в выборку;

  • Anorm — установка параметров в SQL-запрос.

Примечание

Бинды задаются и вызываются только из кода.

Если бинд сформирован через (&params_<Имя атрибута>), он хранит массив значений:

  • Selection-бинд: Array[Any];

  • Anorm-бинд: PgArray (StringPgArray, NumberPgArray, LongPgArray, DatePgArray, GidPgArray).

Если бинд сформирован от (&params), (&CurrentUserID) или (&CurrentUserName), он хранит строковое значение. Для обоих типов доступен метод value, возвращающий NString.

Внимание

Вызов метода value для биндов с массивом значений не поддерживается и приведёт к UnsupportedOperationException.

Использование Selection-биндов

Для получения Selection-биндов в метод generateDiscreteAccessMacros передайте:

useBinds = true
bindType = DiscreteAccessBindType.Selection

Методы для работы с параметрами:

  • asSelectionBind.addVar;

  • asSelectionBind.setVar.

Пример:

override def beforeOpen(): Unit = {
  super.beforeOpen()
  val discrAccess = Btk_AdminPkg().generateDiscreteAccessMacros(
    spAcObject = "Stk_RegistryGdsMov",
    tableAlias = "someTableAlias",
    useBinds = true,
    bindType = DiscreteAccessBindType.Selection
  )

  discrAccess.binds.foreach { bind =>
    if (!varExists(bind.name.get)) {
      bind.asSelectionBind.addVar(selection)
    } else {
      bind.asSelectionBind.setVar(selection)
    }
  }
}

Внимание

Вызов asAnormBind у Selection-бинда запрещён и приведёт к UnsupportedOperationException.

Внимание

Следующий способ устарел:

discrAccess.binds.foreach { bind =>
  if (!varExists(bind.name.get)) {
    selection.addVar(bind.name.get, bind.value, FieldType.ftString)
  } else {
    selection.setVar(bind.name.get, bind.value)
  }
}

Использование Anorm-биндов

Для получения Anorm-биндов в метод generateDiscreteAccessMacros передайте:

useBinds = true
bindType = DiscreteAccessBindType.Anorm

Метод для извлечения бинда:

  • asAnormBind.get — возвращает NamedParameter.

Пример:

override protected def onRefresh: Recs = {
  val discrAccess = Btk_AdminPkg().generateDiscreteAccessMacros(
    spAcObject = Stk_RegistryGdsMovApi().entityName,
    tableAlias = "someTableAlias",
    useBinds = true,
    bindType = DiscreteAccessBindType.Anorm
  )

  val bindBuffer = ArrayBuffer[NamedParameter]()
  discrAccess.binds.foreach { discreetBind =>
    bindBuffer += discreetBind.asAnormBind.get
  }

  for (sel <- new ASelect {
    val id = asNLong("id")
    SQL(
      s"""select someTableAlias.id as id
         |  from Stk_RegistryGdsMov someTableAlias
         | where
         | ${discrAccess.text}""".stripMargin)
    on(bindBuffer.toArray: _*)
  }) yield sel.id()
}

Внимание

Вызов asSelectionBind у Anorm-бинда запрещён и приведёт к UnsupportedOperationException.

Внимание

Следующий способ устарел:

val bindBuffer = ArrayBuffer[NamedParameter]()
macros.binds.foreach { discreetBind =>
  bindBuffer += NamedParameter(discreetBind.name.get, discreetBind.value)
}

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

JEXL-скрипт, возвращающий true, если объект проходит условие, и false — если не проходит.
Используется для проверки ограничения по объектам, используя объектный кеш.

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

  • row:

    • если администрируемый объект создан по классу — ROP проверяемого объекта (обёртка JexlRop);

    • если не по классу — JSON-представление строки дата-сета выборки;

  • params — массив значений параметров со всех ролей пользователя:

    • для примитивного правила — примитивы (строка или число);

    • для составного — строки-JSON.

Совет

Для получения исходного ROP из JexlRop:

var rop = jexlRop.data()

Проверка прав в коде#

Определение объектов для проверки

При проверке дискретных привилегий строки определяются методом acRows.

Алгоритм:

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

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

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

На основе значений параметров всех ролей и настроенных скриптов фильтрации формируется условие ограничения, которое накладывается через макрос &DefUniFltMacros.

Примечание

Для списков также может использоваться «Скрипт проверки строк по объектному кешу», если в нём написан объектный запрос, а не SQL.

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

При открытии карточки выполняется JEXL-скрипт из «Скрипта проверки строк по объектному кешу». Если прав нет — выводится ошибка.

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

На beforeEdit или delete проверяется привилегия edit# через JEXL-скрипт. При отсутствии прав — ошибка.

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

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

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

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

Параметры метода:

  • spAcObject — администрируемый объект;

  • spPriv — привилегия;

  • rops — список объектов, для которых проверяется привилегия;

  • isSuperUserHasPriv — если true, суперпользователь обладает привилегией в любом случае;

  • spSubRule — подправило.

Пример:

def hasObjPriv(
  spAcObject: String,
  spPriv: String,
  rops: Seq[Rop[_ <: AnyRef, _ <: Aro[_ <: AnyRef]]],
  isSuperUserHasPriv: Boolean,
  spSubRule: NString
): Boolean

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

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

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

Алгоритм проверки:

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

  2. Выполнение JEXL-скрипта для полученных строк.

  3. При отсутствии прав — ошибка.

Принудительное управление проверкой#

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

  • Значение 1 — принудительно применить настройки дискретного доступа.

  • Значение 0 — игнорировать настройки дискретного доступа.

Пример включения:

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

Пример выключения:

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

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

Пример проверки подправила#

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

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))
    }
  }
}