Реализация дискретного доступа#
В документе описана техническая реализация дискретного доступа: как работают скрипты фильтрации и бинды, как проверять права в коде, как управлять проверкой через параметры.
Материал отвечает на вопросы: какие макросы использовать в SQL-запросах, как получить бинды для выборок и Anorm-запросов, как проверить привилегию в контексте объекта, как принудительно включить или отключить проверку.
Скрипты фильтрации#
В механизме дискретного доступа используются два основных типа проверок:
фильтрация объектов в списках — через
Скрипт для фильтрации объектных привилегий;проверка доступа к конкретному объекту — через
Скрипт проверки строк по объектному кешу.
Первый тип проверки используется для ограничения данных в выборках и запросах. Второй тип применяется при открытии карточек, переходах по ссылкам и других проверках, где доступ должен определяться для конкретного объекта.
Скрипт для фильтрации объектных привилегий
Используется для фильтрации списков объектов через макрос универсального фильтра &DefUniFltMacros.
Доступные макросы:
(&<Имя атрибута>)— заменяется на имя атрибута (например,t.id);(¶ms)— массив строк с параметрами со всех ролей пользователя;(¶ms_<Имя атрибута>)— массив значений указанного атрибута;(&CurrentUserID)— заменяется на ID текущего пользователя;(&CurrentUserName)— заменяется на имя текущего пользователя.
Запрос обычно представляет собой EXISTS, в котором джойнится таблица класса и накладываются условия фильтрации через массив значений.
Бинды дискретного доступа#
Бинды параметризуют выборки и SQL-запросы. Доступны два вида:
Selection — установка параметров в выборку;
Anorm — установка параметров в SQL-запрос.
Примечание
Бинды задаются и вызываются только из кода.
Если бинд сформирован через (¶ms_<Имя атрибута>), он хранит массив значений:
Selection-бинд:
Array[Any];Anorm-бинд:
PgArray(StringPgArray,NumberPgArray,LongPgArray,DatePgArray,GidPgArray).
Если бинд сформирован от (¶ms), (&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.
Алгоритм:
Если выборка принадлежит классу (
thisApi() != null), для выделенных записей через первичный ключ загружаются провайдеры строк, из них черезgidRoot_dzопределяются шапки БО.Если выборка не принадлежит классу (
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 подписывается на событие выполнения операции.
Алгоритм проверки:
Получение
acRows.Выполнение JEXL-скрипта для полученных строк.
При отсутствии прав — ошибка.
Принудительное управление проверкой#
Параметр 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))
}
}
}