@balena/pinejs 15.0.0-true-boolean-7896b116c446d891d7a0d5e4085c02a13bc9c725 → 15.0.1-build-migrations-clarify-marking-sbvr-optional-d6d0ded8eccc6eadb2492f4697918cf0afd00215-1
Sign up to get free protection for your applications and to get access to all the features.
- package/.dockerignore +4 -0
- package/.github/workflows/flowzone.yml +21 -0
- package/.husky/pre-commit +4 -0
- package/.pinejs-cache.json +1 -0
- package/.resinci.yml +1 -0
- package/.versionbot/CHANGELOG.yml +9678 -2002
- package/CHANGELOG.md +2976 -2
- package/Dockerfile +14 -0
- package/Gruntfile.ts +3 -6
- package/README.md +10 -1
- package/VERSION +1 -0
- package/build/browser.ts +1 -1
- package/build/config.ts +0 -1
- package/docker-compose.npm-test.yml +11 -0
- package/docs/AdvancedUsage.md +77 -63
- package/docs/GettingStarted.md +90 -41
- package/docs/Migrations.md +102 -1
- package/docs/ProjectConfig.md +12 -21
- package/docs/Testing.md +7 -0
- package/out/bin/abstract-sql-compiler.js +17 -17
- package/out/bin/abstract-sql-compiler.js.map +1 -1
- package/out/bin/odata-compiler.js +23 -20
- package/out/bin/odata-compiler.js.map +1 -1
- package/out/bin/sbvr-compiler.js +22 -22
- package/out/bin/sbvr-compiler.js.map +1 -1
- package/out/bin/utils.d.ts +2 -2
- package/out/bin/utils.js +3 -3
- package/out/bin/utils.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +9 -8
- package/out/config-loader/config-loader.js +135 -78
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/config-loader/env.d.ts +41 -16
- package/out/config-loader/env.js +46 -2
- package/out/config-loader/env.js.map +1 -1
- package/out/data-server/sbvr-server.d.ts +2 -19
- package/out/data-server/sbvr-server.js +44 -38
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.d.ts +32 -14
- package/out/database-layer/db.js +120 -41
- package/out/database-layer/db.js.map +1 -1
- package/out/express-emulator/express.js +10 -11
- package/out/express-emulator/express.js.map +1 -1
- package/out/http-transactions/transactions.d.ts +2 -18
- package/out/http-transactions/transactions.js +29 -21
- package/out/http-transactions/transactions.js.map +1 -1
- package/out/migrator/async.d.ts +7 -0
- package/out/migrator/async.js +168 -0
- package/out/migrator/async.js.map +1 -0
- package/out/migrator/migrations.sbvr +43 -0
- package/out/migrator/sync.d.ts +9 -0
- package/out/migrator/sync.js +106 -0
- package/out/migrator/sync.js.map +1 -0
- package/out/migrator/utils.d.ts +78 -0
- package/out/migrator/utils.js +283 -0
- package/out/migrator/utils.js.map +1 -0
- package/out/odata-metadata/odata-metadata-generator.js +10 -13
- package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
- package/out/passport-pinejs/passport-pinejs.d.ts +1 -1
- package/out/passport-pinejs/passport-pinejs.js +8 -7
- package/out/passport-pinejs/passport-pinejs.js.map +1 -1
- package/out/pinejs-session-store/pinejs-session-store.d.ts +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js +20 -6
- package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
- package/out/sbvr-api/abstract-sql.d.ts +3 -2
- package/out/sbvr-api/abstract-sql.js +9 -9
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/cached-compile.js +1 -1
- package/out/sbvr-api/cached-compile.js.map +1 -1
- package/out/sbvr-api/common-types.d.ts +6 -5
- package/out/sbvr-api/control-flow.d.ts +8 -1
- package/out/sbvr-api/control-flow.js +36 -9
- package/out/sbvr-api/control-flow.js.map +1 -1
- package/out/sbvr-api/errors.d.ts +47 -40
- package/out/sbvr-api/errors.js +78 -77
- package/out/sbvr-api/errors.js.map +1 -1
- package/out/sbvr-api/express-extension.d.ts +4 -0
- package/out/sbvr-api/hooks.d.ts +16 -15
- package/out/sbvr-api/hooks.js +74 -48
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/odata-response.d.ts +2 -2
- package/out/sbvr-api/odata-response.js +28 -30
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.d.ts +17 -16
- package/out/sbvr-api/permissions.js +369 -304
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +33 -15
- package/out/sbvr-api/sbvr-utils.js +397 -235
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/translations.d.ts +6 -0
- package/out/sbvr-api/translations.js +150 -0
- package/out/sbvr-api/translations.js.map +1 -0
- package/out/sbvr-api/uri-parser.d.ts +23 -17
- package/out/sbvr-api/uri-parser.js +33 -27
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/out/sbvr-api/user.sbvr +2 -0
- package/out/server-glue/module.d.ts +6 -6
- package/out/server-glue/module.js +4 -2
- package/out/server-glue/module.js.map +1 -1
- package/out/server-glue/server.js +5 -5
- package/out/server-glue/server.js.map +1 -1
- package/package.json +89 -73
- package/pinejs.png +0 -0
- package/repo.yml +9 -9
- package/src/bin/abstract-sql-compiler.ts +5 -7
- package/src/bin/odata-compiler.ts +11 -13
- package/src/bin/sbvr-compiler.ts +11 -17
- package/src/bin/utils.ts +3 -5
- package/src/config-loader/config-loader.ts +167 -53
- package/src/config-loader/env.ts +106 -6
- package/src/data-server/sbvr-server.js +44 -38
- package/src/database-layer/db.ts +205 -64
- package/src/express-emulator/express.js +10 -11
- package/src/http-transactions/transactions.js +29 -21
- package/src/migrator/async.ts +323 -0
- package/src/migrator/migrations.sbvr +43 -0
- package/src/migrator/sync.ts +152 -0
- package/src/migrator/utils.ts +458 -0
- package/src/odata-metadata/odata-metadata-generator.ts +12 -15
- package/src/passport-pinejs/passport-pinejs.ts +9 -7
- package/src/pinejs-session-store/pinejs-session-store.ts +15 -1
- package/src/sbvr-api/abstract-sql.ts +17 -14
- package/src/sbvr-api/common-types.ts +2 -1
- package/src/sbvr-api/control-flow.ts +45 -11
- package/src/sbvr-api/errors.ts +82 -77
- package/src/sbvr-api/express-extension.ts +6 -1
- package/src/sbvr-api/hooks.ts +123 -50
- package/src/sbvr-api/odata-response.ts +23 -28
- package/src/sbvr-api/permissions.ts +548 -415
- package/src/sbvr-api/sbvr-utils.ts +581 -259
- package/src/sbvr-api/translations.ts +248 -0
- package/src/sbvr-api/uri-parser.ts +63 -49
- package/src/sbvr-api/user.sbvr +2 -0
- package/src/server-glue/module.ts +16 -10
- package/src/server-glue/server.ts +5 -5
- package/tsconfig.dev.json +1 -0
- package/tsconfig.json +1 -2
- package/typings/lf-to-abstract-sql.d.ts +6 -9
- package/typings/memoizee.d.ts +1 -1
- package/.github/CODEOWNERS +0 -1
- package/circle.yml +0 -37
- package/docs/todo.txt +0 -22
- package/out/migrator/migrator.d.ts +0 -20
- package/out/migrator/migrator.js +0 -188
- package/out/migrator/migrator.js.map +0 -1
- package/src/migrator/migrator.ts +0 -286
@@ -42,7 +42,7 @@ const methodPermissions = {
|
|
42
42
|
MERGE: 'update',
|
43
43
|
DELETE: 'delete',
|
44
44
|
};
|
45
|
-
const $parsePermissions =
|
45
|
+
const $parsePermissions = env.createCache('parsePermissions', (filter) => {
|
46
46
|
const { tree, binds } = ODataParser.parse(filter, {
|
47
47
|
startRule: 'ProcessRule',
|
48
48
|
rule: 'FilterByExpression',
|
@@ -53,9 +53,8 @@ const $parsePermissions = memoize((filter) => {
|
|
53
53
|
};
|
54
54
|
}, {
|
55
55
|
primitive: true,
|
56
|
-
max: env.cache.parsePermissions.max,
|
57
56
|
});
|
58
|
-
const
|
57
|
+
const rewriteODataBinds = ({ tree, extraBinds }, odataBinds) => {
|
59
58
|
const bindsLength = odataBinds.length;
|
60
59
|
odataBinds.push(...extraBinds);
|
61
60
|
return _.cloneDeepWith(tree, (value) => {
|
@@ -69,9 +68,9 @@ const rewriteBinds = ({ tree, extraBinds }, odataBinds) => {
|
|
69
68
|
};
|
70
69
|
const parsePermissions = (filter, odataBinds) => {
|
71
70
|
const odata = $parsePermissions(filter);
|
72
|
-
return
|
71
|
+
return rewriteODataBinds(odata, odataBinds);
|
73
72
|
};
|
74
|
-
const isAnd = (x) =>
|
73
|
+
const isAnd = (x) => typeof x === 'object' && 'and' in x;
|
75
74
|
const isOr = (x) => typeof x === 'object' && 'or' in x;
|
76
75
|
function nestedCheck(check, stringCallback) {
|
77
76
|
if (typeof check === 'string') {
|
@@ -183,7 +182,10 @@ const namespaceRelationships = (relationships, alias) => {
|
|
183
182
|
namespaceRelationships(relationship, alias);
|
184
183
|
});
|
185
184
|
};
|
186
|
-
const getPermissionsLookup =
|
185
|
+
const getPermissionsLookup = env.createCache('permissionsLookup', (permissions, guestPermissions) => {
|
186
|
+
if (guestPermissions != null) {
|
187
|
+
permissions = [...guestPermissions, ...permissions];
|
188
|
+
}
|
187
189
|
const permissionsLookup = {};
|
188
190
|
for (const permission of permissions) {
|
189
191
|
const [target, condition] = permission.split('?');
|
@@ -191,16 +193,19 @@ const getPermissionsLookup = memoize((permissions) => {
|
|
191
193
|
permissionsLookup[target] = true;
|
192
194
|
}
|
193
195
|
else if (permissionsLookup[target] !== true) {
|
194
|
-
|
195
|
-
permissionsLookup[target] = [];
|
196
|
-
}
|
196
|
+
permissionsLookup[target] ??= [];
|
197
197
|
permissionsLookup[target].push(condition);
|
198
198
|
}
|
199
199
|
}
|
200
|
+
for (const target of Object.keys(permissionsLookup)) {
|
201
|
+
const conditions = permissionsLookup[target];
|
202
|
+
if (conditions !== true) {
|
203
|
+
permissionsLookup[target] = _.uniq(conditions);
|
204
|
+
}
|
205
|
+
}
|
200
206
|
return permissionsLookup;
|
201
207
|
}, {
|
202
|
-
|
203
|
-
max: env.cache.permissionsLookup.max,
|
208
|
+
normalizer: ([permissions, guestPermissions]) => `${permissions}${guestPermissions == null}`,
|
204
209
|
});
|
205
210
|
const $checkPermissions = (permissionsLookup, actionList, vocabulary, resourceName) => {
|
206
211
|
const checkObject = {
|
@@ -245,9 +250,9 @@ const convertToLambda = (filter, identifier) => {
|
|
245
250
|
return;
|
246
251
|
}
|
247
252
|
if (Array.isArray(object)) {
|
248
|
-
|
253
|
+
for (const element of object) {
|
249
254
|
replaceObject(element);
|
250
|
-
}
|
255
|
+
}
|
251
256
|
}
|
252
257
|
if (object.hasOwnProperty('name')) {
|
253
258
|
object.property = { ...object };
|
@@ -265,7 +270,7 @@ const rewriteSubPermissionBindings = (filter, counter) => {
|
|
265
270
|
if (typeof object.bind === 'number') {
|
266
271
|
object.bind = counter + object.bind;
|
267
272
|
}
|
268
|
-
if (Array.isArray(object) ||
|
273
|
+
if (Array.isArray(object) || typeof object === 'object') {
|
269
274
|
_.forEach(object, (v) => {
|
270
275
|
rewrite(v);
|
271
276
|
});
|
@@ -279,7 +284,7 @@ const buildODataPermission = (permissionsLookup, actionList, vocabulary, resourc
|
|
279
284
|
throw constrainedPermissionError;
|
280
285
|
}
|
281
286
|
if (conditionalPerms === true) {
|
282
|
-
return
|
287
|
+
return;
|
283
288
|
}
|
284
289
|
const permissionFilters = nestedCheck(conditionalPerms, (permissionCheck) => {
|
285
290
|
try {
|
@@ -300,32 +305,66 @@ const generateConstrainedAbstractSql = (permissionsLookup, actionList, vocabular
|
|
300
305
|
const abstractSQLModel = sbvrUtils.getAbstractSqlModel({
|
301
306
|
vocabulary,
|
302
307
|
});
|
303
|
-
const odata = uri_parser_1.memoizedParseOdata(`/${resourceName}`);
|
308
|
+
const odata = (0, uri_parser_1.memoizedParseOdata)(`/${resourceName}`);
|
304
309
|
const collapsedPermissionFilters = buildODataPermission(permissionsLookup, actionList, vocabulary, resourceName, odata);
|
310
|
+
if (collapsePermissionFilters == null) {
|
311
|
+
return;
|
312
|
+
}
|
305
313
|
_.set(odata, ['tree', 'options', '$filter'], collapsedPermissionFilters);
|
306
314
|
const lambdaAlias = randomstring.generate(20);
|
307
315
|
let inc = 0;
|
308
316
|
const canAccessTrace = [resourceName];
|
317
|
+
const resolveBind = (maybeBind, extraBinds) => {
|
318
|
+
if ((0, odata_to_abstract_sql_1.isBindReference)(maybeBind)) {
|
319
|
+
const { bind } = maybeBind;
|
320
|
+
if (typeof bind === 'string' || bind < odata.binds.length) {
|
321
|
+
return odata.binds[bind];
|
322
|
+
}
|
323
|
+
return extraBinds[bind - odata.binds.length];
|
324
|
+
}
|
325
|
+
return maybeBind;
|
326
|
+
};
|
309
327
|
const canAccessFunction = function (property) {
|
310
|
-
|
328
|
+
const { method, ...resolvedProperty } = property;
|
311
329
|
if (!this.defaultResource) {
|
312
330
|
throw new Error(`No resource selected in AST.`);
|
313
331
|
}
|
314
|
-
const targetResource = this.NavigateResources(this.defaultResource,
|
315
|
-
const
|
316
|
-
|
317
|
-
|
332
|
+
const targetResource = this.NavigateResources(this.defaultResource, resolvedProperty.name);
|
333
|
+
const lambdaId = `${lambdaAlias}+${inc}`;
|
334
|
+
inc = inc + 1;
|
335
|
+
const targetResourceName = (0, odata_to_abstract_sql_1.sqlNameToODataName)(targetResource.resource.name);
|
336
|
+
const traceIndex = canAccessTrace.findIndex((rName) => rName === targetResourceName);
|
337
|
+
if (traceIndex !== -1) {
|
338
|
+
if (canAccessTrace[canAccessTrace.length - 1] !== targetResourceName) {
|
339
|
+
throw new Error(`Indirectly circular 'canAccess()' permissions are not supported, currently permissions for ${resourceName} form an indirect circle by the following path: ${canAccessTrace.join(' -> ')} -> ${targetResourceName}`);
|
340
|
+
}
|
341
|
+
const { args } = method[1];
|
342
|
+
const depthArg = resolveBind(args[0], this.extraBindVars);
|
343
|
+
if (depthArg == null) {
|
344
|
+
throw new Error(`You must specify a depth if you want to enable directly circular 'canAccess()' permissions, currently permissions for ${resourceName} form a direct circle by the following path: ${canAccessTrace.join(' -> ')} -> ${targetResourceName}`);
|
345
|
+
}
|
346
|
+
const [type, depth] = depthArg;
|
347
|
+
if (type !== 'Real' || !Number.isInteger(depth) || depth < 1) {
|
348
|
+
throw new Error('The depth for `canAccess` must be an integer >= 1');
|
349
|
+
}
|
350
|
+
if (canAccessTrace.length > depth &&
|
351
|
+
canAccessTrace[canAccessTrace.length - depth] === targetResourceName) {
|
352
|
+
resolvedProperty.lambda = {
|
353
|
+
method: 'any',
|
354
|
+
identifier: lambdaId,
|
355
|
+
expression: ['eq', true, false],
|
356
|
+
};
|
357
|
+
return this.Property(resolvedProperty);
|
358
|
+
}
|
318
359
|
}
|
319
|
-
const parentOdata = uri_parser_1.memoizedParseOdata(`/${targetResourceName}`);
|
360
|
+
const parentOdata = (0, uri_parser_1.memoizedParseOdata)(`/${targetResourceName}`);
|
320
361
|
const collapsedParentPermissionFilters = buildODataPermission(permissionsLookup, actionList, vocabulary, targetResourceName, parentOdata);
|
321
|
-
if (collapsedParentPermissionFilters
|
322
|
-
|
362
|
+
if (collapsedParentPermissionFilters == null) {
|
363
|
+
return ['Equals', ['Boolean', true], ['Boolean', true]];
|
323
364
|
}
|
324
|
-
const lambdaId = `${lambdaAlias}+${inc}`;
|
325
|
-
inc = inc + 1;
|
326
365
|
rewriteSubPermissionBindings(collapsedParentPermissionFilters, this.bindVarsLength + this.extraBindVars.length);
|
327
366
|
convertToLambda(collapsedParentPermissionFilters, lambdaId);
|
328
|
-
|
367
|
+
resolvedProperty.lambda = {
|
329
368
|
method: 'any',
|
330
369
|
identifier: lambdaId,
|
331
370
|
expression: collapsedParentPermissionFilters,
|
@@ -333,41 +372,32 @@ const generateConstrainedAbstractSql = (permissionsLookup, actionList, vocabular
|
|
333
372
|
this.extraBindVars.push(...parentOdata.binds);
|
334
373
|
canAccessTrace.push(targetResourceName);
|
335
374
|
try {
|
336
|
-
return this.Property(
|
375
|
+
return this.Property(resolvedProperty);
|
337
376
|
}
|
338
377
|
finally {
|
339
378
|
canAccessTrace.pop();
|
340
379
|
}
|
341
380
|
};
|
342
|
-
const { tree, extraBindVars } = uri_parser_1.memoizedGetOData2AbstractSQL(abstractSQLModel).match(odata.tree, 'GET', [], odata.binds.length, {
|
381
|
+
const { tree, extraBindVars } = (0, uri_parser_1.memoizedGetOData2AbstractSQL)(abstractSQLModel).match(odata.tree, 'GET', [], odata.binds.length, {
|
343
382
|
canAccess: canAccessFunction,
|
344
383
|
});
|
345
384
|
odata.binds.push(...extraBindVars);
|
346
385
|
const odataBinds = odata.binds;
|
347
386
|
const abstractSqlQuery = [...tree];
|
348
|
-
const
|
349
|
-
const select = (abstractSqlQuery[selectIndex] = [
|
350
|
-
...abstractSqlQuery[selectIndex],
|
351
|
-
]);
|
387
|
+
const select = abstractSqlQuery.find((v) => v[0] === 'Select');
|
352
388
|
select[1] = select[1].map((selectField) => {
|
353
389
|
if (selectField[0] === 'Alias') {
|
390
|
+
const sqlName = (0, odata_to_abstract_sql_1.odataNameToSqlName)(selectField[2]);
|
354
391
|
const maybeField = selectField[1];
|
355
|
-
|
356
|
-
|
392
|
+
if ((maybeField[0] === 'ReferencedField' && maybeField[2] === sqlName) ||
|
393
|
+
(maybeField[0] === 'Field' && maybeField[1] === sqlName)) {
|
357
394
|
return maybeField;
|
358
395
|
}
|
359
|
-
return [
|
360
|
-
'Alias',
|
361
|
-
maybeField,
|
362
|
-
odata_to_abstract_sql_1.odataNameToSqlName(selectField[2]),
|
363
|
-
];
|
364
|
-
}
|
365
|
-
if (selectField.length === 2 && Array.isArray(selectField[0])) {
|
366
|
-
return selectField[0];
|
396
|
+
return ['Alias', maybeField, sqlName];
|
367
397
|
}
|
368
398
|
return selectField;
|
369
399
|
});
|
370
|
-
return {
|
400
|
+
return { binds: odataBinds, abstractSql: abstractSqlQuery };
|
371
401
|
};
|
372
402
|
const onceGetter = (obj, propName, fn) => {
|
373
403
|
let nullableFn = fn;
|
@@ -396,14 +426,14 @@ const onceGetter = (obj, propName, fn) => {
|
|
396
426
|
};
|
397
427
|
const deepFreezeExceptDefinition = (obj) => {
|
398
428
|
Object.freeze(obj);
|
399
|
-
Object.getOwnPropertyNames(obj)
|
429
|
+
for (const prop of Object.getOwnPropertyNames(obj)) {
|
400
430
|
if (prop !== 'definition' &&
|
401
431
|
obj.hasOwnProperty(prop) &&
|
402
432
|
obj[prop] !== null &&
|
403
433
|
!['object', 'function'].includes(typeof obj[prop])) {
|
404
434
|
deepFreezeExceptDefinition(obj);
|
405
435
|
}
|
406
|
-
}
|
436
|
+
}
|
407
437
|
};
|
408
438
|
const createBypassDefinition = (definition) => _.cloneDeepWith(definition, (abstractSql) => {
|
409
439
|
if (Array.isArray(abstractSql) &&
|
@@ -423,12 +453,11 @@ const getAlias = (name) => {
|
|
423
453
|
return `permissions${permissionsJSON}`;
|
424
454
|
};
|
425
455
|
const rewriteRelationship = memoizeWeak((value, name, abstractSqlModel, permissionsLookup, vocabulary, odata2AbstractSQL) => {
|
426
|
-
let escapedName = odata_to_abstract_sql_1.sqlNameToODataName(name);
|
456
|
+
let escapedName = (0, odata_to_abstract_sql_1.sqlNameToODataName)(name);
|
427
457
|
if (abstractSqlModel.tables[name]) {
|
428
|
-
escapedName = odata_to_abstract_sql_1.sqlNameToODataName(abstractSqlModel.tables[name].name);
|
458
|
+
escapedName = (0, odata_to_abstract_sql_1.sqlNameToODataName)(abstractSqlModel.tables[name].name);
|
429
459
|
}
|
430
460
|
const rewrite = (object) => {
|
431
|
-
var _a, _b;
|
432
461
|
if ('$' in object && Array.isArray(object.$)) {
|
433
462
|
const mapping = object.$;
|
434
463
|
if (mapping.length === 2 &&
|
@@ -439,14 +468,18 @@ const rewriteRelationship = memoizeWeak((value, name, abstractSqlModel, permissi
|
|
439
468
|
if (possibleTargetResourceName.endsWith('$bypass')) {
|
440
469
|
return;
|
441
470
|
}
|
442
|
-
const targetResourceEscaped = odata_to_abstract_sql_1.sqlNameToODataName(
|
471
|
+
const targetResourceEscaped = (0, odata_to_abstract_sql_1.sqlNameToODataName)(abstractSqlModel.tables[possibleTargetResourceName]?.name ??
|
472
|
+
possibleTargetResourceName);
|
443
473
|
if (targetResourceEscaped.includes('$')) {
|
444
474
|
return;
|
445
475
|
}
|
446
476
|
let foundCanAccessLink = false;
|
447
477
|
try {
|
448
|
-
const odata = uri_parser_1.memoizedParseOdata(`/${targetResourceEscaped}`);
|
478
|
+
const odata = (0, uri_parser_1.memoizedParseOdata)(`/${targetResourceEscaped}`);
|
449
479
|
const collapsedPermissionFilters = buildODataPermission(permissionsLookup, methodPermissions.GET, vocabulary, targetResourceEscaped, odata);
|
480
|
+
if (collapsedPermissionFilters == null) {
|
481
|
+
return;
|
482
|
+
}
|
450
483
|
_.set(odata, ['tree', 'options', '$filter'], collapsedPermissionFilters);
|
451
484
|
const canAccessFunction = function (property) {
|
452
485
|
delete property.method;
|
@@ -454,8 +487,8 @@ const rewriteRelationship = memoizeWeak((value, name, abstractSqlModel, permissi
|
|
454
487
|
throw new Error(`No resource selected in AST.`);
|
455
488
|
}
|
456
489
|
const targetResourceAST = this.NavigateResources(this.defaultResource, property.name);
|
457
|
-
const targetResourceName = odata_to_abstract_sql_1.sqlNameToODataName(targetResourceAST.resource.name);
|
458
|
-
const currentResourceName = odata_to_abstract_sql_1.sqlNameToODataName(this.defaultResource.name);
|
490
|
+
const targetResourceName = (0, odata_to_abstract_sql_1.sqlNameToODataName)(targetResourceAST.resource.name);
|
491
|
+
const currentResourceName = (0, odata_to_abstract_sql_1.sqlNameToODataName)(this.defaultResource.name);
|
459
492
|
if (currentResourceName === targetResourceEscaped &&
|
460
493
|
targetResourceName === escapedName) {
|
461
494
|
foundCanAccessLink = true;
|
@@ -485,7 +518,7 @@ const rewriteRelationship = memoizeWeak((value, name, abstractSqlModel, permissi
|
|
485
518
|
}
|
486
519
|
}
|
487
520
|
}
|
488
|
-
if (Array.isArray(object) ||
|
521
|
+
if (Array.isArray(object) || typeof object === 'object') {
|
489
522
|
_.forEach(object, (v) => {
|
490
523
|
if (typeof v !== 'string') {
|
491
524
|
rewrite(v);
|
@@ -499,7 +532,7 @@ const rewriteRelationships = (abstractSqlModel, relationships, permissionsLookup
|
|
499
532
|
const originalAbstractSQLModel = sbvrUtils.getAbstractSqlModel({
|
500
533
|
vocabulary,
|
501
534
|
});
|
502
|
-
const odata2AbstractSQL = uri_parser_1.memoizedGetOData2AbstractSQL(originalAbstractSQLModel);
|
535
|
+
const odata2AbstractSQL = (0, uri_parser_1.memoizedGetOData2AbstractSQL)(originalAbstractSQLModel);
|
503
536
|
const newRelationships = _.cloneDeep(relationships);
|
504
537
|
_.forOwn(newRelationships, (value, name) => rewriteRelationship(value, name, abstractSqlModel, permissionsLookup, vocabulary, odata2AbstractSQL));
|
505
538
|
return newRelationships;
|
@@ -507,9 +540,12 @@ const rewriteRelationships = (abstractSqlModel, relationships, permissionsLookup
|
|
507
540
|
const stringifiedGetPermissions = JSON.stringify(methodPermissions.GET);
|
508
541
|
const getBoundConstrainedMemoizer = memoizeWeak((abstractSqlModel) => memoizeWeak((permissionsLookup, vocabulary) => {
|
509
542
|
const constrainedAbstractSqlModel = _.cloneDeep(abstractSqlModel);
|
510
|
-
const origSynonyms = Object.
|
543
|
+
const origSynonyms = Object.entries(constrainedAbstractSqlModel.synonyms);
|
511
544
|
constrainedAbstractSqlModel.synonyms = new Proxy(constrainedAbstractSqlModel.synonyms, {
|
512
|
-
get
|
545
|
+
get(synonyms, permissionSynonym, receiver) {
|
546
|
+
if (typeof permissionSynonym === 'symbol') {
|
547
|
+
return Reflect.get(synonyms, permissionSynonym, receiver);
|
548
|
+
}
|
513
549
|
if (synonyms[permissionSynonym]) {
|
514
550
|
return synonyms[permissionSynonym];
|
515
551
|
}
|
@@ -517,9 +553,9 @@ const getBoundConstrainedMemoizer = memoizeWeak((abstractSqlModel) => memoizeWea
|
|
517
553
|
if (!alias) {
|
518
554
|
return;
|
519
555
|
}
|
520
|
-
|
556
|
+
for (const [synonym, canonicalForm] of origSynonyms) {
|
521
557
|
synonyms[`${synonym}$${alias}`] = `${canonicalForm}$${alias}`;
|
522
|
-
}
|
558
|
+
}
|
523
559
|
return synonyms[permissionSynonym];
|
524
560
|
},
|
525
561
|
});
|
@@ -529,20 +565,25 @@ const getBoundConstrainedMemoizer = memoizeWeak((abstractSqlModel) => memoizeWea
|
|
529
565
|
constrainedAbstractSqlModel.tables[bypassResourceName] = {
|
530
566
|
...table,
|
531
567
|
};
|
532
|
-
constrainedAbstractSqlModel.tables[bypassResourceName].resourceName =
|
568
|
+
constrainedAbstractSqlModel.tables[bypassResourceName].resourceName =
|
569
|
+
bypassResourceName;
|
533
570
|
if (table.definition) {
|
534
|
-
constrainedAbstractSqlModel.tables[bypassResourceName].definition =
|
571
|
+
constrainedAbstractSqlModel.tables[bypassResourceName].definition =
|
572
|
+
createBypassDefinition(table.definition);
|
535
573
|
}
|
536
574
|
else {
|
537
575
|
onceGetter(table, 'definition', () => constrainedAbstractSqlModel.tables[`${resourceName}$permissions${stringifiedGetPermissions}`].definition);
|
538
576
|
}
|
539
577
|
});
|
540
578
|
constrainedAbstractSqlModel.tables = new Proxy(constrainedAbstractSqlModel.tables, {
|
541
|
-
get
|
579
|
+
get(tables, permissionResourceName, receiver) {
|
580
|
+
if (typeof permissionResourceName === 'symbol') {
|
581
|
+
return Reflect.get(tables, permissionResourceName, receiver);
|
582
|
+
}
|
542
583
|
if (tables[permissionResourceName]) {
|
543
584
|
return tables[permissionResourceName];
|
544
585
|
}
|
545
|
-
const [resourceName, permissionsJSON
|
586
|
+
const [resourceName, permissionsJSON] = permissionResourceName.split('$permissions');
|
546
587
|
if (!permissionsJSON) {
|
547
588
|
return;
|
548
589
|
}
|
@@ -552,13 +593,16 @@ const getBoundConstrainedMemoizer = memoizeWeak((abstractSqlModel) => memoizeWea
|
|
552
593
|
...table,
|
553
594
|
});
|
554
595
|
permissionsTable.resourceName = permissionResourceName;
|
555
|
-
onceGetter(permissionsTable, 'definition', () => generateConstrainedAbstractSql(permissionsLookup, permissions, vocabulary, odata_to_abstract_sql_1.sqlNameToODataName(permissionsTable.name)));
|
596
|
+
onceGetter(permissionsTable, 'definition', () => generateConstrainedAbstractSql(permissionsLookup, permissions, vocabulary, (0, odata_to_abstract_sql_1.sqlNameToODataName)(permissionsTable.modifyName ?? permissionsTable.name)));
|
556
597
|
return permissionsTable;
|
557
598
|
},
|
558
599
|
});
|
559
600
|
constrainedAbstractSqlModel.relationships = rewriteRelationships(constrainedAbstractSqlModel, constrainedAbstractSqlModel.relationships, permissionsLookup, vocabulary);
|
560
601
|
constrainedAbstractSqlModel.relationships = new Proxy(constrainedAbstractSqlModel.relationships, {
|
561
|
-
get
|
602
|
+
get(relationships, permissionResourceName, receiver) {
|
603
|
+
if (typeof permissionResourceName === 'symbol') {
|
604
|
+
return Reflect.get(relationships, permissionResourceName, receiver);
|
605
|
+
}
|
562
606
|
if (relationships[permissionResourceName]) {
|
563
607
|
return relationships[permissionResourceName];
|
564
608
|
}
|
@@ -606,7 +650,7 @@ const checkPassword = async (username, password) => {
|
|
606
650
|
if (!res) {
|
607
651
|
throw new Error('Passwords do not match');
|
608
652
|
}
|
609
|
-
const permissions = await exports.getUserPermissions(userId);
|
653
|
+
const permissions = await (0, exports.getUserPermissions)(userId);
|
610
654
|
return {
|
611
655
|
id: userId,
|
612
656
|
actor: actorId,
|
@@ -615,58 +659,60 @@ const checkPassword = async (username, password) => {
|
|
615
659
|
};
|
616
660
|
};
|
617
661
|
exports.checkPassword = checkPassword;
|
618
|
-
const
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
$
|
627
|
-
|
628
|
-
|
629
|
-
$
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
{
|
637
|
-
uhp: {
|
638
|
-
expiry_date: { $gt: { $now: null } },
|
662
|
+
const $getUserPermissions = (() => {
|
663
|
+
const getUserPermissionsQuery = _.once(() => sbvrUtils.api.Auth.prepare({
|
664
|
+
resource: 'permission',
|
665
|
+
passthrough: {
|
666
|
+
req: exports.rootRead,
|
667
|
+
},
|
668
|
+
options: {
|
669
|
+
$select: 'name',
|
670
|
+
$filter: {
|
671
|
+
$or: {
|
672
|
+
is_of__user: {
|
673
|
+
$any: {
|
674
|
+
$alias: 'uhp',
|
675
|
+
$expr: {
|
676
|
+
uhp: { user: { '@': 'userId' } },
|
677
|
+
$or: [
|
678
|
+
{
|
679
|
+
uhp: { expiry_date: null },
|
639
680
|
},
|
640
|
-
|
641
|
-
|
681
|
+
{
|
682
|
+
uhp: {
|
683
|
+
expiry_date: { $gt: { $now: null } },
|
684
|
+
},
|
685
|
+
},
|
686
|
+
],
|
687
|
+
},
|
642
688
|
},
|
643
689
|
},
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
uhr: { expiry_date: null },
|
663
|
-
},
|
664
|
-
{
|
665
|
-
uhr: {
|
666
|
-
expiry_date: { $gt: { $now: null } },
|
690
|
+
is_of__role: {
|
691
|
+
$any: {
|
692
|
+
$alias: 'rhp',
|
693
|
+
$expr: {
|
694
|
+
rhp: {
|
695
|
+
role: {
|
696
|
+
$any: {
|
697
|
+
$alias: 'r',
|
698
|
+
$expr: {
|
699
|
+
r: {
|
700
|
+
is_of__user: {
|
701
|
+
$any: {
|
702
|
+
$alias: 'uhr',
|
703
|
+
$expr: {
|
704
|
+
uhr: { user: { '@': 'userId' } },
|
705
|
+
$or: [
|
706
|
+
{
|
707
|
+
uhr: { expiry_date: null },
|
667
708
|
},
|
668
|
-
|
669
|
-
|
709
|
+
{
|
710
|
+
uhr: {
|
711
|
+
expiry_date: { $gt: { $now: null } },
|
712
|
+
},
|
713
|
+
},
|
714
|
+
],
|
715
|
+
},
|
670
716
|
},
|
671
717
|
},
|
672
718
|
},
|
@@ -679,13 +725,23 @@ const getUserPermissionsQuery = _.once(() => sbvrUtils.api.Auth.prepare({
|
|
679
725
|
},
|
680
726
|
},
|
681
727
|
},
|
728
|
+
$orderby: {
|
729
|
+
name: 'asc',
|
730
|
+
},
|
682
731
|
},
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
}));
|
688
|
-
|
732
|
+
}));
|
733
|
+
return env.createCache('userPermissions', async (userId, tx) => {
|
734
|
+
const permissions = (await getUserPermissionsQuery()({
|
735
|
+
userId,
|
736
|
+
}, undefined, { tx }));
|
737
|
+
return permissions.map((permission) => permission.name);
|
738
|
+
}, {
|
739
|
+
primitive: true,
|
740
|
+
promise: true,
|
741
|
+
normalizer: ([userId]) => `${userId}`,
|
742
|
+
});
|
743
|
+
})();
|
744
|
+
const getUserPermissions = async (userId, tx) => {
|
689
745
|
if (typeof userId === 'string') {
|
690
746
|
userId = parseInt(userId, 10);
|
691
747
|
}
|
@@ -693,10 +749,7 @@ const getUserPermissions = async (userId) => {
|
|
693
749
|
throw new Error(`User ID has to be numeric, got: ${typeof userId}`);
|
694
750
|
}
|
695
751
|
try {
|
696
|
-
|
697
|
-
userId,
|
698
|
-
}));
|
699
|
-
return permissions.map((permission) => permission.name);
|
752
|
+
return await $getUserPermissions(userId, tx);
|
700
753
|
}
|
701
754
|
catch (err) {
|
702
755
|
sbvrUtils.api.Auth.logger.error('Error loading user permissions', err);
|
@@ -704,52 +757,76 @@ const getUserPermissions = async (userId) => {
|
|
704
757
|
}
|
705
758
|
};
|
706
759
|
exports.getUserPermissions = getUserPermissions;
|
707
|
-
const
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
$
|
716
|
-
|
717
|
-
|
718
|
-
$
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
$
|
724
|
-
|
725
|
-
|
760
|
+
const $getApiKeyPermissions = (() => {
|
761
|
+
const getApiKeyPermissionsQuery = _.once(() => sbvrUtils.api.Auth.prepare({
|
762
|
+
resource: 'permission',
|
763
|
+
passthrough: {
|
764
|
+
req: exports.rootRead,
|
765
|
+
},
|
766
|
+
options: {
|
767
|
+
$select: 'name',
|
768
|
+
$filter: {
|
769
|
+
$or: {
|
770
|
+
is_of__api_key: {
|
771
|
+
$any: {
|
772
|
+
$alias: 'khp',
|
773
|
+
$expr: {
|
774
|
+
khp: {
|
775
|
+
api_key: {
|
776
|
+
$any: {
|
777
|
+
$alias: 'k',
|
778
|
+
$expr: {
|
779
|
+
k: { key: { '@': 'apiKey' } },
|
780
|
+
$or: [
|
781
|
+
{
|
782
|
+
k: { expiry_date: null },
|
783
|
+
},
|
784
|
+
{
|
785
|
+
k: {
|
786
|
+
expiry_date: { $gt: { $now: null } },
|
787
|
+
},
|
788
|
+
},
|
789
|
+
],
|
790
|
+
},
|
726
791
|
},
|
727
792
|
},
|
728
793
|
},
|
729
794
|
},
|
730
795
|
},
|
731
796
|
},
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
797
|
+
is_of__role: {
|
798
|
+
$any: {
|
799
|
+
$alias: 'rhp',
|
800
|
+
$expr: {
|
801
|
+
rhp: {
|
802
|
+
role: {
|
803
|
+
$any: {
|
804
|
+
$alias: 'r',
|
805
|
+
$expr: {
|
806
|
+
r: {
|
807
|
+
is_of__api_key: {
|
808
|
+
$any: {
|
809
|
+
$alias: 'khr',
|
810
|
+
$expr: {
|
811
|
+
khr: {
|
812
|
+
api_key: {
|
813
|
+
$any: {
|
814
|
+
$alias: 'k',
|
815
|
+
$expr: {
|
816
|
+
k: { key: { '@': 'apiKey' } },
|
817
|
+
$or: [
|
818
|
+
{
|
819
|
+
k: { expiry_date: null },
|
820
|
+
},
|
821
|
+
{
|
822
|
+
k: {
|
823
|
+
expiry_date: {
|
824
|
+
$gt: { $now: null },
|
825
|
+
},
|
826
|
+
},
|
827
|
+
},
|
828
|
+
],
|
829
|
+
},
|
753
830
|
},
|
754
831
|
},
|
755
832
|
},
|
@@ -766,21 +843,28 @@ const getApiKeyPermissionsQuery = _.once(() => sbvrUtils.api.Auth.prepare({
|
|
766
843
|
},
|
767
844
|
},
|
768
845
|
},
|
846
|
+
$orderby: {
|
847
|
+
name: 'asc',
|
848
|
+
},
|
769
849
|
},
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
}));
|
775
|
-
|
850
|
+
}));
|
851
|
+
return env.createCache('apiKeyPermissions', async (apiKey, tx) => {
|
852
|
+
const permissions = (await getApiKeyPermissionsQuery()({
|
853
|
+
apiKey,
|
854
|
+
}, undefined, { tx }));
|
855
|
+
return permissions.map((permission) => permission.name);
|
856
|
+
}, {
|
857
|
+
primitive: true,
|
858
|
+
promise: true,
|
859
|
+
normalizer: ([apiKey]) => apiKey,
|
860
|
+
});
|
861
|
+
})();
|
862
|
+
const getApiKeyPermissions = async (apiKey, tx) => {
|
776
863
|
if (typeof apiKey !== 'string') {
|
777
864
|
throw new Error('API key has to be a string, got: ' + typeof apiKey);
|
778
865
|
}
|
779
866
|
try {
|
780
|
-
|
781
|
-
apiKey,
|
782
|
-
}));
|
783
|
-
return permissions.map((permission) => permission.name);
|
867
|
+
return await $getApiKeyPermissions(apiKey, tx);
|
784
868
|
}
|
785
869
|
catch (err) {
|
786
870
|
sbvrUtils.api.Auth.logger.error('Error loading api key permissions', err);
|
@@ -788,58 +872,54 @@ const getApiKeyPermissions = async (apiKey) => {
|
|
788
872
|
}
|
789
873
|
};
|
790
874
|
exports.getApiKeyPermissions = getApiKeyPermissions;
|
791
|
-
const
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
875
|
+
const getApiKeyActorId = (() => {
|
876
|
+
const getApiKeyActorIdQuery = _.once(() => sbvrUtils.api.Auth.prepare({
|
877
|
+
resource: 'api_key',
|
878
|
+
passthrough: {
|
879
|
+
req: exports.rootRead,
|
880
|
+
},
|
881
|
+
id: {
|
882
|
+
key: { '@': 'apiKey' },
|
883
|
+
},
|
884
|
+
options: {
|
885
|
+
$select: 'is_of__actor',
|
886
|
+
$filter: {
|
887
|
+
$or: [
|
888
|
+
{ expiry_date: null },
|
889
|
+
{ expiry_date: { $gt: { $now: null } } },
|
890
|
+
],
|
891
|
+
},
|
892
|
+
},
|
893
|
+
}));
|
894
|
+
const apiActorPermissionError = new errors_1.PermissionError();
|
895
|
+
return env.createCache('apiKeyActorId', async (apiKey, tx) => {
|
896
|
+
const apiKeyResult = await getApiKeyActorIdQuery()({
|
897
|
+
apiKey,
|
898
|
+
}, undefined, { tx });
|
899
|
+
if (apiKeyResult == null) {
|
900
|
+
throw apiActorPermissionError;
|
901
|
+
}
|
902
|
+
const apiKeyActorID = apiKeyResult.is_of__actor.__id;
|
903
|
+
if (apiKeyActorID == null) {
|
904
|
+
throw new Error('API key is not linked to a actor?!');
|
905
|
+
}
|
906
|
+
return apiKeyActorID;
|
907
|
+
}, {
|
908
|
+
promise: true,
|
909
|
+
primitive: true,
|
910
|
+
normalizer: ([apiKey]) => apiKey,
|
807
911
|
});
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
const
|
812
|
-
|
813
|
-
throw new Error('API key is not linked to a actor?!');
|
814
|
-
}
|
815
|
-
return apiKeyActorID;
|
816
|
-
};
|
817
|
-
const checkApiKey = async (req, apiKey) => {
|
818
|
-
if (apiKey == null || req.apiKey != null) {
|
819
|
-
return;
|
820
|
-
}
|
821
|
-
let permissions;
|
822
|
-
try {
|
823
|
-
permissions = await exports.getApiKeyPermissions(apiKey);
|
824
|
-
}
|
825
|
-
catch (err) {
|
826
|
-
console.warn('Error with API key:', err);
|
827
|
-
permissions = [];
|
828
|
-
}
|
829
|
-
let actor;
|
830
|
-
if (permissions.length > 0) {
|
831
|
-
actor = await getApiKeyActorId(apiKey);
|
832
|
-
}
|
833
|
-
const resolvedApiKey = {
|
912
|
+
})();
|
913
|
+
const checkApiKey = async (apiKey, tx) => {
|
914
|
+
const permissions = await (0, exports.getApiKeyPermissions)(apiKey, tx);
|
915
|
+
const actor = await getApiKeyActorId(apiKey, tx);
|
916
|
+
return {
|
834
917
|
key: apiKey,
|
835
918
|
permissions,
|
919
|
+
actor,
|
836
920
|
};
|
837
|
-
if (actor != null) {
|
838
|
-
resolvedApiKey.actor = actor;
|
839
|
-
}
|
840
|
-
return resolvedApiKey;
|
841
921
|
};
|
842
|
-
const resolveAuthHeader = async (req, expectedScheme = 'Bearer') => {
|
922
|
+
const resolveAuthHeader = async (req, expectedScheme = 'Bearer', tx) => {
|
843
923
|
const auth = req.header('Authorization');
|
844
924
|
if (!auth) {
|
845
925
|
return;
|
@@ -852,52 +932,48 @@ const resolveAuthHeader = async (req, expectedScheme = 'Bearer') => {
|
|
852
932
|
if (scheme.toLowerCase() !== expectedScheme.toLowerCase()) {
|
853
933
|
return;
|
854
934
|
}
|
855
|
-
return await checkApiKey(
|
935
|
+
return await checkApiKey(apiKey, tx);
|
856
936
|
};
|
857
937
|
exports.resolveAuthHeader = resolveAuthHeader;
|
858
938
|
const customAuthorizationMiddleware = (expectedScheme = 'Bearer') => {
|
859
939
|
expectedScheme = expectedScheme.toLowerCase();
|
860
940
|
return async (req, _res, next) => {
|
861
941
|
try {
|
862
|
-
const apiKey = await exports.resolveAuthHeader(req, expectedScheme);
|
942
|
+
const apiKey = await (0, exports.resolveAuthHeader)(req, expectedScheme);
|
863
943
|
if (apiKey) {
|
864
944
|
req.apiKey = apiKey;
|
865
945
|
}
|
866
946
|
}
|
867
947
|
finally {
|
868
|
-
next
|
948
|
+
next?.();
|
869
949
|
}
|
870
950
|
};
|
871
951
|
};
|
872
952
|
exports.customAuthorizationMiddleware = customAuthorizationMiddleware;
|
873
|
-
exports.authorizationMiddleware = exports.customAuthorizationMiddleware();
|
874
|
-
const resolveApiKey = async (req, paramName = 'apikey') => {
|
875
|
-
const apiKey = req.params[paramName]
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
return await checkApiKey(req, apiKey);
|
953
|
+
exports.authorizationMiddleware = (0, exports.customAuthorizationMiddleware)();
|
954
|
+
const resolveApiKey = async (req, paramName = 'apikey', tx) => {
|
955
|
+
const apiKey = req.params[paramName] ?? req.body[paramName] ?? req.query[paramName];
|
956
|
+
if (apiKey == null) {
|
957
|
+
return;
|
958
|
+
}
|
959
|
+
return await checkApiKey(apiKey, tx);
|
881
960
|
};
|
882
961
|
exports.resolveApiKey = resolveApiKey;
|
883
962
|
const customApiKeyMiddleware = (paramName = 'apikey') => {
|
884
|
-
if (paramName == null) {
|
885
|
-
paramName = 'apikey';
|
886
|
-
}
|
887
963
|
return async (req, _res, next) => {
|
888
964
|
try {
|
889
|
-
const apiKey = await exports.resolveApiKey(req, paramName);
|
965
|
+
const apiKey = await (0, exports.resolveApiKey)(req, paramName);
|
890
966
|
if (apiKey) {
|
891
967
|
req.apiKey = apiKey;
|
892
968
|
}
|
893
969
|
}
|
894
970
|
finally {
|
895
|
-
next
|
971
|
+
next?.();
|
896
972
|
}
|
897
973
|
};
|
898
974
|
};
|
899
975
|
exports.customApiKeyMiddleware = customApiKeyMiddleware;
|
900
|
-
exports.apiKeyMiddleware = exports.customApiKeyMiddleware();
|
976
|
+
exports.apiKeyMiddleware = (0, exports.customApiKeyMiddleware)();
|
901
977
|
const checkPermissions = async (req, actionList, resourceName, vocabulary) => {
|
902
978
|
const permissionsLookup = await getReqPermissions(req);
|
903
979
|
return $checkPermissions(permissionsLookup, actionList, vocabulary, resourceName);
|
@@ -905,10 +981,10 @@ const checkPermissions = async (req, actionList, resourceName, vocabulary) => {
|
|
905
981
|
exports.checkPermissions = checkPermissions;
|
906
982
|
const checkPermissionsMiddleware = (action) => async (req, res, next) => {
|
907
983
|
try {
|
908
|
-
const allowed = await exports.checkPermissions(req, action);
|
984
|
+
const allowed = await (0, exports.checkPermissions)(req, action);
|
909
985
|
switch (allowed) {
|
910
986
|
case false:
|
911
|
-
res.
|
987
|
+
res.status(401).end();
|
912
988
|
return;
|
913
989
|
case true:
|
914
990
|
next();
|
@@ -918,11 +994,12 @@ const checkPermissionsMiddleware = (action) => async (req, res, next) => {
|
|
918
994
|
}
|
919
995
|
}
|
920
996
|
catch (err) {
|
921
|
-
sbvrUtils.api.Auth.logger.error('Error checking permissions', err
|
922
|
-
res.
|
997
|
+
sbvrUtils.api.Auth.logger.error('Error checking permissions', err);
|
998
|
+
res.status(503).end();
|
923
999
|
}
|
924
1000
|
};
|
925
1001
|
exports.checkPermissionsMiddleware = checkPermissionsMiddleware;
|
1002
|
+
let guestPermissionsInitialized = false;
|
926
1003
|
const getGuestPermissions = memoize(async () => {
|
927
1004
|
const result = (await sbvrUtils.api.Auth.get({
|
928
1005
|
resource: 'user',
|
@@ -939,75 +1016,56 @@ const getGuestPermissions = memoize(async () => {
|
|
939
1016
|
if (result == null) {
|
940
1017
|
throw new Error('No guest user');
|
941
1018
|
}
|
942
|
-
|
943
|
-
}, { promise: true });
|
944
|
-
const getReqPermissions = async (req, odataBinds = []) => {
|
945
|
-
const [guestPermissions] = await Promise.all([
|
946
|
-
getGuestPermissions(),
|
947
|
-
(async () => {
|
948
|
-
if (req.apiKey != null &&
|
949
|
-
req.apiKey.actor == null &&
|
950
|
-
req.apiKey.permissions != null &&
|
951
|
-
req.apiKey.permissions.length > 0) {
|
952
|
-
const actorId = await getApiKeyActorId(req.apiKey.key);
|
953
|
-
req.apiKey.actor = actorId;
|
954
|
-
}
|
955
|
-
})(),
|
956
|
-
]);
|
1019
|
+
const guestPermissions = _.uniq(await (0, exports.getUserPermissions)(result.id));
|
957
1020
|
if (guestPermissions.some((p) => DEFAULT_ACTOR_BIND_REGEX.test(p))) {
|
958
1021
|
throw new Error('Guest permissions cannot reference actors');
|
959
1022
|
}
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
1023
|
+
guestPermissionsInitialized = true;
|
1024
|
+
return guestPermissions;
|
1025
|
+
}, { promise: true });
|
1026
|
+
const getReqPermissions = async (req, odataBinds = []) => {
|
1027
|
+
const guestPermissions = await (async () => {
|
1028
|
+
if (guestPermissionsInitialized === false &&
|
1029
|
+
(req.user === exports.root.user || req.user === exports.rootRead.user)) {
|
1030
|
+
return [];
|
967
1031
|
}
|
968
|
-
|
969
|
-
|
970
|
-
|
1032
|
+
return await getGuestPermissions();
|
1033
|
+
})();
|
1034
|
+
let actorPermissions = [];
|
1035
|
+
const addActorPermissions = (actorId, perms) => {
|
1036
|
+
odataBinds[DEFAULT_ACTOR_BIND] = ['Real', actorId];
|
1037
|
+
actorPermissions = perms;
|
971
1038
|
};
|
972
|
-
if (req.user
|
1039
|
+
if (req.user?.permissions != null) {
|
973
1040
|
addActorPermissions(req.user.actor, req.user.permissions);
|
974
1041
|
}
|
975
|
-
else if (req.apiKey
|
1042
|
+
else if (req.apiKey?.permissions != null) {
|
976
1043
|
addActorPermissions(req.apiKey.actor, req.apiKey.permissions);
|
977
1044
|
}
|
978
|
-
|
979
|
-
return getPermissionsLookup(permissions);
|
1045
|
+
return getPermissionsLookup(actorPermissions, guestPermissions);
|
980
1046
|
};
|
981
1047
|
const addPermissions = async (req, request) => {
|
982
|
-
|
983
|
-
const
|
984
|
-
let { method } = request;
|
1048
|
+
const { resourceName, odataQuery, odataBinds } = request;
|
1049
|
+
const vocabulary = _.last(request.translateVersions);
|
985
1050
|
let abstractSqlModel = sbvrUtils.getAbstractSqlModel(request);
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
else if (isMetadataEndpoint) {
|
993
|
-
permissionType = 'model';
|
994
|
-
}
|
995
|
-
else {
|
996
|
-
const methodPermission = methodPermissions[method];
|
997
|
-
if (methodPermission != null) {
|
998
|
-
permissionType = methodPermission;
|
1051
|
+
let { permissionType } = request;
|
1052
|
+
if (permissionType == null) {
|
1053
|
+
const method = request.method.toUpperCase();
|
1054
|
+
const isMetadataEndpoint = method === 'OPTIONS' || uri_parser_1.metadataEndpoints.includes(resourceName);
|
1055
|
+
if (isMetadataEndpoint) {
|
1056
|
+
permissionType = 'model';
|
999
1057
|
}
|
1000
1058
|
else {
|
1001
|
-
|
1002
|
-
|
1059
|
+
const methodPermission = methodPermissions[method];
|
1060
|
+
if (methodPermission != null) {
|
1061
|
+
permissionType = methodPermission;
|
1062
|
+
}
|
1063
|
+
else {
|
1064
|
+
console.warn('Unknown method for permissions type check: ', method);
|
1065
|
+
permissionType = 'all';
|
1066
|
+
}
|
1003
1067
|
}
|
1004
1068
|
}
|
1005
|
-
let permissions = (_b = (_a = req.user) === null || _a === void 0 ? void 0 : _a.permissions) !== null && _b !== void 0 ? _b : [];
|
1006
|
-
permissions = permissions.concat((_d = (_c = req.apiKey) === null || _c === void 0 ? void 0 : _c.permissions) !== null && _d !== void 0 ? _d : []);
|
1007
|
-
if (permissions.length > 0 &&
|
1008
|
-
$checkPermissions(getPermissionsLookup(permissions), permissionType, vocabulary) === true) {
|
1009
|
-
return;
|
1010
|
-
}
|
1011
1069
|
const permissionsLookup = await getReqPermissions(req, odataBinds);
|
1012
1070
|
request.abstractSqlModel = abstractSqlModel = memoizedGetConstrainedModel(abstractSqlModel, permissionsLookup, vocabulary);
|
1013
1071
|
if (!_.isEqual(permissionType, methodPermissions.GET)) {
|
@@ -1051,22 +1109,25 @@ exports.config = {
|
|
1051
1109
|
ALTER TABLE "role-has-permission"
|
1052
1110
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1053
1111
|
`,
|
1112
|
+
'14.42.0-api-key-expiry-date': `
|
1113
|
+
ALTER TABLE "api key"
|
1114
|
+
ADD COLUMN IF NOT EXISTS "expiry date" TIMESTAMP NULL;
|
1115
|
+
`,
|
1054
1116
|
},
|
1055
1117
|
},
|
1056
1118
|
],
|
1057
1119
|
};
|
1058
1120
|
const setup = () => {
|
1059
|
-
hooks_1.addHook('all', 'all', 'all', {
|
1121
|
+
(0, hooks_1.addHook)('all', 'all', 'all', {
|
1060
1122
|
sideEffects: false,
|
1061
1123
|
readOnlyTx: true,
|
1062
|
-
PREPARSE: ({ req }) => exports.apiKeyMiddleware(req),
|
1124
|
+
PREPARSE: ({ req }) => (0, exports.apiKeyMiddleware)(req),
|
1063
1125
|
POSTPARSE: async ({ req, request, }) => {
|
1064
1126
|
if (request.abstractSqlQuery != null) {
|
1065
1127
|
return;
|
1066
1128
|
}
|
1067
1129
|
if (request.method === 'POST' &&
|
1068
|
-
request.odataQuery.property
|
1069
|
-
request.odataQuery.property.resource === 'canAccess') {
|
1130
|
+
request.odataQuery.property?.resource === 'canAccess') {
|
1070
1131
|
if (request.odataQuery.key == null) {
|
1071
1132
|
throw new errors_1.BadRequestError();
|
1072
1133
|
}
|
@@ -1086,6 +1147,7 @@ const setup = () => {
|
|
1086
1147
|
}
|
1087
1148
|
const abstractSqlModel = sbvrUtils.getAbstractSqlModel(request);
|
1088
1149
|
request.resourceName = request.resourceName.slice(0, -'#canAccess'.length);
|
1150
|
+
request.originalResourceName = request.originalResourceName.slice(0, -'#canAccess'.length);
|
1089
1151
|
const resourceName = sbvrUtils.resolveSynonym(request);
|
1090
1152
|
const resourceTable = abstractSqlModel.tables[resourceName];
|
1091
1153
|
if (resourceTable == null) {
|
@@ -1101,15 +1163,18 @@ const setup = () => {
|
|
1101
1163
|
request.method = 'GET';
|
1102
1164
|
request.custom.isAction = 'canAccess';
|
1103
1165
|
}
|
1104
|
-
await exports.addPermissions(req, request);
|
1166
|
+
await (0, exports.addPermissions)(req, request);
|
1105
1167
|
},
|
1106
|
-
PRERESPOND: ({ request,
|
1107
|
-
if (request.custom.isAction === 'canAccess' &&
|
1168
|
+
PRERESPOND: ({ request, response }) => {
|
1169
|
+
if (request.custom.isAction === 'canAccess' &&
|
1170
|
+
(response.body == null ||
|
1171
|
+
typeof response.body === 'string' ||
|
1172
|
+
_.isEmpty(response.body?.d))) {
|
1108
1173
|
throw new errors_1.PermissionError();
|
1109
1174
|
}
|
1110
1175
|
},
|
1111
1176
|
});
|
1112
|
-
hooks_1.addPureHook('POST', 'Auth', 'user', {
|
1177
|
+
(0, hooks_1.addPureHook)('POST', 'Auth', 'user', {
|
1113
1178
|
POSTPARSE: async ({ request, api }) => {
|
1114
1179
|
const result = await api.post({
|
1115
1180
|
resource: 'actor',
|
@@ -1118,7 +1183,7 @@ const setup = () => {
|
|
1118
1183
|
request.values.actor = result.id;
|
1119
1184
|
},
|
1120
1185
|
});
|
1121
|
-
hooks_1.addPureHook('DELETE', 'Auth', 'user', {
|
1186
|
+
(0, hooks_1.addPureHook)('DELETE', 'Auth', 'user', {
|
1122
1187
|
POSTRUN: ({ request, api }) => api.delete({
|
1123
1188
|
resource: 'actor',
|
1124
1189
|
id: request.values.actor,
|