@contember/schema-definition 1.1.0-alpha.5 → 1.1.0-alpha.8

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.
Files changed (179) hide show
  1. package/dist/src/acl/{EntityPermissionsBuilder.d.ts → builder/EntityPermissionsBuilder.d.ts} +1 -1
  2. package/dist/src/acl/builder/EntityPermissionsBuilder.d.ts.map +1 -0
  3. package/dist/src/acl/{EntityPermissionsBuilder.js → builder/EntityPermissionsBuilder.js} +1 -1
  4. package/dist/src/acl/builder/EntityPermissionsBuilder.js.map +1 -0
  5. package/dist/src/acl/{EntitySelector.d.ts → builder/EntitySelector.d.ts} +0 -0
  6. package/dist/src/acl/builder/EntitySelector.d.ts.map +1 -0
  7. package/dist/src/acl/{EntitySelector.js → builder/EntitySelector.js} +0 -0
  8. package/dist/src/acl/builder/EntitySelector.js.map +1 -0
  9. package/dist/src/acl/{FieldPermissionsBuilder.d.ts → builder/FieldPermissionsBuilder.d.ts} +1 -1
  10. package/dist/src/acl/builder/FieldPermissionsBuilder.d.ts.map +1 -0
  11. package/dist/src/acl/{FieldPermissionsBuilder.js → builder/FieldPermissionsBuilder.js} +2 -1
  12. package/dist/src/acl/builder/FieldPermissionsBuilder.js.map +1 -0
  13. package/dist/src/acl/{FieldSelector.d.ts → builder/FieldSelector.d.ts} +0 -0
  14. package/dist/src/acl/builder/FieldSelector.d.ts.map +1 -0
  15. package/dist/src/acl/{FieldSelector.js → builder/FieldSelector.js} +0 -0
  16. package/dist/src/acl/builder/FieldSelector.js.map +1 -0
  17. package/dist/src/acl/{PermissionOverrider.d.ts → builder/PermissionOverrider.d.ts} +0 -0
  18. package/dist/src/acl/builder/PermissionOverrider.d.ts.map +1 -0
  19. package/dist/src/acl/{PermissionOverrider.js → builder/PermissionOverrider.js} +1 -1
  20. package/dist/src/acl/builder/PermissionOverrider.js.map +1 -0
  21. package/dist/src/acl/{PermissionsBuilder.d.ts → builder/PermissionsBuilder.d.ts} +1 -1
  22. package/dist/src/acl/builder/PermissionsBuilder.d.ts.map +1 -0
  23. package/dist/src/acl/{PermissionsBuilder.js → builder/PermissionsBuilder.js} +3 -1
  24. package/dist/src/acl/builder/PermissionsBuilder.js.map +1 -0
  25. package/dist/src/acl/{PredicateReference.d.ts → builder/PredicateReference.d.ts} +0 -0
  26. package/dist/src/acl/builder/PredicateReference.d.ts.map +1 -0
  27. package/dist/src/acl/{PredicateReference.js → builder/PredicateReference.js} +0 -0
  28. package/dist/src/acl/builder/PredicateReference.js.map +1 -0
  29. package/dist/src/acl/definition/customPrimary.d.ts +4 -0
  30. package/dist/src/acl/definition/customPrimary.d.ts.map +1 -0
  31. package/dist/src/acl/definition/customPrimary.js +16 -0
  32. package/dist/src/acl/definition/customPrimary.js.map +1 -0
  33. package/dist/src/acl/definition/index.d.ts +8 -0
  34. package/dist/src/acl/definition/index.d.ts.map +1 -0
  35. package/dist/src/acl/definition/index.js +29 -0
  36. package/dist/src/acl/definition/index.js.map +1 -0
  37. package/dist/src/acl/definition/internal/AclFactory.d.ts +18 -0
  38. package/dist/src/acl/definition/internal/AclFactory.d.ts.map +1 -0
  39. package/dist/src/acl/definition/internal/AclFactory.js +112 -0
  40. package/dist/src/acl/definition/internal/AclFactory.js.map +1 -0
  41. package/dist/src/acl/definition/internal/EntityPredicateResolver.d.ts +16 -0
  42. package/dist/src/acl/definition/internal/EntityPredicateResolver.d.ts.map +1 -0
  43. package/dist/src/acl/definition/internal/EntityPredicateResolver.js +130 -0
  44. package/dist/src/acl/definition/internal/EntityPredicateResolver.js.map +1 -0
  45. package/dist/src/acl/definition/internal/MetadataStore.d.ts +7 -0
  46. package/dist/src/acl/definition/internal/MetadataStore.d.ts.map +1 -0
  47. package/dist/src/acl/definition/internal/MetadataStore.js +22 -0
  48. package/dist/src/acl/definition/internal/MetadataStore.js.map +1 -0
  49. package/dist/src/acl/definition/internal/createPredicateName.d.ts +3 -0
  50. package/dist/src/acl/definition/internal/createPredicateName.d.ts.map +1 -0
  51. package/dist/src/acl/definition/internal/createPredicateName.js +9 -0
  52. package/dist/src/acl/definition/internal/createPredicateName.js.map +1 -0
  53. package/dist/src/acl/definition/internal/stores.d.ts +9 -0
  54. package/dist/src/acl/definition/internal/stores.d.ts.map +1 -0
  55. package/dist/src/acl/definition/internal/stores.js +8 -0
  56. package/dist/src/acl/definition/internal/stores.js.map +1 -0
  57. package/dist/src/acl/definition/permissions.d.ts +17 -0
  58. package/dist/src/acl/definition/permissions.d.ts.map +1 -0
  59. package/dist/src/acl/definition/permissions.js +13 -0
  60. package/dist/src/acl/definition/permissions.js.map +1 -0
  61. package/dist/src/acl/definition/references.d.ts +10 -0
  62. package/dist/src/acl/definition/references.d.ts.map +1 -0
  63. package/dist/src/acl/definition/references.js +27 -0
  64. package/dist/src/acl/definition/references.js.map +1 -0
  65. package/dist/src/acl/definition/roles.d.ts +13 -0
  66. package/dist/src/acl/definition/roles.d.ts.map +1 -0
  67. package/dist/src/acl/definition/roles.js +15 -0
  68. package/dist/src/acl/definition/roles.js.map +1 -0
  69. package/dist/src/acl/definition/variables.d.ts +11 -0
  70. package/dist/src/acl/definition/variables.d.ts.map +1 -0
  71. package/dist/src/acl/definition/variables.js +17 -0
  72. package/dist/src/acl/definition/variables.js.map +1 -0
  73. package/dist/src/createSchema.d.ts +3 -0
  74. package/dist/src/createSchema.d.ts.map +1 -0
  75. package/dist/src/createSchema.js +15 -0
  76. package/dist/src/createSchema.js.map +1 -0
  77. package/dist/src/index.d.ts +5 -4
  78. package/dist/src/index.d.ts.map +1 -1
  79. package/dist/src/index.js +12 -7
  80. package/dist/src/index.js.map +1 -1
  81. package/dist/src/model/builder/SchemaBuilder.js +5 -1
  82. package/dist/src/model/builder/SchemaBuilder.js.map +1 -1
  83. package/dist/src/model/builder/SchemaBuilderInternal.d.ts.map +1 -1
  84. package/dist/src/model/builder/SchemaBuilderInternal.js +3 -0
  85. package/dist/src/model/builder/SchemaBuilderInternal.js.map +1 -1
  86. package/dist/src/model/builder/internal/ManyHasManyProcessor.d.ts.map +1 -1
  87. package/dist/src/model/builder/internal/ManyHasManyProcessor.js +3 -0
  88. package/dist/src/model/builder/internal/ManyHasManyProcessor.js.map +1 -1
  89. package/dist/src/model/definition/EventLogDefinition.d.ts +2 -0
  90. package/dist/src/model/definition/EventLogDefinition.d.ts.map +1 -0
  91. package/dist/src/model/definition/EventLogDefinition.js +10 -0
  92. package/dist/src/model/definition/EventLogDefinition.js.map +1 -0
  93. package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.d.ts +2 -0
  94. package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.d.ts.map +1 -1
  95. package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.js +5 -1
  96. package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.js.map +1 -1
  97. package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.d.ts +2 -2
  98. package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.d.ts.map +1 -1
  99. package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.js +8 -9
  100. package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.js.map +1 -1
  101. package/dist/src/model/definition/fieldDefinitions/index.js +5 -1
  102. package/dist/src/model/definition/fieldDefinitions/index.js.map +1 -1
  103. package/dist/src/model/definition/index.d.ts +3 -2
  104. package/dist/src/model/definition/index.d.ts.map +1 -1
  105. package/dist/src/model/definition/index.js +8 -2
  106. package/dist/src/model/definition/index.js.map +1 -1
  107. package/dist/src/model/definition/internal/SchemaBuilder.d.ts.map +1 -1
  108. package/dist/src/model/definition/internal/SchemaBuilder.js +7 -2
  109. package/dist/src/model/definition/internal/SchemaBuilder.js.map +1 -1
  110. package/dist/src/model/definition/internal/index.js +5 -1
  111. package/dist/src/model/definition/internal/index.js.map +1 -1
  112. package/dist/src/model/utils/index.js +5 -1
  113. package/dist/src/model/utils/index.js.map +1 -1
  114. package/dist/src/tsconfig.tsbuildinfo +1 -1
  115. package/dist/src/utils/filterEntityDefinition.d.ts +4 -0
  116. package/dist/src/utils/filterEntityDefinition.d.ts.map +1 -0
  117. package/dist/src/utils/filterEntityDefinition.js +11 -0
  118. package/dist/src/utils/filterEntityDefinition.js.map +1 -0
  119. package/dist/src/utils/index.d.ts +1 -0
  120. package/dist/src/utils/index.d.ts.map +1 -1
  121. package/dist/src/utils/index.js +6 -1
  122. package/dist/src/utils/index.js.map +1 -1
  123. package/dist/src/validation/index.d.ts +2 -5
  124. package/dist/src/validation/index.d.ts.map +1 -1
  125. package/dist/src/validation/index.js +2 -3
  126. package/dist/src/validation/index.js.map +1 -1
  127. package/dist/tests/tsconfig.tsbuildinfo +1 -0
  128. package/dist/tests/unit/readAclDefinitions.test.d.ts +2 -0
  129. package/dist/tests/unit/readAclDefinitions.test.d.ts.map +1 -0
  130. package/dist/tests/unit/readAclDefinitions.test.js +449 -0
  131. package/dist/tests/unit/readAclDefinitions.test.js.map +1 -0
  132. package/package.json +4 -4
  133. package/src/acl/{EntityPermissionsBuilder.ts → builder/EntityPermissionsBuilder.ts} +4 -4
  134. package/src/acl/{EntitySelector.ts → builder/EntitySelector.ts} +0 -0
  135. package/src/acl/{FieldPermissionsBuilder.ts → builder/FieldPermissionsBuilder.ts} +5 -4
  136. package/src/acl/{FieldSelector.ts → builder/FieldSelector.ts} +0 -0
  137. package/src/acl/{PermissionOverrider.ts → builder/PermissionOverrider.ts} +1 -1
  138. package/src/acl/{PermissionsBuilder.ts → builder/PermissionsBuilder.ts} +1 -1
  139. package/src/acl/{PredicateReference.ts → builder/PredicateReference.ts} +0 -0
  140. package/src/acl/definition/customPrimary.ts +13 -0
  141. package/src/acl/definition/index.ts +16 -0
  142. package/src/acl/definition/internal/AclFactory.ts +130 -0
  143. package/src/acl/definition/internal/EntityPredicateResolver.ts +149 -0
  144. package/src/acl/definition/internal/MetadataStore.ts +23 -0
  145. package/src/acl/definition/internal/createPredicateName.ts +6 -0
  146. package/src/acl/definition/internal/stores.ts +13 -0
  147. package/src/acl/definition/permissions.ts +29 -0
  148. package/src/acl/definition/references.ts +21 -0
  149. package/src/acl/definition/roles.ts +19 -0
  150. package/src/acl/definition/variables.ts +27 -0
  151. package/src/createSchema.ts +12 -0
  152. package/src/index.ts +5 -5
  153. package/src/model/builder/SchemaBuilderInternal.ts +6 -3
  154. package/src/model/builder/internal/ManyHasManyProcessor.ts +3 -0
  155. package/src/model/definition/EventLogDefinition.ts +7 -0
  156. package/src/model/definition/fieldDefinitions/ColumnDefinition.ts +8 -1
  157. package/src/model/definition/fieldDefinitions/ManyHasManyDefinition.ts +14 -15
  158. package/src/model/definition/index.ts +5 -3
  159. package/src/model/definition/internal/SchemaBuilder.ts +9 -4
  160. package/src/utils/filterEntityDefinition.ts +8 -0
  161. package/src/utils/index.ts +1 -0
  162. package/src/validation/index.ts +4 -4
  163. package/tests/tsconfig.json +13 -0
  164. package/tests/unit/readAclDefinitions.test.ts +406 -0
  165. package/tsconfig.json +4 -1
  166. package/dist/src/acl/EntityPermissionsBuilder.d.ts.map +0 -1
  167. package/dist/src/acl/EntityPermissionsBuilder.js.map +0 -1
  168. package/dist/src/acl/EntitySelector.d.ts.map +0 -1
  169. package/dist/src/acl/EntitySelector.js.map +0 -1
  170. package/dist/src/acl/FieldPermissionsBuilder.d.ts.map +0 -1
  171. package/dist/src/acl/FieldPermissionsBuilder.js.map +0 -1
  172. package/dist/src/acl/FieldSelector.d.ts.map +0 -1
  173. package/dist/src/acl/FieldSelector.js.map +0 -1
  174. package/dist/src/acl/PermissionOverrider.d.ts.map +0 -1
  175. package/dist/src/acl/PermissionOverrider.js.map +0 -1
  176. package/dist/src/acl/PermissionsBuilder.d.ts.map +0 -1
  177. package/dist/src/acl/PermissionsBuilder.js.map +0 -1
  178. package/dist/src/acl/PredicateReference.d.ts.map +0 -1
  179. package/dist/src/acl/PredicateReference.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import FieldSelector from './FieldSelector'
