@via-profit/ability 1.1.0 → 2.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,123 +1,186 @@
1
1
  # @via-profit/Ability
2
2
 
3
- > Набор сервисов, частичyо реализующих принцип [Attribute Based Access Control](https://en.wikipedia.org/wiki/Attribute-based_access_control)
3
+ > Набор сервисов, частично реализующих
4
+ > принцип [Attribute Based Access Control](https://en.wikipedia.org/wiki/Attribute-based_access_control)
5
+
6
+ Данный сервис позволяет создать правила или политики, а затем применить их по отношению к каким-либо данным для того
7
+ чтобы проверить наличие доступа к этим данным.
8
+
9
+ # Draft
4
10
 
5
11
  ## Содержание
6
12
 
7
- 1. [Описание и общие принципы](#overview)
13
+ 1. [Описание и общие принципы](#overview)
14
+
15
+ 1.1 [Состав пакета](#structure)
16
+
17
+ 1.2 [Общие принципы](#principles)
8
18
 
9
- 2. [Правила](#rules)
19
+ 2. [Правила](#rules)
10
20
 
11
- 2.1 [Синтаксис правил](#rule-syntax)
21
+ 2.1 [Синтаксис правил](#rule-syntax)
12
22
 
13
- 2.2 [Примеры](#rule-recipes)
23
+ 2.2 [Примеры](#rule-recipes)
14
24
 
15
- 2.3 [Класс AbilityRule](#ability-rule-class)
25
+ 2.3 [Класс AbilityRule](#ability-rule-class)
16
26
 
17
- 3. [Политики](#policies)
27
+ 3. [Политики](#policies)
18
28
 
19
- 3.1 [Синтаксис правил](#policy-syntax)
29
+ 3.1 [Синтаксис правил](#policy-syntax)
20
30
 
21
- 3.2 [Примеры](#policy-recipes)
31
+ 3.2 [Примеры](#policy-recipes)
22
32
 
23
- 3.3 [Класс AbilityPolicy](#ability-policy-class)
33
+ 3.3 [Класс AbilityPolicy](#ability-policy-class)
24
34
 
25
- 4. [Создание политики из конфига](#policy-config)
35
+ 4. [Создание политики из конфига](#policy-config)
26
36
 
27
37
  ## Описание и общие принципы <a name="overview"></a>
28
38
 
29
- Данный сервис позволяет создать правила или политики, а затем применить их по отношению к каким-либо данным для того чтобы проверить наличие доступа к этим данным.
39
+ ### Состав пакета <a name="structure"></a>
30
40
 
31
- Принцип работы основан на формировании правил, объединения их в политики и запуске политик и/или правил. Правила можно группировать в политики, а политики могут содержать вложенные политики.
41
+ - `AbilityPolicy` - класс политики
42
+ - `AbilityRuleSet` - класс группы правил
43
+ - `AbilityRule` - класс правила
44
+ - `AbilityParser` - парсер конфигурационных правил из/в JSON
45
+ - `AbilityResolver` - класс управления политиками
46
+ - `AbilityMatch` - Класс констант для определния соответствия правил (`PENDING` `MATCH` `MISMATCH`)
47
+ - `AbilityPolicyEffect` - Класс констант для определния эффекта политик (`DENY` `PERMIT`)
48
+ - `AbilityCompare` - Класс констант для определния способа сравнения правил и групп (`OR` `AND`)
49
+ - `AbilityCondition` - Класс констант для определния метода вычисления правил (`EQUAL` `NOT_EQUAL` `MORE_THAN`
50
+ `LESS_THAN` `LESS_OR_EQUAL` `MORE_OR_EQUAL` `IN` `NOT_IN`)
51
+ - `AbilityError` - Класс инстанса ошибки
52
+ - `AbilityCode` - Базовый клас констанкт
32
53
 
33
- После создания правил и политик их необходимо запустить для проверки соответствия передаваемым данным. Если переданные данные соответствуют всем правилам политики, то такая политика считается разрешенной.
54
+ ### Общие принципы <a name="principles"></a>
34
55
 
35
- ## Правила <a name="rules"></a>
56
+ Принцип работы основан на формировании правил, объединения их в политики и запуске этих политик.
36
57
 
37
- Предположим в системе имееются следующие данные: пользователь; отчеты.
58
+ Предположим, что необходимо запретить доступ пользователям из отдела менеджеров и причастным к ним, за исключением
59
+ администраторов. Пользователи относятся к отделу менеджеров, если их отдел называется `managers`. Причастные
60
+ пользователи являются те пользователи, среди ролей которых имеется роль `manager`. Администраторы - пользователи имеющие
61
+ соответствующую роль (`administrator`).
38
62
 
39
- ```ts
40
- // Пользователь
41
- const user = {
42
- id: '123',
43
- name: 'Oleg',
44
- age: 26,
45
- departament: 'analytics',
46
- };
63
+ Для решения поставленной задачи нам понадобится объединить несколько правил в группы согласно изображению ниже:
47
64
 
48
- // Пользовательские отчёты
49
- const reports = [
50
- {
51
- id: '1',
52
- type: 'analytics',
53
- },
54
- {
55
- id: '2',
56
- type: 'expenses',
57
- },
58
- ];
65
+ ![ability-01.drawio.png](assets/ability-01.drawio.png)
66
+
67
+ JSON представление такой политики будет иметь следующий вид:
68
+
69
+ ```json
70
+ {
71
+ "name": "Запретить доступ пользователям из отдела менеджеров и причастным к ним за исключением администраторов",
72
+ "compareMethod": 1,
73
+ "action": "order.update",
74
+ "effect": 0,
75
+ "ruleSet": [
76
+ {
77
+ "name": "Менеджеры",
78
+ "compareMethod": 0,
79
+ "rules": [
80
+ {
81
+ "name": "Пользователь состоит в отделе managers",
82
+ "matches": [
83
+ "user.department",
84
+ "=",
85
+ "managers"
86
+ ]
87
+ },
88
+ {
89
+ "name": "Пользователь имеет роль manager",
90
+ "matches": [
91
+ "user.roles",
92
+ "in",
93
+ "manager"
94
+ ]
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ "name": "Не администраторы",
100
+ "compareMethod": 1,
101
+ "rules": [
102
+ {
103
+ "name": "Пользователь не является администратором",
104
+ "matches": [
105
+ "user.roles",
106
+ "not in",
107
+ "administrator"
108
+ ]
109
+ }
110
+ ]
111
+ }
112
+ ]
113
+ }
59
114
  ```
60
115
 
61
- Пусть необходимо создать правило, которое будет разрешать доступ пользователю только к тем отчетам, тип которых соответствует его отделу. Тогда в правило будет записано что субъект имеет поле `departament` и оно должно быть равно значению поля `type` ресурса:
116
+ Теперь для того чтобы применить (проверить политику) необходимо её восстановить из JSON (метод `parse`) и запустить
117
+ проверку (метод `check`):
62
118
 
63
119
  ```ts
64
- import { AbilityRule } from '@via-profit/ability';
65
-
66
- // Создание правила
67
- const rule = new AbilityRule('Пользователь должен быть из отдела аналитики', [
68
- 'subject.departament', // субъект и адрес поля, в которое записано название отдела
69
- '=', // оператор сравнения
70
- 'resource.type', // Ресурс и адрес поля, в которое записан тип отчета
71
- ]);
72
- ```
120
+ const jsonConfig = { ... }
73
121
 
74
- Адрес поля `subject.departament`, представляет собой запись в формате [dot notation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Basics#dot_notation), которая обязательно должна начинаться с `subject`, что указывает, что для сравнения данных будет использоваться поле departament именно у субъекта (в данном примере субъект - это объект пользователя).
75
- Для сравнения двух отделов будет использоваться оператор сравнения `=`.
76
- Адрес поля `resource.type` тоже представляет собой декларацию пути с префиксом `resource` в формате [dot notation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Basics#dot_notation).
122
+ const result = AbilityPolicy.parse(jsonConfig).check({
123
+ user: {
124
+ department: 'managers',
125
+ roles: ['manager', 'couch']
126
+ }
127
+ });
128
+ ````
77
129
 
78
- Теперь, для того чтобы проверить правило, необходимо выполнить метод `check` передав необходимые субъект и ресурс. В данном примере субъект - это объект с данными пользователя, а ресурс - это один из отчетов.
130
+ ## Правила <a name="rules"></a>
79
131
 
80
- ```ts
81
- const report = reports[0];
82
- const isPermit = rule.check(user, report); // true
83
- ```
132
+ Правила позволяют создавать условия, которые в последствии будут сгруппированы в группу правил, а те, в свою очередь, в
133
+ политику.
84
134
 
85
- Метод `check` класса `AbilityRule` вернёт `permit` в случае, если переданные **user** и **report** отвечают требованием правила и `deny` в противном случае.
135
+ Класс `AbilityRule`
86
136
 
87
- Полный листинг проверки правила:
137
+ Правило определяется необязательных названием и обязательным массивом `matches`, который и несёт самую важную роль в
138
+ данном модуле.
139
+
140
+ _Пример простого правила:_
88
141
 
89
142
  ```ts
90
- import { AbilityRule } from '@via-profit/ability';
143
+ import { AbilityRule, AbilityCondition } from '@via-profit/ability';
144
+
145
+ const rule = new AbilityRule({
146
+ name: 'Simple rule',
147
+ matches: [
148
+ 'user.department', // dot notation путь до проверяемого поля
149
+ AbilityCondition.EQUAL, // определяет метод сравнения "="
150
+ 'managers' // искомое значение
151
+ ],
152
+ });
91
153
 
92
- // Создание правила
93
- const rule = new AbilityRule('Пользователь должен быть из отдела аналитики', [
94
- 'subject.departament', // субъект и адрес поля, в которое записано название отдела
95
- '=', // оператор сравнения
96
- 'resource.type', // Ресурс и адрес поля, в которое записан тип отчета
97
- ]);
154
+ ```
98
155
 
99
- // Пользователь
100
- const user = {
101
- id: '123',
102
- name: 'Oleg',
103
- age: 26,
104
- departament: 'analytics',
105
- };
156
+ Правило выше будет выполнено в случае, если среди проверямых данных будет ключ `user`, содержащий ключ `department`,
157
+ значение которого будет равно (`=`) `managers`
106
158
 
107
- // Пользовательские отчёты
108
- const reports = [
109
- {
110
- id: '1',
111
- type: 'analytics',
112
- },
113
- {
114
- id: '2',
115
- type: 'expenses',
116
- },
117
- ];
159
+ Адрес поля `user.department`, представляет собой запись в
160
+ формате [dot notation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Basics#dot_notation), что
161
+ указывает, что для сравнения данных будет использоваться поле department ресурса `user`.
162
+ Для сравнения двух отделов будет использоваться оператор сравнения `=`.
163
+
164
+ Теперь, для того чтобы проверить правило, необходимо выполнить метод `check` передав необходимые ресурсы:
165
+
166
+ ```ts
167
+ import { AbilityRule, AbilityCondition } from '@via-profit/ability';
168
+
169
+ const rule = new AbilityRule({
170
+ name: 'Simple rule',
171
+ matches: [
172
+ 'user.department', // dot notation путь до проверяемого поля
173
+ AbilityCondition.EQUAL, // определяет метод сравнения "="
174
+ 'managers' // искомое значение
175
+ ],
176
+ });
177
+
178
+ const result = rule.check({
179
+ user: {
180
+ department: 'managers',
181
+ }
182
+ });
118
183
 
119
- rule.check(user, reports[0]); // --> вернет true
120
- rule.check(user, reports[1]); // --> вернет false, так как user.departament не соответствует значение поля type отчета
121
184
  ```
122
185
 
123
186
  ### Синтаксис правил <a name="rule-syntax"></a>
@@ -127,20 +190,21 @@ rule.check(user, reports[1]); // --> вернет false, так как user.depa
127
190
  ```ts
128
191
  type AbilityRuleMatches = [
129
192
  string, // dot notation путь до поля в субъекте или энвайронменте
130
- '=' | '<>' | '>' | '<' | '<=' | '>=' | 'in', // оператор сравнения
131
- string | number | boolean, // dot notation путь до поля в ресурсе или энвайронменте или просто значение
193
+ '=' | '<>' | '>' | '<' | '<=' | '>=' | 'in', // оператор сравнения
194
+ string | number | boolean, // dot notation путь до поля в ресурсе или энвайронменте или просто значение
132
195
  ];
133
196
  ```
134
197
 
135
198
  ### Операторы сравнения <a name="rule-operators"></a>
136
199
 
137
- `=` Прямое сравнение
138
- `<>` Не равно
139
- `>` Больше
140
- `<` Меньше
141
- `<=` Меньше или равно
142
- `>=` Больше или равно
143
- `in` Вхождение в массив. Позволяет проверять вхождение значения в массив
200
+ - `AbilityCondition.EQUAL` (`=`) Прямое сравнение
201
+ - `AbilityCondition.NOT_EQUAL` (`<>`) Не равно
202
+ - `AbilityCondition.MORE_THAN` (`>`) Больше
203
+ - `AbilityCondition.LESS_THAN` (`<`) Меньше
204
+ - `AbilityCondition.LESS_OR_EQUAL` (`<=`) Меньше или равно
205
+ - `AbilityCondition.MORE_OR_EQUAL` (`>=`) Больше или равно
206
+ - `AbilityCondition.IN` (`in` Вхождение в массив. Позволяет проверять вхождение значения в массив
207
+ - `AbilityCondition.NOT_IN` (`not in` Нет вхождения в массив. Позволяет проверять отсутствие значения в массив
144
208
 
145
209
  ### Примеры правил <a name="rule-recipes"></a>
146
210
 
@@ -196,226 +260,7 @@ const isPermit = new AbilityRule('owner', ['subject.id', '=', 'resource.owner'])
196
260
 
197
261
  Политики позволяют группировать правила или создавать вложенные друг в друга политики.
198
262
 
199
- _Замечание: Одна политика может содержать либо правила, либо иметь вложенные политики. Одновременно иметь и правила и вложенные политики невозможно._
200
-
201
- Предположим, необходимо создать правило, которое будет разрешать доступ к финансовому отчету только пользователю из отдела аналитики (`analytics`) и только при условии, что он является его владельцем. Таким образом нам необходимо сформировать политику, состоящую из двух обязательных правил.
202
-
203
- _Псевдокод:_
204
-
205
- ```
206
-
207
- Политика (разрешить), если:
208
- Правило 1 — пользователь.departament = analytics
209
- (и)
210
- Правило 2 — отчет.владелец = пользователь.id
211
-
212
- ```
213
-
214
- _Реализация:_
215
-
216
- ```ts
217
- import { Abilitypolicy, AbilityRule } from '@via-profit/ability';
218
-
219
- const user = {
220
- id: '1655',
221
- departament: 'analytics',
222
- };
223
-
224
- const report = {
225
- id: '6',
226
- owner: '1655',
227
- };
228
-
229
- // Создание первого правила
230
- const departamentRule = new AbilityRule('Пользователь должен быть из отдела аналитики', [
231
- 'subject.departament',
232
- '=',
233
- 'analytics',
234
- ]);
235
-
236
- // Создание второго правила
237
- const orderOwner = new AbilityRule('Пользователь должен быть владельцем отчета', [
238
- 'subject.id',
239
- '=',
240
- 'resource.owner',
241
- ]);
242
-
243
- // Создание политики
244
- const policy = new AbilityPolicy('Доступ к отчету', '1');
245
-
246
- // Добавление правил в политику
247
- // Второй аргумент («and») устанавливает, что
248
- // политика будет разрешена только если переданные
249
- // данные будут соответствовать обеим правилам сразу.
250
- // Альтернативный вариант - «or». В таком случае, политика
251
- // разрешится, если будет удовлетворено хотя бы одно правило
252
- policy.addRules([departamentRule, orderOwner], 'and');
253
-
254
- // запуск политики
255
- // если данные, переданные в политику удовлетворяют ее правилам,
256
- // то политика позволит выполниться коду, который следует за ней.
257
- // в противном случае будет брошего исключение с сообщением
258
- // о запрете доступа и перечислением правил, которые были нарушены
259
- policy.enforce(user, report);
260
-
261
- // какой-то код
262
- ```
263
-
264
- ### Вложенные политики
265
-
266
- Политики могут состоять не из правил, а из других политик.
267
-
268
- _Замечание: Одна политика может содержать либо правила, либо иметь вложенные политики. Одновременно иметь и правила и вложенные политики невозможно._
269
-
270
- Например, Пользователь может установить статус заказа на «подтвержденный», но только в случаях, если: предыдущий статус заказа был «новый заказ», устанавливаемый статус заказа будет «подтвержденный заказ» и пользователь относится к отделу «manager» или в случае, если пользователь является старшим администратором
271
-
272
- _Псевдокод:_
273
-
274
- ```
275
-
276
- Политика (разрешить), если:
277
- Политика 1
278
- Правило 1 — пользователь.departament = managers
279
- (и)
280
- Правило 2 — заказ.предыдущий статус = новый заказ
281
- (и)
282
- Правило 3 — заказ.новый статус = подтвержденный заказ
283
- (или)
284
- Политика 2
285
- Правило 1 — пользователь.роль = administrator
286
-
287
- ```
288
-
289
- _Реализация:_
290
-
291
- Для создания политики будет задействован механизм парсинга конфиигурацонного файла. Подробнее см. раздел [Создание политики из конфига](#policy-config)
292
-
293
- ```ts
294
- import { Abilitypolicy, AbilityRule } from '@via-profit/ability';
295
-
296
- const user = {
297
- id: '1655',
298
- departament: 'manager',
299
- roles: ['super-admin', 'viewer'],
300
- };
301
-
302
- const order = {
303
- id: '6',
304
- status: 'новый заказ',
305
- };
306
-
307
- const policy = AbilityPolicy.parse({
308
- name: 'Политика',
309
- id: '1',
310
- policiesCompareMethod: 'or',
311
- policies: [
312
- {
313
- id: '2',
314
- name: 'Только менеджер может сменить статус с «новый заказ» на «подтвержденный заказ»',
315
- rulesCompareMethod: 'and',
316
- rules: [
317
- {
318
- name: 'Пользователь должен быть из отдела менеджеров',
319
- matches: ['subject.departament', '=', 'managers'],
320
- },
321
- {
322
- name: 'Предыдущий статус должен быть «новый заказ»',
323
- matches: ['environment.prevStatus', '=', 'новый заказ'],
324
- },
325
- {
326
- name: 'Устанавливаемый статус должен быть «подтвержденный заказ»',
327
- matches: ['environment.nextStatus', '=', 'подтвержденный заказ'],
328
- },
329
- ],
330
- },
331
- {
332
- id: '3',
333
- name: 'Пользователь должен быть старшим администратором',
334
- rules: [
335
- {
336
- name: 'Пользователь должен быть старшим администратором',
337
- matches: ['subject.rules', 'in', 'super-admin'],
338
- },
339
- ],
340
- },
341
- ],
342
- });
343
-
344
- policy.enforce(user, order, {
345
- prevStatus: order.status,
346
- nextStatus: 'подтвержденный заказ',
347
- });
348
- ```
349
263
 
350
264
  ## Создание политики из конфига <a name="policy-config"></a>
351
265
 
352
266
  Политику и правила можно создавать не только по средствам классов, но и при помощи конфигураций.
353
-
354
- Структура конфигураций правила:
355
-
356
- ```ts
357
- type AbilityRuleConfig = {
358
- readonly name: string;
359
- readonly effect?: AbilityRuleStatus;
360
- readonly matches: AbilityRuleMatches;
361
- };
362
-
363
- type AbilityRuleMatches = [
364
- `${SubjectPrefix}${string}`,
365
- '=' | '<>' | '>' | '<' | '<=' | '>=' | 'in'
366
- string | number | boolean,
367
- ];
368
-
369
- ```
370
-
371
- Структура конфигураций политики:
372
-
373
- ```ts
374
- type AbilityPolicyConfig = {
375
- readonly id: string;
376
- readonly name: string;
377
- readonly description?: string;
378
- readonly rulesCompareMethod?: 'or' | 'and';
379
- readonly policiesCompareMethod?: 'or' | 'and';
380
- readonly rules?: AbilityRuleConfig[] | null;
381
- readonly policies?: AbilityPolicyConfig[] | null;
382
- };
383
- ```
384
-
385
- Пример создания простой политики через конфигурацию:
386
-
387
- ```ts
388
- import { AbilityPolicy } from '@via-profit/ability';
389
-
390
- const policy = AbilityPolicy.parse({
391
- name: 'Название политики',
392
- id: '1',
393
- policiesCompareMethod: 'or',
394
- policies: [
395
- {
396
- id: '2',
397
- name: 'Название вложенной политики',
398
- rulesCompareMethod: 'and',
399
- rules: [
400
- { name: 'Правило 1', matches: ['subject.id', '=', 'resource.creatable'] },
401
- { name: 'Правило 2', matches: ['environment.prevStatus', '=', 'unknown'] },
402
- ],
403
- },
404
- ],
405
- });
406
- ```
407
-
408
-
409
- ## Класс AbilityRule <a name="ability-rule-class"></a>
410
-
411
- Класс `AbilityRule` предназначен для создания правил
412
-
413
-
414
- ## Класс AbilityPolicy <a name="ability-policy-class"></a>
415
-
416
- Класс `AbilityPolicy` предназначен для создания политик
417
-
418
- Метод `enforce`. В качестве текста сообщения об ошибке доступа, будет возвращено название политики, причем, только первой, политики, которая не прошла проверку.
419
-
420
-
421
- ### Синтаксис политик <a name="policy-syntax"></a>
Binary file