@contember/schema-utils 1.1.0-alpha.3 → 1.1.0-alpha.6
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/AllowAllPermissionFactory.d.ts.map +1 -1
- package/dist/src/acl/AllowAllPermissionFactory.js.map +1 -1
- package/dist/src/acl/index.js +5 -1
- package/dist/src/acl/index.js.map +1 -1
- package/dist/src/dataUtils.d.ts +2 -2
- package/dist/src/dataUtils.d.ts.map +1 -1
- package/dist/src/dataUtils.js +1 -1
- package/dist/src/dataUtils.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +25 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/model/NamingHelper.d.ts +1 -1
- package/dist/src/model/NamingHelper.d.ts.map +1 -1
- package/dist/src/model/NamingHelper.js.map +1 -1
- package/dist/src/model/index.js +5 -1
- package/dist/src/model/index.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/type-schema/acl.d.ts +8 -0
- package/dist/src/type-schema/acl.d.ts.map +1 -0
- package/dist/src/type-schema/acl.js +88 -0
- package/dist/src/type-schema/acl.js.map +1 -0
- package/dist/src/type-schema/index.d.ts +4 -0
- package/dist/src/type-schema/index.d.ts.map +1 -0
- package/dist/src/type-schema/index.js +20 -0
- package/dist/src/type-schema/index.js.map +1 -0
- package/dist/src/type-schema/model.d.ts +30 -0
- package/dist/src/type-schema/model.d.ts.map +1 -0
- package/dist/src/type-schema/model.js +148 -0
- package/dist/src/type-schema/model.js.map +1 -0
- package/dist/src/type-schema/validation.d.ts +15 -0
- package/dist/src/type-schema/validation.d.ts.map +1 -0
- package/dist/src/type-schema/validation.js +95 -0
- package/dist/src/type-schema/validation.js.map +1 -0
- package/dist/src/utils/index.js +5 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/validation/AclValidator.d.ts +1 -2
- package/dist/src/validation/AclValidator.d.ts.map +1 -1
- package/dist/src/validation/AclValidator.js +22 -197
- package/dist/src/validation/AclValidator.js.map +1 -1
- package/dist/src/validation/ModelValidator.d.ts +1 -1
- package/dist/src/validation/ModelValidator.d.ts.map +1 -1
- package/dist/src/validation/ModelValidator.js +48 -219
- package/dist/src/validation/ModelValidator.js.map +1 -1
- package/dist/src/validation/SchemaValidator.d.ts.map +1 -1
- package/dist/src/validation/SchemaValidator.js +5 -18
- package/dist/src/validation/SchemaValidator.js.map +1 -1
- package/dist/src/validation/ValidationValidator.d.ts +1 -3
- package/dist/src/validation/ValidationValidator.d.ts.map +1 -1
- package/dist/src/validation/ValidationValidator.js +28 -201
- package/dist/src/validation/ValidationValidator.js.map +1 -1
- package/dist/src/validation/index.d.ts +2 -0
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +7 -1
- package/dist/src/validation/index.js.map +1 -1
- package/package.json +3 -2
- package/src/acl/AllowAllPermissionFactory.ts +2 -2
- package/src/dataUtils.ts +3 -3
- package/src/index.ts +8 -0
- package/src/model/NamingHelper.ts +1 -1
- package/src/tsconfig.json +3 -0
- package/src/type-schema/acl.ts +100 -0
- package/src/type-schema/index.ts +3 -0
- package/src/type-schema/model.ts +173 -0
- package/src/type-schema/validation.ts +100 -0
- package/src/validation/AclValidator.ts +42 -239
- package/src/validation/ModelValidator.ts +63 -263
- package/src/validation/SchemaValidator.ts +6 -19
- package/src/validation/ValidationValidator.ts +41 -222
- package/src/validation/index.ts +2 -0
- package/dist/src/validation/utils.d.ts +0 -18
- package/dist/src/validation/utils.d.ts.map +0 -1
- package/dist/src/validation/utils.js +0 -36
- package/dist/src/validation/utils.js.map +0 -1
- package/src/validation/utils.ts +0 -45
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as Typesafe from '@contember/typesafe'
|
|
2
|
+
import { Model } from '@contember/schema'
|
|
3
|
+
|
|
4
|
+
const orderBySchema = Typesafe.array(Typesafe.object({
|
|
5
|
+
path: Typesafe.array(Typesafe.string),
|
|
6
|
+
direction: Typesafe.enumeration<Model.OrderDirection>(Model.OrderDirection.asc, Model.OrderDirection.desc),
|
|
7
|
+
}))
|
|
8
|
+
const joiningColumnSchema = Typesafe.object({
|
|
9
|
+
columnName: Typesafe.string,
|
|
10
|
+
onDelete: Typesafe.enumeration<Model.OnDelete>(Model.OnDelete.cascade, Model.OnDelete.restrict, Model.OnDelete.setNull),
|
|
11
|
+
})
|
|
12
|
+
const oneHasManyRelationSchema = Typesafe.intersection(
|
|
13
|
+
Typesafe.object({
|
|
14
|
+
type: Typesafe.literal(Model.RelationType.OneHasMany),
|
|
15
|
+
name: Typesafe.string,
|
|
16
|
+
target: Typesafe.string,
|
|
17
|
+
ownedBy: Typesafe.string,
|
|
18
|
+
}),
|
|
19
|
+
Typesafe.partial({
|
|
20
|
+
orderBy: orderBySchema,
|
|
21
|
+
}),
|
|
22
|
+
)
|
|
23
|
+
const oneHasManyRelationSchemaCheck: Typesafe.Equals<Model.OneHasManyRelation, ReturnType<typeof oneHasManyRelationSchema>> = true
|
|
24
|
+
|
|
25
|
+
const manyHasOneRelationSchema = Typesafe.intersection(
|
|
26
|
+
Typesafe.object({
|
|
27
|
+
type: Typesafe.literal(Model.RelationType.ManyHasOne),
|
|
28
|
+
name: Typesafe.string,
|
|
29
|
+
target: Typesafe.string,
|
|
30
|
+
joiningColumn: joiningColumnSchema,
|
|
31
|
+
nullable: Typesafe.boolean,
|
|
32
|
+
}),
|
|
33
|
+
Typesafe.partial({
|
|
34
|
+
inversedBy: Typesafe.string,
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const manyHasOneRelationSchemaCheck: Typesafe.Equals<Model.ManyHasOneRelation, ReturnType<typeof manyHasOneRelationSchema>> = true
|
|
39
|
+
|
|
40
|
+
const oneHasOneInverseRelationSchema = Typesafe.object({
|
|
41
|
+
type: Typesafe.literal(Model.RelationType.OneHasOne),
|
|
42
|
+
name: Typesafe.string,
|
|
43
|
+
target: Typesafe.string,
|
|
44
|
+
ownedBy: Typesafe.string,
|
|
45
|
+
nullable: Typesafe.boolean,
|
|
46
|
+
})
|
|
47
|
+
const oneHasOneInverseRelationSchemaCheck: Typesafe.Equals<Model.OneHasOneInverseRelation, ReturnType<typeof oneHasOneInverseRelationSchema>> = true
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const oneHasOneOwningRelationSchema = Typesafe.intersection(
|
|
51
|
+
Typesafe.object({
|
|
52
|
+
type: Typesafe.literal(Model.RelationType.OneHasOne),
|
|
53
|
+
name: Typesafe.string,
|
|
54
|
+
target: Typesafe.string,
|
|
55
|
+
joiningColumn: joiningColumnSchema,
|
|
56
|
+
nullable: Typesafe.boolean,
|
|
57
|
+
}),
|
|
58
|
+
Typesafe.partial({
|
|
59
|
+
inversedBy: Typesafe.string,
|
|
60
|
+
orphanRemoval: Typesafe.literal(true),
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const oneHasOneOwningRelationSchemaCheck: Typesafe.Equals<Model.OneHasOneOwningRelation, ReturnType<typeof oneHasOneOwningRelationSchema>> = true
|
|
65
|
+
|
|
66
|
+
const manyHasManyOwningRelationSchema = Typesafe.intersection(
|
|
67
|
+
Typesafe.object({
|
|
68
|
+
type: Typesafe.literal(Model.RelationType.ManyHasMany),
|
|
69
|
+
name: Typesafe.string,
|
|
70
|
+
target: Typesafe.string,
|
|
71
|
+
joiningTable: Typesafe.object({
|
|
72
|
+
tableName: Typesafe.string,
|
|
73
|
+
joiningColumn: joiningColumnSchema,
|
|
74
|
+
inverseJoiningColumn: joiningColumnSchema,
|
|
75
|
+
eventLog: Typesafe.object({
|
|
76
|
+
enabled: Typesafe.boolean,
|
|
77
|
+
}),
|
|
78
|
+
}),
|
|
79
|
+
}),
|
|
80
|
+
Typesafe.partial({
|
|
81
|
+
inversedBy: Typesafe.string,
|
|
82
|
+
orderBy: orderBySchema,
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
const manyHasManyOwningRelationSchemaCheck: Typesafe.Equals<Model.ManyHasManyOwningRelation, ReturnType<typeof manyHasManyOwningRelationSchema>> = true
|
|
86
|
+
|
|
87
|
+
const manyHasManyInverseRelationSchema = Typesafe.intersection(
|
|
88
|
+
Typesafe.object({
|
|
89
|
+
type: Typesafe.literal(Model.RelationType.ManyHasMany),
|
|
90
|
+
name: Typesafe.string,
|
|
91
|
+
target: Typesafe.string,
|
|
92
|
+
ownedBy: Typesafe.string,
|
|
93
|
+
}),
|
|
94
|
+
Typesafe.partial({
|
|
95
|
+
orderBy: orderBySchema,
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
const manyHasManyInverseRelationSchemaCheck: Typesafe.Equals<Model.ManyHasManyInverseRelation, ReturnType<typeof manyHasManyInverseRelationSchema>> = true
|
|
99
|
+
|
|
100
|
+
const intersectionSchema = Typesafe.intersection(
|
|
101
|
+
Typesafe.object({
|
|
102
|
+
precedence: Typesafe.enumeration('ALWAYS', 'BY DEFAULT'),
|
|
103
|
+
}),
|
|
104
|
+
Typesafe.partial({
|
|
105
|
+
start: Typesafe.number,
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
const intersectionSchemaCheck: Typesafe.Equals<Model.AnyColumn['sequence'], ReturnType<typeof intersectionSchema>> = true
|
|
109
|
+
|
|
110
|
+
const columnSchema = Typesafe.intersection(
|
|
111
|
+
Typesafe.object({
|
|
112
|
+
name: Typesafe.string,
|
|
113
|
+
columnName: Typesafe.string,
|
|
114
|
+
columnType: Typesafe.string,
|
|
115
|
+
nullable: Typesafe.boolean,
|
|
116
|
+
type: Typesafe.enumeration<Model.ColumnType>(...Object.values(Model.ColumnType)),
|
|
117
|
+
}),
|
|
118
|
+
Typesafe.partial({
|
|
119
|
+
typeAlias: Typesafe.string,
|
|
120
|
+
default: Typesafe.union(Typesafe.string, Typesafe.number, Typesafe.boolean, Typesafe.null_),
|
|
121
|
+
sequence: intersectionSchema as Typesafe.Type<Model.AnyColumn['sequence']>,
|
|
122
|
+
}),
|
|
123
|
+
)
|
|
124
|
+
const columnSchemaCheck: Typesafe.Equals<Model.AnyColumn, ReturnType<typeof columnSchema>> = true
|
|
125
|
+
|
|
126
|
+
const fieldSchema: Typesafe.Type<Model.AnyField> = Typesafe.union(
|
|
127
|
+
oneHasOneInverseRelationSchema,
|
|
128
|
+
oneHasOneOwningRelationSchema,
|
|
129
|
+
oneHasManyRelationSchema,
|
|
130
|
+
manyHasOneRelationSchema,
|
|
131
|
+
manyHasManyInverseRelationSchema,
|
|
132
|
+
manyHasManyOwningRelationSchema,
|
|
133
|
+
columnSchema,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const viewSchemaInner = Typesafe.intersection(
|
|
137
|
+
Typesafe.object({
|
|
138
|
+
sql: Typesafe.string,
|
|
139
|
+
}),
|
|
140
|
+
Typesafe.partial({
|
|
141
|
+
dependencies: Typesafe.array(Typesafe.string),
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
const viewSchemaCheck: Typesafe.Equals<Model.View, ReturnType<typeof viewSchemaInner>> = true
|
|
145
|
+
const viewSchema: Typesafe.Type<Model.View> = viewSchemaInner
|
|
146
|
+
|
|
147
|
+
const entitySchema = Typesafe.intersection(
|
|
148
|
+
Typesafe.object({
|
|
149
|
+
name: Typesafe.string,
|
|
150
|
+
primary: Typesafe.string,
|
|
151
|
+
primaryColumn: Typesafe.string,
|
|
152
|
+
tableName: Typesafe.string,
|
|
153
|
+
fields: Typesafe.record(Typesafe.string, fieldSchema),
|
|
154
|
+
unique: Typesafe.record(Typesafe.string, Typesafe.object({
|
|
155
|
+
fields: Typesafe.array(Typesafe.string),
|
|
156
|
+
name: Typesafe.string,
|
|
157
|
+
})),
|
|
158
|
+
eventLog: Typesafe.object({
|
|
159
|
+
enabled: Typesafe.boolean,
|
|
160
|
+
}),
|
|
161
|
+
}),
|
|
162
|
+
Typesafe.partial({
|
|
163
|
+
view: viewSchema,
|
|
164
|
+
}),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
const entitySchemaCheck: Typesafe.Equals<Model.Entity, ReturnType<typeof entitySchema>> = true
|
|
168
|
+
|
|
169
|
+
export const modelSchema = Typesafe.object({
|
|
170
|
+
entities: Typesafe.record(Typesafe.string, entitySchema),
|
|
171
|
+
enums: Typesafe.record(Typesafe.string, Typesafe.array(Typesafe.string)),
|
|
172
|
+
})
|
|
173
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as Typesafe from '@contember/typesafe'
|
|
2
|
+
import { Validation } from '@contember/schema'
|
|
3
|
+
|
|
4
|
+
const messageSchema = Typesafe.intersection(
|
|
5
|
+
Typesafe.object({
|
|
6
|
+
text: Typesafe.string,
|
|
7
|
+
}),
|
|
8
|
+
Typesafe.partial({
|
|
9
|
+
parameters: Typesafe.array(Typesafe.union(Typesafe.string, Typesafe.number)),
|
|
10
|
+
}),
|
|
11
|
+
)
|
|
12
|
+
const literalArgumentFactory = <T extends Typesafe.Json>(inner: Typesafe.Type<T>) => Typesafe.object({
|
|
13
|
+
type: Typesafe.literal(Validation.ArgumentType.literal),
|
|
14
|
+
value: inner,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const validatorArgumentSchema = Typesafe.object({
|
|
18
|
+
type: Typesafe.literal(Validation.ArgumentType.validator),
|
|
19
|
+
validator: (v, p): Validation.Validator => validatorSchema(v, p),
|
|
20
|
+
})
|
|
21
|
+
const pathArgumentSchema = Typesafe.object({
|
|
22
|
+
type: Typesafe.literal(Validation.ArgumentType.path),
|
|
23
|
+
path: Typesafe.array(Typesafe.string),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const validatorSchema = Typesafe.union(
|
|
27
|
+
Typesafe.object({
|
|
28
|
+
operation: Typesafe.literal('and'),
|
|
29
|
+
args: Typesafe.array(validatorArgumentSchema),
|
|
30
|
+
}),
|
|
31
|
+
Typesafe.object({
|
|
32
|
+
operation: Typesafe.literal('or'),
|
|
33
|
+
args: Typesafe.array(validatorArgumentSchema),
|
|
34
|
+
}),
|
|
35
|
+
Typesafe.object({
|
|
36
|
+
operation: Typesafe.literal('conditional'),
|
|
37
|
+
args: Typesafe.tuple(validatorArgumentSchema, validatorArgumentSchema),
|
|
38
|
+
}),
|
|
39
|
+
Typesafe.object({
|
|
40
|
+
operation: Typesafe.literal('pattern'),
|
|
41
|
+
args: Typesafe.tuple(literalArgumentFactory(Typesafe.tuple(Typesafe.string, Typesafe.string))),
|
|
42
|
+
}),
|
|
43
|
+
Typesafe.object({
|
|
44
|
+
operation: Typesafe.literal('lengthRange'),
|
|
45
|
+
args: Typesafe.tuple(
|
|
46
|
+
literalArgumentFactory(Typesafe.union(Typesafe.null_, Typesafe.number)),
|
|
47
|
+
literalArgumentFactory(Typesafe.union(Typesafe.null_, Typesafe.number)),
|
|
48
|
+
),
|
|
49
|
+
}),
|
|
50
|
+
Typesafe.object({
|
|
51
|
+
operation: Typesafe.literal('range'),
|
|
52
|
+
args: Typesafe.tuple(
|
|
53
|
+
literalArgumentFactory(Typesafe.union(Typesafe.null_, Typesafe.number)),
|
|
54
|
+
literalArgumentFactory(Typesafe.union(Typesafe.null_, Typesafe.number)),
|
|
55
|
+
),
|
|
56
|
+
}),
|
|
57
|
+
Typesafe.object({
|
|
58
|
+
operation: Typesafe.literal('equals'),
|
|
59
|
+
args: Typesafe.tuple(literalArgumentFactory(Typesafe.anyJson)),
|
|
60
|
+
}),
|
|
61
|
+
Typesafe.object({
|
|
62
|
+
operation: Typesafe.literal('not'),
|
|
63
|
+
args: Typesafe.tuple(validatorArgumentSchema),
|
|
64
|
+
}),
|
|
65
|
+
Typesafe.object({
|
|
66
|
+
operation: Typesafe.literal('empty'),
|
|
67
|
+
args: Typesafe.tuple(),
|
|
68
|
+
}),
|
|
69
|
+
Typesafe.object({
|
|
70
|
+
operation: Typesafe.literal('defined'),
|
|
71
|
+
args: Typesafe.tuple(),
|
|
72
|
+
}),
|
|
73
|
+
Typesafe.object({
|
|
74
|
+
operation: Typesafe.literal('inContext'),
|
|
75
|
+
args: Typesafe.tuple(pathArgumentSchema, validatorArgumentSchema),
|
|
76
|
+
}),
|
|
77
|
+
Typesafe.object({
|
|
78
|
+
operation: Typesafe.literal('every'),
|
|
79
|
+
args: Typesafe.tuple(validatorArgumentSchema),
|
|
80
|
+
}),
|
|
81
|
+
Typesafe.object({
|
|
82
|
+
operation: Typesafe.literal('any'),
|
|
83
|
+
args: Typesafe.tuple(validatorArgumentSchema),
|
|
84
|
+
}),
|
|
85
|
+
Typesafe.object({
|
|
86
|
+
operation: Typesafe.literal('filter'),
|
|
87
|
+
args: Typesafe.tuple(validatorArgumentSchema, validatorArgumentSchema),
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
const validatorSchemaCheck: Typesafe.Equals<Validation.Validator, ReturnType<typeof validatorSchema>> = true
|
|
91
|
+
|
|
92
|
+
const validationRuleSchema = Typesafe.object({
|
|
93
|
+
validator: validatorSchema as Typesafe.Type<Validation.Validator>,
|
|
94
|
+
message: messageSchema,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export const validationSchema = Typesafe.record(
|
|
98
|
+
Typesafe.string,
|
|
99
|
+
Typesafe.record(Typesafe.string, Typesafe.array(validationRuleSchema)),
|
|
100
|
+
)
|
|
@@ -1,199 +1,71 @@
|
|
|
1
1
|
import { Acl, Model } from '@contember/schema'
|
|
2
|
-
import { checkExtraProperties, everyIs, hasStringProperty, isObject } from './utils'
|
|
3
2
|
import { PredicateDefinitionProcessor } from '../acl'
|
|
4
3
|
import { getEntity } from '../model'
|
|
5
4
|
import { ErrorBuilder, ValidationError } from './errors'
|
|
6
5
|
|
|
6
|
+
|
|
7
7
|
export class AclValidator {
|
|
8
8
|
constructor(private readonly model: Model.Schema) {}
|
|
9
9
|
|
|
10
|
-
public validate(schema:
|
|
10
|
+
public validate(schema: Acl.Schema): ValidationError[] {
|
|
11
11
|
const errorBuilder = new ErrorBuilder([], ['acl'])
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
errorBuilder.add('Must be an object')
|
|
15
|
-
validSchema = { roles: {} }
|
|
16
|
-
} else if (!schema.roles) {
|
|
17
|
-
errorBuilder.for('roles').add('Property is missing')
|
|
18
|
-
validSchema = { roles: {} }
|
|
19
|
-
} else {
|
|
20
|
-
validSchema = { roles: this.validateRoles(schema.roles, errorBuilder) }
|
|
21
|
-
}
|
|
22
|
-
return [validSchema, errorBuilder.errors]
|
|
12
|
+
this.validateRoles(schema.roles, errorBuilder)
|
|
13
|
+
return errorBuilder.errors
|
|
23
14
|
}
|
|
24
15
|
|
|
25
|
-
private validateRoles(roles:
|
|
26
|
-
if (!isObject(roles)) {
|
|
27
|
-
errorBuilder.add('Must be an object')
|
|
28
|
-
return {}
|
|
29
|
-
}
|
|
30
|
-
const validRoles: Acl.Schema['roles'] = {}
|
|
16
|
+
private validateRoles(roles: Acl.Roles, errorBuilder: ErrorBuilder): void {
|
|
31
17
|
for (const role in roles) {
|
|
32
18
|
if (!roles.hasOwnProperty(role)) {
|
|
33
19
|
continue
|
|
34
20
|
}
|
|
35
|
-
|
|
36
|
-
// errorBuilder.add('Role "admin" is reserved.')
|
|
37
|
-
// continue
|
|
38
|
-
// }
|
|
39
|
-
const rolePermissions = this.validateRolePermissions(roles[role], Object.keys(roles), errorBuilder.for(role))
|
|
40
|
-
if (rolePermissions !== undefined) {
|
|
41
|
-
validRoles[role] = rolePermissions
|
|
42
|
-
}
|
|
21
|
+
this.validateRolePermissions(roles[role], Object.keys(roles), errorBuilder.for(role))
|
|
43
22
|
}
|
|
44
|
-
return validRoles
|
|
45
23
|
}
|
|
46
24
|
|
|
47
25
|
private validateRolePermissions(
|
|
48
|
-
permissions:
|
|
26
|
+
permissions: Acl.RolePermissions,
|
|
49
27
|
roles: string[],
|
|
50
28
|
errorBuilder: ErrorBuilder,
|
|
51
|
-
):
|
|
52
|
-
if (!isObject(permissions)) {
|
|
53
|
-
errorBuilder.add('Must be an object')
|
|
54
|
-
return
|
|
55
|
-
}
|
|
29
|
+
): void {
|
|
56
30
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
stages: stagesIn,
|
|
61
|
-
entities: entitiesIn,
|
|
62
|
-
implicit: implicitIn,
|
|
63
|
-
...plugins
|
|
64
|
-
} = permissions
|
|
65
|
-
|
|
66
|
-
const inherits = this.validateInherits(inheritsIn, roles, errorBuilder.for('inherits'))
|
|
67
|
-
const variables = this.validateVariables(variablesIn, errorBuilder.for('variables'))
|
|
68
|
-
const stages = this.validateStagesDefinition(stagesIn, errorBuilder.for('stages'))
|
|
69
|
-
const entities = this.validatePermissions(
|
|
70
|
-
entitiesIn,
|
|
71
|
-
(permissions.variables as Acl.Variables) || {},
|
|
72
|
-
errorBuilder.for('entities'),
|
|
73
|
-
)
|
|
31
|
+
this.validateInherits(permissions.inherits, roles, errorBuilder.for('inherits'))
|
|
32
|
+
this.validateVariables(permissions.variables, errorBuilder.for('variables'))
|
|
33
|
+
this.validatePermissions(permissions.entities, (permissions.variables as Acl.Variables) || {}, errorBuilder.for('entities'))
|
|
74
34
|
// todo: plugins validation
|
|
75
|
-
|
|
76
|
-
const result: Acl.RolePermissions = { variables, stages, entities }
|
|
77
|
-
if (inherits !== undefined) {
|
|
78
|
-
result.inherits = inherits
|
|
79
|
-
}
|
|
80
|
-
if (implicitIn !== undefined) {
|
|
81
|
-
if (typeof implicitIn !== 'boolean') {
|
|
82
|
-
errorBuilder.for('implicit').add('Must be boolean')
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
result.implicit = implicitIn
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { ...plugins, ...result }
|
|
89
35
|
}
|
|
90
36
|
|
|
91
|
-
private validateInherits(inherits:
|
|
37
|
+
private validateInherits(inherits: Acl.RolePermissions['inherits'], roles: string[], errorBuilder: ErrorBuilder): void {
|
|
92
38
|
if (inherits === undefined) {
|
|
93
39
|
return undefined
|
|
94
40
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
const validRoles: string[] = []
|
|
100
|
-
for (const inheritsFrom of inherits) {
|
|
101
|
-
if (!roles.includes(inheritsFrom)) {
|
|
102
|
-
// todo: check recursion
|
|
103
|
-
errorBuilder.for(inheritsFrom).add('Referenced role not exists.')
|
|
104
|
-
} else {
|
|
105
|
-
validRoles.push(inheritsFrom)
|
|
106
|
-
}
|
|
41
|
+
for (const inheritsFrom of inherits) {
|
|
42
|
+
if (!roles.includes(inheritsFrom)) {
|
|
43
|
+
errorBuilder.for(inheritsFrom).add('Referenced role not exists.')
|
|
107
44
|
}
|
|
108
|
-
return validRoles
|
|
109
45
|
}
|
|
110
46
|
}
|
|
111
47
|
|
|
112
|
-
private validateVariables(variables:
|
|
113
|
-
let validVariables: Acl.Variables = {}
|
|
114
|
-
if (!isObject(variables)) {
|
|
115
|
-
errorBuilder.add('Must be an object')
|
|
116
|
-
return {}
|
|
117
|
-
}
|
|
48
|
+
private validateVariables(variables: Acl.Variables, errorBuilder: ErrorBuilder): void {
|
|
118
49
|
for (const varName in variables) {
|
|
119
50
|
if (!variables.hasOwnProperty(varName)) {
|
|
120
51
|
continue
|
|
121
52
|
}
|
|
122
|
-
|
|
123
|
-
if (validVariable) {
|
|
124
|
-
validVariables[varName] = validVariable
|
|
125
|
-
}
|
|
53
|
+
this.validateVariable(variables[varName], errorBuilder.for(varName))
|
|
126
54
|
}
|
|
127
|
-
return validVariables
|
|
128
55
|
}
|
|
129
56
|
|
|
130
|
-
private validateVariable(variable:
|
|
131
|
-
if (!isObject(variable)) {
|
|
132
|
-
errorBuilder.add('Must be an object')
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
if (!hasStringProperty(variable, 'type')) {
|
|
136
|
-
errorBuilder.add('Variable type is not defined')
|
|
137
|
-
return
|
|
138
|
-
}
|
|
57
|
+
private validateVariable(variable: Acl.Variable, errorBuilder: ErrorBuilder): void {
|
|
139
58
|
switch (variable.type) {
|
|
140
|
-
case
|
|
141
|
-
if (!hasStringProperty(variable, 'entityName')) {
|
|
142
|
-
errorBuilder.add('Entity name must be specified for this type of variable')
|
|
143
|
-
return
|
|
144
|
-
}
|
|
59
|
+
case 'entity':
|
|
145
60
|
if (!this.model.entities[variable.entityName]) {
|
|
146
61
|
errorBuilder.add(`Entity "${variable.entityName}" not found`)
|
|
147
62
|
return
|
|
148
63
|
}
|
|
149
|
-
const extra = checkExtraProperties(variable, ['entityName', 'type'])
|
|
150
|
-
if (extra.length) {
|
|
151
|
-
errorBuilder.add('Unsupported properties found: ' + extra.join(', '))
|
|
152
|
-
}
|
|
153
|
-
return { type: Acl.VariableType.entity, entityName: variable.entityName }
|
|
154
|
-
|
|
155
|
-
case Acl.VariableType.predefined:
|
|
156
|
-
if (!hasStringProperty(variable, 'value')) {
|
|
157
|
-
errorBuilder.add('Variable value type must be specified for this type of variable')
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
if (variable.value !== 'identityID' && variable.value !== 'personID') {
|
|
161
|
-
errorBuilder.add('Unknown predefined variable')
|
|
162
|
-
return
|
|
163
|
-
}
|
|
164
|
-
const extra2 = checkExtraProperties(variable, ['type', 'value'])
|
|
165
|
-
if (extra2.length) {
|
|
166
|
-
errorBuilder.add('Unsupported properties found: ' + extra2.join(', '))
|
|
167
|
-
}
|
|
168
|
-
return { type: variable.type, value: variable.value }
|
|
169
|
-
default:
|
|
170
|
-
errorBuilder.add(`Variable type "${variable.type}" is not supported.`)
|
|
171
64
|
}
|
|
172
65
|
return
|
|
173
66
|
}
|
|
174
67
|
|
|
175
|
-
private
|
|
176
|
-
if (stages === '*') {
|
|
177
|
-
return '*'
|
|
178
|
-
}
|
|
179
|
-
if (!Array.isArray(stages) || !everyIs(stages, (it): it is string => typeof it === 'string')) {
|
|
180
|
-
errorBuilder.add('Stages must be either "*" or array of stage names')
|
|
181
|
-
return []
|
|
182
|
-
}
|
|
183
|
-
// todo validate stages ??
|
|
184
|
-
return stages
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private validatePermissions(
|
|
188
|
-
permissions: unknown,
|
|
189
|
-
variables: Acl.Variables,
|
|
190
|
-
errorBuilder: ErrorBuilder,
|
|
191
|
-
): Acl.Permissions {
|
|
192
|
-
if (!isObject(permissions)) {
|
|
193
|
-
errorBuilder.add('Must be an object')
|
|
194
|
-
return {}
|
|
195
|
-
}
|
|
196
|
-
const validPermissions: Acl.Permissions = {}
|
|
68
|
+
private validatePermissions(permissions: Acl.Permissions, variables: Acl.Variables, errorBuilder: ErrorBuilder): void {
|
|
197
69
|
for (const entityName in permissions) {
|
|
198
70
|
if (!permissions.hasOwnProperty(entityName)) {
|
|
199
71
|
continue
|
|
@@ -204,161 +76,99 @@ export class AclValidator {
|
|
|
204
76
|
}
|
|
205
77
|
const entity = getEntity(this.model, entityName)
|
|
206
78
|
|
|
207
|
-
|
|
79
|
+
this.validateEntityPermissions(
|
|
208
80
|
permissions[entityName],
|
|
209
81
|
entity,
|
|
210
82
|
variables,
|
|
211
83
|
errorBuilder.for(entityName),
|
|
212
84
|
)
|
|
213
|
-
if (entityPermissions) {
|
|
214
|
-
validPermissions[entityName] = entityPermissions
|
|
215
|
-
}
|
|
216
85
|
}
|
|
217
|
-
return validPermissions
|
|
218
86
|
}
|
|
219
87
|
|
|
220
88
|
private validateEntityPermissions(
|
|
221
|
-
entityPermissions:
|
|
89
|
+
entityPermissions: Acl.EntityPermissions,
|
|
222
90
|
entity: Model.Entity,
|
|
223
91
|
variables: Acl.Variables,
|
|
224
92
|
errorBuilder: ErrorBuilder,
|
|
225
|
-
):
|
|
226
|
-
|
|
227
|
-
errorBuilder.add('Must be an object')
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
const extra = checkExtraProperties(entityPermissions, ['predicates', 'operations'])
|
|
231
|
-
if (extra.length) {
|
|
232
|
-
errorBuilder.add('Unsupported properties found: ' + extra.join(', '))
|
|
233
|
-
}
|
|
234
|
-
const predicates = this.validatePredicates(
|
|
93
|
+
): void {
|
|
94
|
+
this.validatePredicates(
|
|
235
95
|
entityPermissions.predicates,
|
|
236
96
|
entity,
|
|
237
97
|
variables,
|
|
238
98
|
errorBuilder.for('predicates'),
|
|
239
99
|
)
|
|
240
|
-
|
|
100
|
+
this.validateOperations(
|
|
241
101
|
entityPermissions.operations,
|
|
242
102
|
entity,
|
|
243
103
|
entityPermissions.predicates as Acl.PredicateMap,
|
|
244
104
|
errorBuilder.for('operations'),
|
|
245
105
|
)
|
|
246
|
-
|
|
247
|
-
return { operations, predicates }
|
|
248
106
|
}
|
|
249
107
|
|
|
250
|
-
private validatePredicates(
|
|
251
|
-
predicates: unknown,
|
|
252
|
-
entity: Model.Entity,
|
|
253
|
-
variables: Acl.Variables,
|
|
254
|
-
errorBuilder: ErrorBuilder,
|
|
255
|
-
): Acl.PredicateMap {
|
|
256
|
-
if (!isObject(predicates)) {
|
|
257
|
-
errorBuilder.add('Must be an object')
|
|
258
|
-
return {}
|
|
259
|
-
}
|
|
260
|
-
const validPredicates: Acl.PredicateMap = {}
|
|
108
|
+
private validatePredicates(predicates: Acl.PredicateMap, entity: Model.Entity, variables: Acl.Variables, errorBuilder: ErrorBuilder): void {
|
|
261
109
|
for (const predicateName in predicates) {
|
|
262
110
|
if (!predicates.hasOwnProperty(predicateName)) {
|
|
263
111
|
continue
|
|
264
112
|
}
|
|
265
|
-
|
|
113
|
+
this.validatePredicateDefinition(
|
|
266
114
|
predicates[predicateName],
|
|
267
115
|
entity,
|
|
268
116
|
variables,
|
|
269
117
|
errorBuilder.for(predicateName),
|
|
270
118
|
)
|
|
271
|
-
if (predicate) {
|
|
272
|
-
validPredicates[predicateName] = predicate
|
|
273
|
-
}
|
|
274
119
|
}
|
|
275
|
-
return validPredicates
|
|
276
120
|
}
|
|
277
121
|
|
|
278
122
|
private validatePredicateDefinition(
|
|
279
|
-
predicate:
|
|
123
|
+
predicate: Acl.PredicateDefinition,
|
|
280
124
|
entity: Model.Entity,
|
|
281
125
|
variables: Acl.Variables,
|
|
282
126
|
errorBuilder: ErrorBuilder,
|
|
283
|
-
):
|
|
284
|
-
if (!isObject(predicate)) {
|
|
285
|
-
errorBuilder.add('Must be an object')
|
|
286
|
-
return
|
|
287
|
-
}
|
|
127
|
+
): void {
|
|
288
128
|
const processor = new PredicateDefinitionProcessor(this.model)
|
|
289
|
-
|
|
290
|
-
const result = processor.process(entity, predicate, {
|
|
129
|
+
processor.process(entity, predicate, {
|
|
291
130
|
handleColumn: ctx => {
|
|
292
131
|
if (typeof ctx.value === 'string' && !variables[ctx.value]) {
|
|
293
|
-
valid = false
|
|
294
132
|
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`)
|
|
295
133
|
}
|
|
296
134
|
return ctx.value
|
|
297
135
|
},
|
|
298
136
|
handleRelation: ctx => {
|
|
299
137
|
if (typeof ctx.value === 'string' && !variables[ctx.value]) {
|
|
300
|
-
valid = false
|
|
301
138
|
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`)
|
|
302
139
|
}
|
|
303
140
|
return ctx.value
|
|
304
141
|
},
|
|
305
142
|
handleUndefinedField: ctx => {
|
|
306
143
|
errorBuilder.for(...ctx.path).add(`Undefined field ${ctx.name} on entity ${ctx.entity.name}`)
|
|
307
|
-
valid = false
|
|
308
144
|
return ctx.value
|
|
309
145
|
},
|
|
310
146
|
})
|
|
311
|
-
if (!valid) {
|
|
312
|
-
return
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return result
|
|
316
147
|
}
|
|
317
148
|
|
|
318
149
|
private validateOperations(
|
|
319
|
-
operations:
|
|
150
|
+
operations: Acl.EntityOperations,
|
|
320
151
|
entity: Model.Entity,
|
|
321
152
|
predicates: Acl.PredicateDefinition,
|
|
322
153
|
errorBuilder: ErrorBuilder,
|
|
323
|
-
):
|
|
324
|
-
if (!isObject(operations)) {
|
|
325
|
-
errorBuilder.add('Must be an object')
|
|
326
|
-
return {}
|
|
327
|
-
}
|
|
328
|
-
const extra = checkExtraProperties(operations, ['read', 'create', 'update', 'delete', 'customPrimary'])
|
|
329
|
-
if (extra.length) {
|
|
330
|
-
errorBuilder.add('Unsupported properties found: ' + extra.join(', '))
|
|
331
|
-
}
|
|
332
|
-
const validOperations: Acl.EntityOperations = {}
|
|
154
|
+
): void {
|
|
333
155
|
if (operations.delete !== undefined) {
|
|
334
|
-
|
|
156
|
+
this.validatePredicate(operations.delete, predicates, errorBuilder.for('delete'))
|
|
335
157
|
}
|
|
336
158
|
for (const op of ['read', 'create', 'update'] as const) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
if (operations.customPrimary !== undefined) {
|
|
342
|
-
if (typeof operations.customPrimary !== 'boolean') {
|
|
343
|
-
errorBuilder.for('customPrimary').add('Must be boolean')
|
|
159
|
+
const operation = operations[op]
|
|
160
|
+
if (operation !== undefined) {
|
|
161
|
+
this.validateFieldPermissions(operation, entity, predicates, errorBuilder.for(op))
|
|
344
162
|
}
|
|
345
|
-
validOperations.customPrimary = operations.customPrimary as boolean
|
|
346
163
|
}
|
|
347
|
-
|
|
348
|
-
return validOperations
|
|
349
164
|
}
|
|
350
165
|
|
|
351
166
|
private validateFieldPermissions(
|
|
352
|
-
permissions:
|
|
167
|
+
permissions: Acl.FieldPermissions,
|
|
353
168
|
entity: Model.Entity,
|
|
354
169
|
predicates: Acl.PredicateDefinition,
|
|
355
170
|
errorBuilder: ErrorBuilder,
|
|
356
|
-
):
|
|
357
|
-
if (!isObject(permissions)) {
|
|
358
|
-
errorBuilder.add('Must be an object')
|
|
359
|
-
return {}
|
|
360
|
-
}
|
|
361
|
-
const valid: Acl.FieldPermissions = {}
|
|
171
|
+
): void {
|
|
362
172
|
for (const field in permissions) {
|
|
363
173
|
if (!permissions.hasOwnProperty(field)) {
|
|
364
174
|
continue
|
|
@@ -366,27 +176,20 @@ export class AclValidator {
|
|
|
366
176
|
if (!entity.fields[field]) {
|
|
367
177
|
errorBuilder.add(`Field ${field} not found on entity ${entity.name}`)
|
|
368
178
|
}
|
|
369
|
-
|
|
179
|
+
this.validatePredicate(permissions[field], predicates, errorBuilder.for(field))
|
|
370
180
|
}
|
|
371
|
-
return valid
|
|
372
181
|
}
|
|
373
182
|
|
|
374
183
|
private validatePredicate(
|
|
375
|
-
predicate:
|
|
184
|
+
predicate: Acl.Predicate,
|
|
376
185
|
predicates: Acl.PredicateDefinition,
|
|
377
186
|
errorBuilder: ErrorBuilder,
|
|
378
|
-
):
|
|
187
|
+
): void {
|
|
379
188
|
if (predicate === false || predicate === true) {
|
|
380
|
-
return
|
|
381
|
-
}
|
|
382
|
-
if (typeof predicate !== 'string') {
|
|
383
|
-
errorBuilder.add('Predicate must be either boolean or predicate reference')
|
|
384
|
-
return false
|
|
189
|
+
return
|
|
385
190
|
}
|
|
386
191
|
if (!predicates[predicate]) {
|
|
387
192
|
errorBuilder.add(`Predicate ${predicate} not found`)
|
|
388
|
-
return false
|
|
389
193
|
}
|
|
390
|
-
return predicate
|
|
391
194
|
}
|
|
392
195
|
}
|