@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.
- 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} +2 -1
- 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 +12 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/model/builder/SchemaBuilder.js +5 -1
- package/dist/src/model/builder/SchemaBuilder.js.map +1 -1
- package/dist/src/model/builder/SchemaBuilderInternal.d.ts.map +1 -1
- package/dist/src/model/builder/SchemaBuilderInternal.js +3 -0
- package/dist/src/model/builder/SchemaBuilderInternal.js.map +1 -1
- package/dist/src/model/builder/internal/ManyHasManyProcessor.d.ts.map +1 -1
- package/dist/src/model/builder/internal/ManyHasManyProcessor.js +3 -0
- package/dist/src/model/builder/internal/ManyHasManyProcessor.js.map +1 -1
- package/dist/src/model/definition/EventLogDefinition.d.ts +2 -0
- package/dist/src/model/definition/EventLogDefinition.d.ts.map +1 -0
- package/dist/src/model/definition/EventLogDefinition.js +10 -0
- package/dist/src/model/definition/EventLogDefinition.js.map +1 -0
- package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.d.ts +2 -0
- package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.d.ts.map +1 -1
- package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.js +5 -1
- package/dist/src/model/definition/fieldDefinitions/ColumnDefinition.js.map +1 -1
- package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.d.ts +2 -2
- package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.d.ts.map +1 -1
- package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.js +8 -9
- package/dist/src/model/definition/fieldDefinitions/ManyHasManyDefinition.js.map +1 -1
- package/dist/src/model/definition/fieldDefinitions/index.js +5 -1
- package/dist/src/model/definition/fieldDefinitions/index.js.map +1 -1
- package/dist/src/model/definition/index.d.ts +3 -2
- package/dist/src/model/definition/index.d.ts.map +1 -1
- package/dist/src/model/definition/index.js +8 -2
- package/dist/src/model/definition/index.js.map +1 -1
- package/dist/src/model/definition/internal/SchemaBuilder.d.ts.map +1 -1
- package/dist/src/model/definition/internal/SchemaBuilder.js +7 -2
- package/dist/src/model/definition/internal/SchemaBuilder.js.map +1 -1
- package/dist/src/model/definition/internal/index.js +5 -1
- package/dist/src/model/definition/internal/index.js.map +1 -1
- package/dist/src/model/utils/index.js +5 -1
- package/dist/src/model/utils/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 +6 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/validation/index.d.ts +2 -5
- 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} +4 -4
- package/src/acl/{EntitySelector.ts → builder/EntitySelector.ts} +0 -0
- package/src/acl/{FieldPermissionsBuilder.ts → builder/FieldPermissionsBuilder.ts} +5 -4
- 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/builder/SchemaBuilderInternal.ts +6 -3
- package/src/model/builder/internal/ManyHasManyProcessor.ts +3 -0
- package/src/model/definition/EventLogDefinition.ts +7 -0
- package/src/model/definition/fieldDefinitions/ColumnDefinition.ts +8 -1
- package/src/model/definition/fieldDefinitions/ManyHasManyDefinition.ts +14 -15
- package/src/model/definition/index.ts +5 -3
- package/src/model/definition/internal/SchemaBuilder.ts +9 -4
- package/src/utils/filterEntityDefinition.ts +8 -0
- package/src/utils/index.ts +1 -0
- package/src/validation/index.ts +4 -4
- 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
|
@@ -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
|
-
|
|
52
|
+
entityOperations[op] = {
|
|
52
53
|
...(permissions[entity.name].operations[op] || {}),
|
|
53
54
|
[field.name]: predicate,
|
|
54
55
|
}
|
|
File without changes
|
|
File without changes
|
|
@@ -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,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 }
|
|
@@ -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
|
|
|
@@ -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
|