@nymphjs/tilmeld 1.0.0-alpha.3 → 1.0.0-alpha.33
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/CHANGELOG.md +151 -0
- package/dist/Group.js +11 -6
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +6 -0
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +3 -6
- package/dist/Tilmeld.js +196 -90
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +40 -2
- package/dist/User.js +109 -7
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +6 -0
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +3 -0
- package/dist/conf/defaults.js +25 -101
- package/dist/conf/defaults.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.d.ts +12 -0
- package/dist/tilmeldJoiProps.js +20 -0
- package/dist/tilmeldJoiProps.js.map +1 -0
- package/package.json +16 -16
- package/src/Group.test.ts +8 -0
- package/src/Group.ts +21 -7
- package/src/Tilmeld.ts +300 -108
- package/src/User.test.ts +8 -0
- package/src/User.ts +235 -10
- package/src/conf/d.ts +30 -0
- package/src/conf/defaults.ts +10 -57
- package/src/index.ts +3 -0
- package/src/tilmeldJoiProps.ts +46 -0
package/src/Tilmeld.ts
CHANGED
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
FormattedSelector,
|
|
5
5
|
Options,
|
|
6
6
|
TilmeldInterface,
|
|
7
|
+
TilmeldAccessLevels,
|
|
7
8
|
} from '@nymphjs/nymph';
|
|
8
9
|
import { Request, Response } from 'express';
|
|
10
|
+
import { xor } from 'lodash';
|
|
9
11
|
|
|
10
12
|
import { Config, ConfigDefaults as defaults } from './conf';
|
|
11
13
|
import { AccessControlError } from './errors';
|
|
@@ -23,11 +25,6 @@ import { AccessControlData } from './Tilmeld.types';
|
|
|
23
25
|
* @see http://nymph.io/
|
|
24
26
|
*/
|
|
25
27
|
export default class Tilmeld implements TilmeldInterface {
|
|
26
|
-
public static NO_ACCESS = 0;
|
|
27
|
-
public static READ_ACCESS = 1;
|
|
28
|
-
public static WRITE_ACCESS = 2;
|
|
29
|
-
public static FULL_ACCESS = 4;
|
|
30
|
-
|
|
31
28
|
/**
|
|
32
29
|
* The Nymph instance.
|
|
33
30
|
*/
|
|
@@ -117,13 +114,15 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
117
114
|
class NymphUser extends User {}
|
|
118
115
|
NymphUser.nymph = this.nymph;
|
|
119
116
|
NymphUser.tilmeld = this;
|
|
117
|
+
(NymphUser as any).skipOnClone = true;
|
|
120
118
|
this.User = NymphUser;
|
|
121
|
-
this.nymph.
|
|
119
|
+
this.nymph.addEntityClass(NymphUser);
|
|
122
120
|
class NymphGroup extends Group {}
|
|
123
121
|
NymphGroup.nymph = this.nymph;
|
|
124
122
|
NymphGroup.tilmeld = this;
|
|
123
|
+
(NymphUser as any).skipOnClone = true;
|
|
125
124
|
this.Group = NymphGroup;
|
|
126
|
-
this.nymph.
|
|
125
|
+
this.nymph.addEntityClass(NymphGroup);
|
|
127
126
|
|
|
128
127
|
this.initAccessControl();
|
|
129
128
|
if (this.request != null && !this.skipAuthenticate) {
|
|
@@ -149,13 +148,13 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
private initAccessControl() {
|
|
152
|
-
const tilmeld = this;
|
|
153
|
-
|
|
154
151
|
// Check for the skip access control option and add AC selectors.
|
|
155
152
|
const handleQuery = function (
|
|
153
|
+
nymph: Nymph,
|
|
156
154
|
options: Options,
|
|
157
155
|
selectors: FormattedSelector[]
|
|
158
156
|
) {
|
|
157
|
+
const tilmeld = nymph.tilmeld as Tilmeld;
|
|
159
158
|
if (
|
|
160
159
|
!options ||
|
|
161
160
|
!('skipAc' in options) ||
|
|
@@ -164,20 +163,20 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
164
163
|
) {
|
|
165
164
|
if (options?.source === 'client') {
|
|
166
165
|
if (
|
|
167
|
-
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
166
|
+
!tilmeld.gatekeeper('tilmeld/admin') && // The user is not an admin.
|
|
168
167
|
((!tilmeld.config.enableUserSearch &&
|
|
169
|
-
options.class?.factorySync() instanceof User) ||
|
|
168
|
+
options.class?.factorySync() instanceof User) || // The use is searching for a user and searching is disabled.
|
|
170
169
|
(!tilmeld.config.enableGroupSearch &&
|
|
171
|
-
options.class?.factorySync() instanceof Group)) &&
|
|
172
|
-
(selectors[0] == null ||
|
|
173
|
-
selectors[0].type !== '&' ||
|
|
174
|
-
(!('guid' in selectors[0]) && !('equal' in selectors[0])) ||
|
|
170
|
+
options.class?.factorySync() instanceof Group)) && // Or the same, but for a group.
|
|
171
|
+
(selectors[0] == null || // No selector specified.
|
|
172
|
+
selectors[0].type !== '&' || // Or the type is not and.
|
|
173
|
+
(!('guid' in selectors[0]) && !('equal' in selectors[0])) || // Or they're searching for something other than guid or equal.
|
|
175
174
|
('guid' in selectors[0] &&
|
|
176
|
-
selectors[0].guid?.length
|
|
177
|
-
|
|
175
|
+
(!selectors[0].guid?.length ||
|
|
176
|
+
!selectors[0].guid?.[0]?.length)) || // Or they're not searching for a specific guid.
|
|
178
177
|
('equal' in selectors[0] &&
|
|
179
|
-
selectors[0].equal?.
|
|
180
|
-
selectors[0].equal?.[0]?.[0] !== '
|
|
178
|
+
selectors[0].equal?.[0]?.[0] !== 'username' &&
|
|
179
|
+
selectors[0].equal?.[0]?.[0] !== 'groupname')) // Or they're not searching for a specific username/groupname.
|
|
181
180
|
) {
|
|
182
181
|
// If the user is not specifically searching for a GUID or username,
|
|
183
182
|
// and they're not allowed to search, it should fail.
|
|
@@ -188,10 +187,14 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
188
187
|
tilmeld.addAccessControlSelectors(options, selectors);
|
|
189
188
|
}
|
|
190
189
|
};
|
|
191
|
-
handleQuery.
|
|
190
|
+
handleQuery.skipOnClone = true;
|
|
192
191
|
|
|
193
192
|
// Filter entities being deleted for user permissions.
|
|
194
|
-
const checkPermissionsDelete = function (
|
|
193
|
+
const checkPermissionsDelete = function (
|
|
194
|
+
nymph: Nymph,
|
|
195
|
+
entity: EntityInterface
|
|
196
|
+
) {
|
|
197
|
+
const tilmeld = nymph.tilmeld as Tilmeld;
|
|
195
198
|
if (
|
|
196
199
|
typeof entity.$tilmeldDeleteSkipAC === 'function' &&
|
|
197
200
|
entity.$tilmeldDeleteSkipAC()
|
|
@@ -199,32 +202,35 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
199
202
|
return;
|
|
200
203
|
}
|
|
201
204
|
// Test for permissions.
|
|
202
|
-
if (tilmeld.checkPermissions(entity,
|
|
205
|
+
if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
|
|
203
206
|
throw new AccessControlError('No permission to delete.');
|
|
204
207
|
}
|
|
205
208
|
};
|
|
206
|
-
checkPermissionsDelete.
|
|
209
|
+
checkPermissionsDelete.skipOnClone = true;
|
|
207
210
|
|
|
208
211
|
// Filter entities being deleted for user permissions.
|
|
209
212
|
const checkPermissionsDeleteByID = function (
|
|
213
|
+
nymph: Nymph,
|
|
210
214
|
guid: string,
|
|
211
215
|
className?: string
|
|
212
216
|
) {
|
|
213
|
-
const entity =
|
|
214
|
-
{ class:
|
|
217
|
+
const entity = nymph.driver.getEntitySync(
|
|
218
|
+
{ class: nymph.getEntityClass(className ?? 'Entity') },
|
|
215
219
|
{ type: '&', guid: guid }
|
|
216
220
|
);
|
|
217
221
|
if (entity != null) {
|
|
218
|
-
checkPermissionsDelete(entity);
|
|
222
|
+
checkPermissionsDelete(nymph, entity);
|
|
219
223
|
}
|
|
220
224
|
};
|
|
221
|
-
checkPermissionsDeleteByID.
|
|
225
|
+
checkPermissionsDeleteByID.skipOnClone = true;
|
|
222
226
|
|
|
223
227
|
// Filter entities being saved for user permissions, and filter any
|
|
224
228
|
// disallowed changes to AC properties.
|
|
225
229
|
const checkPermissionsSaveAndFilterAcChanges = function (
|
|
230
|
+
nymph: Nymph,
|
|
226
231
|
entity: EntityInterface & AccessControlData
|
|
227
232
|
) {
|
|
233
|
+
const tilmeld = nymph.tilmeld as Tilmeld;
|
|
228
234
|
if (!entity) {
|
|
229
235
|
return;
|
|
230
236
|
}
|
|
@@ -240,15 +246,15 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
240
246
|
// allowing a change to ac properties.
|
|
241
247
|
|
|
242
248
|
const originalAc = entity.$getOriginalAcValues();
|
|
243
|
-
const newAc = {
|
|
244
|
-
user: entity.user ??
|
|
245
|
-
group: entity.group ??
|
|
246
|
-
acUser: entity.acUser ??
|
|
247
|
-
acGroup: entity.acGroup ??
|
|
248
|
-
acOther: entity.acOther ??
|
|
249
|
-
acRead: entity.acRead ??
|
|
250
|
-
acWrite: entity.acWrite ??
|
|
251
|
-
acFull: entity.acFull ??
|
|
249
|
+
const newAc: AccessControlData = {
|
|
250
|
+
user: entity.user ?? undefined,
|
|
251
|
+
group: entity.group ?? undefined,
|
|
252
|
+
acUser: entity.acUser ?? undefined,
|
|
253
|
+
acGroup: entity.acGroup ?? undefined,
|
|
254
|
+
acOther: entity.acOther ?? undefined,
|
|
255
|
+
acRead: entity.acRead ?? undefined,
|
|
256
|
+
acWrite: entity.acWrite ?? undefined,
|
|
257
|
+
acFull: entity.acFull ?? undefined,
|
|
252
258
|
};
|
|
253
259
|
|
|
254
260
|
const setAcProperties = (acValues: { [k: string]: any }) => {
|
|
@@ -264,20 +270,112 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
264
270
|
|
|
265
271
|
// Restore original AC properties and check permissions.
|
|
266
272
|
setAcProperties(originalAc);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
273
|
+
|
|
274
|
+
function areAcPropertiesChanged(
|
|
275
|
+
a: AccessControlData,
|
|
276
|
+
b: AccessControlData
|
|
277
|
+
) {
|
|
278
|
+
for (const name of ['user', 'group'] as ('user' | 'group')[]) {
|
|
279
|
+
const aVal = a[name];
|
|
280
|
+
const bVal = b[name];
|
|
281
|
+
if (aVal == null && bVal == null) {
|
|
282
|
+
continue;
|
|
283
|
+
} else if (aVal == null || bVal == null) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (!aVal.$is(bVal)) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (const name of ['acUser', 'acGroup', 'acOther'] as (
|
|
291
|
+
| 'acUser'
|
|
292
|
+
| 'acGroup'
|
|
293
|
+
| 'acOther'
|
|
294
|
+
)[]) {
|
|
295
|
+
const aVal = a[name];
|
|
296
|
+
const bVal = b[name];
|
|
297
|
+
if (aVal == null && bVal == null) {
|
|
298
|
+
continue;
|
|
299
|
+
} else if (aVal == null || bVal == null) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (aVal !== bVal) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const name of ['acRead', 'acWrite', 'acFull'] as (
|
|
307
|
+
| 'acRead'
|
|
308
|
+
| 'acWrite'
|
|
309
|
+
| 'acFull'
|
|
310
|
+
)[]) {
|
|
311
|
+
const aVal = a[name];
|
|
312
|
+
const bVal = b[name];
|
|
313
|
+
if (aVal == null && bVal == null) {
|
|
314
|
+
continue;
|
|
315
|
+
} else if (aVal == null || bVal == null) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (aVal.length !== bVal.length) {
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
if (
|
|
322
|
+
xor(
|
|
323
|
+
aVal.map((entity) => entity.guid),
|
|
324
|
+
bVal.map((entity) => entity.guid)
|
|
325
|
+
).length
|
|
326
|
+
) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (areAcPropertiesChanged(originalAc, newAc)) {
|
|
334
|
+
if (
|
|
335
|
+
!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)
|
|
336
|
+
) {
|
|
337
|
+
// Only allow changes to AC properties if the user has full access.
|
|
338
|
+
throw new AccessControlError(
|
|
339
|
+
'No permission to change access control properties.'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (
|
|
344
|
+
((originalAc.user && !originalAc.user.$is(newAc.user)) ||
|
|
345
|
+
(!originalAc.user &&
|
|
346
|
+
newAc.user &&
|
|
347
|
+
!newAc.user.$is(tilmeld.currentUser))) &&
|
|
348
|
+
!tilmeld.gatekeeper('tilmeld/admin')
|
|
349
|
+
) {
|
|
350
|
+
throw new AccessControlError(
|
|
351
|
+
'No permission to assign to another user.'
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (
|
|
356
|
+
newAc.group &&
|
|
357
|
+
!newAc.group.$is(originalAc.group) &&
|
|
358
|
+
!(
|
|
359
|
+
newAc.group.$is(tilmeld.currentUser?.group) ||
|
|
360
|
+
newAc.group.$inArray(tilmeld.currentUser?.groups ?? [])
|
|
361
|
+
) &&
|
|
362
|
+
!tilmeld.gatekeeper('tilmeld/admin')
|
|
363
|
+
) {
|
|
364
|
+
throw new AccessControlError(
|
|
365
|
+
'No permission to assign to another group.'
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
271
369
|
setAcProperties(newAc);
|
|
272
370
|
}
|
|
273
371
|
}
|
|
274
372
|
|
|
275
373
|
// Test for permissions.
|
|
276
|
-
if (!tilmeld.checkPermissions(entity,
|
|
374
|
+
if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.WRITE_ACCESS)) {
|
|
277
375
|
throw new AccessControlError('No permission to write.');
|
|
278
376
|
}
|
|
279
377
|
};
|
|
280
|
-
checkPermissionsSaveAndFilterAcChanges.
|
|
378
|
+
checkPermissionsSaveAndFilterAcChanges.skipOnClone = true;
|
|
281
379
|
|
|
282
380
|
/*
|
|
283
381
|
* Add the current user's "user", "group", and access control to new entity.
|
|
@@ -291,11 +389,15 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
291
389
|
*
|
|
292
390
|
* Default access control is
|
|
293
391
|
*
|
|
294
|
-
* - acUser =
|
|
295
|
-
* - acGroup =
|
|
296
|
-
* - acOther =
|
|
392
|
+
* - acUser = TilmeldAccessLevels.FULL_ACCESS
|
|
393
|
+
* - acGroup = TilmeldAccessLevels.READ_ACCESS
|
|
394
|
+
* - acOther = TilmeldAccessLevels.NO_ACCESS
|
|
297
395
|
*/
|
|
298
|
-
const addAccess = function (
|
|
396
|
+
const addAccess = function (
|
|
397
|
+
nymph: Nymph,
|
|
398
|
+
entity: EntityInterface & AccessControlData
|
|
399
|
+
) {
|
|
400
|
+
const tilmeld = nymph.tilmeld as Tilmeld;
|
|
299
401
|
const user = tilmeld.currentUser;
|
|
300
402
|
if (
|
|
301
403
|
user != null &&
|
|
@@ -314,13 +416,13 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
314
416
|
entity.group = user.group;
|
|
315
417
|
}
|
|
316
418
|
if (entity.acUser == null) {
|
|
317
|
-
entity.acUser =
|
|
419
|
+
entity.acUser = TilmeldAccessLevels.FULL_ACCESS;
|
|
318
420
|
}
|
|
319
421
|
if (entity.acGroup == null) {
|
|
320
|
-
entity.acGroup =
|
|
422
|
+
entity.acGroup = TilmeldAccessLevels.READ_ACCESS;
|
|
321
423
|
}
|
|
322
424
|
if (entity.acOther == null) {
|
|
323
|
-
entity.acOther =
|
|
425
|
+
entity.acOther = TilmeldAccessLevels.NO_ACCESS;
|
|
324
426
|
}
|
|
325
427
|
if (entity.acRead == null) {
|
|
326
428
|
entity.acRead = [];
|
|
@@ -333,40 +435,53 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
333
435
|
}
|
|
334
436
|
}
|
|
335
437
|
};
|
|
336
|
-
addAccess.
|
|
438
|
+
addAccess.skipOnClone = true;
|
|
337
439
|
|
|
338
|
-
const validate = function (
|
|
440
|
+
const validate = function (
|
|
441
|
+
_nymph: Nymph,
|
|
442
|
+
entity: EntityInterface & AccessControlData
|
|
443
|
+
) {
|
|
339
444
|
if (!(entity instanceof User) && !(entity instanceof Group)) {
|
|
340
445
|
const ownershipAcPropertyValidator = (prop: any) => {
|
|
341
446
|
if (
|
|
342
447
|
typeof prop != 'number' ||
|
|
343
|
-
prop <
|
|
344
|
-
prop >
|
|
448
|
+
prop < TilmeldAccessLevels.NO_ACCESS ||
|
|
449
|
+
prop > TilmeldAccessLevels.FULL_ACCESS
|
|
345
450
|
) {
|
|
346
|
-
throw new AccessControlError(
|
|
451
|
+
throw new AccessControlError(
|
|
452
|
+
'Invalid access control property: ' + prop
|
|
453
|
+
);
|
|
347
454
|
}
|
|
348
455
|
};
|
|
349
456
|
|
|
350
457
|
const accessAcPropertyValidator = (prop: any) => {
|
|
351
458
|
if (!Array.isArray(prop)) {
|
|
352
|
-
throw new AccessControlError(
|
|
459
|
+
throw new AccessControlError(
|
|
460
|
+
'Invalid access control property: ' + prop
|
|
461
|
+
);
|
|
353
462
|
}
|
|
354
463
|
prop.forEach((value) => {
|
|
355
464
|
if (!(value instanceof User || value instanceof Group)) {
|
|
356
|
-
throw new AccessControlError(
|
|
465
|
+
throw new AccessControlError(
|
|
466
|
+
'Invalid access control property: ' + prop
|
|
467
|
+
);
|
|
357
468
|
}
|
|
358
469
|
});
|
|
359
470
|
};
|
|
360
471
|
|
|
361
472
|
if ('user' in entity) {
|
|
362
473
|
if (!(entity.user instanceof User)) {
|
|
363
|
-
throw new AccessControlError(
|
|
474
|
+
throw new AccessControlError(
|
|
475
|
+
'Invalid access control property: user'
|
|
476
|
+
);
|
|
364
477
|
}
|
|
365
478
|
}
|
|
366
479
|
|
|
367
480
|
if ('group' in entity) {
|
|
368
|
-
if (!(entity.
|
|
369
|
-
throw new AccessControlError(
|
|
481
|
+
if (!(entity.group instanceof Group)) {
|
|
482
|
+
throw new AccessControlError(
|
|
483
|
+
'Invalid access control property: group'
|
|
484
|
+
);
|
|
370
485
|
}
|
|
371
486
|
}
|
|
372
487
|
|
|
@@ -390,7 +505,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
390
505
|
}
|
|
391
506
|
}
|
|
392
507
|
};
|
|
393
|
-
validate.
|
|
508
|
+
validate.skipOnClone = true;
|
|
394
509
|
|
|
395
510
|
this.nymph.on('query', handleQuery);
|
|
396
511
|
|
|
@@ -433,16 +548,16 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
433
548
|
return;
|
|
434
549
|
}
|
|
435
550
|
|
|
436
|
-
if (user
|
|
551
|
+
if (user == null || user.guid == null) {
|
|
437
552
|
selectors.push({
|
|
438
553
|
type: '|',
|
|
439
554
|
// Other access control is sufficient.
|
|
440
|
-
gte: [['acOther',
|
|
555
|
+
gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
|
|
441
556
|
// The user and group are not set.
|
|
442
557
|
selector: [
|
|
443
558
|
{
|
|
444
559
|
type: '&',
|
|
445
|
-
'!defined': ['user', 'group'],
|
|
560
|
+
'!defined': [['user', 'group']],
|
|
446
561
|
},
|
|
447
562
|
],
|
|
448
563
|
});
|
|
@@ -450,63 +565,50 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
450
565
|
const subSelectors: FormattedSelector[] = [
|
|
451
566
|
{
|
|
452
567
|
type: '&',
|
|
453
|
-
'!defined': ['user', 'group'],
|
|
568
|
+
'!defined': [['user', 'group']],
|
|
454
569
|
},
|
|
455
570
|
// It is owned by the user.
|
|
456
571
|
{
|
|
457
572
|
type: '&',
|
|
458
|
-
ref: [['user', user]],
|
|
459
|
-
gte: [['acUser',
|
|
573
|
+
ref: [['user', user.guid]],
|
|
574
|
+
gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
|
|
460
575
|
},
|
|
461
576
|
];
|
|
462
|
-
const selector: FormattedSelector = {
|
|
463
|
-
type: '|',
|
|
464
|
-
// Other access control is sufficient.
|
|
465
|
-
gte: [['acOther', Tilmeld.READ_ACCESS]],
|
|
466
|
-
// The user and group are not set.
|
|
467
|
-
selector: subSelectors,
|
|
468
|
-
// The user is listed in acRead, acWrite, or acFull.
|
|
469
|
-
ref: [
|
|
470
|
-
['acRead', user],
|
|
471
|
-
['acWrite', user],
|
|
472
|
-
['acFull', user],
|
|
473
|
-
],
|
|
474
|
-
};
|
|
475
577
|
const groupRefs: FormattedSelector['ref'] = [];
|
|
476
578
|
const acRefs: FormattedSelector['ref'] = [];
|
|
477
579
|
if (user.group != null && user.group.guid != null) {
|
|
478
580
|
// It belongs to the user's primary group.
|
|
479
|
-
groupRefs.push(['group', user.group]);
|
|
581
|
+
groupRefs.push(['group', user.group.guid]);
|
|
480
582
|
// User's primary group is listed in acRead, acWrite, or acFull.
|
|
481
|
-
acRefs.push(['acRead', user.group]);
|
|
482
|
-
acRefs.push(['acWrite', user.group]);
|
|
483
|
-
acRefs.push(['acFull', user.group]);
|
|
583
|
+
acRefs.push(['acRead', user.group.guid]);
|
|
584
|
+
acRefs.push(['acWrite', user.group.guid]);
|
|
585
|
+
acRefs.push(['acFull', user.group.guid]);
|
|
484
586
|
}
|
|
485
587
|
for (let curSecondaryGroup of user.groups ?? []) {
|
|
486
588
|
if (curSecondaryGroup != null && curSecondaryGroup.guid != null) {
|
|
487
589
|
// It belongs to the user's secondary group.
|
|
488
|
-
groupRefs.push(['group', curSecondaryGroup]);
|
|
590
|
+
groupRefs.push(['group', curSecondaryGroup.guid]);
|
|
489
591
|
// User's secondary group is listed in acRead, acWrite, or acFull.
|
|
490
|
-
acRefs.push(['acRead', curSecondaryGroup]);
|
|
491
|
-
acRefs.push(['acWrite', curSecondaryGroup]);
|
|
492
|
-
acRefs.push(['acFull', curSecondaryGroup]);
|
|
592
|
+
acRefs.push(['acRead', curSecondaryGroup.guid]);
|
|
593
|
+
acRefs.push(['acWrite', curSecondaryGroup.guid]);
|
|
594
|
+
acRefs.push(['acFull', curSecondaryGroup.guid]);
|
|
493
595
|
}
|
|
494
596
|
}
|
|
495
597
|
for (let curDescendantGroup of user.$getDescendantGroupsSync()) {
|
|
496
598
|
if (curDescendantGroup != null && curDescendantGroup.guid != null) {
|
|
497
599
|
// It belongs to the user's secondary group.
|
|
498
|
-
groupRefs.push(['group', curDescendantGroup]);
|
|
600
|
+
groupRefs.push(['group', curDescendantGroup.guid]);
|
|
499
601
|
// User's secondary group is listed in acRead, acWrite, or acFull.
|
|
500
|
-
acRefs.push(['acRead', curDescendantGroup]);
|
|
501
|
-
acRefs.push(['acWrite', curDescendantGroup]);
|
|
502
|
-
acRefs.push(['acFull', curDescendantGroup]);
|
|
602
|
+
acRefs.push(['acRead', curDescendantGroup.guid]);
|
|
603
|
+
acRefs.push(['acWrite', curDescendantGroup.guid]);
|
|
604
|
+
acRefs.push(['acFull', curDescendantGroup.guid]);
|
|
503
605
|
}
|
|
504
606
|
}
|
|
505
607
|
// All the group refs.
|
|
506
608
|
if (groupRefs.length) {
|
|
507
609
|
subSelectors.push({
|
|
508
610
|
type: '&',
|
|
509
|
-
gte: [['acGroup',
|
|
611
|
+
gte: [['acGroup', TilmeldAccessLevels.READ_ACCESS]],
|
|
510
612
|
selector: [
|
|
511
613
|
{
|
|
512
614
|
type: '|',
|
|
@@ -522,6 +624,19 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
522
624
|
ref: acRefs,
|
|
523
625
|
});
|
|
524
626
|
}
|
|
627
|
+
const selector: FormattedSelector = {
|
|
628
|
+
type: '|',
|
|
629
|
+
// Other access control is sufficient.
|
|
630
|
+
gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
|
|
631
|
+
// The user and group are not set.
|
|
632
|
+
selector: subSelectors,
|
|
633
|
+
// The user is listed in acRead, acWrite, or acFull.
|
|
634
|
+
ref: [
|
|
635
|
+
['acRead', user.guid],
|
|
636
|
+
['acWrite', user.guid],
|
|
637
|
+
['acFull', user.guid],
|
|
638
|
+
],
|
|
639
|
+
};
|
|
525
640
|
selectors.push(selector);
|
|
526
641
|
}
|
|
527
642
|
}
|
|
@@ -555,9 +670,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
555
670
|
*
|
|
556
671
|
* These properties defaults to:
|
|
557
672
|
*
|
|
558
|
-
* - acUser =
|
|
559
|
-
* - acGroup =
|
|
560
|
-
* - acOther =
|
|
673
|
+
* - acUser = TilmeldAccessLevels.FULL_ACCESS
|
|
674
|
+
* - acGroup = TilmeldAccessLevels.READ_ACCESS
|
|
675
|
+
* - acOther = TilmeldAccessLevels.NO_ACCESS
|
|
561
676
|
*
|
|
562
677
|
* "acRead", "acWrite", and "acFull" are arrays of users and/or groups that
|
|
563
678
|
* also have those permissions.
|
|
@@ -586,13 +701,13 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
586
701
|
* - None of the above. (Check other AC.)
|
|
587
702
|
*
|
|
588
703
|
* @param entity The entity to check.
|
|
589
|
-
* @param type The lowest level of permission to consider a pass.
|
|
704
|
+
* @param type The lowest level of permission to consider a pass.
|
|
590
705
|
* @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
|
|
591
706
|
* @returns Whether the current user has at least `type` permission for the entity.
|
|
592
707
|
*/
|
|
593
708
|
public checkPermissions(
|
|
594
709
|
entity: EntityInterface & AccessControlData,
|
|
595
|
-
type =
|
|
710
|
+
type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
|
|
596
711
|
user?: (User & UserData) | false
|
|
597
712
|
) {
|
|
598
713
|
// Only works for entities.
|
|
@@ -618,7 +733,8 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
618
733
|
// Users and groups are always readable. Editable by Tilmeld admins.
|
|
619
734
|
if (
|
|
620
735
|
(entity instanceof User || entity instanceof Group) &&
|
|
621
|
-
(type ===
|
|
736
|
+
(type === TilmeldAccessLevels.READ_ACCESS ||
|
|
737
|
+
userOrEmpty.$gatekeeper('tilmeld/admin'))
|
|
622
738
|
) {
|
|
623
739
|
return true;
|
|
624
740
|
}
|
|
@@ -629,9 +745,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
629
745
|
}
|
|
630
746
|
|
|
631
747
|
// Load access control, since we need it now...
|
|
632
|
-
const acUser = entity.acUser ??
|
|
633
|
-
const acGroup = entity.acGroup ??
|
|
634
|
-
const acOther = entity.acOther ??
|
|
748
|
+
const acUser = entity.acUser ?? TilmeldAccessLevels.FULL_ACCESS;
|
|
749
|
+
const acGroup = entity.acGroup ?? TilmeldAccessLevels.READ_ACCESS;
|
|
750
|
+
const acOther = entity.acOther ?? TilmeldAccessLevels.NO_ACCESS;
|
|
635
751
|
|
|
636
752
|
if (userOrNull === null) {
|
|
637
753
|
return acOther >= type;
|
|
@@ -647,7 +763,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
647
763
|
userOrEmpty.group != null &&
|
|
648
764
|
typeof userOrEmpty.group.$is === 'function' &&
|
|
649
765
|
userOrEmpty.group.$is(entity) &&
|
|
650
|
-
type ===
|
|
766
|
+
type === TilmeldAccessLevels.READ_ACCESS
|
|
651
767
|
) {
|
|
652
768
|
return true;
|
|
653
769
|
}
|
|
@@ -659,9 +775,18 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
659
775
|
|
|
660
776
|
// Check access ac properties.
|
|
661
777
|
const checks = [
|
|
662
|
-
{
|
|
663
|
-
|
|
664
|
-
|
|
778
|
+
{
|
|
779
|
+
type: TilmeldAccessLevels.FULL_ACCESS,
|
|
780
|
+
array: [...(entity.acFull ?? [])],
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
type: TilmeldAccessLevels.WRITE_ACCESS,
|
|
784
|
+
array: [...(entity.acWrite ?? [])],
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
type: TilmeldAccessLevels.READ_ACCESS,
|
|
788
|
+
array: [...(entity.acRead ?? [])],
|
|
789
|
+
},
|
|
665
790
|
];
|
|
666
791
|
for (let curCheck of checks) {
|
|
667
792
|
if (type <= curCheck.type) {
|
|
@@ -697,6 +822,73 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
697
822
|
return acOther >= type;
|
|
698
823
|
}
|
|
699
824
|
|
|
825
|
+
/**
|
|
826
|
+
* Check a UID's permissions for a user.
|
|
827
|
+
*
|
|
828
|
+
* ## THIS ONLY CHECKS AUTOMATICALLY FOR CLIENT REQUESTS.
|
|
829
|
+
*
|
|
830
|
+
* UID functions on the Node.js side are not checked automatically. This
|
|
831
|
+
* function is only run automatically for UID functions run from the client.
|
|
832
|
+
* You should call this function manually if you're running a UID function on
|
|
833
|
+
* the Node.js side and you want it gated.
|
|
834
|
+
*
|
|
835
|
+
* This will check the Tilmeld config and the user's abilities.
|
|
836
|
+
*
|
|
837
|
+
* - READ_ACCESS, the UID is listed in clientReadableUIDs or the user has the
|
|
838
|
+
* "uid/get/nameofuid" ability.
|
|
839
|
+
* - WRITE_ACCESS, the UID is listed in clientEnabledUIDs or the user has the
|
|
840
|
+
* "uid/new/nameofuid" ability.
|
|
841
|
+
* - FULL_ACCESS, the UID is listed in clientSetabledUIDs or the user has the
|
|
842
|
+
* "uid/set/nameofuid" ability.
|
|
843
|
+
*
|
|
844
|
+
* @param name The UID to check.
|
|
845
|
+
* @param type The lowest level of permission to consider a pass.
|
|
846
|
+
* @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
|
|
847
|
+
* @returns Whether the current user has at least `type` permission for the UID.
|
|
848
|
+
*/
|
|
849
|
+
public checkClientUIDPermissions(
|
|
850
|
+
name: string,
|
|
851
|
+
type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
|
|
852
|
+
user?: (User & UserData) | false
|
|
853
|
+
) {
|
|
854
|
+
let userOrEmpty: User & UserData = this.User.factorySync();
|
|
855
|
+
// Calculate the user.
|
|
856
|
+
if (user == null) {
|
|
857
|
+
userOrEmpty = this.User.current(true);
|
|
858
|
+
} else if (user !== false) {
|
|
859
|
+
userOrEmpty = user;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (userOrEmpty.$gatekeeper('system/admin')) {
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (type === TilmeldAccessLevels.FULL_ACCESS) {
|
|
867
|
+
return (
|
|
868
|
+
this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
|
|
869
|
+
userOrEmpty.$gatekeeper(`uid/set/${name}`)
|
|
870
|
+
);
|
|
871
|
+
} else if (type === TilmeldAccessLevels.WRITE_ACCESS) {
|
|
872
|
+
return (
|
|
873
|
+
this.config.clientEnabledUIDs.indexOf(name) !== -1 ||
|
|
874
|
+
this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
|
|
875
|
+
userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
|
|
876
|
+
userOrEmpty.$gatekeeper(`uid/set/${name}`)
|
|
877
|
+
);
|
|
878
|
+
} else if (type === TilmeldAccessLevels.READ_ACCESS) {
|
|
879
|
+
return (
|
|
880
|
+
this.config.clientReadableUIDs.indexOf(name) !== -1 ||
|
|
881
|
+
this.config.clientEnabledUIDs.indexOf(name) !== -1 ||
|
|
882
|
+
this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
|
|
883
|
+
userOrEmpty.$gatekeeper(`uid/get/${name}`) ||
|
|
884
|
+
userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
|
|
885
|
+
userOrEmpty.$gatekeeper(`uid/set/${name}`)
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
|
|
700
892
|
/**
|
|
701
893
|
* Fill session user data.
|
|
702
894
|
*
|
|
@@ -779,9 +971,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
779
971
|
// to provide the auth token.
|
|
780
972
|
let fromAuthHeader = false;
|
|
781
973
|
let authToken: string;
|
|
782
|
-
if (this.request.header('
|
|
974
|
+
if (this.request.header('x-tilmeldauth') != null) {
|
|
783
975
|
fromAuthHeader = true;
|
|
784
|
-
authToken = this.request.header('
|
|
976
|
+
authToken = this.request.header('x-tilmeldauth') as string;
|
|
785
977
|
} else if ('TILMELDAUTH' in cookies) {
|
|
786
978
|
fromAuthHeader = false;
|
|
787
979
|
authToken = cookies.TILMELDAUTH;
|
|
@@ -800,7 +992,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
800
992
|
} else {
|
|
801
993
|
// The request is for something else, so check for a valid XSRF token,
|
|
802
994
|
// unless the auth token is provided by a header (instead of a cookie).
|
|
803
|
-
const xsrfToken = this.request.header('
|
|
995
|
+
const xsrfToken = this.request.header('x-xsrf-token');
|
|
804
996
|
if (xsrfToken == null && !fromAuthHeader) {
|
|
805
997
|
return false;
|
|
806
998
|
}
|