@nymphjs/tilmeld 1.0.0-beta.9 → 1.0.0-beta.90
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 +408 -0
- package/README.md +15 -2
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +18 -6
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +140 -15
- package/dist/Group.js +466 -191
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +83 -13
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +239 -12
- package/dist/Tilmeld.js +634 -254
- package/dist/Tilmeld.js.map +1 -1
- package/dist/Tilmeld.types.d.ts +2 -2
- package/dist/Tilmeld.types.js +1 -2
- package/dist/User.d.ts +365 -22
- package/dist/User.js +1265 -364
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +132 -13
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +287 -7
- package/dist/conf/d.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +188 -141
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +2 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +18 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -6
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -6
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -6
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -6
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js +1 -5
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js.map +1 -1
- package/dist/errors/EmailChangeRateLimitExceededError.js +1 -5
- package/dist/errors/EmailChangeRateLimitExceededError.js.map +1 -1
- package/dist/errors/index.d.ts +6 -6
- package/dist/errors/index.js +6 -22
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +16 -35
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.js +13 -18
- package/dist/tilmeldJoiProps.js.map +1 -1
- package/jest.config.js +11 -2
- package/package.json +34 -31
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +104 -6
- package/src/Group.ts +478 -226
- package/src/Tilmeld.ts +418 -254
- package/src/Tilmeld.types.ts +2 -2
- package/src/User.test.ts +163 -6
- package/src/User.ts +1428 -444
- package/src/conf/d.ts +63 -17
- package/src/conf/defaults.ts +162 -109
- package/src/conf/index.ts +2 -2
- package/src/enforceTilmeld.ts +20 -0
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/src/errors/index.ts +6 -6
- package/src/index.ts +12 -10
- package/src/tilmeldJoiProps.ts +11 -11
- package/src/type/locutus.d.ts +3 -0
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
package/src/Group.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import type {
|
|
3
|
+
Nymph,
|
|
2
4
|
EntityData,
|
|
3
|
-
EntityInterface,
|
|
4
5
|
EntityJson,
|
|
5
6
|
EntityPatch,
|
|
6
7
|
Options,
|
|
7
8
|
Selector,
|
|
8
9
|
SerializedEntityData,
|
|
9
10
|
} from '@nymphjs/nymph';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
11
|
+
import { difference, xor } from 'lodash-es';
|
|
12
|
+
import { splitn } from '@sciactive/splitn';
|
|
12
13
|
|
|
13
|
-
import
|
|
14
|
-
import AbleObject from './AbleObject';
|
|
14
|
+
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
15
|
+
import AbleObject from './AbleObject.js';
|
|
15
16
|
import {
|
|
17
|
+
AccessControlError,
|
|
16
18
|
BadDataError,
|
|
17
19
|
BadEmailError,
|
|
18
20
|
BadUsernameError,
|
|
19
21
|
CouldNotChangeDefaultPrimaryGroupError,
|
|
20
|
-
} from './errors';
|
|
21
|
-
import
|
|
22
|
+
} from './errors/index.js';
|
|
23
|
+
import UserClass from './User.js';
|
|
24
|
+
import type { UserData } from './User.js';
|
|
22
25
|
|
|
23
26
|
export type GroupData = {
|
|
24
27
|
/**
|
|
@@ -52,7 +55,7 @@ export type GroupData = {
|
|
|
52
55
|
/**
|
|
53
56
|
* If generatePrimary is on, this will be the user who generated this group.
|
|
54
57
|
*/
|
|
55
|
-
user?: (
|
|
58
|
+
user?: (UserClass & UserData) | null;
|
|
56
59
|
|
|
57
60
|
/**
|
|
58
61
|
* Whether the group can be used.
|
|
@@ -85,7 +88,6 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
85
88
|
/**
|
|
86
89
|
* The instance of Tilmeld to use for queries.
|
|
87
90
|
*/
|
|
88
|
-
static tilmeld: Tilmeld;
|
|
89
91
|
static ETYPE = 'tilmeld_group';
|
|
90
92
|
static class = 'Group';
|
|
91
93
|
|
|
@@ -112,16 +114,20 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
112
114
|
protected $allowlistTags?: string[] = [];
|
|
113
115
|
|
|
114
116
|
/**
|
|
115
|
-
* This
|
|
117
|
+
* This should only be used by the backend.
|
|
116
118
|
*/
|
|
117
119
|
private $skipAcWhenSaving = false;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
/**
|
|
121
|
+
* This should only be used by the backend.
|
|
122
|
+
*/
|
|
123
|
+
private $skipAcWhenDeleting = false;
|
|
124
|
+
/**
|
|
125
|
+
* Used to save the current groupname for domain admin permissions.
|
|
126
|
+
*/
|
|
127
|
+
public $originalGroupname?: string;
|
|
122
128
|
|
|
123
129
|
static async factoryGroupname(
|
|
124
|
-
groupname?: string
|
|
130
|
+
groupname?: string,
|
|
125
131
|
): Promise<Group & GroupData> {
|
|
126
132
|
const entity = new this();
|
|
127
133
|
if (groupname != null) {
|
|
@@ -132,7 +138,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
132
138
|
{
|
|
133
139
|
type: '&',
|
|
134
140
|
ilike: ['groupname', groupname.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
135
|
-
}
|
|
141
|
+
},
|
|
136
142
|
);
|
|
137
143
|
if (entity != null) {
|
|
138
144
|
return entity;
|
|
@@ -141,21 +147,6 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
141
147
|
return entity;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
static factorySync(guid?: string): Group & GroupData {
|
|
145
|
-
return super.factorySync(guid) as Group & GroupData;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
constructor(guid?: string) {
|
|
149
|
-
super(guid);
|
|
150
|
-
|
|
151
|
-
if (this.guid == null) {
|
|
152
|
-
// Defaults.
|
|
153
|
-
this.$data.enabled = true;
|
|
154
|
-
this.$data.abilities = [];
|
|
155
|
-
this.$updateDataProtection();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
150
|
/**
|
|
160
151
|
* Get all the groups that can be assigned as primary groups.
|
|
161
152
|
*
|
|
@@ -165,16 +156,17 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
165
156
|
*/
|
|
166
157
|
public static async getPrimaryGroups(
|
|
167
158
|
options?: Options,
|
|
168
|
-
selectors?: Selector[]
|
|
159
|
+
selectors?: Selector[],
|
|
169
160
|
) {
|
|
170
|
-
|
|
161
|
+
const tilmeld = enforceTilmeld(this);
|
|
162
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
171
163
|
throw new Error("You don't have permission to do that.");
|
|
172
164
|
}
|
|
173
165
|
|
|
174
166
|
return await this.getAssignableGroups(
|
|
175
|
-
|
|
176
|
-
{ ...options, class:
|
|
177
|
-
[...(selectors ?? [])]
|
|
167
|
+
tilmeld.config.highestPrimary,
|
|
168
|
+
{ ...options, class: tilmeld.Group, return: 'entity' },
|
|
169
|
+
[...(selectors ?? [])],
|
|
178
170
|
);
|
|
179
171
|
}
|
|
180
172
|
|
|
@@ -187,24 +179,26 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
187
179
|
*/
|
|
188
180
|
public static async getSecondaryGroups(
|
|
189
181
|
options?: Options,
|
|
190
|
-
selectors?: Selector[]
|
|
182
|
+
selectors?: Selector[],
|
|
191
183
|
) {
|
|
192
|
-
|
|
184
|
+
const tilmeld = enforceTilmeld(this);
|
|
185
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
193
186
|
throw new Error("You don't have permission to do that.");
|
|
194
187
|
}
|
|
195
188
|
|
|
196
189
|
return await this.getAssignableGroups(
|
|
197
|
-
|
|
198
|
-
{ ...options, class:
|
|
199
|
-
[...(selectors ?? [])]
|
|
190
|
+
tilmeld.config.highestSecondary,
|
|
191
|
+
{ ...options, class: tilmeld.Group, return: 'entity' },
|
|
192
|
+
[...(selectors ?? [])],
|
|
200
193
|
);
|
|
201
194
|
}
|
|
202
195
|
|
|
203
196
|
private static async getAssignableGroups(
|
|
204
197
|
highestParent: string | boolean,
|
|
205
198
|
options: Options<typeof Group>,
|
|
206
|
-
selectors: Selector[]
|
|
199
|
+
selectors: Selector[],
|
|
207
200
|
) {
|
|
201
|
+
const tilmeld = enforceTilmeld(this);
|
|
208
202
|
let assignableGroups: (Group & GroupData)[] = [];
|
|
209
203
|
|
|
210
204
|
if (highestParent === false) {
|
|
@@ -212,24 +206,68 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
212
206
|
}
|
|
213
207
|
|
|
214
208
|
assignableGroups = await this.nymph.getEntities(
|
|
215
|
-
{ ...options, class:
|
|
216
|
-
...selectors
|
|
209
|
+
{ ...options, class: tilmeld.Group },
|
|
210
|
+
...selectors,
|
|
217
211
|
);
|
|
218
212
|
if (highestParent !== true) {
|
|
219
|
-
assignableGroups =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
213
|
+
assignableGroups = (
|
|
214
|
+
await Promise.all(
|
|
215
|
+
assignableGroups.map(async (group) => {
|
|
216
|
+
let curGroup = group;
|
|
217
|
+
let parent = curGroup.parent;
|
|
218
|
+
await parent?.$wake();
|
|
219
|
+
while (parent != null && parent?.cdate != null) {
|
|
220
|
+
if (parent?.guid === highestParent) {
|
|
221
|
+
return group;
|
|
222
|
+
}
|
|
223
|
+
curGroup = parent;
|
|
224
|
+
parent = curGroup.parent;
|
|
225
|
+
await parent?.$wake();
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
).filter((group) => group != null) as (Group & GroupData)[];
|
|
229
231
|
}
|
|
230
232
|
return assignableGroups;
|
|
231
233
|
}
|
|
232
234
|
|
|
235
|
+
constructor() {
|
|
236
|
+
super();
|
|
237
|
+
|
|
238
|
+
// Defaults.
|
|
239
|
+
this.$data.enabled = true;
|
|
240
|
+
this.$data.abilities = [];
|
|
241
|
+
this.$updateDataProtection();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
$setNymph(nymph: Nymph) {
|
|
245
|
+
this.$nymph = nymph;
|
|
246
|
+
if (!this.$asleep()) {
|
|
247
|
+
if (this.$data.user && this.$data.user.$nymph !== nymph) {
|
|
248
|
+
this.$data.user.$setNymph(nymph);
|
|
249
|
+
}
|
|
250
|
+
if (this.$data.parent && this.$data.parent.$nymph !== nymph) {
|
|
251
|
+
this.$data.parent.$setNymph(nymph);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async $getUniques(): Promise<string[]> {
|
|
257
|
+
const tilmeld = enforceTilmeld(this);
|
|
258
|
+
const uniques = [`u:${this.$data.groupname}`];
|
|
259
|
+
if (
|
|
260
|
+
!tilmeld.config.emailUsernames &&
|
|
261
|
+
tilmeld.config.userFields.includes('email')
|
|
262
|
+
) {
|
|
263
|
+
uniques.push(`e:${this.$data.email}`);
|
|
264
|
+
}
|
|
265
|
+
if (this.$data.defaultPrimary) {
|
|
266
|
+
uniques.push(`o:default-primary`);
|
|
267
|
+
}
|
|
268
|
+
return uniques;
|
|
269
|
+
}
|
|
270
|
+
|
|
233
271
|
public $getAvatar() {
|
|
234
272
|
if (this.$data.avatar != null && this.$data.avatar !== '') {
|
|
235
273
|
return this.$data.avatar;
|
|
@@ -239,49 +277,104 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
239
277
|
}
|
|
240
278
|
return (
|
|
241
279
|
'https://secure.gravatar.com/avatar/' +
|
|
242
|
-
|
|
280
|
+
crypto
|
|
281
|
+
.createHash('sha256')
|
|
282
|
+
.update(this.$data.email.trim().toLowerCase())
|
|
283
|
+
.digest('hex')
|
|
284
|
+
.toLowerCase() +
|
|
243
285
|
'?d=identicon&s=40'
|
|
244
286
|
);
|
|
245
287
|
}
|
|
246
288
|
|
|
247
289
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
248
|
-
|
|
249
|
-
this.$referenceWake();
|
|
290
|
+
this.$check();
|
|
250
291
|
|
|
251
|
-
if (
|
|
252
|
-
input.data.abilities
|
|
253
|
-
this.$data.abilities?.indexOf('system/admin') === -1 &&
|
|
254
|
-
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
255
|
-
!tilmeld.gatekeeper('system/admin')
|
|
256
|
-
) {
|
|
257
|
-
throw new BadDataError(
|
|
258
|
-
"You don't have the authority to make this group a system admin."
|
|
259
|
-
);
|
|
292
|
+
if ('abilities' in input.data) {
|
|
293
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
260
294
|
}
|
|
261
295
|
|
|
262
296
|
super.$jsonAcceptData(input, allowConflict);
|
|
263
297
|
}
|
|
264
298
|
|
|
265
299
|
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
this.$check();
|
|
301
|
+
|
|
302
|
+
if ('abilities' in patch.set) {
|
|
303
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
super.$jsonAcceptPatch(patch, allowConflict);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private $checkIncomingAbilities(incoming: any) {
|
|
310
|
+
const tilmeld = enforceTilmeld(this);
|
|
268
311
|
|
|
269
312
|
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
313
|
+
!Array.isArray(incoming) ||
|
|
314
|
+
incoming.find((ability) => typeof ability !== 'string')
|
|
315
|
+
) {
|
|
316
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
incoming.includes('system/admin') !==
|
|
321
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
322
|
+
(!tilmeld.currentUser ||
|
|
323
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
274
324
|
) {
|
|
275
325
|
throw new BadDataError(
|
|
276
|
-
"You don't have the authority to
|
|
326
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
277
327
|
);
|
|
278
328
|
}
|
|
279
329
|
|
|
280
|
-
|
|
330
|
+
if (
|
|
331
|
+
incoming.includes('tilmeld/admin') !==
|
|
332
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
333
|
+
(!tilmeld.currentUser ||
|
|
334
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
335
|
+
) {
|
|
336
|
+
throw new BadDataError(
|
|
337
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (
|
|
342
|
+
incoming.includes('tilmeld/switch') !==
|
|
343
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
344
|
+
(!tilmeld.currentUser ||
|
|
345
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
346
|
+
) {
|
|
347
|
+
throw new BadDataError(
|
|
348
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (
|
|
353
|
+
xor(
|
|
354
|
+
incoming.filter((ability: string) => ability.startsWith('tilmeld/')),
|
|
355
|
+
this.$data.abilities?.filter((ability: string) =>
|
|
356
|
+
ability.startsWith('tilmeld/'),
|
|
357
|
+
) ?? [],
|
|
358
|
+
).length &&
|
|
359
|
+
(!tilmeld.currentUser ||
|
|
360
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
361
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))
|
|
362
|
+
) {
|
|
363
|
+
throw new BadDataError(
|
|
364
|
+
"You don't have the authority to grant or revoke Tilmeld abilities.",
|
|
365
|
+
);
|
|
366
|
+
}
|
|
281
367
|
}
|
|
282
368
|
|
|
283
|
-
public $putData(
|
|
284
|
-
|
|
369
|
+
public $putData(
|
|
370
|
+
data: EntityData,
|
|
371
|
+
sdata?: SerializedEntityData,
|
|
372
|
+
source?: 'server',
|
|
373
|
+
) {
|
|
374
|
+
super.$putData(data, sdata, source);
|
|
375
|
+
if (source === 'server') {
|
|
376
|
+
this.$originalGroupname = this.$data.groupname;
|
|
377
|
+
}
|
|
285
378
|
this.$updateDataProtection();
|
|
286
379
|
}
|
|
287
380
|
|
|
@@ -290,9 +383,16 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
290
383
|
*
|
|
291
384
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
292
385
|
*/
|
|
293
|
-
public $updateDataProtection(givenUser?:
|
|
294
|
-
const tilmeld = this
|
|
386
|
+
public $updateDataProtection(givenUser?: UserClass & UserData) {
|
|
387
|
+
const tilmeld = enforceTilmeld(this);
|
|
295
388
|
let user = givenUser ?? tilmeld.User.current();
|
|
389
|
+
let [_groupname, domain] = tilmeld.config.domainSupport
|
|
390
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
391
|
+
: [this.$data.groupname, null];
|
|
392
|
+
// Lowercase the domain part.
|
|
393
|
+
if (domain) {
|
|
394
|
+
domain = domain.toLowerCase();
|
|
395
|
+
}
|
|
296
396
|
|
|
297
397
|
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
298
398
|
this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
@@ -303,8 +403,8 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
303
403
|
|
|
304
404
|
if (
|
|
305
405
|
user != null &&
|
|
306
|
-
(
|
|
307
|
-
|
|
406
|
+
(user.abilities?.includes('tilmeld/admin') ||
|
|
407
|
+
user.abilities?.includes('system/admin'))
|
|
308
408
|
) {
|
|
309
409
|
// Users who can edit groups can see their data.
|
|
310
410
|
this.$privateData = [];
|
|
@@ -312,10 +412,18 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
312
412
|
} else if (
|
|
313
413
|
this.$data.user != null &&
|
|
314
414
|
user != null &&
|
|
315
|
-
this.$data.user
|
|
415
|
+
this.$data.user.guid === user.guid
|
|
316
416
|
) {
|
|
317
417
|
// Users can see their group's data.
|
|
318
418
|
this.$privateData = [];
|
|
419
|
+
} else if (
|
|
420
|
+
user != null &&
|
|
421
|
+
tilmeld.config.domainSupport &&
|
|
422
|
+
domain &&
|
|
423
|
+
user.abilities?.includes(`tilmeld/domain/${domain}/admin`)
|
|
424
|
+
) {
|
|
425
|
+
// Domain admins can see their groups' data.
|
|
426
|
+
this.$privateData = [];
|
|
319
427
|
}
|
|
320
428
|
}
|
|
321
429
|
|
|
@@ -325,11 +433,13 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
325
433
|
* @param group The group, or the group's GUID.
|
|
326
434
|
* @returns True or false.
|
|
327
435
|
*/
|
|
328
|
-
public $isDescendant(
|
|
329
|
-
|
|
436
|
+
public async $isDescendant(
|
|
437
|
+
givenGroup: (Group & GroupData) | string,
|
|
438
|
+
): Promise<boolean> {
|
|
439
|
+
const tilmeld = enforceTilmeld(this);
|
|
330
440
|
let group: Group & GroupData;
|
|
331
441
|
if (typeof givenGroup === 'string') {
|
|
332
|
-
group = tilmeld.Group.
|
|
442
|
+
group = await tilmeld.Group.factory(givenGroup);
|
|
333
443
|
} else {
|
|
334
444
|
group = givenGroup;
|
|
335
445
|
}
|
|
@@ -337,13 +447,15 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
337
447
|
return false;
|
|
338
448
|
}
|
|
339
449
|
// Check to see if the group is a descendant of the given group.
|
|
340
|
-
|
|
450
|
+
const parent = this.$data.parent;
|
|
451
|
+
await parent?.$wake();
|
|
452
|
+
if (parent == null) {
|
|
341
453
|
return false;
|
|
342
454
|
}
|
|
343
|
-
if (
|
|
455
|
+
if (parent.$is(group)) {
|
|
344
456
|
return true;
|
|
345
457
|
}
|
|
346
|
-
if (
|
|
458
|
+
if (await parent.$isDescendant(group)) {
|
|
347
459
|
return true;
|
|
348
460
|
}
|
|
349
461
|
return false;
|
|
@@ -355,14 +467,14 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
355
467
|
* @returns An array of groups.
|
|
356
468
|
*/
|
|
357
469
|
public async $getChildren() {
|
|
358
|
-
const tilmeld = this
|
|
470
|
+
const tilmeld = enforceTilmeld(this);
|
|
359
471
|
return await this.$nymph.getEntities(
|
|
360
472
|
{ class: tilmeld.Group },
|
|
361
473
|
{
|
|
362
474
|
type: '&',
|
|
363
475
|
equal: ['enabled', true],
|
|
364
476
|
ref: ['parent', this],
|
|
365
|
-
}
|
|
477
|
+
},
|
|
366
478
|
);
|
|
367
479
|
}
|
|
368
480
|
|
|
@@ -373,9 +485,9 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
373
485
|
* @returns An array of groups.
|
|
374
486
|
*/
|
|
375
487
|
public async $getDescendants(
|
|
376
|
-
andSelf = false
|
|
488
|
+
andSelf = false,
|
|
377
489
|
): Promise<(Group & GroupData)[]> {
|
|
378
|
-
const tilmeld = this
|
|
490
|
+
const tilmeld = enforceTilmeld(this);
|
|
379
491
|
let groups: (Group & GroupData)[] = [];
|
|
380
492
|
const entities = await this.$nymph.getEntities(
|
|
381
493
|
{ class: tilmeld.Group },
|
|
@@ -383,7 +495,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
383
495
|
type: '&',
|
|
384
496
|
equal: ['enabled', true],
|
|
385
497
|
ref: ['parent', this],
|
|
386
|
-
}
|
|
498
|
+
},
|
|
387
499
|
);
|
|
388
500
|
for (let entity of entities) {
|
|
389
501
|
groups = (await entity.$getDescendants(true)).concat(groups);
|
|
@@ -394,37 +506,6 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
394
506
|
return groups;
|
|
395
507
|
}
|
|
396
508
|
|
|
397
|
-
/**
|
|
398
|
-
* Gets an array of the group's descendant groups.
|
|
399
|
-
*
|
|
400
|
-
* @param andSelf Include this group in the returned array.
|
|
401
|
-
* @returns An array of groups.
|
|
402
|
-
*/
|
|
403
|
-
public $getDescendantsSync(andSelf = false): (Group & GroupData)[] {
|
|
404
|
-
const tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
405
|
-
let groups: (Group & GroupData)[] = [];
|
|
406
|
-
let entity: EntityInterface | null;
|
|
407
|
-
let offset = 0;
|
|
408
|
-
do {
|
|
409
|
-
entity = this.$nymph.driver.getEntitySync(
|
|
410
|
-
{ class: tilmeld.Group, offset },
|
|
411
|
-
{
|
|
412
|
-
type: '&',
|
|
413
|
-
equal: ['enabled', true],
|
|
414
|
-
ref: ['parent', this],
|
|
415
|
-
}
|
|
416
|
-
);
|
|
417
|
-
if (entity != null) {
|
|
418
|
-
groups = entity.$getDescendantsSync(true).concat(groups);
|
|
419
|
-
}
|
|
420
|
-
offset++;
|
|
421
|
-
} while (entity != null);
|
|
422
|
-
if (andSelf) {
|
|
423
|
-
groups.push(this);
|
|
424
|
-
}
|
|
425
|
-
return groups;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
509
|
/**
|
|
429
510
|
* Get the number of parents the group has.
|
|
430
511
|
*
|
|
@@ -436,13 +517,17 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
436
517
|
*
|
|
437
518
|
* @returns The level of the group.
|
|
438
519
|
*/
|
|
439
|
-
public $getLevel() {
|
|
440
|
-
const tilmeld = this
|
|
441
|
-
let group = tilmeld.Group.
|
|
520
|
+
public async $getLevel() {
|
|
521
|
+
const tilmeld = enforceTilmeld(this);
|
|
522
|
+
let group = await tilmeld.Group.factory(this.guid ?? undefined);
|
|
523
|
+
let parent = group.parent;
|
|
524
|
+
await parent?.$wake();
|
|
442
525
|
let level = 0;
|
|
443
|
-
while (
|
|
526
|
+
while (parent != null && parent.cdate != null && level < 1024) {
|
|
444
527
|
level++;
|
|
445
|
-
group =
|
|
528
|
+
group = parent;
|
|
529
|
+
parent = group.parent;
|
|
530
|
+
await parent?.$wake();
|
|
446
531
|
}
|
|
447
532
|
return level;
|
|
448
533
|
}
|
|
@@ -458,9 +543,9 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
458
543
|
public async $getUsers(
|
|
459
544
|
descendants = false,
|
|
460
545
|
limit?: number,
|
|
461
|
-
offset?: number
|
|
462
|
-
): Promise<(
|
|
463
|
-
const tilmeld = this
|
|
546
|
+
offset?: number,
|
|
547
|
+
): Promise<(UserClass & UserData)[]> {
|
|
548
|
+
const tilmeld = enforceTilmeld(this);
|
|
464
549
|
let groups: (Group & GroupData)[] = [];
|
|
465
550
|
if (descendants) {
|
|
466
551
|
groups = await this.$getDescendants();
|
|
@@ -489,7 +574,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
489
574
|
group,
|
|
490
575
|
]),
|
|
491
576
|
],
|
|
492
|
-
}
|
|
577
|
+
},
|
|
493
578
|
);
|
|
494
579
|
}
|
|
495
580
|
|
|
@@ -502,15 +587,36 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
502
587
|
result: boolean;
|
|
503
588
|
message: string;
|
|
504
589
|
}> {
|
|
505
|
-
const tilmeld = this
|
|
590
|
+
const tilmeld = enforceTilmeld(this);
|
|
506
591
|
if (!tilmeld.config.emailUsernames) {
|
|
507
|
-
|
|
508
|
-
|
|
592
|
+
let [groupname, domain] = tilmeld.config.domainSupport
|
|
593
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
594
|
+
: [this.$data.groupname, null];
|
|
595
|
+
|
|
596
|
+
// Lowercase the domain part.
|
|
597
|
+
if (domain) {
|
|
598
|
+
domain = domain.toLowerCase();
|
|
599
|
+
this.$data.groupname = `${groupname}@${domain}`;
|
|
509
600
|
}
|
|
601
|
+
|
|
510
602
|
if (
|
|
511
|
-
|
|
512
|
-
this.$data.groupname.length
|
|
603
|
+
this.$data.groupname == null ||
|
|
604
|
+
!this.$data.groupname.length ||
|
|
605
|
+
groupname == null ||
|
|
606
|
+
!groupname.length
|
|
513
607
|
) {
|
|
608
|
+
return { result: false, message: 'Please specify a groupname.' };
|
|
609
|
+
}
|
|
610
|
+
if (groupname.length < tilmeld.config.minUsernameLength) {
|
|
611
|
+
return {
|
|
612
|
+
result: false,
|
|
613
|
+
message:
|
|
614
|
+
'Groupnames must be at least ' +
|
|
615
|
+
tilmeld.config.minUsernameLength +
|
|
616
|
+
' characters.',
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (groupname.length > tilmeld.config.maxUsernameLength) {
|
|
514
620
|
return {
|
|
515
621
|
result: false,
|
|
516
622
|
message:
|
|
@@ -520,22 +626,38 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
520
626
|
};
|
|
521
627
|
}
|
|
522
628
|
if (
|
|
523
|
-
difference(
|
|
524
|
-
|
|
525
|
-
tilmeld.config.validChars.split('')
|
|
526
|
-
).length
|
|
629
|
+
difference(groupname.split(''), tilmeld.config.validChars.split(''))
|
|
630
|
+
.length
|
|
527
631
|
) {
|
|
528
632
|
return {
|
|
529
633
|
result: false,
|
|
530
634
|
message: tilmeld.config.validCharsNotice,
|
|
531
635
|
};
|
|
532
636
|
}
|
|
533
|
-
if (!tilmeld.config.validRegex.test(
|
|
637
|
+
if (!tilmeld.config.validRegex.test(groupname)) {
|
|
534
638
|
return {
|
|
535
639
|
result: false,
|
|
536
640
|
message: tilmeld.config.validRegexNotice,
|
|
537
641
|
};
|
|
538
642
|
}
|
|
643
|
+
if (domain != null) {
|
|
644
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
645
|
+
return {
|
|
646
|
+
result: false,
|
|
647
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (
|
|
651
|
+
!this.$is(tilmeld.currentUser?.group) &&
|
|
652
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)
|
|
653
|
+
) {
|
|
654
|
+
return {
|
|
655
|
+
result: false,
|
|
656
|
+
message:
|
|
657
|
+
"You don't have the authority to manage groups on this domain.",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
539
661
|
|
|
540
662
|
const selector: Selector = {
|
|
541
663
|
type: '&',
|
|
@@ -549,7 +671,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
549
671
|
}
|
|
550
672
|
const test = await this.$nymph.getEntity(
|
|
551
673
|
{ class: tilmeld.Group, skipAc: true },
|
|
552
|
-
selector
|
|
674
|
+
selector,
|
|
553
675
|
);
|
|
554
676
|
if (test != null) {
|
|
555
677
|
return { result: false, message: 'That groupname is taken.' };
|
|
@@ -565,10 +687,16 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
565
687
|
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
566
688
|
return { result: false, message: 'Please specify an email.' };
|
|
567
689
|
}
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
690
|
+
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
691
|
+
return {
|
|
692
|
+
result: false,
|
|
693
|
+
message:
|
|
694
|
+
'Emails must be at least ' +
|
|
695
|
+
tilmeld.config.minUsernameLength +
|
|
696
|
+
' characters.',
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
572
700
|
return {
|
|
573
701
|
result: false,
|
|
574
702
|
message:
|
|
@@ -588,7 +716,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
588
716
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
589
717
|
*/
|
|
590
718
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
591
|
-
const tilmeld = this
|
|
719
|
+
const tilmeld = enforceTilmeld(this);
|
|
592
720
|
if (this.$data.email === '') {
|
|
593
721
|
return { result: true, message: '' };
|
|
594
722
|
}
|
|
@@ -610,7 +738,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
610
738
|
}
|
|
611
739
|
const test = await this.$nymph.getEntity(
|
|
612
740
|
{ class: tilmeld.Group, skipAc: true },
|
|
613
|
-
selector
|
|
741
|
+
selector,
|
|
614
742
|
);
|
|
615
743
|
if (test != null) {
|
|
616
744
|
return {
|
|
@@ -627,11 +755,64 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
627
755
|
}
|
|
628
756
|
|
|
629
757
|
public async $save() {
|
|
630
|
-
const tilmeld = this
|
|
758
|
+
const tilmeld = enforceTilmeld(this);
|
|
631
759
|
if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
|
|
632
760
|
return false;
|
|
633
761
|
}
|
|
634
762
|
|
|
763
|
+
// Lowercase the domain part.
|
|
764
|
+
if (tilmeld.config.domainSupport) {
|
|
765
|
+
const [groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
766
|
+
if (domain) {
|
|
767
|
+
this.$data.groupname = `${groupname}@${domain.toLowerCase()}`;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (tilmeld.config.domainSupport) {
|
|
772
|
+
const [_groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
773
|
+
const [_originalGroupname, originalDomain] = splitn(
|
|
774
|
+
this.$originalGroupname ?? '',
|
|
775
|
+
'@',
|
|
776
|
+
2,
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
if (
|
|
780
|
+
this.guid != null &&
|
|
781
|
+
((originalDomain == null && domain != null) ||
|
|
782
|
+
(domain == null && originalDomain != null))
|
|
783
|
+
) {
|
|
784
|
+
throw new AccessControlError(
|
|
785
|
+
"Groups can't switch to or from having a domain once created.",
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (
|
|
790
|
+
!this.$skipAcWhenSaving &&
|
|
791
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
792
|
+
!tilmeld.gatekeeper('system/admin')
|
|
793
|
+
) {
|
|
794
|
+
if (
|
|
795
|
+
domain != null &&
|
|
796
|
+
!this.$is(tilmeld.currentUser) &&
|
|
797
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)
|
|
798
|
+
) {
|
|
799
|
+
throw new AccessControlError(
|
|
800
|
+
"You don't have the authority to modify groups on this domain.",
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (
|
|
804
|
+
originalDomain != null &&
|
|
805
|
+
originalDomain !== domain &&
|
|
806
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
807
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))
|
|
808
|
+
) {
|
|
809
|
+
throw new AccessControlError(
|
|
810
|
+
"You don't have the authority to modify groups on this domain.",
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
635
816
|
// Formatting.
|
|
636
817
|
this.$data.groupname = this.$data.groupname.trim();
|
|
637
818
|
if (tilmeld.config.emailUsernames) {
|
|
@@ -643,16 +824,22 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
643
824
|
this.$data.enabled = !!this.$data.enabled;
|
|
644
825
|
|
|
645
826
|
// Clear empty values.
|
|
646
|
-
if (this.$data.name === '') {
|
|
827
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
647
828
|
delete this.$data.name;
|
|
648
829
|
}
|
|
649
|
-
if (
|
|
830
|
+
if (
|
|
831
|
+
this.$data.email === '' ||
|
|
832
|
+
!tilmeld.config.userFields.includes('email')
|
|
833
|
+
) {
|
|
650
834
|
delete this.$data.email;
|
|
651
835
|
}
|
|
652
836
|
if (this.$data.avatar === '') {
|
|
653
837
|
delete this.$data.avatar;
|
|
654
838
|
}
|
|
655
|
-
if (
|
|
839
|
+
if (
|
|
840
|
+
this.$data.phone === '' ||
|
|
841
|
+
!tilmeld.config.userFields.includes('phone')
|
|
842
|
+
) {
|
|
656
843
|
delete this.$data.phone;
|
|
657
844
|
}
|
|
658
845
|
if (this.$data.parent == null) {
|
|
@@ -671,20 +858,33 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
671
858
|
delete this.$data.unverifiedSecondary;
|
|
672
859
|
}
|
|
673
860
|
|
|
674
|
-
// Groups should never have 'system/admin'
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
861
|
+
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
862
|
+
// 'tilmeld/switch', or domain admin abilities.
|
|
863
|
+
this.$data.abilities = (this.$data.abilities ?? []).filter((ability) => {
|
|
864
|
+
if (
|
|
865
|
+
['system/admin', 'tilmeld/admin', 'tilmeld/switch'].indexOf(ability) !==
|
|
866
|
+
-1
|
|
867
|
+
) {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
if (ability == null) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
if (ability.startsWith('tilmeld/domain/') && ability.endsWith('/admin')) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
return true;
|
|
877
|
+
}) as string[];
|
|
681
878
|
|
|
682
879
|
// Verification.
|
|
683
880
|
const unCheck = await this.$checkGroupname();
|
|
684
881
|
if (!unCheck.result) {
|
|
685
882
|
throw new BadUsernameError(unCheck.message);
|
|
686
883
|
}
|
|
687
|
-
if (
|
|
884
|
+
if (
|
|
885
|
+
!tilmeld.config.emailUsernames &&
|
|
886
|
+
tilmeld.config.userFields.includes('email')
|
|
887
|
+
) {
|
|
688
888
|
const emCheck = await this.$checkEmail();
|
|
689
889
|
if (!emCheck.result) {
|
|
690
890
|
throw new BadEmailError(emCheck.message);
|
|
@@ -692,19 +892,21 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
692
892
|
}
|
|
693
893
|
|
|
694
894
|
// Validate group parent. Make sure it's not a descendant of this group.
|
|
895
|
+
const parent = this.$data.parent;
|
|
896
|
+
await parent?.$wake();
|
|
695
897
|
if (
|
|
696
|
-
|
|
697
|
-
(
|
|
698
|
-
this.$is(
|
|
699
|
-
|
|
898
|
+
parent &&
|
|
899
|
+
(parent.cdate == null ||
|
|
900
|
+
this.$is(parent) ||
|
|
901
|
+
(await parent.$isDescendant(this)))
|
|
700
902
|
) {
|
|
701
903
|
throw new BadDataError(
|
|
702
|
-
"Group parent can't be itself or descendant of itself."
|
|
904
|
+
"Group parent can't be itself or descendant of itself.",
|
|
703
905
|
);
|
|
704
906
|
}
|
|
705
907
|
|
|
706
908
|
try {
|
|
707
|
-
tilmeld.config.validatorGroup(this);
|
|
909
|
+
tilmeld.config.validatorGroup(tilmeld, this);
|
|
708
910
|
} catch (e: any) {
|
|
709
911
|
throw new BadDataError(e?.message);
|
|
710
912
|
}
|
|
@@ -713,20 +915,28 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
713
915
|
if (this.$data.defaultPrimary) {
|
|
714
916
|
const currentPrimary = await this.$nymph.getEntity(
|
|
715
917
|
{ class: tilmeld.Group },
|
|
716
|
-
{ type: '&', truthy: 'defaultPrimary' }
|
|
918
|
+
{ type: '&', truthy: 'defaultPrimary' },
|
|
717
919
|
);
|
|
718
920
|
if (currentPrimary != null && !this.$is(currentPrimary)) {
|
|
719
921
|
currentPrimary.defaultPrimary = false;
|
|
720
|
-
if (
|
|
922
|
+
if (
|
|
923
|
+
this.$skipAcWhenSaving
|
|
924
|
+
? !(await currentPrimary.$saveSkipAC())
|
|
925
|
+
: !(await currentPrimary.$save())
|
|
926
|
+
) {
|
|
721
927
|
throw new CouldNotChangeDefaultPrimaryGroupError(
|
|
722
928
|
'Could not change new user primary group from ' +
|
|
723
|
-
`${currentPrimary.groupname}
|
|
929
|
+
`${currentPrimary.groupname}.`,
|
|
724
930
|
);
|
|
725
931
|
}
|
|
726
932
|
}
|
|
727
933
|
}
|
|
728
934
|
|
|
729
|
-
|
|
935
|
+
const ret = await super.$save();
|
|
936
|
+
if (ret) {
|
|
937
|
+
this.$originalGroupname = this.$data.groupname;
|
|
938
|
+
}
|
|
939
|
+
return ret;
|
|
730
940
|
}
|
|
731
941
|
|
|
732
942
|
/*
|
|
@@ -746,76 +956,118 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
746
956
|
}
|
|
747
957
|
|
|
748
958
|
public async $delete() {
|
|
749
|
-
let tilmeld = this
|
|
750
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
751
|
-
throw new
|
|
959
|
+
let tilmeld = enforceTilmeld(this);
|
|
960
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
961
|
+
throw new AccessControlError(
|
|
962
|
+
"You don't have the authority to delete groups.",
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
if (this.$data.groupname !== this.$originalGroupname) {
|
|
966
|
+
throw new AccessControlError(
|
|
967
|
+
"You can't delete a group while switching its groupname.",
|
|
968
|
+
);
|
|
752
969
|
}
|
|
753
970
|
|
|
754
971
|
const transaction = 'tilmeld-delete-' + this.guid;
|
|
755
972
|
const nymph = this.$nymph;
|
|
756
973
|
const tnymph = await nymph.startTransaction(transaction);
|
|
757
|
-
this.$
|
|
758
|
-
tilmeld = this
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
974
|
+
this.$setNymph(tnymph);
|
|
975
|
+
tilmeld = enforceTilmeld(this);
|
|
976
|
+
const User = tnymph.getEntityClass(UserClass);
|
|
977
|
+
|
|
978
|
+
try {
|
|
979
|
+
// Delete descendants.
|
|
980
|
+
const descendants = await this.$getDescendants();
|
|
981
|
+
if (descendants.length) {
|
|
982
|
+
for (let curGroup of descendants) {
|
|
983
|
+
if (
|
|
984
|
+
this.$skipAcWhenDeleting
|
|
985
|
+
? !(await curGroup.$deleteSkipAC())
|
|
986
|
+
: !(await curGroup.$delete())
|
|
987
|
+
) {
|
|
988
|
+
await tnymph.rollback(transaction);
|
|
989
|
+
this.$setNymph(nymph);
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Remove users from this primary group.
|
|
996
|
+
const primaryUsers = await tnymph.getEntities(
|
|
997
|
+
{
|
|
998
|
+
class: User,
|
|
999
|
+
skipAc: true,
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
type: '&',
|
|
1003
|
+
ref: ['group', this],
|
|
1004
|
+
},
|
|
1005
|
+
);
|
|
1006
|
+
for (let user of primaryUsers) {
|
|
1007
|
+
delete user.group;
|
|
1008
|
+
if (
|
|
1009
|
+
this.$skipAcWhenDeleting
|
|
1010
|
+
? !(await user.$saveSkipAC())
|
|
1011
|
+
: !(await user.$save())
|
|
1012
|
+
) {
|
|
765
1013
|
await tnymph.rollback(transaction);
|
|
766
|
-
this.$nymph
|
|
1014
|
+
this.$setNymph(nymph);
|
|
767
1015
|
return false;
|
|
768
1016
|
}
|
|
769
1017
|
}
|
|
770
|
-
}
|
|
771
1018
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1019
|
+
// Remove users from this secondary group.
|
|
1020
|
+
const secondaryUsers = await tnymph.getEntities(
|
|
1021
|
+
{
|
|
1022
|
+
class: User,
|
|
1023
|
+
skipAc: true,
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
type: '&',
|
|
1027
|
+
ref: ['groups', this],
|
|
1028
|
+
},
|
|
1029
|
+
);
|
|
1030
|
+
for (let user of secondaryUsers) {
|
|
1031
|
+
user.$delGroup(this);
|
|
1032
|
+
if (
|
|
1033
|
+
this.$skipAcWhenDeleting
|
|
1034
|
+
? !(await user.$saveSkipAC())
|
|
1035
|
+
: !(await user.$save())
|
|
1036
|
+
) {
|
|
1037
|
+
await tnymph.rollback(transaction);
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
789
1040
|
}
|
|
790
|
-
}
|
|
791
1041
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
{
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
type: '&',
|
|
800
|
-
ref: ['groups', this],
|
|
801
|
-
}
|
|
802
|
-
);
|
|
803
|
-
for (let user of secondaryUsers) {
|
|
804
|
-
user.$delGroup(this);
|
|
805
|
-
if (!(await user.$save())) {
|
|
1042
|
+
// Delete the group.
|
|
1043
|
+
let success = await super.$delete();
|
|
1044
|
+
if (success) {
|
|
1045
|
+
success = await tnymph.commit(transaction);
|
|
1046
|
+
} else {
|
|
806
1047
|
await tnymph.rollback(transaction);
|
|
807
|
-
return false;
|
|
808
1048
|
}
|
|
1049
|
+
this.$setNymph(nymph);
|
|
1050
|
+
return success;
|
|
1051
|
+
} catch (e: any) {
|
|
1052
|
+
await tnymph.rollback(transaction);
|
|
1053
|
+
this.$setNymph(nymph);
|
|
1054
|
+
throw e;
|
|
809
1055
|
}
|
|
1056
|
+
}
|
|
810
1057
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1058
|
+
/*
|
|
1059
|
+
* This should *never* be accessible on the client.
|
|
1060
|
+
*/
|
|
1061
|
+
async $deleteSkipAC() {
|
|
1062
|
+
this.$skipAcWhenDeleting = true;
|
|
1063
|
+
return await this.$delete();
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
$tilmeldDeleteSkipAC() {
|
|
1067
|
+
if (this.$skipAcWhenDeleting) {
|
|
1068
|
+
this.$skipAcWhenDeleting = false;
|
|
1069
|
+
return true;
|
|
817
1070
|
}
|
|
818
|
-
|
|
819
|
-
return success;
|
|
1071
|
+
return false;
|
|
820
1072
|
}
|
|
821
1073
|
}
|