@platecms/delta-types 1.17.0 → 1.18.0
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/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/lib/permissions.type.spec.ts +524 -0
- package/src/lib/permissions.type.ts +698 -0
- package/src/lib/action-type.enum.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platecms/delta-types",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "This package contains various utility types.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"src/**/*"
|
|
18
18
|
],
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@platecms/delta-cast": "1.
|
|
21
|
-
"@platecms/delta-plate-resource-notation": "1.
|
|
20
|
+
"@platecms/delta-cast": "1.18.0",
|
|
21
|
+
"@platecms/delta-plate-resource-notation": "1.18.0",
|
|
22
22
|
"class-transformer": "0.5.1",
|
|
23
23
|
"reflect-metadata": "0.2.2",
|
|
24
24
|
"tslib": "2.8.1"
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ export * from "./lib/content-value-type.type";
|
|
|
4
4
|
export * from "./lib/primitive.type";
|
|
5
5
|
export * from "./lib/stage.enum";
|
|
6
6
|
export * from "./lib/header-keys.enum";
|
|
7
|
-
export * from "./lib/
|
|
7
|
+
export * from "./lib/permissions.type";
|
|
8
8
|
export * from "./lib/processed-asset-options.interface";
|
|
9
9
|
export * from "./lib/language-status.enum";
|
|
10
10
|
export type * from "./lib/language-option.interface";
|
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PRESET_PERMISSION_SETS,
|
|
3
|
+
PermissionAction,
|
|
4
|
+
PermissionResource,
|
|
5
|
+
Permissions,
|
|
6
|
+
compactPermissions,
|
|
7
|
+
getMissingPermissions,
|
|
8
|
+
hasPermissions,
|
|
9
|
+
isValidPermissions,
|
|
10
|
+
mergePermissions,
|
|
11
|
+
} from "./permissions.type";
|
|
12
|
+
|
|
13
|
+
describe("isValidPermissions", () => {
|
|
14
|
+
const unknownResource = "PlateMaintainer";
|
|
15
|
+
|
|
16
|
+
it.each([
|
|
17
|
+
{ description: "null", value: null },
|
|
18
|
+
{ description: "undefined", value: undefined },
|
|
19
|
+
{ description: "boolean", value: true },
|
|
20
|
+
{ description: "number", value: 1 },
|
|
21
|
+
{ description: "string", value: "x" },
|
|
22
|
+
{ description: "array", value: [] },
|
|
23
|
+
{ description: "empty object", value: {} },
|
|
24
|
+
{
|
|
25
|
+
description: "only explicit false grants",
|
|
26
|
+
value: {
|
|
27
|
+
[PermissionResource.CHANNEL]: {
|
|
28
|
+
[PermissionAction.READ]: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
description: "resource with empty actions object",
|
|
34
|
+
value: {
|
|
35
|
+
[PermissionResource.CHANNEL]: {},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
description: "invalid resource key",
|
|
40
|
+
value: { [unknownResource]: { read: true } },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
description: "resource value is null",
|
|
44
|
+
value: { [PermissionResource.CHANNEL]: null },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
description: "resource value is a string",
|
|
48
|
+
value: { [PermissionResource.CHANNEL]: "read" },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
description: "resource value is a number",
|
|
52
|
+
value: { [PermissionResource.CHANNEL]: 1 },
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
description: "resource value is a boolean",
|
|
56
|
+
value: { [PermissionResource.CHANNEL]: true },
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
description: "resource value is an array",
|
|
60
|
+
value: { [PermissionResource.CHANNEL]: ["read"] },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
description: "action not allowed on resource",
|
|
64
|
+
value: {
|
|
65
|
+
[PermissionResource.ORGANIZATION]: {
|
|
66
|
+
[PermissionAction.CREATE]: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
description: "unknown action key",
|
|
72
|
+
value: {
|
|
73
|
+
[PermissionResource.CHANNEL]: {
|
|
74
|
+
notAnAction: true,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
description: "unknown resource key alongside a valid grant",
|
|
80
|
+
value: {
|
|
81
|
+
[PermissionResource.CHANNEL]: {
|
|
82
|
+
[PermissionAction.READ]: true,
|
|
83
|
+
},
|
|
84
|
+
[unknownResource]: {
|
|
85
|
+
[PermissionAction.READ]: true,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
description: "unknown action key alongside a valid grant",
|
|
91
|
+
value: {
|
|
92
|
+
[PermissionResource.CHANNEL]: {
|
|
93
|
+
[PermissionAction.READ]: true,
|
|
94
|
+
notAnAction: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
description: "grant value is a string",
|
|
100
|
+
value: {
|
|
101
|
+
[PermissionResource.CHANNEL]: {
|
|
102
|
+
[PermissionAction.READ]: "yes",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
description: "grant value is a number",
|
|
108
|
+
value: {
|
|
109
|
+
[PermissionResource.CHANNEL]: {
|
|
110
|
+
[PermissionAction.READ]: 1,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
description: "grant value is null",
|
|
116
|
+
value: {
|
|
117
|
+
[PermissionResource.CHANNEL]: {
|
|
118
|
+
[PermissionAction.READ]: null,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
description: "grant value is undefined",
|
|
124
|
+
value: {
|
|
125
|
+
[PermissionResource.CHANNEL]: {
|
|
126
|
+
[PermissionAction.READ]: undefined,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
description: "grant value is an object",
|
|
132
|
+
value: {
|
|
133
|
+
[PermissionResource.CHANNEL]: {
|
|
134
|
+
[PermissionAction.READ]: {},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
])("returns false for $description", ({ value }) => {
|
|
139
|
+
// Act & Assert
|
|
140
|
+
expect(isValidPermissions(value)).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it.each([
|
|
144
|
+
{
|
|
145
|
+
description: "mixed true and false grants across resources",
|
|
146
|
+
value: {
|
|
147
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
148
|
+
[PermissionAction.READ]: true,
|
|
149
|
+
[PermissionAction.UPDATE]: false,
|
|
150
|
+
},
|
|
151
|
+
[PermissionResource.CHANNEL]: {
|
|
152
|
+
[PermissionAction.READ]: false,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
description: "true grant after earlier all-false resource",
|
|
158
|
+
value: {
|
|
159
|
+
[PermissionResource.CHANNEL]: {
|
|
160
|
+
[PermissionAction.READ]: false,
|
|
161
|
+
},
|
|
162
|
+
[PermissionResource.ROLE]: {
|
|
163
|
+
[PermissionAction.CREATE]: true,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
description: "organization read and update",
|
|
169
|
+
value: {
|
|
170
|
+
[PermissionResource.ORGANIZATION]: {
|
|
171
|
+
[PermissionAction.READ]: true,
|
|
172
|
+
[PermissionAction.UPDATE]: true,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
description: "true grant with null sibling actions",
|
|
178
|
+
value: {
|
|
179
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
180
|
+
[PermissionAction.READ]: true,
|
|
181
|
+
[PermissionAction.UPDATE]: null,
|
|
182
|
+
[PermissionAction.CREATE]: undefined,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
])("returns true for $description", ({ value }) => {
|
|
187
|
+
// Act & Assert
|
|
188
|
+
expect(isValidPermissions(value)).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe("mergePermissions", () => {
|
|
193
|
+
it.each([
|
|
194
|
+
{
|
|
195
|
+
description: "no inputs",
|
|
196
|
+
inputs: [] as Parameters<typeof mergePermissions>,
|
|
197
|
+
expected: {},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
description: "empty permission objects",
|
|
201
|
+
inputs: [{}, {}],
|
|
202
|
+
expected: {},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
description: "null and undefined resource actions are skipped",
|
|
206
|
+
inputs: [
|
|
207
|
+
{
|
|
208
|
+
[PermissionResource.CHANNEL]: null,
|
|
209
|
+
[PermissionResource.ROLE]: undefined,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
expected: {},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
description: "false grants are ignored",
|
|
216
|
+
inputs: [
|
|
217
|
+
{
|
|
218
|
+
[PermissionResource.CHANNEL]: {
|
|
219
|
+
[PermissionAction.READ]: false,
|
|
220
|
+
[PermissionAction.CREATE]: false,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
expected: {
|
|
225
|
+
[PermissionResource.CHANNEL]: {},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
description: "empty actions object is kept as empty resource map",
|
|
230
|
+
inputs: [
|
|
231
|
+
{
|
|
232
|
+
[PermissionResource.CHANNEL]: {},
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
expected: {
|
|
236
|
+
[PermissionResource.CHANNEL]: {},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
description: "true grants are kept",
|
|
241
|
+
inputs: [
|
|
242
|
+
{
|
|
243
|
+
[PermissionResource.CHANNEL]: {
|
|
244
|
+
[PermissionAction.READ]: true,
|
|
245
|
+
},
|
|
246
|
+
[PermissionResource.ROLE]: {
|
|
247
|
+
[PermissionAction.CREATE]: true,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
expected: {
|
|
252
|
+
[PermissionResource.CHANNEL]: {
|
|
253
|
+
[PermissionAction.READ]: true,
|
|
254
|
+
},
|
|
255
|
+
[PermissionResource.ROLE]: {
|
|
256
|
+
[PermissionAction.CREATE]: true,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
description: "OR-merges true grants across inputs",
|
|
262
|
+
inputs: [
|
|
263
|
+
{
|
|
264
|
+
[PermissionResource.CHANNEL]: {
|
|
265
|
+
[PermissionAction.READ]: true,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
[PermissionResource.CHANNEL]: {
|
|
270
|
+
[PermissionAction.UPDATE]: true,
|
|
271
|
+
},
|
|
272
|
+
[PermissionResource.ROLE]: {
|
|
273
|
+
[PermissionAction.READ]: true,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
expected: {
|
|
278
|
+
[PermissionResource.CHANNEL]: {
|
|
279
|
+
[PermissionAction.READ]: true,
|
|
280
|
+
[PermissionAction.UPDATE]: true,
|
|
281
|
+
},
|
|
282
|
+
[PermissionResource.ROLE]: {
|
|
283
|
+
[PermissionAction.READ]: true,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
description: "later false does not clear an earlier true",
|
|
289
|
+
inputs: [
|
|
290
|
+
{
|
|
291
|
+
[PermissionResource.CHANNEL]: {
|
|
292
|
+
[PermissionAction.READ]: true,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
[PermissionResource.CHANNEL]: {
|
|
297
|
+
[PermissionAction.READ]: false,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
expected: {
|
|
302
|
+
[PermissionResource.CHANNEL]: {
|
|
303
|
+
[PermissionAction.READ]: true,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
description: "earlier false then later true results in true",
|
|
309
|
+
inputs: [
|
|
310
|
+
{
|
|
311
|
+
[PermissionResource.CHANNEL]: {
|
|
312
|
+
[PermissionAction.READ]: false,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
[PermissionResource.CHANNEL]: {
|
|
317
|
+
[PermissionAction.READ]: true,
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
expected: {
|
|
322
|
+
[PermissionResource.CHANNEL]: {
|
|
323
|
+
[PermissionAction.READ]: true,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
])("returns expected result for $description", ({ inputs, expected }) => {
|
|
328
|
+
// Act
|
|
329
|
+
const result = mergePermissions(...(inputs as Permissions[]));
|
|
330
|
+
|
|
331
|
+
// Assert
|
|
332
|
+
expect(result).toEqual(expected);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("getMissingPermissions", () => {
|
|
337
|
+
it("returns only requested true grants that are unavailable", () => {
|
|
338
|
+
// Arrange
|
|
339
|
+
const availablePermissions: Permissions = {
|
|
340
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
341
|
+
[PermissionAction.READ]: true,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
const permissions: Permissions = {
|
|
345
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
346
|
+
[PermissionAction.READ]: true,
|
|
347
|
+
[PermissionAction.UPDATE]: true,
|
|
348
|
+
[PermissionAction.REMOVE]: false,
|
|
349
|
+
},
|
|
350
|
+
[PermissionResource.TAG]: {
|
|
351
|
+
[PermissionAction.READ]: true,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Act
|
|
356
|
+
const result = getMissingPermissions(availablePermissions, permissions);
|
|
357
|
+
|
|
358
|
+
// Assert
|
|
359
|
+
expect(result).toStrictEqual({
|
|
360
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
361
|
+
[PermissionAction.UPDATE]: true,
|
|
362
|
+
},
|
|
363
|
+
[PermissionResource.TAG]: {
|
|
364
|
+
[PermissionAction.READ]: true,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe("hasPermissions", () => {
|
|
371
|
+
it("returns true when all requested grants are available", () => {
|
|
372
|
+
// Act & Assert
|
|
373
|
+
expect(
|
|
374
|
+
hasPermissions(PRESET_PERMISSION_SETS.contentEditor, {
|
|
375
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
376
|
+
[PermissionAction.READ]: true,
|
|
377
|
+
},
|
|
378
|
+
}),
|
|
379
|
+
).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("returns false when a requested grant is unavailable", () => {
|
|
383
|
+
// Act & Assert
|
|
384
|
+
expect(
|
|
385
|
+
hasPermissions(PRESET_PERMISSION_SETS.contentViewer, {
|
|
386
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
387
|
+
[PermissionAction.UPDATE]: true,
|
|
388
|
+
},
|
|
389
|
+
}),
|
|
390
|
+
).toBe(false);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe("compactPermissions", () => {
|
|
395
|
+
it.each([
|
|
396
|
+
{
|
|
397
|
+
description: "empty object",
|
|
398
|
+
value: {},
|
|
399
|
+
expected: {},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
description: "already compact true grants",
|
|
403
|
+
value: {
|
|
404
|
+
[PermissionResource.CHANNEL]: {
|
|
405
|
+
[PermissionAction.READ]: true,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
expected: {
|
|
409
|
+
[PermissionResource.CHANNEL]: {
|
|
410
|
+
[PermissionAction.READ]: true,
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
description: "false grants are omitted",
|
|
416
|
+
value: {
|
|
417
|
+
[PermissionResource.CHANNEL]: {
|
|
418
|
+
[PermissionAction.READ]: true,
|
|
419
|
+
[PermissionAction.UPDATE]: false,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
expected: {
|
|
423
|
+
[PermissionResource.CHANNEL]: {
|
|
424
|
+
[PermissionAction.READ]: true,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
description: "null and undefined grants are omitted",
|
|
430
|
+
value: {
|
|
431
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
432
|
+
[PermissionAction.READ]: true,
|
|
433
|
+
[PermissionAction.UPDATE]: null,
|
|
434
|
+
[PermissionAction.CREATE]: undefined,
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
expected: {
|
|
438
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
439
|
+
[PermissionAction.READ]: true,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
description: "resource with only false grants is omitted",
|
|
445
|
+
value: {
|
|
446
|
+
[PermissionResource.CHANNEL]: {
|
|
447
|
+
[PermissionAction.READ]: false,
|
|
448
|
+
[PermissionAction.CREATE]: false,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
expected: {},
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
description: "empty actions object is omitted",
|
|
455
|
+
value: {
|
|
456
|
+
[PermissionResource.CHANNEL]: {},
|
|
457
|
+
},
|
|
458
|
+
expected: {},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
description: "null and undefined resource actions are omitted",
|
|
462
|
+
value: {
|
|
463
|
+
[PermissionResource.CHANNEL]: null,
|
|
464
|
+
[PermissionResource.ROLE]: undefined,
|
|
465
|
+
},
|
|
466
|
+
expected: {},
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
description: "empty resources are omitted next to compact grants",
|
|
470
|
+
value: {
|
|
471
|
+
[PermissionResource.CHANNEL]: {
|
|
472
|
+
[PermissionAction.READ]: false,
|
|
473
|
+
},
|
|
474
|
+
[PermissionResource.ROLE]: {
|
|
475
|
+
[PermissionAction.CREATE]: true,
|
|
476
|
+
},
|
|
477
|
+
[PermissionResource.ORGANIZATION]: {},
|
|
478
|
+
},
|
|
479
|
+
expected: {
|
|
480
|
+
[PermissionResource.ROLE]: {
|
|
481
|
+
[PermissionAction.CREATE]: true,
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
])("returns expected result for $description", ({ value, expected }) => {
|
|
486
|
+
// Act
|
|
487
|
+
const result = compactPermissions(value as Permissions);
|
|
488
|
+
|
|
489
|
+
// Assert
|
|
490
|
+
expect(result).toEqual(expected);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("does not mutate the input object", () => {
|
|
494
|
+
// Arrange
|
|
495
|
+
const value = {
|
|
496
|
+
[PermissionResource.CHANNEL]: {
|
|
497
|
+
[PermissionAction.READ]: true,
|
|
498
|
+
[PermissionAction.UPDATE]: false,
|
|
499
|
+
},
|
|
500
|
+
[PermissionResource.ROLE]: {
|
|
501
|
+
[PermissionAction.CREATE]: false,
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// Act
|
|
506
|
+
const result = compactPermissions(value);
|
|
507
|
+
|
|
508
|
+
// Assert
|
|
509
|
+
expect(result).toEqual({
|
|
510
|
+
[PermissionResource.CHANNEL]: {
|
|
511
|
+
[PermissionAction.READ]: true,
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
expect(value).toEqual({
|
|
515
|
+
[PermissionResource.CHANNEL]: {
|
|
516
|
+
[PermissionAction.READ]: true,
|
|
517
|
+
[PermissionAction.UPDATE]: false,
|
|
518
|
+
},
|
|
519
|
+
[PermissionResource.ROLE]: {
|
|
520
|
+
[PermissionAction.CREATE]: false,
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
});
|
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resources that can appear in a permission grant.
|
|
3
|
+
* String values match GraphQL / OPA resource type names (e.g. `"ContentItem"`).
|
|
4
|
+
*/
|
|
5
|
+
export enum PermissionResource {
|
|
6
|
+
CONTENT_TYPE = "ContentType",
|
|
7
|
+
CONTENT_ITEM = "ContentItem",
|
|
8
|
+
CONTENT_VALUE = "ContentValue",
|
|
9
|
+
ASSET = "Asset",
|
|
10
|
+
TAG = "Tag",
|
|
11
|
+
CONTENT_EXPERIENCE = "ContentExperience",
|
|
12
|
+
EXPERIENCE_COMPONENT = "ExperienceComponent",
|
|
13
|
+
BUILDING_BLOCK = "BuildingBlock",
|
|
14
|
+
BUILDING_BLOCK_FIELD = "BuildingBlockField",
|
|
15
|
+
BUILDING_BLOCK_FIELD_FULFILLMENT = "BuildingBlockFieldFulfillment",
|
|
16
|
+
PATH_PART = "PathPart",
|
|
17
|
+
CONTENT_FIELD = "ContentField",
|
|
18
|
+
CHANNEL = "Channel",
|
|
19
|
+
THEME = "Theme",
|
|
20
|
+
TEMPLATE = "Template",
|
|
21
|
+
BLUEPRINT = "Blueprint",
|
|
22
|
+
GRID = "Grid",
|
|
23
|
+
GRID_DEFINITION = "GridDefinition",
|
|
24
|
+
GRID_PLACEMENT = "GridPlacement",
|
|
25
|
+
ORGANIZATION = "Organization",
|
|
26
|
+
ROLE = "Role",
|
|
27
|
+
ROLE_ASSIGNMENT = "RoleAssignment",
|
|
28
|
+
INVITATION = "Invitation",
|
|
29
|
+
API_TOKEN = "ApiToken",
|
|
30
|
+
LANGUAGE = "Language",
|
|
31
|
+
ISSUE = "Issue",
|
|
32
|
+
PUBLICATION = "Publication",
|
|
33
|
+
CONTENT_VALIDATION_RULE = "ContentValidationRule",
|
|
34
|
+
SCHEDULED_PUBLICATION = "ScheduledPublication",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resources that OPA authorizes but a Role cannot grant.
|
|
39
|
+
* @remarks Deliberately kept out of {@link PermissionResource}: these are application-level and decided by the OPA
|
|
40
|
+
* policy, so they must never appear in a Role's {@link Permissions}.
|
|
41
|
+
*/
|
|
42
|
+
export enum AuthorizationOnlyResource {
|
|
43
|
+
PLATE_MAINTAINER = "PlateMaintainer",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Every resource that can be sent to OPA as `input.resource.type`.
|
|
48
|
+
*/
|
|
49
|
+
export type AuthorizationResource = AuthorizationOnlyResource | PermissionResource;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Actions that can be granted on a {@link PermissionResource}.
|
|
53
|
+
* String values are what we send to OPA as `input.action` (e.g. `"read"`).
|
|
54
|
+
* @remarks Not every action applies to every resource — see {@link RESOURCE_ACTIONS}.
|
|
55
|
+
*/
|
|
56
|
+
export enum PermissionAction {
|
|
57
|
+
READ = "read",
|
|
58
|
+
CREATE = "create",
|
|
59
|
+
UPDATE = "update",
|
|
60
|
+
REMOVE = "remove",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const CRUD_ACTIONS = [
|
|
64
|
+
PermissionAction.READ,
|
|
65
|
+
PermissionAction.CREATE,
|
|
66
|
+
PermissionAction.UPDATE,
|
|
67
|
+
PermissionAction.REMOVE,
|
|
68
|
+
] as const;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Catalog of which actions exist per resource.
|
|
72
|
+
* Keep this the single source of truth when adding resources or actions.
|
|
73
|
+
* @remarks This is applicability, not grants. Grants are {@link Permissions} objects.
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* RESOURCE_ACTIONS[PermissionResource.CONTENT_ITEM] // ["read", "create", "update", "remove"]
|
|
77
|
+
* RESOURCE_ACTIONS[PermissionResource.ORGANIZATION] // ["read", "update"]
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export const RESOURCE_ACTIONS: Record<PermissionResource, readonly PermissionAction[]> = {
|
|
81
|
+
[PermissionResource.CONTENT_TYPE]: CRUD_ACTIONS,
|
|
82
|
+
[PermissionResource.CONTENT_ITEM]: CRUD_ACTIONS,
|
|
83
|
+
[PermissionResource.CONTENT_VALUE]: CRUD_ACTIONS,
|
|
84
|
+
[PermissionResource.ASSET]: CRUD_ACTIONS,
|
|
85
|
+
[PermissionResource.TAG]: CRUD_ACTIONS,
|
|
86
|
+
[PermissionResource.CONTENT_EXPERIENCE]: CRUD_ACTIONS,
|
|
87
|
+
[PermissionResource.EXPERIENCE_COMPONENT]: CRUD_ACTIONS,
|
|
88
|
+
[PermissionResource.BUILDING_BLOCK]: CRUD_ACTIONS,
|
|
89
|
+
[PermissionResource.BUILDING_BLOCK_FIELD]: CRUD_ACTIONS,
|
|
90
|
+
[PermissionResource.BUILDING_BLOCK_FIELD_FULFILLMENT]: CRUD_ACTIONS,
|
|
91
|
+
[PermissionResource.PATH_PART]: CRUD_ACTIONS,
|
|
92
|
+
[PermissionResource.CONTENT_FIELD]: CRUD_ACTIONS,
|
|
93
|
+
[PermissionResource.CHANNEL]: CRUD_ACTIONS,
|
|
94
|
+
[PermissionResource.THEME]: CRUD_ACTIONS,
|
|
95
|
+
[PermissionResource.TEMPLATE]: CRUD_ACTIONS,
|
|
96
|
+
[PermissionResource.BLUEPRINT]: CRUD_ACTIONS,
|
|
97
|
+
[PermissionResource.GRID]: CRUD_ACTIONS,
|
|
98
|
+
[PermissionResource.GRID_DEFINITION]: CRUD_ACTIONS,
|
|
99
|
+
[PermissionResource.GRID_PLACEMENT]: CRUD_ACTIONS,
|
|
100
|
+
[PermissionResource.ORGANIZATION]: [PermissionAction.READ, PermissionAction.UPDATE],
|
|
101
|
+
[PermissionResource.ROLE]: CRUD_ACTIONS,
|
|
102
|
+
[PermissionResource.ROLE_ASSIGNMENT]: [PermissionAction.READ, PermissionAction.CREATE, PermissionAction.REMOVE],
|
|
103
|
+
[PermissionResource.INVITATION]: [PermissionAction.READ, PermissionAction.CREATE, PermissionAction.REMOVE],
|
|
104
|
+
[PermissionResource.API_TOKEN]: CRUD_ACTIONS,
|
|
105
|
+
[PermissionResource.LANGUAGE]: CRUD_ACTIONS,
|
|
106
|
+
[PermissionResource.ISSUE]: CRUD_ACTIONS,
|
|
107
|
+
[PermissionResource.PUBLICATION]: [PermissionAction.READ, PermissionAction.CREATE, PermissionAction.REMOVE],
|
|
108
|
+
[PermissionResource.CONTENT_VALIDATION_RULE]: CRUD_ACTIONS,
|
|
109
|
+
[PermissionResource.SCHEDULED_PUBLICATION]: CRUD_ACTIONS,
|
|
110
|
+
} as const satisfies Record<PermissionResource, readonly PermissionAction[]>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Actions allowed for a specific resource according to {@link RESOURCE_ACTIONS}.
|
|
114
|
+
* @remarks An {@link AuthorizationOnlyResource} has no entry there, so every action applies — which ones are
|
|
115
|
+
* actually permitted is up to the OPA policy.
|
|
116
|
+
*/
|
|
117
|
+
export type ActionForResource<TResource extends AuthorizationResource> = TResource extends PermissionResource
|
|
118
|
+
? (typeof RESOURCE_ACTIONS)[TResource][number]
|
|
119
|
+
: PermissionAction;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Nested grant map keyed by {@link PermissionResource}, then action.
|
|
123
|
+
* @remarks Additive only — missing keys or `false` means not granted. There is no explicit deny.
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const permissions: Permissions = {
|
|
127
|
+
* [PermissionResource.CONTENT_ITEM]: {
|
|
128
|
+
* [PermissionAction.READ]: true,
|
|
129
|
+
* [PermissionAction.UPDATE]: true,
|
|
130
|
+
* },
|
|
131
|
+
* };
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export type Permissions = {
|
|
135
|
+
[TResource in PermissionResource]?: Partial<Record<ActionForResource<TResource>, boolean>>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A single resource + action check (e.g. for `@AuthorizationOptions` / OPA input).
|
|
140
|
+
*/
|
|
141
|
+
export interface AuthorizationCheck<TResource extends PermissionResource = PermissionResource> {
|
|
142
|
+
resource: TResource;
|
|
143
|
+
action: ActionForResource<TResource>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const PERMISSION_RESOURCES = new Set<string>(Object.values(PermissionResource));
|
|
147
|
+
|
|
148
|
+
export const AUTHORIZATION_RESOURCES = new Set<string>([
|
|
149
|
+
...PERMISSION_RESOURCES,
|
|
150
|
+
...Object.values(AuthorizationOnlyResource),
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
export const PRESET_PERMISSION_SETS: Record<string, Permissions> = {
|
|
154
|
+
admin: {
|
|
155
|
+
[PermissionResource.CONTENT_TYPE]: {
|
|
156
|
+
[PermissionAction.READ]: true,
|
|
157
|
+
[PermissionAction.CREATE]: true,
|
|
158
|
+
[PermissionAction.UPDATE]: true,
|
|
159
|
+
[PermissionAction.REMOVE]: true,
|
|
160
|
+
},
|
|
161
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
162
|
+
[PermissionAction.READ]: true,
|
|
163
|
+
[PermissionAction.CREATE]: true,
|
|
164
|
+
[PermissionAction.UPDATE]: true,
|
|
165
|
+
[PermissionAction.REMOVE]: true,
|
|
166
|
+
},
|
|
167
|
+
[PermissionResource.CONTENT_VALUE]: {
|
|
168
|
+
[PermissionAction.READ]: true,
|
|
169
|
+
[PermissionAction.CREATE]: true,
|
|
170
|
+
[PermissionAction.UPDATE]: true,
|
|
171
|
+
[PermissionAction.REMOVE]: true,
|
|
172
|
+
},
|
|
173
|
+
[PermissionResource.ASSET]: {
|
|
174
|
+
[PermissionAction.READ]: true,
|
|
175
|
+
[PermissionAction.CREATE]: true,
|
|
176
|
+
[PermissionAction.UPDATE]: true,
|
|
177
|
+
[PermissionAction.REMOVE]: true,
|
|
178
|
+
},
|
|
179
|
+
[PermissionResource.TAG]: {
|
|
180
|
+
[PermissionAction.READ]: true,
|
|
181
|
+
[PermissionAction.CREATE]: true,
|
|
182
|
+
[PermissionAction.UPDATE]: true,
|
|
183
|
+
[PermissionAction.REMOVE]: true,
|
|
184
|
+
},
|
|
185
|
+
[PermissionResource.CONTENT_EXPERIENCE]: {
|
|
186
|
+
[PermissionAction.READ]: true,
|
|
187
|
+
[PermissionAction.CREATE]: true,
|
|
188
|
+
[PermissionAction.UPDATE]: true,
|
|
189
|
+
[PermissionAction.REMOVE]: true,
|
|
190
|
+
},
|
|
191
|
+
[PermissionResource.EXPERIENCE_COMPONENT]: {
|
|
192
|
+
[PermissionAction.READ]: true,
|
|
193
|
+
[PermissionAction.CREATE]: true,
|
|
194
|
+
[PermissionAction.UPDATE]: true,
|
|
195
|
+
[PermissionAction.REMOVE]: true,
|
|
196
|
+
},
|
|
197
|
+
[PermissionResource.BUILDING_BLOCK]: {
|
|
198
|
+
[PermissionAction.READ]: true,
|
|
199
|
+
[PermissionAction.CREATE]: true,
|
|
200
|
+
[PermissionAction.UPDATE]: true,
|
|
201
|
+
[PermissionAction.REMOVE]: true,
|
|
202
|
+
},
|
|
203
|
+
[PermissionResource.BUILDING_BLOCK_FIELD]: {
|
|
204
|
+
[PermissionAction.READ]: true,
|
|
205
|
+
[PermissionAction.CREATE]: true,
|
|
206
|
+
[PermissionAction.UPDATE]: true,
|
|
207
|
+
[PermissionAction.REMOVE]: true,
|
|
208
|
+
},
|
|
209
|
+
[PermissionResource.BUILDING_BLOCK_FIELD_FULFILLMENT]: {
|
|
210
|
+
[PermissionAction.READ]: true,
|
|
211
|
+
[PermissionAction.CREATE]: true,
|
|
212
|
+
[PermissionAction.UPDATE]: true,
|
|
213
|
+
[PermissionAction.REMOVE]: true,
|
|
214
|
+
},
|
|
215
|
+
[PermissionResource.PATH_PART]: {
|
|
216
|
+
[PermissionAction.READ]: true,
|
|
217
|
+
[PermissionAction.CREATE]: true,
|
|
218
|
+
[PermissionAction.UPDATE]: true,
|
|
219
|
+
[PermissionAction.REMOVE]: true,
|
|
220
|
+
},
|
|
221
|
+
[PermissionResource.CONTENT_FIELD]: {
|
|
222
|
+
[PermissionAction.READ]: true,
|
|
223
|
+
[PermissionAction.CREATE]: true,
|
|
224
|
+
[PermissionAction.UPDATE]: true,
|
|
225
|
+
[PermissionAction.REMOVE]: true,
|
|
226
|
+
},
|
|
227
|
+
[PermissionResource.CHANNEL]: {
|
|
228
|
+
[PermissionAction.READ]: true,
|
|
229
|
+
[PermissionAction.CREATE]: true,
|
|
230
|
+
[PermissionAction.UPDATE]: true,
|
|
231
|
+
[PermissionAction.REMOVE]: true,
|
|
232
|
+
},
|
|
233
|
+
[PermissionResource.THEME]: {
|
|
234
|
+
[PermissionAction.READ]: true,
|
|
235
|
+
[PermissionAction.CREATE]: true,
|
|
236
|
+
[PermissionAction.UPDATE]: true,
|
|
237
|
+
[PermissionAction.REMOVE]: true,
|
|
238
|
+
},
|
|
239
|
+
[PermissionResource.TEMPLATE]: {
|
|
240
|
+
[PermissionAction.READ]: true,
|
|
241
|
+
[PermissionAction.CREATE]: true,
|
|
242
|
+
[PermissionAction.UPDATE]: true,
|
|
243
|
+
[PermissionAction.REMOVE]: true,
|
|
244
|
+
},
|
|
245
|
+
[PermissionResource.BLUEPRINT]: {
|
|
246
|
+
[PermissionAction.READ]: true,
|
|
247
|
+
[PermissionAction.CREATE]: true,
|
|
248
|
+
[PermissionAction.UPDATE]: true,
|
|
249
|
+
[PermissionAction.REMOVE]: true,
|
|
250
|
+
},
|
|
251
|
+
[PermissionResource.GRID]: {
|
|
252
|
+
[PermissionAction.READ]: true,
|
|
253
|
+
[PermissionAction.CREATE]: true,
|
|
254
|
+
[PermissionAction.UPDATE]: true,
|
|
255
|
+
[PermissionAction.REMOVE]: true,
|
|
256
|
+
},
|
|
257
|
+
[PermissionResource.GRID_DEFINITION]: {
|
|
258
|
+
[PermissionAction.READ]: true,
|
|
259
|
+
[PermissionAction.CREATE]: true,
|
|
260
|
+
[PermissionAction.UPDATE]: true,
|
|
261
|
+
[PermissionAction.REMOVE]: true,
|
|
262
|
+
},
|
|
263
|
+
[PermissionResource.GRID_PLACEMENT]: {
|
|
264
|
+
[PermissionAction.READ]: true,
|
|
265
|
+
[PermissionAction.CREATE]: true,
|
|
266
|
+
[PermissionAction.UPDATE]: true,
|
|
267
|
+
[PermissionAction.REMOVE]: true,
|
|
268
|
+
},
|
|
269
|
+
[PermissionResource.ORGANIZATION]: {
|
|
270
|
+
[PermissionAction.READ]: true,
|
|
271
|
+
[PermissionAction.UPDATE]: true,
|
|
272
|
+
},
|
|
273
|
+
[PermissionResource.ROLE]: {
|
|
274
|
+
[PermissionAction.READ]: true,
|
|
275
|
+
[PermissionAction.CREATE]: true,
|
|
276
|
+
[PermissionAction.UPDATE]: true,
|
|
277
|
+
[PermissionAction.REMOVE]: true,
|
|
278
|
+
},
|
|
279
|
+
[PermissionResource.ROLE_ASSIGNMENT]: {
|
|
280
|
+
[PermissionAction.READ]: true,
|
|
281
|
+
[PermissionAction.CREATE]: true,
|
|
282
|
+
[PermissionAction.REMOVE]: true,
|
|
283
|
+
},
|
|
284
|
+
[PermissionResource.INVITATION]: {
|
|
285
|
+
[PermissionAction.READ]: true,
|
|
286
|
+
[PermissionAction.CREATE]: true,
|
|
287
|
+
[PermissionAction.REMOVE]: true,
|
|
288
|
+
},
|
|
289
|
+
[PermissionResource.API_TOKEN]: {
|
|
290
|
+
[PermissionAction.READ]: true,
|
|
291
|
+
[PermissionAction.CREATE]: true,
|
|
292
|
+
[PermissionAction.UPDATE]: true,
|
|
293
|
+
[PermissionAction.REMOVE]: true,
|
|
294
|
+
},
|
|
295
|
+
[PermissionResource.LANGUAGE]: {
|
|
296
|
+
[PermissionAction.READ]: true,
|
|
297
|
+
[PermissionAction.CREATE]: true,
|
|
298
|
+
[PermissionAction.UPDATE]: true,
|
|
299
|
+
[PermissionAction.REMOVE]: true,
|
|
300
|
+
},
|
|
301
|
+
[PermissionResource.ISSUE]: {
|
|
302
|
+
[PermissionAction.READ]: true,
|
|
303
|
+
[PermissionAction.CREATE]: true,
|
|
304
|
+
[PermissionAction.UPDATE]: true,
|
|
305
|
+
[PermissionAction.REMOVE]: true,
|
|
306
|
+
},
|
|
307
|
+
[PermissionResource.PUBLICATION]: {
|
|
308
|
+
[PermissionAction.READ]: true,
|
|
309
|
+
[PermissionAction.CREATE]: true,
|
|
310
|
+
[PermissionAction.REMOVE]: true,
|
|
311
|
+
},
|
|
312
|
+
[PermissionResource.SCHEDULED_PUBLICATION]: {
|
|
313
|
+
[PermissionAction.READ]: true,
|
|
314
|
+
[PermissionAction.CREATE]: true,
|
|
315
|
+
[PermissionAction.UPDATE]: true,
|
|
316
|
+
[PermissionAction.REMOVE]: true,
|
|
317
|
+
},
|
|
318
|
+
[PermissionResource.CONTENT_VALIDATION_RULE]: {
|
|
319
|
+
[PermissionAction.READ]: true,
|
|
320
|
+
[PermissionAction.CREATE]: true,
|
|
321
|
+
[PermissionAction.UPDATE]: true,
|
|
322
|
+
[PermissionAction.REMOVE]: true,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
contentEditor: {
|
|
326
|
+
[PermissionResource.CONTENT_TYPE]: {
|
|
327
|
+
[PermissionAction.READ]: true,
|
|
328
|
+
},
|
|
329
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
330
|
+
[PermissionAction.READ]: true,
|
|
331
|
+
[PermissionAction.CREATE]: true,
|
|
332
|
+
[PermissionAction.UPDATE]: true,
|
|
333
|
+
[PermissionAction.REMOVE]: true,
|
|
334
|
+
},
|
|
335
|
+
[PermissionResource.CONTENT_VALUE]: {
|
|
336
|
+
[PermissionAction.READ]: true,
|
|
337
|
+
[PermissionAction.CREATE]: true,
|
|
338
|
+
[PermissionAction.UPDATE]: true,
|
|
339
|
+
[PermissionAction.REMOVE]: true,
|
|
340
|
+
},
|
|
341
|
+
[PermissionResource.ASSET]: {
|
|
342
|
+
[PermissionAction.READ]: true,
|
|
343
|
+
[PermissionAction.CREATE]: true,
|
|
344
|
+
[PermissionAction.UPDATE]: true,
|
|
345
|
+
[PermissionAction.REMOVE]: true,
|
|
346
|
+
},
|
|
347
|
+
[PermissionResource.TAG]: {
|
|
348
|
+
[PermissionAction.READ]: true,
|
|
349
|
+
[PermissionAction.CREATE]: true,
|
|
350
|
+
[PermissionAction.UPDATE]: true,
|
|
351
|
+
[PermissionAction.REMOVE]: true,
|
|
352
|
+
},
|
|
353
|
+
[PermissionResource.CONTENT_EXPERIENCE]: {
|
|
354
|
+
[PermissionAction.READ]: true,
|
|
355
|
+
[PermissionAction.CREATE]: true,
|
|
356
|
+
[PermissionAction.UPDATE]: true,
|
|
357
|
+
[PermissionAction.REMOVE]: true,
|
|
358
|
+
},
|
|
359
|
+
[PermissionResource.EXPERIENCE_COMPONENT]: {
|
|
360
|
+
[PermissionAction.READ]: true,
|
|
361
|
+
[PermissionAction.CREATE]: true,
|
|
362
|
+
[PermissionAction.UPDATE]: true,
|
|
363
|
+
[PermissionAction.REMOVE]: true,
|
|
364
|
+
},
|
|
365
|
+
[PermissionResource.BUILDING_BLOCK]: {
|
|
366
|
+
[PermissionAction.READ]: true,
|
|
367
|
+
},
|
|
368
|
+
[PermissionResource.BUILDING_BLOCK_FIELD]: {
|
|
369
|
+
[PermissionAction.READ]: true,
|
|
370
|
+
},
|
|
371
|
+
[PermissionResource.BUILDING_BLOCK_FIELD_FULFILLMENT]: {
|
|
372
|
+
[PermissionAction.READ]: true,
|
|
373
|
+
[PermissionAction.CREATE]: true,
|
|
374
|
+
[PermissionAction.UPDATE]: true,
|
|
375
|
+
[PermissionAction.REMOVE]: true,
|
|
376
|
+
},
|
|
377
|
+
[PermissionResource.PATH_PART]: {
|
|
378
|
+
[PermissionAction.READ]: true,
|
|
379
|
+
[PermissionAction.CREATE]: true,
|
|
380
|
+
[PermissionAction.UPDATE]: true,
|
|
381
|
+
[PermissionAction.REMOVE]: true,
|
|
382
|
+
},
|
|
383
|
+
[PermissionResource.CONTENT_FIELD]: {
|
|
384
|
+
[PermissionAction.READ]: true,
|
|
385
|
+
},
|
|
386
|
+
[PermissionResource.CHANNEL]: {
|
|
387
|
+
[PermissionAction.READ]: true,
|
|
388
|
+
[PermissionAction.CREATE]: true,
|
|
389
|
+
[PermissionAction.UPDATE]: true,
|
|
390
|
+
[PermissionAction.REMOVE]: true,
|
|
391
|
+
},
|
|
392
|
+
[PermissionResource.THEME]: {
|
|
393
|
+
[PermissionAction.READ]: true,
|
|
394
|
+
},
|
|
395
|
+
[PermissionResource.TEMPLATE]: {
|
|
396
|
+
[PermissionAction.READ]: true,
|
|
397
|
+
},
|
|
398
|
+
[PermissionResource.BLUEPRINT]: {
|
|
399
|
+
[PermissionAction.READ]: true,
|
|
400
|
+
[PermissionAction.CREATE]: true,
|
|
401
|
+
[PermissionAction.UPDATE]: true,
|
|
402
|
+
[PermissionAction.REMOVE]: true,
|
|
403
|
+
},
|
|
404
|
+
[PermissionResource.GRID]: {
|
|
405
|
+
[PermissionAction.READ]: true,
|
|
406
|
+
[PermissionAction.CREATE]: true,
|
|
407
|
+
[PermissionAction.UPDATE]: true,
|
|
408
|
+
[PermissionAction.REMOVE]: true,
|
|
409
|
+
},
|
|
410
|
+
[PermissionResource.GRID_DEFINITION]: {
|
|
411
|
+
[PermissionAction.READ]: true,
|
|
412
|
+
},
|
|
413
|
+
[PermissionResource.GRID_PLACEMENT]: {
|
|
414
|
+
[PermissionAction.READ]: true,
|
|
415
|
+
[PermissionAction.CREATE]: true,
|
|
416
|
+
[PermissionAction.UPDATE]: true,
|
|
417
|
+
[PermissionAction.REMOVE]: true,
|
|
418
|
+
},
|
|
419
|
+
[PermissionResource.ORGANIZATION]: {
|
|
420
|
+
[PermissionAction.READ]: true,
|
|
421
|
+
},
|
|
422
|
+
[PermissionResource.ROLE]: {
|
|
423
|
+
[PermissionAction.READ]: true,
|
|
424
|
+
},
|
|
425
|
+
[PermissionResource.ROLE_ASSIGNMENT]: {
|
|
426
|
+
[PermissionAction.READ]: true,
|
|
427
|
+
},
|
|
428
|
+
[PermissionResource.INVITATION]: {
|
|
429
|
+
[PermissionAction.READ]: true,
|
|
430
|
+
},
|
|
431
|
+
[PermissionResource.API_TOKEN]: {
|
|
432
|
+
[PermissionAction.READ]: true,
|
|
433
|
+
},
|
|
434
|
+
[PermissionResource.LANGUAGE]: {
|
|
435
|
+
[PermissionAction.READ]: true,
|
|
436
|
+
},
|
|
437
|
+
[PermissionResource.ISSUE]: {
|
|
438
|
+
[PermissionAction.READ]: true,
|
|
439
|
+
[PermissionAction.CREATE]: true,
|
|
440
|
+
[PermissionAction.UPDATE]: true,
|
|
441
|
+
[PermissionAction.REMOVE]: true,
|
|
442
|
+
},
|
|
443
|
+
[PermissionResource.PUBLICATION]: {
|
|
444
|
+
[PermissionAction.READ]: true,
|
|
445
|
+
[PermissionAction.CREATE]: true,
|
|
446
|
+
[PermissionAction.REMOVE]: true,
|
|
447
|
+
},
|
|
448
|
+
[PermissionResource.SCHEDULED_PUBLICATION]: {
|
|
449
|
+
[PermissionAction.READ]: true,
|
|
450
|
+
[PermissionAction.CREATE]: true,
|
|
451
|
+
[PermissionAction.UPDATE]: true,
|
|
452
|
+
[PermissionAction.REMOVE]: true,
|
|
453
|
+
},
|
|
454
|
+
[PermissionResource.CONTENT_VALIDATION_RULE]: {
|
|
455
|
+
[PermissionAction.READ]: true,
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
contentViewer: {
|
|
459
|
+
[PermissionResource.CONTENT_TYPE]: {
|
|
460
|
+
[PermissionAction.READ]: true,
|
|
461
|
+
},
|
|
462
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
463
|
+
[PermissionAction.READ]: true,
|
|
464
|
+
},
|
|
465
|
+
[PermissionResource.CONTENT_VALUE]: {
|
|
466
|
+
[PermissionAction.READ]: true,
|
|
467
|
+
},
|
|
468
|
+
[PermissionResource.ASSET]: {
|
|
469
|
+
[PermissionAction.READ]: true,
|
|
470
|
+
},
|
|
471
|
+
[PermissionResource.TAG]: {
|
|
472
|
+
[PermissionAction.READ]: true,
|
|
473
|
+
},
|
|
474
|
+
[PermissionResource.CONTENT_EXPERIENCE]: {
|
|
475
|
+
[PermissionAction.READ]: true,
|
|
476
|
+
},
|
|
477
|
+
[PermissionResource.EXPERIENCE_COMPONENT]: {
|
|
478
|
+
[PermissionAction.READ]: true,
|
|
479
|
+
},
|
|
480
|
+
[PermissionResource.BUILDING_BLOCK]: {
|
|
481
|
+
[PermissionAction.READ]: true,
|
|
482
|
+
},
|
|
483
|
+
[PermissionResource.BUILDING_BLOCK_FIELD]: {
|
|
484
|
+
[PermissionAction.READ]: true,
|
|
485
|
+
},
|
|
486
|
+
[PermissionResource.BUILDING_BLOCK_FIELD_FULFILLMENT]: {
|
|
487
|
+
[PermissionAction.READ]: true,
|
|
488
|
+
},
|
|
489
|
+
[PermissionResource.PATH_PART]: {
|
|
490
|
+
[PermissionAction.READ]: true,
|
|
491
|
+
},
|
|
492
|
+
[PermissionResource.CONTENT_FIELD]: {
|
|
493
|
+
[PermissionAction.READ]: true,
|
|
494
|
+
},
|
|
495
|
+
[PermissionResource.CHANNEL]: {
|
|
496
|
+
[PermissionAction.READ]: true,
|
|
497
|
+
},
|
|
498
|
+
[PermissionResource.THEME]: {
|
|
499
|
+
[PermissionAction.READ]: true,
|
|
500
|
+
},
|
|
501
|
+
[PermissionResource.TEMPLATE]: {
|
|
502
|
+
[PermissionAction.READ]: true,
|
|
503
|
+
},
|
|
504
|
+
[PermissionResource.BLUEPRINT]: {
|
|
505
|
+
[PermissionAction.READ]: true,
|
|
506
|
+
},
|
|
507
|
+
[PermissionResource.GRID]: {
|
|
508
|
+
[PermissionAction.READ]: true,
|
|
509
|
+
},
|
|
510
|
+
[PermissionResource.GRID_DEFINITION]: {
|
|
511
|
+
[PermissionAction.READ]: true,
|
|
512
|
+
},
|
|
513
|
+
[PermissionResource.GRID_PLACEMENT]: {
|
|
514
|
+
[PermissionAction.READ]: true,
|
|
515
|
+
},
|
|
516
|
+
[PermissionResource.ORGANIZATION]: {
|
|
517
|
+
[PermissionAction.READ]: true,
|
|
518
|
+
},
|
|
519
|
+
[PermissionResource.ROLE]: {
|
|
520
|
+
[PermissionAction.READ]: true,
|
|
521
|
+
},
|
|
522
|
+
[PermissionResource.ROLE_ASSIGNMENT]: {
|
|
523
|
+
[PermissionAction.READ]: true,
|
|
524
|
+
},
|
|
525
|
+
[PermissionResource.INVITATION]: {
|
|
526
|
+
[PermissionAction.READ]: true,
|
|
527
|
+
},
|
|
528
|
+
[PermissionResource.API_TOKEN]: {
|
|
529
|
+
[PermissionAction.READ]: true,
|
|
530
|
+
},
|
|
531
|
+
[PermissionResource.LANGUAGE]: {
|
|
532
|
+
[PermissionAction.READ]: true,
|
|
533
|
+
},
|
|
534
|
+
[PermissionResource.ISSUE]: {
|
|
535
|
+
[PermissionAction.READ]: true,
|
|
536
|
+
},
|
|
537
|
+
[PermissionResource.PUBLICATION]: {
|
|
538
|
+
[PermissionAction.READ]: true,
|
|
539
|
+
},
|
|
540
|
+
[PermissionResource.SCHEDULED_PUBLICATION]: {
|
|
541
|
+
[PermissionAction.READ]: true,
|
|
542
|
+
},
|
|
543
|
+
[PermissionResource.CONTENT_VALIDATION_RULE]: {
|
|
544
|
+
[PermissionAction.READ]: true,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
cdaApiToken: {
|
|
548
|
+
[PermissionResource.CONTENT_EXPERIENCE]: {
|
|
549
|
+
[PermissionAction.READ]: true,
|
|
550
|
+
},
|
|
551
|
+
[PermissionResource.CONTENT_ITEM]: {
|
|
552
|
+
[PermissionAction.READ]: true,
|
|
553
|
+
},
|
|
554
|
+
[PermissionResource.TAG]: {
|
|
555
|
+
[PermissionAction.READ]: true,
|
|
556
|
+
},
|
|
557
|
+
[PermissionResource.LANGUAGE]: {
|
|
558
|
+
[PermissionAction.READ]: true,
|
|
559
|
+
},
|
|
560
|
+
[PermissionResource.PATH_PART]: {
|
|
561
|
+
[PermissionAction.READ]: true,
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Merges permissions with additive OR semantics.
|
|
568
|
+
* @remarks Missing or false entries never clear an existing true grant.
|
|
569
|
+
*/
|
|
570
|
+
export function mergePermissions(...permissionsList: Permissions[]): Permissions {
|
|
571
|
+
const result: Permissions = {};
|
|
572
|
+
|
|
573
|
+
for (const permissions of permissionsList) {
|
|
574
|
+
for (const [resource, actions] of Object.entries(permissions) as [
|
|
575
|
+
keyof Permissions,
|
|
576
|
+
Permissions[keyof Permissions],
|
|
577
|
+
][]) {
|
|
578
|
+
if (actions == null) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const mergedActions: Record<string, boolean> = {
|
|
583
|
+
...(result[resource] as Record<string, boolean> | undefined),
|
|
584
|
+
};
|
|
585
|
+
for (const [action, granted] of Object.entries(actions)) {
|
|
586
|
+
if (granted === true) {
|
|
587
|
+
mergedActions[action] = true;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
result[resource] = mergedActions as Permissions[keyof Permissions];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Returns a copy of {@link permissions} containing only `true` grants.
|
|
599
|
+
*/
|
|
600
|
+
export function compactPermissions(permissions: Permissions): Permissions {
|
|
601
|
+
const result: Permissions = {};
|
|
602
|
+
|
|
603
|
+
for (const [resource, actions] of Object.entries(permissions) as [
|
|
604
|
+
keyof Permissions,
|
|
605
|
+
Permissions[keyof Permissions],
|
|
606
|
+
][]) {
|
|
607
|
+
if (actions == null) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const grantedActions: Record<string, true> = {};
|
|
612
|
+
for (const [action, granted] of Object.entries(actions)) {
|
|
613
|
+
if (granted === true) {
|
|
614
|
+
grantedActions[action] = true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (Object.keys(grantedActions).length > 0) {
|
|
619
|
+
result[resource] = grantedActions as Permissions[keyof Permissions];
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Whether {@link permissions} is a valid permissions object against {@link Permissions}.
|
|
628
|
+
* @remarks Requires at least one `true` grant. Unknown resources or actions are invalid.
|
|
629
|
+
*/
|
|
630
|
+
export function isValidPermissions(permissions: unknown): permissions is Permissions {
|
|
631
|
+
if (permissions === null || typeof permissions !== "object" || Array.isArray(permissions)) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
let hasGrantedPermission = false;
|
|
636
|
+
|
|
637
|
+
for (const [resource, actions] of Object.entries(permissions)) {
|
|
638
|
+
if (
|
|
639
|
+
!PERMISSION_RESOURCES.has(resource) ||
|
|
640
|
+
actions === null ||
|
|
641
|
+
typeof actions !== "object" ||
|
|
642
|
+
Array.isArray(actions)
|
|
643
|
+
) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const allowedActions = new Set<string>(RESOURCE_ACTIONS[resource as PermissionResource]);
|
|
648
|
+
|
|
649
|
+
for (const [action, granted] of Object.entries(actions as Record<string, unknown>)) {
|
|
650
|
+
if (!allowedActions.has(action)) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (granted === true) {
|
|
655
|
+
hasGrantedPermission = true;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (granted !== false && granted != null) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return hasGrantedPermission;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Returns the permissions from {@link permissions} that are not granted by {@link availablePermissions}.
|
|
670
|
+
*/
|
|
671
|
+
export function getMissingPermissions(availablePermissions: Permissions, permissions: Permissions): Permissions {
|
|
672
|
+
const result: Permissions = {};
|
|
673
|
+
|
|
674
|
+
for (const [resource, actions] of Object.entries(permissions)) {
|
|
675
|
+
if (actions == null) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
for (const [action, granted] of Object.entries(actions)) {
|
|
680
|
+
if (
|
|
681
|
+
granted === true &&
|
|
682
|
+
availablePermissions[resource as PermissionResource]?.[action as PermissionAction] !== true
|
|
683
|
+
) {
|
|
684
|
+
result[resource as PermissionResource] ??= {};
|
|
685
|
+
result[resource as PermissionResource]![action as PermissionAction] = true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Whether {@link availablePermissions} contains all granted permissions in {@link permissions}.
|
|
695
|
+
*/
|
|
696
|
+
export function hasPermissions(availablePermissions: Permissions, permissions: Permissions): boolean {
|
|
697
|
+
return Object.keys(getMissingPermissions(availablePermissions, permissions)).length === 0;
|
|
698
|
+
}
|