# Выражения

В системе контроля доступа в формате [ABAC (Attribute Based Access Control)](https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0_%D0%BD%D0%B0_%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B5_%D0%B0%D1%82%D1%80%D0%B8%D0%B1%D1%83%D1%82%D0%BE%D0%B2) для получения решения о доступе **Разрешить/Запретить** выполняется вычисление **Политик**. Кроме свойств основных элементов политик (**Правило**, **Политика** и **Набор Политик**) на результат вычисления влияют логические выражения, которые в этих элементах содержатся.

Сами логические выражения записываются в виде строк для упрощения описания элементов **Политик**. Это подразумевает реализацию небольшого языка, а следовательно и парсера, и логики вычисления этих выражений.

Поскольку контроль доступа требуется в разных компонентах, которые реализованы на разных ЯП, необходимо иметь несколько реализаций языка выражений (под каждый язык). Произвольно взятое выражение должно обрабатываться одинаково в любой реализации для корректного вычисления. Потому, в качестве источника истины необходима общая спецификация, на которую нужно ориентироваться при его реализации. Описанию этой спецификации и посвящена данная статья.

## Грамматика

Ниже описаны лексемы, используемые в выражениях.

| Лексема              | Примеры                                                             | Описание                                                                                                                                                                                                                                                                                                                                                                                                                        |
|----------------------|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Строковые литералы   | `"строка"`<br><br>`'строка'`<br><br>`"строка с \"экранированием\""` | Строки в кавычках либо одинарных, либо в двойных.<br><ul><li>Смешанные кавычки (`'строка"`) запрещены</li><li>Двойные кавычки внутри одинарных (и наоборот) должны интерпретироваться как обычный символ</li><li>Экранирование через backslash доступно только для кавычек, с которыми записана строка: либо `"\"строка\""`, либо `'\'строка\''`, но не `"\'строка\'"`</li><li>В остальных случаях backslash запрещен</li></ul> |
| Логические константы | `true`<br><br>`false`                                               | Должны быть регистронезависимы.                                                                                                                                                                                                                                                                                                                                                                                                 |
| Null-литерал         | `null`                                                              | Должен быть регистронезависим.                                                                                                                                                                                                                                                                                                                                                                                                  |
| Имена                | `foo`<br><br>`bar`<br><br>`fizz_buzz`<br><br>`FIZZ_BUZZ`            | Предполагаются как имена функций или как ключи атрибута.<br><ul><li>Должны обрабатываться как регистронезависимые</li><li>Допустимые символы: A-Z, a-z и нижнее подчеркивание</li></ul>                                                                                                                                                                                                                                         |
| Точка                | `.`                                                                 | Является разделителем ключей атрибута.                                                                                                                                                                                                                                                                                                                                                                                          |
| Запятая              | `,`                                                                 | Предназначена для перечисления элементов списков и аргументов при вызове функций.                                                                                                                                                                                                                                                                                                                                               |
| Круглые скобки       | `()`                                                                | Предназначены для вызова функции.                                                                                                                                                                                                                                                                                                                                                                                               |
| Квадратные скобки    | `[]`                                                                | Предназначены для обозначения списков.                                                                                                                                                                                                                                                                                                                                                                                          |
| Операторы сравнения  | `<`, `>`, `=`, `<=`, `>=` `!=`                                      | Бинарные операторы сравнения.                                                                                                                                                                                                                                                                                                                                                                                                   |
| Операторы включения  | `IN`<br><br>`NOT IN`                                                | Бинарные операторы включения/невключения (для проверки наличия или отсутствия значения в списке).<br><ul><li>Должны быть регистронезависимыми</li><li>`NOT IN` не должен быть чувствительным к пробелам посередине</li></ul>                                                                                                                                                                                                    |

Ниже приведены составные синтаксические конструкции.

