@contember/engine-content-api 2.0.0-rc.1 → 2.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-content-api",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/production/index.js",
6
6
  "typings": "./dist/types/index.d.ts",
@@ -26,18 +26,18 @@
26
26
  "generate": "gql-gen --config graphql.codegen.yml"
27
27
  },
28
28
  "dependencies": {
29
- "@contember/database": "2.0.0-rc.1",
30
- "@contember/dic": "2.0.0-rc.1",
31
- "@contember/graphql-utils": "2.0.0-rc.1",
32
- "@contember/logger": "2.0.0-rc.1",
33
- "@contember/schema": "2.0.0-rc.1",
34
- "@contember/schema-definition": "2.0.0-rc.1",
35
- "@contember/schema-utils": "2.0.0-rc.1",
29
+ "@contember/database": "2.0.0",
30
+ "@contember/dic": "2.0.0",
31
+ "@contember/graphql-utils": "2.0.0",
32
+ "@contember/logger": "2.0.0",
33
+ "@contember/schema": "2.0.0",
34
+ "@contember/schema-definition": "2.0.0",
35
+ "@contember/schema-utils": "2.0.0",
36
36
  "fast-deep-equal": "^3.1.3"
37
37
  },
38
38
  "devDependencies": {
39
- "@contember/database-tester": "2.0.0-rc.1",
40
- "@contember/schema-definition": "2.0.0-rc.1",
39
+ "@contember/database-tester": "2.0.0",
40
+ "@contember/schema-definition": "2.0.0",
41
41
  "@types/node": "^20.16.11",
42
42
  "graphql": "^16.9.0",
43
43
  "pg": "^8.12.0"
@@ -76,7 +76,7 @@ export class PermissionFactory {
76
76
  let idPermissions: Acl.Predicate = fieldPermissions[entity.primary] || false
77
77
 
78
78
  for (let predicateReference of predicateReferences) {
79
- const [predicateDefinition, predicate] = this.mergePredicates(
79
+ const { predicateDefinition, predicate } = this.mergePredicates(
80
80
  entityPredicates,
81
81
  idPermissions,
82
82
  entityPredicates,
@@ -118,45 +118,36 @@ export class PermissionFactory {
118
118
  }
119
119
  const noRoot: `${Acl.Operation}`[] = []
120
120
 
121
- const operationNames: (keyof Pick<Acl.EntityOperations, 'create' | 'read' | 'update'>)[] = [
121
+ for (let operation of [
122
122
  'create',
123
123
  'read',
124
124
  'update',
125
- ]
125
+ ] as const) {
126
126
 
127
- for (let operation of operationNames) {
128
- const leftNoRoot = left.operations.noRoot?.includes(operation) || false
129
- const rightNoRoot = right.operations.noRoot?.includes(operation) || false
130
- if (leftNoRoot && rightNoRoot) {
127
+ const { predicates: opPredicates, permissions: fieldPermissions, noRoot: opNoRoot } = this.mergeOperationPermissions(left, right, operation)
128
+
129
+ if (opNoRoot) {
131
130
  noRoot.push(operation)
132
131
  }
133
132
 
134
- const leftFieldPermissions: Acl.FieldPermissions = leftNoRoot && !rightNoRoot ? {} : (left.operations[operation] || {})
135
- const rightFieldPermissions: Acl.FieldPermissions = rightNoRoot && !leftNoRoot ? {} : (right.operations[operation] || {})
136
- const [operationPredicates, fieldPermissions] = this.mergeFieldPermissions(
137
- left.predicates,
138
- leftFieldPermissions,
139
- right.predicates,
140
- rightFieldPermissions,
141
- )
142
- predicates = { ...predicates, ...operationPredicates }
133
+ predicates = { ...predicates, ...opPredicates }
143
134
  if (Object.keys(fieldPermissions).length > 0) {
144
135
  operations[operation] = fieldPermissions
145
136
  }
146
137
  }
147
138
 
148
- const [predicateDefinition, predicate] = this.mergePredicates(
149
- left.predicates,
150
- left.operations.delete || false,
151
- right.predicates,
152
- right.operations.delete || false,
153
- )
139
+ const { predicateDefinition, predicate, noRoot: opNoRoot } = this.mergeDeletePermissions(left, right)
140
+ if (opNoRoot) {
141
+ noRoot.push('delete')
142
+ }
154
143
  if (predicate === true) {
155
144
  operations.delete = true
156
145
  } else if (predicateDefinition !== undefined && typeof predicate === 'string') {
157
146
  predicates[predicate] = predicateDefinition
158
147
  operations.delete = predicate
159
148
  }
149
+
150
+
160
151
  if (noRoot.length > 0) {
161
152
  operations.noRoot = noRoot
162
153
  }
@@ -167,62 +158,134 @@ export class PermissionFactory {
167
158
  }
168
159
  }
169
160
 
161
+
162
+ private mergeOperationPermissions(left: Acl.EntityPermissions, right: Acl.EntityPermissions, operation: 'create' | 'read' | 'update'): {
163
+ noRoot: boolean
164
+ predicates: Acl.PredicateMap
165
+ permissions: Acl.FieldPermissions
166
+ } {
167
+ const { noRoot, leftPermissions, rightPermissions } = this.resolveNoRoot(left, right, operation)
168
+ return {
169
+ noRoot,
170
+ ...this.mergeFieldPermissions(
171
+ left.predicates,
172
+ leftPermissions,
173
+ right.predicates,
174
+ rightPermissions,
175
+ ),
176
+ }
177
+ }
178
+
179
+ private mergeDeletePermissions(left: Acl.EntityPermissions, right: Acl.EntityPermissions): {
180
+ noRoot: boolean
181
+ predicate: Acl.PredicateReference | boolean
182
+ predicateDefinition: Acl.PredicateDefinition | undefined
183
+ } {
184
+ const { noRoot, leftPermissions, rightPermissions } = this.resolveNoRoot(left, right, 'delete')
185
+
186
+ return {
187
+ noRoot,
188
+ ...this.mergePredicates(
189
+ left.predicates,
190
+ leftPermissions,
191
+ right.predicates,
192
+ rightPermissions,
193
+ ),
194
+ }
195
+ }
196
+
197
+ private resolveNoRoot<const Op extends`${Acl.Operation}`>(left: Acl.EntityPermissions, right: Acl.EntityPermissions, operation: Op): {
198
+ noRoot: boolean
199
+ leftPermissions: Acl.EntityPermissions['operations'][Op] | undefined
200
+ rightPermissions: Acl.EntityPermissions['operations'][Op] | undefined
201
+ } {
202
+ const leftRootForbidden = left.operations.noRoot?.includes(operation) || false
203
+ const rightRootForbidden = right.operations.noRoot?.includes(operation) || false
204
+ const leftPermissions = left.operations[operation]
205
+ const rightPermissions = right.operations[operation]
206
+
207
+ const rootForbidden = (leftRootForbidden && rightRootForbidden)
208
+ || (leftRootForbidden && !rightPermissions)
209
+ || (rightRootForbidden && !leftPermissions)
210
+
211
+ const resolvedLeftPermissions = !rootForbidden && leftRootForbidden ? undefined : leftPermissions
212
+ const resolvedRightPermissions = !rootForbidden && rightRootForbidden ? undefined : rightPermissions
213
+
214
+ return {
215
+ noRoot: rootForbidden,
216
+ leftPermissions: resolvedLeftPermissions,
217
+ rightPermissions: resolvedRightPermissions,
218
+ }
219
+ }
220
+
170
221
  private mergeFieldPermissions(
171
222
  leftPredicates: Acl.PredicateMap,
172
- leftFieldPermissions: Acl.FieldPermissions,
223
+ leftFieldPermissions: Acl.FieldPermissions | undefined = {},
173
224
  rightPredicates: Acl.PredicateMap,
174
- rightFieldPermissions: Acl.FieldPermissions,
175
- ): [Acl.PredicateMap, Acl.FieldPermissions] {
176
- const fields: Writable<Acl.FieldPermissions> = {}
225
+ rightFieldPermissions: Acl.FieldPermissions | undefined = {},
226
+ ): {predicates: Acl.PredicateMap; permissions: Acl.FieldPermissions} {
227
+ const permissions: Writable<Acl.FieldPermissions> = {}
177
228
  const predicates: Writable<Acl.PredicateMap> = {}
178
229
 
179
230
  for (let field in { ...leftFieldPermissions, ...rightFieldPermissions }) {
180
- const [predicateDefinition, predicate] = this.mergePredicates(
231
+ const { predicateDefinition, predicate } = this.mergePredicates(
181
232
  leftPredicates,
182
233
  leftFieldPermissions[field] || false,
183
234
  rightPredicates,
184
235
  rightFieldPermissions[field] || false,
185
236
  )
186
237
  if (predicate === true) {
187
- fields[field] = true
238
+ permissions[field] = true
188
239
  } else if (predicateDefinition !== undefined && typeof predicate === 'string') {
189
- fields[field] = predicate
240
+ permissions[field] = predicate
190
241
  predicates[predicate] = predicateDefinition
191
242
  }
192
243
  }
193
244
 
194
- return [predicates, fields]
245
+ return { predicates, permissions }
195
246
  }
196
247
 
197
248
  private mergePredicates(
198
249
  leftPredicates: Acl.PredicateMap,
199
- leftReference: Acl.Predicate,
250
+ leftReference: Acl.Predicate | undefined = false,
200
251
  rightPredicates: Acl.PredicateMap,
201
- rightReference: Acl.Predicate,
202
- ): [Acl.PredicateDefinition, Acl.PredicateReference] | [undefined, boolean] {
252
+ rightReference: Acl.Predicate | undefined = false,
253
+ ): {
254
+ predicate: Acl.PredicateReference | boolean
255
+ predicateDefinition: Acl.PredicateDefinition | undefined
256
+ } {
203
257
  if (leftReference === true || rightReference === true) {
204
- return [undefined, true]
258
+ return {
259
+ predicate: true,
260
+ predicateDefinition: undefined,
261
+ }
205
262
  }
206
263
 
207
264
  if (leftReference !== false && rightReference !== false) {
208
265
  const leftPredicate: Acl.PredicateDefinition = leftPredicates[leftReference]
209
266
  const rightPredicate: Acl.PredicateDefinition = rightPredicates[rightReference]
210
267
  if (leftPredicate === rightPredicate) {
211
- return [leftPredicate, leftReference]
268
+ return {
269
+ predicate: leftReference,
270
+ predicateDefinition: leftPredicate,
271
+ }
212
272
  }
213
273
 
214
274
  let predicateName = '__merge__' + leftReference + '__' + rightReference
215
275
  while (leftPredicates[predicateName]) {
216
276
  predicateName += '_'
217
277
  }
218
- return [
219
- {
278
+ return {
279
+ predicate: predicateName,
280
+ predicateDefinition: {
220
281
  or: [leftPredicate, rightPredicate],
221
282
  } as Acl.PredicateDefinition,
222
- predicateName,
223
- ]
283
+ }
224
284
  } else if (leftReference !== false) {
225
- return [leftPredicates[leftReference], leftReference]
285
+ return {
286
+ predicate: leftReference,
287
+ predicateDefinition: leftPredicates[leftReference],
288
+ }
226
289
  } else if (rightReference !== false) {
227
290
  let predicateName = rightReference
228
291
  if (rightPredicates !== leftPredicates) {
@@ -230,9 +293,15 @@ export class PermissionFactory {
230
293
  predicateName += '_'
231
294
  }
232
295
  }
233
- return [rightPredicates[rightReference], predicateName]
296
+ return {
297
+ predicate: predicateName,
298
+ predicateDefinition: rightPredicates[rightReference],
299
+ }
234
300
  } else {
235
- return [undefined, false]
301
+ return {
302
+ predicate: false,
303
+ predicateDefinition: undefined,
304
+ }
236
305
  }
237
306
  }
238
307
  }
@@ -550,6 +550,186 @@ describe('Permission merger', () => {
550
550
  })
551
551
 
552
552
 
553
+ it('merge noRootOperations', () => {
554
+ execute({
555
+ acl: {
556
+ roles: {
557
+ role1: {
558
+ variables: {},
559
+ entities: {
560
+ Entity2: {
561
+ predicates: {},
562
+ operations: {
563
+ read: {
564
+ title: true,
565
+ },
566
+ noRoot: ['read'],
567
+ },
568
+ },
569
+ },
570
+ },
571
+ role2: {
572
+ variables: {},
573
+ entities: {
574
+ Entity2: {
575
+ predicates: {},
576
+ operations: {
577
+ read: {
578
+ title: true,
579
+ },
580
+ update: {
581
+ title: true,
582
+ },
583
+ noRoot: ['read', 'update'],
584
+ },
585
+ },
586
+ },
587
+ },
588
+ },
589
+ },
590
+ roles: ['role1', 'role2'],
591
+ result: {
592
+ Entity2: {
593
+ operations: {
594
+ update: {
595
+ id: true,
596
+ title: true,
597
+ },
598
+ read: {
599
+ id: true,
600
+ title: true,
601
+ },
602
+ noRoot: ['read', 'update'],
603
+ },
604
+ predicates: {},
605
+ },
606
+ },
607
+ })
608
+ })
609
+
610
+
611
+ it('merge delete #1', () => {
612
+ execute({
613
+ acl: {
614
+ roles: {
615
+ role1: {
616
+ variables: {},
617
+ entities: {
618
+ Entity2: {
619
+ predicates: {},
620
+ operations: {
621
+ delete: true,
622
+ noRoot: ['delete'],
623
+ },
624
+ },
625
+ },
626
+ },
627
+ role2: {
628
+ variables: {},
629
+ entities: {
630
+ Entity2: {
631
+ predicates: {},
632
+ operations: {},
633
+ },
634
+ },
635
+ },
636
+ },
637
+ },
638
+ roles: ['role1', 'role2'],
639
+ result: {
640
+ Entity2: {
641
+ operations: {
642
+ delete: true,
643
+ noRoot: ['delete'],
644
+ },
645
+ predicates: {},
646
+ },
647
+ },
648
+ })
649
+ })
650
+
651
+
652
+ it('merge delete #2', () => {
653
+ execute({
654
+ acl: {
655
+ roles: {
656
+ role1: {
657
+ variables: {},
658
+ entities: {
659
+ Entity2: {
660
+ predicates: {},
661
+ operations: {
662
+ delete: true,
663
+ noRoot: ['delete'],
664
+ },
665
+ },
666
+ },
667
+ },
668
+ role2: {
669
+ variables: {},
670
+ entities: {
671
+ Entity2: {
672
+ predicates: {},
673
+ operations: {
674
+ delete: true,
675
+ },
676
+ },
677
+ },
678
+ },
679
+ },
680
+ },
681
+ roles: ['role1', 'role2'],
682
+ result: {
683
+ Entity2: {
684
+ operations: {
685
+ delete: true,
686
+ },
687
+ predicates: {},
688
+ },
689
+ },
690
+ })
691
+ })
692
+
693
+ it('merge delete #2', () => {
694
+ execute({
695
+ acl: {
696
+ roles: {
697
+ role1: {
698
+ variables: {},
699
+ entities: {
700
+ Entity2: {
701
+ predicates: {},
702
+ operations: {
703
+ delete: true,
704
+ noRoot: ['delete'],
705
+ },
706
+ },
707
+ },
708
+ },
709
+ role2: {
710
+ variables: {},
711
+ entities: {
712
+ Entity2: {
713
+ predicates: {},
714
+ operations: {},
715
+ },
716
+ },
717
+ },
718
+ },
719
+ },
720
+ roles: ['role1', 'role2'],
721
+ result: {
722
+ Entity2: {
723
+ operations: {
724
+ delete: true,
725
+ noRoot: ['delete'],
726
+ },
727
+ predicates: {},
728
+ },
729
+ },
730
+ })
731
+ })
732
+
553
733
 
554
734
  it('prefer permissions with root allowed', () => {
555
735
  execute({