@via-profit/ability
v2.1.0
Published
Via-Profit Ability service
Readme
@via-profit/Ability
Набор сервисов, частично реализующих принцип Attribute Based Access Control
Этот сервис позволяет создавать правила и политики, применять их к данным и проверять доступ на их основе.
Содержание
Оглавление
Обзор
Состав пакета
AbilityRule— класс отдельного правилаAbilityRuleSet— класс группы правилAbilityPolicy— класс политикиAbilityResolver— управление политикамиAbilityMatch— константы состояния правил (pending,match,mismatch)AbilityCompare— способы сравнения (or,and)AbilityCondition— методы вычисления (equal,not_equal,more_than,less_than,in,not_inи др.)AbilityPolicyEffect— эффекты политики (deny,permit)AbilityParser— парсер конфигурационных правил (JSON)AbilityError— инстанс ошибок
Основные принципы
Работа сервиса основана на формировании правил, объединении их в политики и проверке доступа с их помощью.
Пример: необходимо запретить доступ пользователям, связанным с отделом менеджеров, за исключением администраторов.
- Менеджеры — если их отдел
managersили есть рольmanager - Администраторы — пользователи с ролью
administrator
Структура политики:

JSON-конфигурация:
{
"name": "Запрет доступа для менеджеров (исключение: администраторы)",
"compareMethod": "and",
"action": "order.update",
"effect": "deny",
"ruleSet": [
{
"name": "Менеджеры",
"compareMethod": "or",
"rules": [
{
"name": "Отдел managers",
"subject": "user.department",
"resource": "managers",
"condition": "in"
},
{
"name": "Роль manager",
"subject": "user.roles",
"resource": "manager",
"condition": "in"
}
]
},
{
"name": "Не администраторы",
"compareMethod": "and",
"rules": [
{
"name": "Нет роли administrator",
"subject": "user.roles",
"resource": "administrator",
"condition": "not in"
}
]
}
]
}Применение политики:
const jsonConfig = { ... };
AbilityPolicy.parse(jsonConfig).check({
user: {
department: 'managers',
roles: ['manager', 'coach'],
}
});Правила
Правила выполняют условие проверки и возвращают результат. Основная цель - выполнить сравнение переданных значений субъекта и ресурса, а затем вернуть результат такого сравнения.
Создание правила
Создать правило можно двумя способами: создание через конструктор класса и парсинг JSON-конфига правила.
При создании необходимо указать следующие параметры:
- id -
stringУникальный идентификатор. - name -
stringНазвание правила. - condition -
AbilityConditionОпределяет условия сравнения переданных данных - subject -
stringDot notation путь в проверяемом субъекте, например:user.name. - resource -
string | number | boolean | (string | number)[]Dot notation путь в проверяемом ресурсе, например:user.nameили значение, которое может быть строкой, числом, булеан значением или массивом строк или чисел.
Создание правила через конструктор класса:
import { AbilityRule, AbilityCondition } from '@via-profit/ability';
const rule = new AbilityRule({
id: '<rule-id>',
name: 'Пользователь из отдела managers',
subject: 'user.department',
resource: 'managers',
condition: AbilityCondition.equal
});
Создание правила через парсинг JSON-конфигурации:
import { AbilityRule } from '@via-profit/ability';
const rule = AbilityRule.parse({
"id": "<rule-id>",
"name": "Пользователь из отдела managers",
"subject": "user.department",
"resource": "managers",
"condition": "="
});
Проверка правила
Для проверки правила следует вызвать метод check класса AbilityRule передав объект проверяемого ресурса. Этот метод
вернёт экземпляр класса
AbilityMatch, при помощи методов которого можно определить имеется ли совпадение правила и переданных значений.
import { AbilityRule } from '@via-profit/ability';
const rule = AbilityRule.parse({
"id": "<rule-id>",
"name": "Пользователь из отдела managers",
"subject": "user.department",
"resource": "managers",
"condition": "="
});
const match = rule.check({
user: {
department: 'managers',
},
});
const is = match.isEqual(AbilityMatch.match); // true
Группы правил
Группы правил необходимы для объединения нескольких правил в группу. Основная цель - выполнить проверку каждого правила в группе и вернуть лишь один результат.
Создавая группу следует указывать метод сравнения (compareMethod), который необходим для вычисления значения всей
группы при проверке правил.
При создании необходимо указать следующие параметры:
- id -
stringУникальный идентификатор. - name -
stringНазвание группы. - compareMethod -
AbilityCompareСпособ сравнения правил в группе (orилиand).
Влияние compareMethod на результат вычисления группы:
or- Результат всей группы примет значениеmatch, если хотя бы одно из правил вернулоmatch.and- Результат всей группы примет значениеmatch, если все правила вернулиmatch.
Создание группы правил
Создать группу правил можно двумя способами: создание через конструктор класса и парсинг JSON-конфига группы.
Создание группы через конструктор класса:
import { AbilityRuleSet, AbilityCompare } from '@via-profit/ability';
const ruleSet = new AbilityRuleSet({
id: '<set-id>',
name: 'Название группы',
compareMethod: AbilityCompare.and,
});
// Добавление правил в группу
ruleSet.addRules([
new AbilityRule(...),
new AbilityRule(...),
]);
Создание группы через парсинг JSON-конфига группы:
import { AbilityRuleSet } from '@via-profit/ability';
const ruleSet = AbilityRuleSet.parse({
'id': '<set-id>',
'name': 'Название группы',
'compareMethod': 'and',
'rules': [
{
'id': '<rule-id>',
'name': 'Пользователь из отдела managers',
'subject': 'user.department',
'resource': 'managers',
'condition': '=',
},
],
});
Проверка группы правил
Для проверки группы правил следует вызвать метод check класса AbilityRuleSet передав объект проверяемого ресурса.
Этот метод вернёт экземпляр класса AbilityMatch, при помощи методов которого можно определить имеется ли совпадение
для группы и переданных значений.
import { AbilityRuleSet, AbilityCompare } from '@via-profit/ability';
const ruleSet = new AbilityRuleSet({
id: '<set-id>',
name: 'Название группы',
compareMethod: AbilityCompare.and,
}).addRules([
new AbilityRule(...),
new AbilityRule(...),
]);
const match = rule.check({ ... });
const is = match.isEqual(AbilityMatch.match);Политики
Политики включают в себя группы правил. Основная цель - выполнить проверку всех вложенных групп, сравнить результат выполнения групп и вернуть один единственный результат.
Создание политики
Создать политику можно двумя способами: создание через конструктор класса и парсинг JSON-конфига политики.
При создании политики необходимо указать следующие параметры:
- id -
stringУникальный идентификатор. - name -
stringНазвание политики. - action -
stringКлюч политики, в формате Dot notation, определяющий схожесть политик. В названии может применяться символ звездочки (*). Политики с одинаковым экшеном обрабатываются вместе как группа политик. Экшенusers.accountне считается похожим с экшеномusers.account.login, но в это же времяusers.account.*равен экшенуusers.account.login(из-за использования звездочки). - compareMethod -
AbilityCompareМетод сравнения групп правил, входящих в политику (orилиand) - effect -
AbilityPolicyEffectОпределяет итоговый результат всех вычислений (permitилиdeny). В слчае использования классаAbilityResolver(методenforce) последний выкинет исключениеAbilityError, если политика вернётdeny. Текст сообщенияAbilityErrorбудет соответствовать названию сработавшей политики. В остальных случаях ничего не произойдет. - ruleSet -
AbilityRuleSet[]Массив групп (см. Группы правил)
Замечание - Политика может быть запрещающей (effect = deny) и разрешающей (effect = permit). Если вам
необходимо ограничить какой-либо доступ, например, пользователю с недостаточными правами, то следует создавать политику
с эффектом deny.
Создание политики через конструктор класса:
import { AbilityPolicy, AbilityCondition } from '@via-profit/ability';
const policy = new AbilityPolicy({
id: '<policy-id>',
name: 'Пример политики',
effect: 'deny',
action: 'users.update',
compareMethod: 'and',
ruleSet: [
new AbilityRule({
id: '<rule-id>',
name: 'Пользователь является владельцем заказа',
subject: 'user.id',
resource: 'order.owner',
condition: AbilityCondition.equal
})
]
});
Создание политики через парсинг JSON-конфига:
import { AbilityPolicy } from '@via-profit/ability';
const policy = AbilityPolicy.parse({
"id": "bb758c1b-1015-4894-ba25-d23156e063cf",
"name": "Status hui",
"action": "order.status",
"effect": "deny",
"compareMethod": "and",
"ruleSet": [
{
"id": "9cc009e5-0aa9-453a-a668-cb3f418ced92",
"name": "Не администратор",
"compareMethod": "and",
"rules": [
{
"id": "4093cd50-e54f-4062-8053-2d3b5966fad3",
"name": "Нет роли администраторв",
"subject": "account.roles",
"resource": "administrator",
"condition": "<>"
}
]
}
]
});
Проверка политики
Для проверки политики правил следует вызвать метод check класса AbilityPolicy передав объект проверяемого ресурса.
Этот метод вернёт экземпляр класса AbilityMatch, при помощи методов которого можно определить имеется ли совпадение
для группы и переданных значений.
import { AbilityPolicy } from '@via-profit/ability';
const policy = AbilityPolicy.parse({ ... });
const match = policy.check({ ... });
const is = match.isEqual(AbilityMatch.match);Управление политиками
Для управления политиками реализован специальный класс AbilityResolver.
В случае, если вам необходимо запустить лишь разовую проверку данных, то данный раздел можно опустить.
AbilityResolver необходим для возможности запуска проверки разных политик в разный период времени.
Политики содержат название экшена (поле action) определяемого разработчиком. Запуск метода enforce или resolve
отберет из всех переданных политик только те, которые попадают под указанный экшен.
import { AbilityPolicy, AbilityPolicyConfig, AbilityResolver } from './AbilityPolicy';
const config: AbilityPolicyConfig[] = [...]; // массив различных политик (JSON)
const policies: AbilityPolicy<Resources>[] = config.map(cfg => AbilityPolicy.parse(cfg)); // массив уже созданных политик
// Проверка политик с экшеном `order.create`
// Варинат 1. Будет выброшено исключение AbilityError
// c название политики, которая вернула deny,
// либо ничего не произойдет, если ни одна из политик
// не вернет deny
new AbilityResolver(policies).enforce('order.create', {
user: { department: 'managers' },
});
// Вариант 2.
const isDeny = new AbilityResolver(policies)
.resolve('order.create', {
user: { department: 'managers' },
})
.isDeny();
if (isDeny) {
throw new AbilityError('Permission denied');
}
// Типы ресурсов, где каждый ключ будет являться название экшена
type Resources = {
['order.status']: { // <-- название экшена
readonly account: { // <-- данные ресурса
readonly roles: readonly string[];
};
};
['order.create']: {
readonly user: {
readonly department: string;
};
};
...
};
Пояснение примера выше. В данном примере создается массив всех политик, а затем запускается проверка политик подходящих
по указанному экшену, а самое главное, что при помощи типа Resources, который необходимо формировать
вручную, TypeScript подскажет какие именно данные следует передать вторым аргументом (ресурс).