| Название             | Примеры                                                                                         | Описание                                                                                                                                                                                                                                                                                                                                                                                                                         |
|----------------------|-------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Обращение к атрибуту | `subj`<br><br>`subg.type`                                                                       | Одно или несколько имён, разделённых точками                                                                                                                                                                                                                                                                                                                                                                                     |
| Список               | `[]`<br><br>`[1, 2, 3]`<br><br>`['foo', 'bar']`                                                 | Перечисления значений через запятую, обёрнутые в квадратные скобки.<br><ul><li>в списках допускаются только литералы</li><li>Для простоты, допускаются значения разных типов в одном списке</li></ul>                                                                                                                                                                                                                            |
| Вызов функции        | `not(false)`<br><br>`length([])`<br><br>`intersects(subj.roles, ['role_a', 'role_b'])`          | Имя функции и перечисление аргументов через запятую, обёрнутые в круглые скобки.<br><ul><li>Допускается отсутствие аргументов</li><li>В качестве аргументов могут выступать любые значения (см. ниже)</li></ul>                                                                                                                                                                                                                  |
| Условие              | `subj.type = 'user'`<br><br>`length([1, 2, 3]) >= 3`<br><br>`subj.role in ['role_a', 'role_b']` | Применяет бинарный оператор (оператора сравнения или включения) к паре значений.<br><ul><li>Условие может быть использовано только как целое выражение. Его нельзя передавать как аргумент при вызове функции и т. п.</li><li>В качестве операндов могут выступать любые значения (см. ниже)</li><li>Операторы имеют разные требования по типам. Эти требования описаны в секции <a href="#calculation">Вычисление</a></li></ul> |

Значением считается: литерал, обращение к атрибуту, список и вызов функции.

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

## Модель данных, атрибуты

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

  * `integer` - целое число

  * `float` - вещественное число

  * `string` - символьные строки

  * `boolean` - константы `true` и `false`

  * `null` - константа `null`

Также есть составной тип данных "список". Списки должны подчиняться следующим правилам:

  * Список не следует считать упорядоченным

  * Список может содержать значения разных типов

Язык выражений позволяет записать значения каждого из атомарных типов и списки из атомарных значений.

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

Сущность может быть как обобщенной - известен только тип сущности, либо конкретной - известен как тип, так и некий уникальный идентификатор.

Сущность может быть использована, например, в качестве значения атрибутов субъекта и объекта доступа (например, `subj = (тип="user", id=12)`), для указания некой связанной с субъектом сущности (например, `subj.office = (тип="office", id=2)`) и т. п. Список сущностей может быть использован для указания множества сущностей, например, связанных с субъектом: `subj.departments = [(тип="department", id=1), (тип="department", id=2)]`.

Для сущностей должны быть определены операторы проверки на равенство и неравенство: если у сущностей одинаковый тип и одинаковый не-null идентификатор, тогда это одна и та же сущность.

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

## Вычисление

### Операторы

**Равенство/неравенство**:

Проверки:

  * на равенство: `left = right`

  * на неравенство: `left != right`

Эти операторы должны быть определены для следующих вариантов сравнения:

  * `(integer | float) = (integer | float)` – сравнение дробного с целым допустимо

  * `string = string`

  * `boolean = boolean`

  * `(list | integer | float | string | boolean | null) = null` – и наоборот

  * `сущность = сущность` – только для конкретных сущностей

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

Примеры сравнения значений:

| Выражение            | Результат вычисления                                                                            |
|----------------------|-------------------------------------------------------------------------------------------------|
| `subj.type = 'user'` | `true`, если тип субъекта это `user`, иначе `false`                                             |
| `subj.type = 42`     | *Ошибка типа*, т. к. значение атрибута соответствует типу `string`, а правый операнд - `integer` |
| `[] != null`         | `true`, т. к. список это не `null`                                                              |
| `[1, 2] = [1, 2]`    | *Ошибка типа*, т. к. сравнение списков не определено                                            |
| `1 = true`           | *Ошибка типа*, т. к. `true` это не то же самое, что число `1`                                   |

Примеры сравнения сущностей:

| subj                  | obj                         | Выражение    | Результат вычисления                                                          |
|-----------------------|-----------------------------|--------------|-------------------------------------------------------------------------------|
| `(тип="user", id=12)` | `(тип="user", id=12)`       | `subj = obj` | `true`, т. к. тип и `id` сущностей совпадает                                  |
| `(тип="user", id=12)` | `(тип="department", id=12)` | `subj = obj` | `false`, т. к. тип сущностей не совпадает                                     |
| `(тип="user", id=12)` | `(тип="user")`              | `subj = obj` | *Ошибка типа*, т. к. сравнение конкретной сущности с обобщенной не определено |

