@contember/schema-definition 1.1.0-alpha.6 → 1.1.0-beta.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/dist/src/acl/{EntityPermissionsBuilder.d.ts → builder/EntityPermissionsBuilder.d.ts} +1 -1
- package/dist/src/acl/builder/EntityPermissionsBuilder.d.ts.map +1 -0
- package/dist/src/acl/{EntityPermissionsBuilder.js → builder/EntityPermissionsBuilder.js} +1 -1
- package/dist/src/acl/builder/EntityPermissionsBuilder.js.map +1 -0
- package/dist/src/acl/{EntitySelector.d.ts → builder/EntitySelector.d.ts} +0 -0
- package/dist/src/acl/builder/EntitySelector.d.ts.map +1 -0
- package/dist/src/acl/{EntitySelector.js → builder/EntitySelector.js} +0 -0
- package/dist/src/acl/builder/EntitySelector.js.map +1 -0
- package/dist/src/acl/{FieldPermissionsBuilder.d.ts → builder/FieldPermissionsBuilder.d.ts} +1 -1
- package/dist/src/acl/builder/FieldPermissionsBuilder.d.ts.map +1 -0
- package/dist/src/acl/{FieldPermissionsBuilder.js → builder/FieldPermissionsBuilder.js} +0 -0
- package/dist/src/acl/builder/FieldPermissionsBuilder.js.map +1 -0
- package/dist/src/acl/{FieldSelector.d.ts → builder/FieldSelector.d.ts} +0 -0
- package/dist/src/acl/builder/FieldSelector.d.ts.map +1 -0
- package/dist/src/acl/{FieldSelector.js → builder/FieldSelector.js} +0 -0
- package/dist/src/acl/builder/FieldSelector.js.map +1 -0
- package/dist/src/acl/{PermissionOverrider.d.ts → builder/PermissionOverrider.d.ts} +0 -0
- package/dist/src/acl/builder/PermissionOverrider.d.ts.map +1 -0
- package/dist/src/acl/{PermissionOverrider.js → builder/PermissionOverrider.js} +1 -1
- package/dist/src/acl/builder/PermissionOverrider.js.map +1 -0
- package/dist/src/acl/{PermissionsBuilder.d.ts → builder/PermissionsBuilder.d.ts} +1 -1
- package/dist/src/acl/builder/PermissionsBuilder.d.ts.map +1 -0
- package/dist/src/acl/{PermissionsBuilder.js → builder/PermissionsBuilder.js} +3 -1
- package/dist/src/acl/builder/PermissionsBuilder.js.map +1 -0
- package/dist/src/acl/{PredicateReference.d.ts → builder/PredicateReference.d.ts} +0 -0
- package/dist/src/acl/builder/PredicateReference.d.ts.map +1 -0
- package/dist/src/acl/{PredicateReference.js → builder/PredicateReference.js} +0 -0
- package/dist/src/acl/builder/PredicateReference.js.map +1 -0
- package/dist/src/acl/definition/customPrimary.d.ts +4 -0
- package/dist/src/acl/definition/customPrimary.d.ts.map +1 -0
- package/dist/src/acl/definition/customPrimary.js +16 -0
- package/dist/src/acl/definition/customPrimary.js.map +1 -0
- package/dist/src/acl/definition/index.d.ts +8 -0
- package/dist/src/acl/definition/index.d.ts.map +1 -0
- package/dist/src/acl/definition/index.js +29 -0
- package/dist/src/acl/definition/index.js.map +1 -0
- package/dist/src/acl/definition/internal/AclFactory.d.ts +18 -0
- package/dist/src/acl/definition/internal/AclFactory.d.ts.map +1 -0
- package/dist/src/acl/definition/internal/AclFactory.js +112 -0
- package/dist/src/acl/definition/internal/AclFactory.js.map +1 -0
- package/dist/src/acl/definition/internal/EntityPredicateResolver.d.ts +16 -0
- package/dist/src/acl/definition/internal/EntityPredicateResolver.d.ts.map +1 -0
- package/dist/src/acl/definition/internal/EntityPredicateResolver.js +130 -0
- package/dist/src/acl/definition/internal/EntityPredicateResolver.js.map +1 -0
- package/dist/src/acl/definition/internal/MetadataStore.d.ts +7 -0
- package/dist/src/acl/definition/internal/MetadataStore.d.ts.map +1 -0
- package/dist/src/acl/definition/internal/MetadataStore.js +22 -0
- package/dist/src/acl/definition/internal/MetadataStore.js.map +1 -0
- package/dist/src/acl/definition/internal/createPredicateName.d.ts +3 -0
- package/dist/src/acl/definition/internal/createPredicateName.d.ts.map +1 -0
- package/dist/src/acl/definition/internal/createPredicateName.js +9 -0
- package/dist/src/acl/definition/internal/createPredicateName.js.map +1 -0
- package/dist/src/acl/definition/internal/stores.d.ts +9 -0
- package/dist/src/acl/definition/internal/stores.d.ts.map +1 -0
- package/dist/src/acl/definition/internal/stores.js +8 -0
- package/dist/src/acl/definition/internal/stores.js.map +1 -0
- package/dist/src/acl/definition/permissions.d.ts +17 -0
- package/dist/src/acl/definition/permissions.d.ts.map +1 -0
- package/dist/src/acl/definition/permissions.js +13 -0
- package/dist/src/acl/definition/permissions.js.map +1 -0
- package/dist/src/acl/definition/references.d.ts +10 -0
- package/dist/src/acl/definition/references.d.ts.map +1 -0
- package/dist/src/acl/definition/references.js +27 -0
- package/dist/src/acl/definition/references.js.map +1 -0
- package/dist/src/acl/definition/roles.d.ts +13 -0
- package/dist/src/acl/definition/roles.d.ts.map +1 -0
- package/dist/src/acl/definition/roles.js +15 -0
- package/dist/src/acl/definition/roles.js.map +1 -0
- package/dist/src/acl/definition/variables.d.ts +11 -0
- package/dist/src/acl/definition/variables.d.ts.map +1 -0
- package/dist/src/acl/definition/variables.js +17 -0
- package/dist/src/acl/definition/variables.js.map +1 -0
- package/dist/src/createSchema.d.ts +3 -0
- package/dist/src/createSchema.d.ts.map +1 -0
- package/dist/src/createSchema.js +15 -0
- package/dist/src/createSchema.js.map +1 -0
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/model/definition/index.d.ts +2 -2
- package/dist/src/model/definition/index.d.ts.map +1 -1
- package/dist/src/model/definition/index.js +2 -1
- package/dist/src/model/definition/index.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/utils/filterEntityDefinition.d.ts +4 -0
- package/dist/src/utils/filterEntityDefinition.d.ts.map +1 -0
- package/dist/src/utils/filterEntityDefinition.js +11 -0
- package/dist/src/utils/filterEntityDefinition.js.map +1 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/validation/index.d.ts +1 -4
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +2 -3
- package/dist/src/validation/index.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -0
- package/dist/tests/unit/readAclDefinitions.test.d.ts +2 -0
- package/dist/tests/unit/readAclDefinitions.test.d.ts.map +1 -0
- package/dist/tests/unit/readAclDefinitions.test.js +449 -0
- package/dist/tests/unit/readAclDefinitions.test.js.map +1 -0
- package/package.json +4 -4
- package/src/acl/{EntityPermissionsBuilder.ts → builder/EntityPermissionsBuilder.ts} +2 -2
- package/src/acl/{EntitySelector.ts → builder/EntitySelector.ts} +0 -0
- package/src/acl/{FieldPermissionsBuilder.ts → builder/FieldPermissionsBuilder.ts} +1 -1
- package/src/acl/{FieldSelector.ts → builder/FieldSelector.ts} +0 -0
- package/src/acl/{PermissionOverrider.ts → builder/PermissionOverrider.ts} +1 -1
- package/src/acl/{PermissionsBuilder.ts → builder/PermissionsBuilder.ts} +1 -1
- package/src/acl/{PredicateReference.ts → builder/PredicateReference.ts} +0 -0
- package/src/acl/definition/customPrimary.ts +13 -0
- package/src/acl/definition/index.ts +16 -0
- package/src/acl/definition/internal/AclFactory.ts +130 -0
- package/src/acl/definition/internal/EntityPredicateResolver.ts +149 -0
- package/src/acl/definition/internal/MetadataStore.ts +23 -0
- package/src/acl/definition/internal/createPredicateName.ts +6 -0
- package/src/acl/definition/internal/stores.ts +13 -0
- package/src/acl/definition/permissions.ts +29 -0
- package/src/acl/definition/references.ts +21 -0
- package/src/acl/definition/roles.ts +19 -0
- package/src/acl/definition/variables.ts +27 -0
- package/src/createSchema.ts +12 -0
- package/src/index.ts +5 -5
- package/src/model/definition/index.ts +4 -3
- package/src/utils/filterEntityDefinition.ts +8 -0
- package/src/utils/index.ts +1 -0
- package/src/validation/index.ts +3 -3
- package/tests/tsconfig.json +13 -0
- package/tests/unit/readAclDefinitions.test.ts +406 -0
- package/tsconfig.json +4 -1
- package/dist/src/acl/EntityPermissionsBuilder.d.ts.map +0 -1
- package/dist/src/acl/EntityPermissionsBuilder.js.map +0 -1
- package/dist/src/acl/EntitySelector.d.ts.map +0 -1
- package/dist/src/acl/EntitySelector.js.map +0 -1
- package/dist/src/acl/FieldPermissionsBuilder.d.ts.map +0 -1
- package/dist/src/acl/FieldPermissionsBuilder.js.map +0 -1
- package/dist/src/acl/FieldSelector.d.ts.map +0 -1
- package/dist/src/acl/FieldSelector.js.map +0 -1
- package/dist/src/acl/PermissionOverrider.d.ts.map +0 -1
- package/dist/src/acl/PermissionOverrider.js.map +0 -1
- package/dist/src/acl/PermissionsBuilder.d.ts.map +0 -1
- package/dist/src/acl/PermissionsBuilder.js.map +0 -1
- package/dist/src/acl/PredicateReference.d.ts.map +0 -1
- package/dist/src/acl/PredicateReference.js.map +0 -1
|
@@ -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,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
|
-
|
|
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 }
|
|
@@ -5,6 +5,7 @@ import { SchemaBuilder } from './internal'
|
|
|
5
5
|
import { DefaultNamingConventions } from './NamingConventions'
|
|
6
6
|
import 'reflect-metadata'
|
|
7
7
|
import { FieldDefinition } from './fieldDefinitions'
|
|
8
|
+
import { isEntityConstructor } from '../../utils'
|
|
8
9
|
|
|
9
10
|
export * from './fieldDefinitions'
|
|
10
11
|
export * from './EventLogDefinition'
|
|
@@ -18,7 +19,7 @@ export abstract class Entity {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export type ModelDefinition<M> = {
|
|
21
|
-
[K in keyof M]:
|
|
22
|
+
[K in keyof M]: unknown
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function createModel<M extends ModelDefinition<M>>(definitions: M): Model.Schema {
|
|
@@ -26,8 +27,8 @@ export function createModel<M extends ModelDefinition<M>>(definitions: M): Model
|
|
|
26
27
|
for (const [name, definition] of Object.entries(definitions)) {
|
|
27
28
|
if (definition instanceof EnumDefinition) {
|
|
28
29
|
schemaBuilder.addEnum(name, definition)
|
|
29
|
-
} else {
|
|
30
|
-
schemaBuilder.addEntity(name, definition
|
|
30
|
+
} else if (isEntityConstructor(definition)) {
|
|
31
|
+
schemaBuilder.addEntity(name, definition)
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
const schema = schemaBuilder.createSchema()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EntityConstructor } from '../model/definition/types'
|
|
2
|
+
|
|
3
|
+
export const filterEntityDefinition = (definitions: Record<string, unknown>): [string, EntityConstructor][] => Object.entries(definitions)
|
|
4
|
+
.filter((it): it is [string, EntityConstructor] => isEntityConstructor(it[1]))
|
|
5
|
+
|
|
6
|
+
export const isEntityConstructor = (it: unknown): it is EntityConstructor => {
|
|
7
|
+
return typeof it === 'function' && 'constructor' in it && 'prototype' in it
|
|
8
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/validation/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { tuple } from '../utils'
|
|
|
2
2
|
import { SchemaDefinition } from '../model'
|
|
3
3
|
import 'reflect-metadata'
|
|
4
4
|
import { Validation } from '@contember/schema'
|
|
5
|
+
import { filterEntityDefinition } from '../utils'
|
|
5
6
|
|
|
6
7
|
type MessageOrString = Validation.Message | string
|
|
7
8
|
|
|
@@ -196,10 +197,9 @@ export const combine =
|
|
|
196
197
|
decorators.forEach(it => it(target, propertyKey))
|
|
197
198
|
|
|
198
199
|
export function parseDefinition(
|
|
199
|
-
definitions: Record<string,
|
|
200
|
+
definitions: Record<string, any>,
|
|
200
201
|
): Validation.Schema {
|
|
201
|
-
return
|
|
202
|
-
.filter((it): it is [string, { new (): any }] => !(it[1] instanceof SchemaDefinition.EnumDefinition))
|
|
202
|
+
return filterEntityDefinition(definitions)
|
|
203
203
|
.map(([name, definition]) => {
|
|
204
204
|
const target = definition.prototype
|
|
205
205
|
const defInstance = new definition()
|