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

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

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

В механизме дискретного доступа используются два основных типа проверок:
- фильтрация объектов в списках — через `Скрипт для фильтрации объектных привилегий`;
- проверка доступа к конкретному объекту — через `Скрипт проверки строк по объектному кешу`.

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

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

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

Доступные макросы:
- `(&<Имя атрибута>)` — заменяется на имя атрибута (например, `t.id`);
- `(&params)` — массив строк с параметрами со всех ролей пользователя;
- `(&params_<Имя атрибута>)` — массив значений указанного атрибута;
- `(&CurrentUserID)` — заменяется на ID текущего пользователя;
- `(&CurrentUserName)` — заменяется на имя текущего пользователя.

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

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

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

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

Если бинд сформирован через `(&params_<Имя атрибута>)`, он хранит массив значений:
- Selection-бинд: `Array[Any]`;
- Anorm-бинд: `PgArray` (`StringPgArray`, `NumberPgArray`, `LongPgArray`, `DatePgArray`, `GidPgArray`).

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

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

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

Для получения Selection-биндов в метод `generateDiscreteAccessMacros` передайте:
```scala
useBinds = true
bindType = DiscreteAccessBindType.Selection
```

Методы для работы с параметрами:
- `asSelectionBind.addVar`;
- `asSelectionBind.setVar`.

Пример:
```scala
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)
    }
  }
}
```

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

```{admonition} Внимание
Следующий способ устарел:

~~~scala
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` передайте:

```scala
useBinds = true
bindType = DiscreteAccessBindType.Anorm
```

Метод для извлечения бинда:
- `asAnormBind.get` — возвращает `NamedParameter`.

Пример:
```scala
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()
}
```

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

```{admonition} Внимание
Следующий способ устарел:

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

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

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

Доступные переменные:
- `row`:
  - если администрируемый объект создан по классу — `ROP` проверяемого объекта (обёртка `JexlRop`);
  - если не по классу — JSON-представление строки дата-сета выборки;
- `params` — массив значений параметров со всех ролей пользователя:
  - для примитивного правила — примитивы (строка или число);
  - для составного — строки-JSON.

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

~~~scala
var rop = jexlRop.data()
~~~
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

**Параметры метода:**
- `spAcObject` — администрируемый объект;
- `spPriv` — привилегия;
- `rops` — список объектов, для которых проверяется привилегия;
- `isSuperUserHasPriv` — если `true`, суперпользователь обладает привилегией в любом случае;
- `spSubRule` — подправило.

Пример:
```scala
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` — игнорировать настройки дискретного доступа.

Пример включения:
```scala
Bs_BisObjAvi.processIdMCEvent(
  event,
  { id => setClientidBisObjFrom(editRef(), id) },
  params = Map(RefEventParam.UseDiscreteAccess -> 1)
)
```

Пример выключения:
```scala
Bs_BisObjAvi.processIdMCEvent(
  event,
  { id => setClientidBisObjFrom(editRef(), id) },
  params = Map(RefEventParam.UseDiscreteAccess -> 0)
)
```

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

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

Пример проверки подправила в выборке:
```scala
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))
    }
  }
}
```