**Сравнения порядка**

Проверки:

  * `value_a > value_b`

  * `value_a >= value_b`

  * `value_a < value_b`

  * `value_a <= value_b`

Эти операторы должны быть определены только для `integer` и `float`. Сравнения `integer` и `float`, как и для проверки на равенство, допускаются.

**Включение/невключение**

Проверки:

  * `atomic_value IN some_list`

  * `atomic_value NOT IN some_list`

Левым операндом может быть значение любого атомарного типа или сущностью, правым операндом – список.

Оператор `IN` возвращает `true`, если список содержит указанное в значение. Оператор `NOT IN`, наоборот, возвращает `true`, если указанного в списке значения нет.

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

Примеры включения для списков значений:

| Выражение                        | Результат вычисления                            |
|----------------------------------|-------------------------------------------------|
| `'foo' IN ['foo', 'bar']`        | `true`                                          |
| `'foo' NOT IN [1, 2, 3, 'test']` | `true`, т. к. строка `foo` отсутствует в списке |

Примеры проверки включения для списка сущностей; для `subj.departments = [(тип="department", id=1), (тип="department", id=2)]` и `obj = (тип="department", id=1)`:

| Выражение                 | Результат вычисления                                 |
|---------------------------|------------------------------------------------------|
| `obj IN subj.departments` | `true`, т. к. в отдел с `id=1` присутствует в списке |
| `1 IN subj.departments`   | `false`, т. к. в `subj.departments` нет числа `1`    |

### Функции

Секция определяет набор функций, доступных для использования в выражениях.

Функция имеет имя и сигнатуру. Сигнатура описывает количество принимаемых аргументов, их тип и тип результата.

Если типы передаваемых аргументов не совпадают с указанными, результатом вычисления должна быть *Ошибка типов*.

В таблице ниже предоставлены реализованные функции:

| Имя          | Сигнатура                 | Примеры                                                                                                                                        | Описание                                                                                                                                                                              |
|--------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `not`        | `(boolean) -> boolean`    | `not(false) -> true`<br><br>`not([1, 2, 3]) -> ошибка`                                                                                         | Инвертирует булево значение                                                                                                                                                           |
| `length`     | `(list) -> integer`       | `length([]) -> 0`<br><br>`length(['a', 'b', 'c']) -> 3`<br><br>`length('string') -> ошибка`                                                    | Возвращает количество элементов в списке                                                                                                                                              |
| `intersects` | `(list, list) -> boolean` | `intersects(['a', 'b'], ['b', 'c']) -> true`<br><br>`intersects([], ['a', 'b', 'c']) -> false`<br><br>`intersects(['a', 'b'], 'ab') -> ошибка` | Возвращает `true`, если пара списков содержит хотя бы одно одинаковое значение.<br><br>Проверка на равенство значений должна выполняться по тем же правилам, что и для оператора `=`. |

### Выражения

В качестве выражения может выступать либо условие, либо значение. Значение это литерал, список или вызов функции. Но результатом вычисления выражения должно быть значение типа `boolean`. Потому, если конструкция, выступающая в роли выражения, возвращает что-то отличное от `true` или `false`, результатом должна быть *Ошибка типа*.

Примеры выражений:

| Выражение               | Результат     | Причина                                                  |
|-------------------------|---------------|----------------------------------------------------------|
| `true`                  | `true`        | -                                                        |
| `1`                     | *Ошибка типа* | `1` - это не `true`                                      |
| `'string'`              | *Ошибка типа* | `string` - это не `boolean`                              |
| `'string' != ''`        | `true`        | Проверка на неравенство возвращает `true`                |
| `[1, 2, 3]`             | *Ошибка типа* | Список - это не `boolean`                                |
| `length([1, 2, 3])`     | *Ошибка типа* | `length` возвращает `integer`                            |
| `length([1, 2, 3]) > 0` | `true`        | Сравнение `integer > integer` здесь вернет `true`        |
| `obj.some_number`       | *Ошибка типа* | Указанный атрибут - это число                            |
| `obj.is_deleted`        | `false`       | Указанный атрибут - это `boolean` (со значением `false`) |

---

Источник: https://webim.ru/kb/for-admins/access-control-system/expressions.html