2
2
  import EntityPermissionsBuilder from './EntityPermissionsBuilder'
3
- import { Acl, Model } from '@contember/schema'
3
+ import { Acl, Model, Writable } from '@contember/schema'
4
4
  import EntitySelector from './EntitySelector'
5
- import PermissionsBuilder from './PermissionsBuilder'
5
+ import { PermissionsBuilder } from './PermissionsBuilder'
6
6
 
7
7
  export default class FieldPermissionsBuilder {
8
8
  public constructor(
@@ -42,13 +42,14 @@ export default class FieldPermissionsBuilder {
42
42
  operations: (Acl.Operation.create | Acl.Operation.read | Acl.Operation.update)[],
43
43
  predicate: Acl.Predicate = true,
44
44
  ): FieldPermissionsBuilder {
45
- const permissions: Acl.Permissions = {}
45
+ const permissions: Writable<Acl.Permissions> = {}
46
46
  for (const [entity, field] of this.fields) {
47
47
  if (!permissions[entity.name]) {
48
48
  permissions[entity.name] = { operations: {}, predicates: {} }
49
49
  }
50
+ const entityOperations: Writable<Acl.EntityOperations> = permissions[entity.name].operations
50
51
  for (const op of operations) {
51
- permissions[entity.name].operations[op] = {
52
+ entityOperations[op] = {
52
53
  ...(permissions[entity.name].operations[op] || {}),
53
54
  [field.name]: predicate,
54
55
  }
@@ -1,5 +1,5 @@
1
1
  import { Acl } from '@contember/schema'
2
- import { tuple } from '../utils'
2
+ import { tuple } from '../../utils'
3
3
 
4
4
  export default class PermissionOverrider {
5
5
  public override(original: Acl.Permissions, overrides: Acl.Permissions): Acl.Permissions {
@@ -58,4 +58,4 @@ namespace PermissionsBuilder {
58
58
  }
59
59
  export const predicateReference = (name: Acl.PredicateReference) => new Reference(name)
60
60
  }
61
- export default PermissionsBuilder
61
+ export { PermissionsBuilder }
@@ -0,0 +1,13 @@
1
+ import { Role } from './roles'
2
+ import { allowCustomPrimaryAllRolesStore, allowCustomPrimaryStore } from './internal/stores'
3
+ import { DecoratorFunction, EntityConstructor } from '../../model/definition/types'
4
+
5
+ export const allowCustomPrimary = (role?: Role | Role[]): DecoratorFunction<any> => {
6
+ return (entity: EntityConstructor) => {
7
+ if (!role) {
8
+ allowCustomPrimaryAllRolesStore.update(entity, () => true)
9
+ } else {
10
+ allowCustomPrimaryStore.update(entity, val => [...val, ...(Array.isArray(role) ? role : [role])])
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,16 @@
1
+ import { Acl, Model } from '@contember/schema'
2
+ import { AclFactory } from './internal/AclFactory'
3
+
4
+ export * from './customPrimary'
5
+ export * from './permissions'
6
+ export * from './references'
7
+ export * from './roles'
8
+ export * from './variables'
9
+
10
+ export const createAcl = (
11
+ exportedDefinitions: Record<string, any>,
12
+ model: Model.Schema,
13
+ ): Acl.Schema => {
14
+ const aclFactory = new AclFactory(model)
15
+ return aclFactory.create(exportedDefinitions)
16
+ }
@@ -0,0 +1,130 @@
1
+ import { Acl, Model, Writable } from '@contember/schema'
2
+ import { getEntity } from '@contember/schema-utils'
3
+
4
+ import { EntityPredicatesResolver } from './EntityPredicateResolver'
5
+ import { AllowDefinition } from '../permissions'
6
+ import {
7
+ allowCustomPrimaryAllRolesStore,
8
+ allowCustomPrimaryStore,
9
+ allowDefinitionsStore,
10
+ EntityPermissionsDefinition,
11
+ } from './stores'
12
+ import { Role } from '../roles'
13
+ import { EntityVariableDefinition, VariableDefinition } from '../variables'
14
+ import { filterEntityDefinition } from '../../../utils'
15
+
16
+ export class AclFactory {
17
+ constructor(
18
+ private model: Model.Schema,
19
+ ) {
20
+ }
21
+
22
+ public create(
23
+ exportedDefinitions: Record<string, any>,
24
+ ): Acl.Schema {
25
+ const entityLikeDefinition = filterEntityDefinition(exportedDefinitions)
26
+ const roles: Role[] = Object.values(exportedDefinitions).filter(it => it instanceof Role)
27
+ const variables: VariableDefinition[] = Object.values(exportedDefinitions).filter(it => it instanceof EntityVariableDefinition)
28
+
29
+ const groupedPermissions = AclFactory.groupPermissions(entityLikeDefinition, roles)
30
+
31
+ return {
32
+ roles: Object.fromEntries(roles.map((role): [string, Acl.RolePermissions] => {
33
+ const rolePermissions = groupedPermissions.get(role)
34
+ return [
35
+ role.name,
36
+ {
37
+ stages: role.options.stages ?? '*',
38
+ entities: this.createPermissions(rolePermissions),
39
+ variables: this.createVariables(role, variables),
40
+ },
41
+ ]
42
+ })),
43
+ }
44
+ }
45
+
46
+ private createPermissions(rolePermissions: PermissionsByEntity | undefined): Acl.Permissions {
47
+ if (!rolePermissions) {
48
+ return {}
49
+ }
50
+ return Object.fromEntries(Array.from(rolePermissions.keys()).map((entityName): [string, Acl.EntityPermissions] => {
51
+ const entity = getEntity(this.model, entityName)
52
+
53
+ const predicatesResolver = EntityPredicatesResolver.create(rolePermissions, this.model, entity)
54
+
55
+ const entityOperations: Writable<Acl.EntityOperations> = {}
56
+ for (const op of ['create', 'update', 'read'] as const) {
57
+ const fieldPermissions: Writable<Acl.FieldPermissions> = {}
58
+ for (const field of Object.keys(entity.fields)) {
59
+ if (field === entity.primary) {
60
+ continue
61
+ }
62
+ const predicate = predicatesResolver.createFieldPredicate(op, field)
63
+ if (predicate !== undefined) {
64
+ fieldPermissions[field] = predicate
65
+ }
66
+ }
67
+ if (Object.keys(fieldPermissions).length > 0) {
68
+ entityOperations[op] = fieldPermissions
69
+ }
70
+ }
71
+ const delPredicate = predicatesResolver.createFieldPredicate('delete', '')
72
+ if (delPredicate !== undefined) {
73
+ entityOperations.delete = delPredicate
74
+ }
75
+ if (rolePermissions.get(entityName)?.allowCustomPrimary) {
76
+ entityOperations.customPrimary = true
77
+ }
78
+ return [entityName, {
79
+ predicates: predicatesResolver.getUsedPredicates(),
80
+ operations: entityOperations,
81
+ }]
82
+ }))
83
+ }
84
+
85
+ private createVariables(role: Role, variables: VariableDefinition[]): Acl.Variables {
86
+ const roleVariables = variables.filter(it => it.roles.includes(role))
87
+ return Object.fromEntries(roleVariables.map((variable): [string, Acl.Variable] => {
88
+ switch (variable.type) {
89
+ case 'entity':
90
+ return [variable.name, { type: Acl.VariableType.entity, entityName: variable.entityName }]
91
+ }
92
+ }))
93
+ }
94
+
95
+ private static groupPermissions(entityLikeDefinition: [string, { new(): any }][], roles: Role[]): PermissionsByRoleAndEntity {
96
+ const groupedPermissions: PermissionsByRoleAndEntity = new Map()
97
+ for (const [name, entity] of entityLikeDefinition) {
98
+ const initEntityPermissions = (role: Role): EntityPermissions => {
99
+ const rolePermissions: PermissionsByEntity = groupedPermissions.get(role) ?? new Map()
100
+ groupedPermissions.set(role, rolePermissions)
101
+ const entityPermissions = rolePermissions.get(name) ?? {
102
+ allowCustomPrimary: false,
103
+ definitions: [],
104
+ }
105
+ rolePermissions.set(name, entityPermissions)
106
+ return entityPermissions
107
+ }
108
+ const metadata: EntityPermissionsDefinition[] = allowDefinitionsStore.get(entity)
109
+ for (const { role, ...definition } of metadata) {
110
+ if (!roles.includes(role)) {
111
+ throw `Role ${role.name} used on entity ${name} is not registered. Have you exported it?`
112
+ }
113
+ const entityPermissions = initEntityPermissions(role)
114
+ entityPermissions.definitions.push(definition)
115
+ }
116
+
117
+ const rolesWithCustomPrimary = allowCustomPrimaryAllRolesStore.get(entity) ? roles : allowCustomPrimaryStore.get(entity)
118
+ for (const role of rolesWithCustomPrimary) {
119
+ initEntityPermissions(role).allowCustomPrimary = true
120
+ }
121
+ }
122
+
123
+ return groupedPermissions
124
+ }
125
+ }
126
+
127
+
128
+ export type EntityPermissions = { definitions: AllowDefinition<any>[]; allowCustomPrimary: boolean }
129
+ export type PermissionsByEntity = Map<string, EntityPermissions>
130
+ export type PermissionsByRoleAndEntity = Map<Role, PermissionsByEntity>
@@ -0,0 +1,149 @@
1
+ import { isDeepStrictEqual } from 'util'
2
+
3
+ import { Acl, Input, Model, Writable } from '@contember/schema'
4
+ import { PredicateDefinitionProcessor } from '@contember/schema-utils'
5
+
6
+ import { PermissionsByEntity } from './AclFactory'
7
+ import { createPredicateName } from './createPredicateName'
8
+ import { AllowDefinition, PredicateExtra, WhenDefinition } from '../permissions'
9
+ import { PredicateEvaluationReference } from '../references'
10
+ import { EntityVariableDefinition } from '../variables'
11
+
12
+ export class EntityPredicatesResolver {
13
+ private usedPredicates: string[] = []
14
+
15
+ private constructor(
16
+ private aclDefinitions: AllowDefinition<any>[],
17
+ private predicates: Writable<Acl.PredicateMap>,
18
+ private predicateNamesMap: Map<WhenDefinition, string>,
19
+ private generatedNames: Set<string>,
20
+ ) {
21
+ }
22
+
23
+ public static create(
24
+ rolePermissions: PermissionsByEntity,
25
+ model: Model.Schema,
26
+ entity: Model.Entity,
27
+ ): EntityPredicatesResolver {
28
+ const predicates: Writable<Acl.PredicateMap> = {}
29
+ const predicateNamesMap = new Map<WhenDefinition, string>()
30
+ const aclDefinitions = rolePermissions.get(entity.name)?.definitions ?? []
31
+ const generatedNames = new Set<string>()
32
+ const definedNames = new Set<string>(aclDefinitions.map(it => it.name).filter((it): it is string => it !== undefined))
33
+ for (const permission of aclDefinitions) {
34
+ if (!permission.when) {
35
+ continue
36
+ }
37
+ const resolvedPredicate = EntityPredicatesResolver.resolvePredicate(model, entity, permission.when, rolePermissions)
38
+ let predicateName = permission.name ?? createPredicateName(resolvedPredicate)
39
+ while (predicateName in predicates || definedNames.has(predicateName)) {
40
+ predicateName += '_'
41
+ }
42
+ if (!permission.name) {
43
+ generatedNames.add(predicateName)
44
+ }
45
+ predicates[predicateName] = resolvedPredicate
46
+ predicateNamesMap.set(permission.when, predicateName)
47
+ }
48
+ return new EntityPredicatesResolver(aclDefinitions, predicates, predicateNamesMap, generatedNames)
49
+ }
50
+
51
+ createFieldPredicate(op: 'create' | 'update' | 'read' | 'delete', field: string): true | undefined | string {
52
+ const fieldWhens = EntityPredicatesResolver.getMatchingWhens(this.aclDefinitions, op, field)
53
+ if (fieldWhens.length === 0) {
54
+ return undefined
55
+ }
56
+ if (fieldWhens.includes(true)) {
57
+ return true
58
+ }
59
+ const predicateNames = (fieldWhens as WhenDefinition[]).map(it => {
60
+ const name = this.predicateNamesMap.get(it)
61
+ if (!name) throw new Error('implementation error')
62
+ return name
63
+ })
64
+ if (predicateNames.length === 1) {
65
+ this.usedPredicates.push(predicateNames[0])
66
+ return predicateNames[0]
67
+ }
68
+
69
+
70
+ const predicate = { or: predicateNames.map(it => this.predicates[it]) }
71
+ const predicateName = predicateNames.every(it => this.generatedNames.has(it)) ? createPredicateName(predicate) : predicateNames.join('_or_')
72
+ if (!(predicateName in this.predicates)) {
73
+ this.predicates[predicateName] = predicate
74
+ } else if (!isDeepStrictEqual(predicate, this.predicates[predicateName])) {
75
+ throw new Error('Duplicate predicate')
76
+ }
77
+ this.usedPredicates.push(predicateName)
78
+ return predicateName
79
+ }
80
+
81
+ getUsedPredicates(): Acl.PredicateMap {
82
+ return Object.fromEntries(Object.entries(this.predicates).filter(([name]) => this.usedPredicates.includes(name)))
83
+ }
84
+
85
+
86
+ private static resolvePredicate(
87
+ model: Model.Schema,
88
+ entity: Model.Entity,
89
+ when: WhenDefinition,
90
+ rolePermissionsByEntity: PermissionsByEntity,
91
+ ): Acl.PredicateDefinition {
92
+ const processor = new PredicateDefinitionProcessor(model)
93
+ return processor.process<Acl.PredicateReference | Input.Condition, PredicateExtra>(entity, when, {
94
+ handleColumn(ctx) {
95
+ if (ctx.value instanceof EntityVariableDefinition) {
96
+ return ctx.value.name
97
+ }
98
+ if (ctx.value instanceof PredicateEvaluationReference) {
99
+ throw `Predicate references are allowed only on relations. \nYou cannot use "can${upperCaseFirst(ctx.value.operation)}(${ctx.value.field ? JSON.stringify(ctx.value.field) : ''})" on a column "${ctx.column.name}" of entity "${ctx.entity.name}".`
100
+ }
101
+ return ctx.value
102
+ },
103
+ handleRelation(ctx) {
104
+ if (ctx.value instanceof EntityVariableDefinition) {
105
+ return { [ctx.targetEntity.primary]: ctx.value.name }
106
+ }
107
+ if (ctx.value instanceof PredicateEvaluationReference) {
108
+ const targetPermissions = rolePermissionsByEntity.get(ctx.targetEntity.name)
109
+ if (!targetPermissions) {
110
+ return { [ctx.targetEntity.primary]: { never: true } }
111
+ }
112
+ const ref = ctx.value
113
+ const whens = EntityPredicatesResolver.getMatchingWhens(targetPermissions.definitions, ref.operation, ref.field ?? '')
114
+ if (whens.length === 0) {
115
+ return { [ctx.targetEntity.primary]: { never: true } }
116
+ }
117
+ if (whens.includes(true)) {
118
+ return { [ctx.targetEntity.primary]: { always: true } }
119
+ }
120
+ const predicates = (whens as WhenDefinition[]).map(it =>
121
+ EntityPredicatesResolver.resolvePredicate(model, ctx.targetEntity, it, rolePermissionsByEntity),
122
+ )
123
+ if (predicates.length > 1) {
124
+ return { or: predicates }
125
+ }
126
+ return predicates[0]
127
+ }
128
+ return ctx.value
129
+ },
130
+ })
131
+ }
132
+
133
+ private static getMatchingWhens(permissions: AllowDefinition<any>[], operation: 'delete' | 'read' | 'update' | 'create', field: string) {
134
+ return permissions
135
+ .filter(it => {
136
+ if (operation === 'delete') {
137
+ return !!it.delete
138
+ }
139
+ const op = it[operation]
140
+ if (!op) {
141
+ return false
142
+ }
143
+ return op === true || op.includes(field)
144
+ })
145
+ .map(it => it.when ?? true)
146
+ }
147
+ }
148
+
149
+ const upperCaseFirst = (string: string) => `${string.charAt(0).toUpperCase()}${string.substring(1)}`
@@ -0,0 +1,23 @@
1
+ import 'reflect-metadata'
2
+
3
+ export interface MetadataStore<V> {
4
+ get(target: Object): V
5
+ update(target: Object, generator: (current: V) => V): void
6
+ }
7
+
8
+ export const createMetadataStore = <V>(initialValue: V): MetadataStore<V> => {
9
+ const key = Symbol()
10
+ const get = (target: Object): V => {
11
+ return Reflect.hasMetadata(key, target)
12
+ ? Reflect.getMetadata(key, target)
13
+ : initialValue
14
+ }
15
+ return {
16
+ get,
17
+ update: (target, generator) => {
18
+ const current = get(target)
19
+ const newMetadata = generator(current)
20
+ Reflect.defineMetadata(key, newMetadata, target)
21
+ },
22
+ }
23
+ }
@@ -0,0 +1,6 @@
1
+ import { WhenDefinition } from '../permissions'
2
+
3
+ export const createPredicateName = (when: WhenDefinition) => {
4
+ const json = JSON.stringify(when)
5
+ return json.replace(/[^A-Za-z0-9]+/g, '_').slice(0, 30).replace(/^_|_$/g, '')
6
+ }
@@ -0,0 +1,13 @@
1
+ import { createMetadataStore } from './MetadataStore'
2
+ import { Role } from '../roles'
3
+ import { AllowDefinition } from '../permissions'
4
+
5
+ export const allowCustomPrimaryStore = createMetadataStore<Role[]>([])
6
+ export const allowCustomPrimaryAllRolesStore = createMetadataStore<boolean>(false)
7
+
8
+ export type EntityPermissionsDefinition =
9
+ & AllowDefinition<any>
10
+ & {
11
+ role: Role<string>
12
+ }
13
+ export const allowDefinitionsStore = createMetadataStore<EntityPermissionsDefinition[]>([])
@@ -0,0 +1,29 @@
1
+ import { Acl } from '@contember/schema'
2
+ import { Role } from './roles'
3
+ import { VariableDefinition } from './variables'
4
+ import { PredicateEvaluationReference } from './references'
5
+ import { allowDefinitionsStore } from './internal/stores'
6
+ import { DecoratorFunction, EntityConstructor } from '../../model/definition/types'
7
+
8
+ export type PredicateExtra = PredicateEvaluationReference | VariableDefinition
9
+ export type WhenDefinition = Acl.PredicateDefinition<PredicateExtra>
10
+
11
+ export interface AllowDefinition<E> {
12
+ when?: WhenDefinition
13
+ name?: string
14
+ create?: true | (keyof E)[]
15
+ update?: true | (keyof E)[]
16
+ read?: true | (keyof E)[]
17
+ delete?: true
18
+ }
19
+
20
+ export const allow = <E, R extends Role<string>>(role: R | R[], args: AllowDefinition<E>): DecoratorFunction<E> => {
21
+ return (entity: EntityConstructor<E>) => {
22
+ (Array.isArray(role) ? role : [role]).forEach(role => {
23
+ allowDefinitionsStore.update(entity, val => [...val, { ...args, role }])
24
+ })
25
+ }
26
+ }
27
+
28
+
29
+
@@ -0,0 +1,21 @@
1
+ export class PredicateEvaluationReference {
2
+ constructor(
3
+ public readonly operation: 'update' | 'delete' | 'create' | 'read',
4
+ public readonly field?: string,
5
+ ) {
6
+ }
7
+ }
8
+
9
+ export const canUpdate = (field: string) => {
10
+ return new PredicateEvaluationReference('update', field)
11
+ }
12
+
13
+ export const canRead = (field: string) => {
14
+ return new PredicateEvaluationReference('read', field)
15
+ }
16
+ export const canCreate = (field: string) => {
17
+ return new PredicateEvaluationReference('create', field)
18
+ }
19
+ export const canDelete = () => {
20
+ return new PredicateEvaluationReference('delete')
21
+ }
@@ -0,0 +1,19 @@
1
+ import { Acl } from '@contember/schema'
2
+
3
+ export interface RoleOptions {
4
+ stages?: Acl.StagesDefinition
5
+ tenant?: Acl.TenantPermissions
6
+ system?: Acl.SystemPermissions
7
+ }
8
+
9
+ export class Role<Name extends string = string> {
10
+ public constructor(
11
+ public readonly name: Name,
12
+ public readonly options: RoleOptions,
13
+ ) {
14
+ }
15
+ }
16
+
17
+ export const createRole = <Name extends string>(name: Name, options: RoleOptions = {}): Role<Name> => {
18
+ return new Role(name, options)
19
+ }
@@ -0,0 +1,27 @@
1
+ import { Role } from './roles'
2
+
3
+ export class EntityVariableDefinition<Name extends string, Roles extends Role<string>> {
4
+ public readonly type = 'entity' as const
5
+
6
+ constructor(
7
+ public readonly name: Name,
8
+ public readonly entityName: string,
9
+ public readonly roles: Roles[],
10
+ ) {
11
+ }
12
+ }
13
+
14
+ export type VariableDefinition =
15
+ | EntityVariableDefinition<string, Role<string>>
16
+
17
+ export const createEntityVariable = <R extends Role<string>, Name extends string>(
18
+ name: Name,
19
+ entityName: string,
20
+ roles: R | R[],
21
+ ): EntityVariableDefinition<Name, R> => {
22
+ return new EntityVariableDefinition(
23
+ name,
24
+ entityName,
25
+ Array.isArray(roles) ? roles : [roles],
26
+ )
27
+ }
@@ -0,0 +1,12 @@
1
+ import { SchemaDefinition } from './model'
2
+ import * as InputValidation from './validation'
3
+ import * as AclDefinition from './acl/definition'
4
+ import { Schema } from '@contember/schema'
5
+
6
+ export const createSchema = (definitions: Record<string, any>, modifyCallback?: (schema: Schema) => Schema): Schema => {
7
+ const model = SchemaDefinition.createModel(definitions)
8
+ const validation = InputValidation.parseDefinition(definitions)
9
+ const acl = AclDefinition.createAcl(definitions, model)
10
+ const schema = { model, validation, acl }
11
+ return modifyCallback ? modifyCallback(schema) : schema
12
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { AllowAllPermissionFactory } from '@contember/schema-utils'
1
+ export { AllowAllPermissionFactory } from '@contember/schema-utils'
2
+ export { PermissionsBuilder } from './acl/builder/PermissionsBuilder'
3
+ export * as InputValidation from './validation'
4
+ export * as AclDefinition from './acl/definition'
5
+ export * from './createSchema'
2
6
  export * from './model'
3
- import PermissionsBuilder from './acl/PermissionsBuilder'
4
- import * as InputValidation from './validation'
5
-
6
- export { AllowAllPermissionFactory, PermissionsBuilder, InputValidation }
@@ -1,4 +1,4 @@
1
- import { Model } from '@contember/schema'
1
+ import { Model, Writable } from '@contember/schema'
2
2
  import { NamingHelper } from '@contember/schema-utils'
3
3
  import EntityBuilder from './EntityBuilder'
4
4
  import { SchemaBuilderError } from './SchemaBuilderError'
@@ -12,7 +12,7 @@ import { NamingConventions } from './../definition/NamingConventions'
12
12
  import FieldBuilder from './FieldBuilder'
13
13
 
14
14
  export default class SchemaBuilderInternal {
15
- private entities: { [name: string]: Model.Entity } = {}
15
+ private entities: { [name: string]: Model.Entity & { fields: Record<string, Model.AnyField> } } = {}
16
16
 
17
17
  private fieldOptions: { [entity: string]: FieldBuilder.Map } = {}
18
18
 
@@ -40,6 +40,9 @@ export default class SchemaBuilderInternal {
40
40
  unique: this.createUnique(name, options, fieldOptions),
41
41
  fields: {},
42
42
  tableName: options.tableName || this.conventions.getTableName(name),
43
+ eventLog: {
44
+ enabled: true,
45
+ },
43
46
  }
44
47
  }
45
48
 
@@ -155,7 +158,7 @@ export default class SchemaBuilderInternal {
155
158
  options: EntityBuilder.EntityOptions,
156
159
  fieldOptions: FieldBuilder.Map,
157
160
  ): Model.UniqueConstraints {
158
- const unique: Model.UniqueConstraints = {}
161
+ const unique: Writable<Model.UniqueConstraints> = {}
159
162
  for (const singleUnique of options.unique || []) {
160
163
  const name = singleUnique.name || NamingHelper.createUniqueConstraintName(entityName, singleUnique.fields)
161
164
  unique[name] = { fields: singleUnique.fields, name: name }
@@ -52,6 +52,9 @@ export default class ManyHasManyProcessor implements FieldProcessor<ManyHasManyB
52
52
  tableName: this.conventions.getJoiningTableName(entityName, fieldName),
53
53
  joiningColumn: { columnName: columnNames[0], onDelete: Model.OnDelete.cascade },
54
54
  inverseJoiningColumn: { columnName: columnNames[1], onDelete: Model.OnDelete.cascade },
55
+ eventLog: {
56
+ enabled: true,
57
+ },
55
58
  }
56
59
  }
57
60
 
@@ -0,0 +1,7 @@
1
+ import { extendEntity } from './extensions'
2
+
3
+ export const DisableEventLog = () =>
4
+ extendEntity(({ entity }) => ({
5
+ ...entity,
6
+ eventLog: { enabled: false },
7
+ }))
@@ -17,6 +17,7 @@ export class ColumnDefinition extends FieldDefinition<ColumnDefinitionOptions> {
17
17
  })
18
18
  }
19
19
 
20
+
20
21
  public columnName(columnName: string): Interface<ColumnDefinition> {
21
22
  return this.withOption('columnName', columnName)
22
23
  }
@@ -33,6 +34,10 @@ export class ColumnDefinition extends FieldDefinition<ColumnDefinitionOptions> {
33
34
  return this.withOption('nullable', false)
34
35
  }
35
36
 
37
+ public sequence(options?: Model.ColumnTypeDefinition['sequence']): Interface<ColumnDefinition> {
38
+ return this.withOption('sequence', options ?? { precedence: 'BY DEFAULT' })
39
+ }
40
+
36
41
  public unique(): Interface<ColumnDefinition> {
37
42
  return this.withOption('unique', true)
38
43
  }
@@ -46,12 +51,13 @@ export class ColumnDefinition extends FieldDefinition<ColumnDefinitionOptions> {
46
51
  }
47
52
 
48
53
  createField({ name, conventions, enumRegistry, entityName }: CreateFieldContext): Model.AnyField {
49
- const { type, nullable, columnName, enumDefinition, default: defaultValue, columnType, typeAlias } = this.options
54
+ const { type, nullable, columnName, enumDefinition, default: defaultValue, columnType, typeAlias, sequence } = this.options
50
55
  const common = {
51
56
  name: name,
52
57
  columnName: columnName || conventions.getColumnName(name),
53
58
  nullable: nullable === undefined ? true : nullable,
54
59
  ...(defaultValue !== undefined ? { default: defaultValue } : {}),
60
+ ...(sequence !== undefined ? { sequence } : {}),
55
61
  }
56
62
  if (type === Model.ColumnType.Enum) {
57
63
  if (typeAlias) {
@@ -130,4 +136,5 @@ export type ColumnDefinitionOptions = {
130
136
  unique?: boolean
131
137
  nullable?: boolean
132
138
  default?: Model.ColumnTypeDefinition['default']
139
+ sequence?: Model.ColumnTypeDefinition['sequence']
133
140
  } & ColumnTypeOptions