@nymphjs/tilmeld 1.0.0-beta.122 → 1.0.0-beta.123
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 +10 -0
- package/dist/Group.d.ts +4 -3
- package/dist/Group.js +780 -816
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.js +6 -15
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +3 -3
- package/dist/User.js +1828 -1949
- package/dist/User.js.map +1 -1
- package/package.json +5 -5
- package/src/Group.ts +91 -166
- package/src/Tilmeld.ts +6 -35
- package/src/User.ts +342 -495
package/dist/Group.js
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
1
35
|
import crypto from 'node:crypto';
|
|
2
|
-
import {
|
|
36
|
+
import { transaction, transactional, } from '@nymphjs/nymph';
|
|
3
37
|
import { nanoid } from '@nymphjs/guid';
|
|
4
38
|
import { difference, xor } from 'lodash-es';
|
|
5
39
|
import { splitn } from '@sciactive/splitn';
|
|
@@ -7,846 +41,787 @@ import { enforceTilmeld } from './enforceTilmeld.js';
|
|
|
7
41
|
import AbleObject from './AbleObject.js';
|
|
8
42
|
import { AccessControlError, BadDataError, BadEmailError, BadUsernameError, CouldNotChangeDefaultPrimaryGroupError, } from './errors/index.js';
|
|
9
43
|
import UserClass from './User.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return super.getFTSText(name, value);
|
|
75
|
-
}
|
|
76
|
-
static async factoryGroupname(groupname) {
|
|
77
|
-
const entity = new this();
|
|
78
|
-
if (groupname != null) {
|
|
79
|
-
const entity = await this.nymph.getEntity({
|
|
80
|
-
class: this,
|
|
81
|
-
}, {
|
|
82
|
-
type: '&',
|
|
83
|
-
ilike: ['groupname', groupname.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
84
|
-
});
|
|
85
|
-
if (entity != null) {
|
|
86
|
-
return entity;
|
|
44
|
+
let Group = (() => {
|
|
45
|
+
let _classSuper = AbleObject;
|
|
46
|
+
let _instanceExtraInitializers = [];
|
|
47
|
+
let _$delete_decorators;
|
|
48
|
+
return class Group extends _classSuper {
|
|
49
|
+
static {
|
|
50
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
51
|
+
_$delete_decorators = [transactional];
|
|
52
|
+
__esDecorate(this, null, _$delete_decorators, { kind: "method", name: "$delete", static: false, private: false, access: { has: obj => "$delete" in obj, get: obj => obj.$delete }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
53
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The instance of Tilmeld to use for queries.
|
|
57
|
+
*/
|
|
58
|
+
static ETYPE = 'tilmeld_group';
|
|
59
|
+
static class = 'Group';
|
|
60
|
+
static checkGroupnameCallbacks = [];
|
|
61
|
+
static beforeSaveCallbacks = [];
|
|
62
|
+
static afterSaveCallbacks = [];
|
|
63
|
+
static beforeDeleteCallbacks = [];
|
|
64
|
+
static afterDeleteCallbacks = [];
|
|
65
|
+
static DEFAULT_PRIVATE_DATA = ['email', 'phone', 'abilities', 'user'];
|
|
66
|
+
static DEFAULT_ALLOWLIST_DATA = [];
|
|
67
|
+
$tags = (__runInitializers(this, _instanceExtraInitializers), []);
|
|
68
|
+
$clientEnabledMethods = [
|
|
69
|
+
'$checkGroupname',
|
|
70
|
+
'$checkEmail',
|
|
71
|
+
'$getAvatar',
|
|
72
|
+
'$getChildren',
|
|
73
|
+
'$getDescendants',
|
|
74
|
+
'$getLevel',
|
|
75
|
+
'$isDescendant',
|
|
76
|
+
];
|
|
77
|
+
static clientEnabledStaticMethods = [
|
|
78
|
+
'getPrimaryGroups',
|
|
79
|
+
'getSecondaryGroups',
|
|
80
|
+
];
|
|
81
|
+
$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
82
|
+
static searchRestrictedData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
83
|
+
$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
84
|
+
$allowlistTags = [];
|
|
85
|
+
/**
|
|
86
|
+
* This should only be used by the backend.
|
|
87
|
+
*/
|
|
88
|
+
$skipAcWhenSaving = false;
|
|
89
|
+
/**
|
|
90
|
+
* This should only be used by the backend.
|
|
91
|
+
*/
|
|
92
|
+
$skipAcWhenDeleting = false;
|
|
93
|
+
/**
|
|
94
|
+
* Used to save the current groupname for domain admin permissions.
|
|
95
|
+
*/
|
|
96
|
+
$originalGroupname;
|
|
97
|
+
static getFTSText(name, value) {
|
|
98
|
+
if ([
|
|
99
|
+
'abilities',
|
|
100
|
+
'parent',
|
|
101
|
+
'user',
|
|
102
|
+
'enabled',
|
|
103
|
+
'defaultPrimary',
|
|
104
|
+
'defaultSecondary',
|
|
105
|
+
'unverifiedSecondary',
|
|
106
|
+
].indexOf(name) !== -1) {
|
|
107
|
+
return null;
|
|
87
108
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
throw new Error("You don't have permission to do that.");
|
|
102
|
-
}
|
|
103
|
-
return await this.getAssignableGroups(tilmeld.config.highestPrimary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Get all the groups that can be assigned as secondary groups.
|
|
107
|
-
*
|
|
108
|
-
* @param options The options for an optional search query.
|
|
109
|
-
* @param selectors The selectors for an optional search query.
|
|
110
|
-
* @returns An array of the assignable secondary groups.
|
|
111
|
-
*/
|
|
112
|
-
static async getSecondaryGroups(options, selectors) {
|
|
113
|
-
const tilmeld = enforceTilmeld(this);
|
|
114
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
115
|
-
throw new Error("You don't have permission to do that.");
|
|
116
|
-
}
|
|
117
|
-
return await this.getAssignableGroups(tilmeld.config.highestSecondary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
|
|
118
|
-
}
|
|
119
|
-
static async getAssignableGroups(highestParent, options, selectors) {
|
|
120
|
-
const tilmeld = enforceTilmeld(this);
|
|
121
|
-
let assignableGroups = [];
|
|
122
|
-
if (highestParent === false) {
|
|
123
|
-
return assignableGroups;
|
|
124
|
-
}
|
|
125
|
-
assignableGroups = await this.nymph.getEntities({ ...options, class: tilmeld.Group }, ...selectors);
|
|
126
|
-
if (highestParent !== true) {
|
|
127
|
-
assignableGroups = (await Promise.all(assignableGroups.map(async (group) => {
|
|
128
|
-
let curGroup = group;
|
|
129
|
-
let parent = curGroup.parent;
|
|
130
|
-
await parent?.$wake();
|
|
131
|
-
while (parent != null && parent?.cdate != null) {
|
|
132
|
-
if (parent?.guid === highestParent) {
|
|
133
|
-
return group;
|
|
134
|
-
}
|
|
135
|
-
curGroup = parent;
|
|
136
|
-
parent = curGroup.parent;
|
|
137
|
-
await parent?.$wake();
|
|
109
|
+
return super.getFTSText(name, value);
|
|
110
|
+
}
|
|
111
|
+
static async factoryGroupname(groupname) {
|
|
112
|
+
const entity = new this();
|
|
113
|
+
if (groupname != null) {
|
|
114
|
+
const entity = await this.nymph.getEntity({
|
|
115
|
+
class: this,
|
|
116
|
+
}, {
|
|
117
|
+
type: '&',
|
|
118
|
+
ilike: ['groupname', groupname.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
119
|
+
});
|
|
120
|
+
if (entity != null) {
|
|
121
|
+
return entity;
|
|
138
122
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!Array.isArray(incoming) ||
|
|
195
|
-
incoming.find((ability) => typeof ability !== 'string')) {
|
|
196
|
-
throw new BadDataError('Abilities must be an array of strings.');
|
|
197
|
-
}
|
|
198
|
-
if (incoming.includes('system/admin') !==
|
|
199
|
-
this.$data.abilities?.includes('system/admin') &&
|
|
200
|
-
(!tilmeld.currentUser ||
|
|
201
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
202
|
-
throw new BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
203
|
-
}
|
|
204
|
-
if (incoming.includes('tilmeld/admin') !==
|
|
205
|
-
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
206
|
-
(!tilmeld.currentUser ||
|
|
207
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
208
|
-
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
209
|
-
}
|
|
210
|
-
if (incoming.includes('tilmeld/switch') !==
|
|
211
|
-
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
212
|
-
(!tilmeld.currentUser ||
|
|
213
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
214
|
-
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
215
|
-
}
|
|
216
|
-
if (xor(incoming.filter((ability) => ability.startsWith('tilmeld/')), this.$data.abilities?.filter((ability) => ability.startsWith('tilmeld/')) ?? []).length &&
|
|
217
|
-
(!tilmeld.currentUser ||
|
|
218
|
-
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
219
|
-
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))) {
|
|
220
|
-
throw new BadDataError("You don't have the authority to grant or revoke Tilmeld abilities.");
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
$putData(data, sdata, source) {
|
|
224
|
-
super.$putData(data, sdata, source);
|
|
225
|
-
// TODO: Fix these being added by the drivers.
|
|
226
|
-
// @ts-ignore
|
|
227
|
-
delete this.$data.acRead;
|
|
228
|
-
// @ts-ignore
|
|
229
|
-
delete this.$data.acWrite;
|
|
230
|
-
// @ts-ignore
|
|
231
|
-
delete this.$data.acFull;
|
|
232
|
-
if (source === 'server') {
|
|
233
|
-
this.$originalGroupname = this.$data.groupname;
|
|
234
|
-
}
|
|
235
|
-
this.$updateDataProtection();
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Update the data protection arrays for a user.
|
|
239
|
-
*
|
|
240
|
-
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
241
|
-
*/
|
|
242
|
-
$updateDataProtection(givenUser) {
|
|
243
|
-
const tilmeld = enforceTilmeld(this);
|
|
244
|
-
let user = givenUser ?? tilmeld.User.current();
|
|
245
|
-
let [_groupname, domain] = tilmeld.config.domainSupport
|
|
246
|
-
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
247
|
-
: [this.$data.groupname, null];
|
|
248
|
-
// Lowercase the domain part.
|
|
249
|
-
if (domain) {
|
|
250
|
-
domain = domain.toLowerCase();
|
|
251
|
-
}
|
|
252
|
-
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
253
|
-
this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
254
|
-
if (tilmeld.config.emailUsernames) {
|
|
255
|
-
this.$privateData.push('groupname');
|
|
256
|
-
}
|
|
257
|
-
if (user != null &&
|
|
258
|
-
(user.abilities?.includes('tilmeld/admin') ||
|
|
259
|
-
user.abilities?.includes('system/admin'))) {
|
|
260
|
-
// Users who can edit groups can see their data.
|
|
261
|
-
this.$privateData = [];
|
|
262
|
-
this.$allowlistData = undefined;
|
|
263
|
-
}
|
|
264
|
-
else if (this.$data.user != null &&
|
|
265
|
-
user != null &&
|
|
266
|
-
this.$data.user.guid === user.guid) {
|
|
267
|
-
// Users can see their group's data.
|
|
268
|
-
this.$privateData = [];
|
|
269
|
-
}
|
|
270
|
-
else if (user != null &&
|
|
271
|
-
tilmeld.config.domainSupport &&
|
|
272
|
-
domain &&
|
|
273
|
-
user.abilities?.includes(`tilmeld/domain/${domain}/admin`)) {
|
|
274
|
-
// Domain admins can see their groups' data.
|
|
275
|
-
this.$privateData = [];
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Check whether the group is a descendant of a group.
|
|
280
|
-
*
|
|
281
|
-
* @param group The group, or the group's GUID.
|
|
282
|
-
* @returns True or false.
|
|
283
|
-
*/
|
|
284
|
-
async $isDescendant(givenGroup) {
|
|
285
|
-
const tilmeld = enforceTilmeld(this);
|
|
286
|
-
let group;
|
|
287
|
-
if (typeof givenGroup === 'string') {
|
|
288
|
-
group = await tilmeld.Group.factory(givenGroup);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
group = givenGroup;
|
|
292
|
-
}
|
|
293
|
-
if (group.guid == null) {
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
// Check to see if the group is a descendant of the given group.
|
|
297
|
-
const parent = this.$data.parent;
|
|
298
|
-
await parent?.$wake();
|
|
299
|
-
if (parent == null) {
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
if (parent.$is(group)) {
|
|
303
|
-
return true;
|
|
304
|
-
}
|
|
305
|
-
if (await parent.$isDescendant(group)) {
|
|
306
|
-
return true;
|
|
307
|
-
}
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Gets an array of the group's child groups.
|
|
312
|
-
*
|
|
313
|
-
* @returns An array of groups.
|
|
314
|
-
*/
|
|
315
|
-
async $getChildren() {
|
|
316
|
-
const tilmeld = enforceTilmeld(this);
|
|
317
|
-
return await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
318
|
-
type: '&',
|
|
319
|
-
equal: ['enabled', true],
|
|
320
|
-
ref: ['parent', this],
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Gets an array of the group's descendant groups.
|
|
325
|
-
*
|
|
326
|
-
* @param andSelf Include this group in the returned array.
|
|
327
|
-
* @returns An array of groups.
|
|
328
|
-
*/
|
|
329
|
-
async $getDescendants(andSelf = false) {
|
|
330
|
-
const tilmeld = enforceTilmeld(this);
|
|
331
|
-
let groups = [];
|
|
332
|
-
const entities = await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
333
|
-
type: '&',
|
|
334
|
-
equal: ['enabled', true],
|
|
335
|
-
ref: ['parent', this],
|
|
336
|
-
});
|
|
337
|
-
for (let entity of entities) {
|
|
338
|
-
groups = (await entity.$getDescendants(true)).concat(groups);
|
|
339
|
-
}
|
|
340
|
-
if (andSelf) {
|
|
341
|
-
groups.push(this);
|
|
342
|
-
}
|
|
343
|
-
return groups;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Get the number of parents the group has.
|
|
347
|
-
*
|
|
348
|
-
* If the group is a top level group, this will return 0. If it is a child of
|
|
349
|
-
* a top level group, this will return 1. If it is a grandchild of a top level
|
|
350
|
-
* group, this will return 2, and so on.
|
|
351
|
-
*
|
|
352
|
-
* Levels will max out at 1024 to avoid recursive loops.
|
|
353
|
-
*
|
|
354
|
-
* @returns The level of the group.
|
|
355
|
-
*/
|
|
356
|
-
async $getLevel() {
|
|
357
|
-
const tilmeld = enforceTilmeld(this);
|
|
358
|
-
let group = await tilmeld.Group.factory(this.guid ?? undefined);
|
|
359
|
-
let parent = group.parent;
|
|
360
|
-
await parent?.$wake();
|
|
361
|
-
let level = 0;
|
|
362
|
-
while (parent != null && parent.cdate != null && level < 1024) {
|
|
363
|
-
level++;
|
|
364
|
-
group = parent;
|
|
365
|
-
parent = group.parent;
|
|
366
|
-
await parent?.$wake();
|
|
367
|
-
}
|
|
368
|
-
return level;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Gets an array of users in the group.
|
|
372
|
-
*
|
|
373
|
-
* @param descendants Include users in all descendant groups too.
|
|
374
|
-
* @param limit The limit for the query.
|
|
375
|
-
* @param offset The offset for the query.
|
|
376
|
-
* @returns An array of users.
|
|
377
|
-
*/
|
|
378
|
-
async $getUsers(descendants = false, limit, offset) {
|
|
379
|
-
const tilmeld = enforceTilmeld(this);
|
|
380
|
-
let groups = [];
|
|
381
|
-
if (descendants) {
|
|
382
|
-
groups = await this.$getDescendants();
|
|
123
|
+
}
|
|
124
|
+
return entity;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all the groups that can be assigned as primary groups.
|
|
128
|
+
*
|
|
129
|
+
* @param options The options for an optional search query.
|
|
130
|
+
* @param selectors The selectors for an optional search query.
|
|
131
|
+
* @returns An array of the assignable primary groups.
|
|
132
|
+
*/
|
|
133
|
+
static async getPrimaryGroups(options, selectors) {
|
|
134
|
+
const tilmeld = enforceTilmeld(this);
|
|
135
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
136
|
+
throw new Error("You don't have permission to do that.");
|
|
137
|
+
}
|
|
138
|
+
return await this.getAssignableGroups(tilmeld.config.highestPrimary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get all the groups that can be assigned as secondary groups.
|
|
142
|
+
*
|
|
143
|
+
* @param options The options for an optional search query.
|
|
144
|
+
* @param selectors The selectors for an optional search query.
|
|
145
|
+
* @returns An array of the assignable secondary groups.
|
|
146
|
+
*/
|
|
147
|
+
static async getSecondaryGroups(options, selectors) {
|
|
148
|
+
const tilmeld = enforceTilmeld(this);
|
|
149
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
150
|
+
throw new Error("You don't have permission to do that.");
|
|
151
|
+
}
|
|
152
|
+
return await this.getAssignableGroups(tilmeld.config.highestSecondary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
|
|
153
|
+
}
|
|
154
|
+
static async getAssignableGroups(highestParent, options, selectors) {
|
|
155
|
+
const tilmeld = enforceTilmeld(this);
|
|
156
|
+
let assignableGroups = [];
|
|
157
|
+
if (highestParent === false) {
|
|
158
|
+
return assignableGroups;
|
|
159
|
+
}
|
|
160
|
+
assignableGroups = await this.nymph.getEntities({ ...options, class: tilmeld.Group }, ...selectors);
|
|
161
|
+
if (highestParent !== true) {
|
|
162
|
+
assignableGroups = (await Promise.all(assignableGroups.map(async (group) => {
|
|
163
|
+
let curGroup = group;
|
|
164
|
+
let parent = curGroup.parent;
|
|
165
|
+
await parent?.$wake();
|
|
166
|
+
while (parent != null && parent?.cdate != null) {
|
|
167
|
+
if (parent?.guid === highestParent) {
|
|
168
|
+
return group;
|
|
169
|
+
}
|
|
170
|
+
curGroup = parent;
|
|
171
|
+
parent = curGroup.parent;
|
|
172
|
+
await parent?.$wake();
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}))).filter((group) => group != null);
|
|
176
|
+
}
|
|
177
|
+
return assignableGroups;
|
|
383
178
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
179
|
+
constructor() {
|
|
180
|
+
super();
|
|
181
|
+
// Defaults.
|
|
182
|
+
this.$data.enabled = true;
|
|
183
|
+
this.$data.abilities = [];
|
|
184
|
+
this.$updateDataProtection();
|
|
185
|
+
}
|
|
186
|
+
async $getUniques() {
|
|
187
|
+
const tilmeld = enforceTilmeld(this);
|
|
188
|
+
const uniques = [`u:${this.$data.groupname}`];
|
|
189
|
+
if (!tilmeld.config.emailUsernames &&
|
|
190
|
+
tilmeld.config.userFields.includes('email')) {
|
|
191
|
+
uniques.push(`e:${this.$data.email}`);
|
|
192
|
+
}
|
|
193
|
+
if (this.$data.defaultPrimary) {
|
|
194
|
+
uniques.push(`o:default-primary`);
|
|
195
|
+
}
|
|
196
|
+
return uniques;
|
|
197
|
+
}
|
|
198
|
+
$getAvatar() {
|
|
199
|
+
if (this.$data.avatar != null && this.$data.avatar !== '') {
|
|
200
|
+
return this.$data.avatar;
|
|
201
|
+
}
|
|
202
|
+
if (this.$data.email == null || !this.$data.email.length) {
|
|
203
|
+
return 'https://secure.gravatar.com/avatar/?d=mm&s=40';
|
|
204
|
+
}
|
|
205
|
+
return ('https://secure.gravatar.com/avatar/' +
|
|
206
|
+
crypto
|
|
207
|
+
.createHash('sha256')
|
|
208
|
+
.update(this.$data.email.trim().toLowerCase())
|
|
209
|
+
.digest('hex')
|
|
210
|
+
.toLowerCase() +
|
|
211
|
+
'?d=identicon&s=40');
|
|
212
|
+
}
|
|
213
|
+
$jsonAcceptData(input, allowConflict = false) {
|
|
214
|
+
this.$check();
|
|
215
|
+
if ('abilities' in input.data) {
|
|
216
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
217
|
+
}
|
|
218
|
+
super.$jsonAcceptData(input, allowConflict);
|
|
219
|
+
}
|
|
220
|
+
$jsonAcceptPatch(patch, allowConflict = false) {
|
|
221
|
+
this.$check();
|
|
222
|
+
if ('abilities' in patch.set) {
|
|
223
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
224
|
+
}
|
|
225
|
+
super.$jsonAcceptPatch(patch, allowConflict);
|
|
226
|
+
}
|
|
227
|
+
$checkIncomingAbilities(incoming) {
|
|
228
|
+
const tilmeld = enforceTilmeld(this);
|
|
229
|
+
if (!Array.isArray(incoming) ||
|
|
230
|
+
incoming.find((ability) => typeof ability !== 'string')) {
|
|
231
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
232
|
+
}
|
|
233
|
+
if (incoming.includes('system/admin') !==
|
|
234
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
235
|
+
(!tilmeld.currentUser ||
|
|
236
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
237
|
+
throw new BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
238
|
+
}
|
|
239
|
+
if (incoming.includes('tilmeld/admin') !==
|
|
240
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
241
|
+
(!tilmeld.currentUser ||
|
|
242
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
243
|
+
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
244
|
+
}
|
|
245
|
+
if (incoming.includes('tilmeld/switch') !==
|
|
246
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
247
|
+
(!tilmeld.currentUser ||
|
|
248
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
249
|
+
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
250
|
+
}
|
|
251
|
+
if (xor(incoming.filter((ability) => ability.startsWith('tilmeld/')), this.$data.abilities?.filter((ability) => ability.startsWith('tilmeld/')) ?? []).length &&
|
|
252
|
+
(!tilmeld.currentUser ||
|
|
253
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
254
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))) {
|
|
255
|
+
throw new BadDataError("You don't have the authority to grant or revoke Tilmeld abilities.");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
$putData(data, sdata, source) {
|
|
259
|
+
super.$putData(data, sdata, source);
|
|
260
|
+
// TODO: Fix these being added by the drivers.
|
|
261
|
+
// @ts-ignore
|
|
262
|
+
delete this.$data.acRead;
|
|
263
|
+
// @ts-ignore
|
|
264
|
+
delete this.$data.acWrite;
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
delete this.$data.acFull;
|
|
267
|
+
if (source === 'server') {
|
|
268
|
+
this.$originalGroupname = this.$data.groupname;
|
|
269
|
+
}
|
|
270
|
+
this.$updateDataProtection();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Update the data protection arrays for a user.
|
|
274
|
+
*
|
|
275
|
+
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
276
|
+
*/
|
|
277
|
+
$updateDataProtection(givenUser) {
|
|
278
|
+
const tilmeld = enforceTilmeld(this);
|
|
279
|
+
let user = givenUser ?? tilmeld.User.current();
|
|
280
|
+
let [_groupname, domain] = tilmeld.config.domainSupport
|
|
416
281
|
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
417
282
|
: [this.$data.groupname, null];
|
|
418
283
|
// Lowercase the domain part.
|
|
419
284
|
if (domain) {
|
|
420
285
|
domain = domain.toLowerCase();
|
|
421
|
-
this.$data.groupname = `${groupname}@${domain}`;
|
|
422
286
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
287
|
+
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
288
|
+
this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
289
|
+
if (tilmeld.config.emailUsernames) {
|
|
290
|
+
this.$privateData.push('groupname');
|
|
291
|
+
}
|
|
292
|
+
if (user != null &&
|
|
293
|
+
(user.abilities?.includes('tilmeld/admin') ||
|
|
294
|
+
user.abilities?.includes('system/admin'))) {
|
|
295
|
+
// Users who can edit groups can see their data.
|
|
296
|
+
this.$privateData = [];
|
|
297
|
+
this.$allowlistData = undefined;
|
|
298
|
+
}
|
|
299
|
+
else if (this.$data.user != null &&
|
|
300
|
+
user != null &&
|
|
301
|
+
this.$data.user.guid === user.guid) {
|
|
302
|
+
// Users can see their group's data.
|
|
303
|
+
this.$privateData = [];
|
|
304
|
+
}
|
|
305
|
+
else if (user != null &&
|
|
306
|
+
tilmeld.config.domainSupport &&
|
|
307
|
+
domain &&
|
|
308
|
+
user.abilities?.includes(`tilmeld/domain/${domain}/admin`)) {
|
|
309
|
+
// Domain admins can see their groups' data.
|
|
310
|
+
this.$privateData = [];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Check whether the group is a descendant of the given group.
|
|
315
|
+
*
|
|
316
|
+
* @param group The group, or the group's GUID.
|
|
317
|
+
* @returns True or false.
|
|
318
|
+
*/
|
|
319
|
+
async $isDescendant(givenGroup) {
|
|
320
|
+
const tilmeld = enforceTilmeld(this);
|
|
321
|
+
let group;
|
|
322
|
+
if (typeof givenGroup === 'string') {
|
|
323
|
+
group = await tilmeld.Group.factory(givenGroup);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
group = givenGroup;
|
|
327
|
+
}
|
|
328
|
+
if (group.guid == null) {
|
|
329
|
+
return false;
|
|
428
330
|
}
|
|
429
|
-
if
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
' characters.',
|
|
435
|
-
};
|
|
331
|
+
// Check to see if the group is a descendant of the given group.
|
|
332
|
+
const parent = this.$data.parent;
|
|
333
|
+
await parent?.$wake();
|
|
334
|
+
if (parent == null) {
|
|
335
|
+
return false;
|
|
436
336
|
}
|
|
437
|
-
if (
|
|
438
|
-
return
|
|
439
|
-
result: false,
|
|
440
|
-
message: 'Groupnames must not exceed ' +
|
|
441
|
-
tilmeld.config.maxUsernameLength +
|
|
442
|
-
' characters.',
|
|
443
|
-
};
|
|
337
|
+
if (parent.$is(group)) {
|
|
338
|
+
return true;
|
|
444
339
|
}
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
return {
|
|
448
|
-
result: false,
|
|
449
|
-
message: tilmeld.config.validCharsNotice,
|
|
450
|
-
};
|
|
340
|
+
if (await parent.$isDescendant(group)) {
|
|
341
|
+
return true;
|
|
451
342
|
}
|
|
452
|
-
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Gets an array of the group's child groups.
|
|
347
|
+
*
|
|
348
|
+
* @returns An array of groups.
|
|
349
|
+
*/
|
|
350
|
+
async $getChildren() {
|
|
351
|
+
const tilmeld = enforceTilmeld(this);
|
|
352
|
+
return await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
353
|
+
type: '&',
|
|
354
|
+
equal: ['enabled', true],
|
|
355
|
+
ref: ['parent', this],
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Gets an array of the group's descendant groups.
|
|
360
|
+
*
|
|
361
|
+
* @param andSelf Include this group in the returned array.
|
|
362
|
+
* @param andDisabled Include disabled groups.
|
|
363
|
+
* @returns An array of groups.
|
|
364
|
+
*/
|
|
365
|
+
async $getDescendants(andSelf = false, andDisabled = false) {
|
|
366
|
+
const tilmeld = enforceTilmeld(this);
|
|
367
|
+
let groups = [];
|
|
368
|
+
const entities = await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
369
|
+
type: '&',
|
|
370
|
+
...(andDisabled ? {} : { equal: ['enabled', true] }),
|
|
371
|
+
ref: ['parent', this],
|
|
372
|
+
});
|
|
373
|
+
for (let entity of entities) {
|
|
374
|
+
groups = (await entity.$getDescendants(true)).concat(groups);
|
|
375
|
+
}
|
|
376
|
+
if (andSelf) {
|
|
377
|
+
groups.push(this);
|
|
378
|
+
}
|
|
379
|
+
return groups;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get the number of parents the group has.
|
|
383
|
+
*
|
|
384
|
+
* If the group is a top level group, this will return 0. If it is a child of
|
|
385
|
+
* a top level group, this will return 1. If it is a grandchild of a top level
|
|
386
|
+
* group, this will return 2, and so on.
|
|
387
|
+
*
|
|
388
|
+
* Levels will max out at 1024 to avoid recursive loops.
|
|
389
|
+
*
|
|
390
|
+
* @returns The level of the group.
|
|
391
|
+
*/
|
|
392
|
+
async $getLevel() {
|
|
393
|
+
const tilmeld = enforceTilmeld(this);
|
|
394
|
+
let group = await tilmeld.Group.factory(this.guid ?? undefined);
|
|
395
|
+
let parent = group.parent;
|
|
396
|
+
await parent?.$wake();
|
|
397
|
+
let level = 0;
|
|
398
|
+
while (parent != null && parent.cdate != null && level < 1024) {
|
|
399
|
+
level++;
|
|
400
|
+
group = parent;
|
|
401
|
+
parent = group.parent;
|
|
402
|
+
await parent?.$wake();
|
|
403
|
+
}
|
|
404
|
+
return level;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Gets an array of enabled users in the group.
|
|
408
|
+
*
|
|
409
|
+
* @param descendants Include users in all descendant groups too.
|
|
410
|
+
* @param limit The limit for the query.
|
|
411
|
+
* @param offset The offset for the query.
|
|
412
|
+
* @returns An array of users.
|
|
413
|
+
*/
|
|
414
|
+
async $getUsers(descendants = false, limit, offset) {
|
|
415
|
+
const tilmeld = enforceTilmeld(this);
|
|
416
|
+
let groups = [];
|
|
417
|
+
if (descendants) {
|
|
418
|
+
groups = await this.$getDescendants();
|
|
419
|
+
}
|
|
420
|
+
return await this.$nymph.getEntities({
|
|
421
|
+
class: tilmeld.User,
|
|
422
|
+
limit,
|
|
423
|
+
offset,
|
|
424
|
+
}, { type: '&', equal: ['enabled', true] }, {
|
|
425
|
+
type: '|',
|
|
426
|
+
ref: [
|
|
427
|
+
['group', this],
|
|
428
|
+
['groups', this],
|
|
429
|
+
...groups.map((group) => [
|
|
430
|
+
'group',
|
|
431
|
+
group,
|
|
432
|
+
]),
|
|
433
|
+
...groups.map((group) => [
|
|
434
|
+
'groups',
|
|
435
|
+
group,
|
|
436
|
+
]),
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Check that a groupname is valid.
|
|
442
|
+
*
|
|
443
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
444
|
+
*/
|
|
445
|
+
async $checkGroupname() {
|
|
446
|
+
const tilmeld = enforceTilmeld(this);
|
|
447
|
+
if (!tilmeld.config.emailUsernames) {
|
|
448
|
+
let [groupname, domain] = tilmeld.config.domainSupport
|
|
449
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
450
|
+
: [this.$data.groupname, null];
|
|
451
|
+
// Lowercase the domain part.
|
|
452
|
+
if (domain) {
|
|
453
|
+
domain = domain.toLowerCase();
|
|
454
|
+
this.$data.groupname = `${groupname}@${domain}`;
|
|
455
|
+
}
|
|
456
|
+
if (this.$data.groupname == null ||
|
|
457
|
+
!this.$data.groupname.length ||
|
|
458
|
+
groupname == null ||
|
|
459
|
+
!groupname.length) {
|
|
460
|
+
return { result: false, message: 'Please specify a groupname.' };
|
|
461
|
+
}
|
|
462
|
+
if (groupname.length < tilmeld.config.minUsernameLength) {
|
|
463
|
+
return {
|
|
464
|
+
result: false,
|
|
465
|
+
message: 'Groupnames must be at least ' +
|
|
466
|
+
tilmeld.config.minUsernameLength +
|
|
467
|
+
' characters.',
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
if (groupname.length > tilmeld.config.maxUsernameLength) {
|
|
471
|
+
return {
|
|
472
|
+
result: false,
|
|
473
|
+
message: 'Groupnames must not exceed ' +
|
|
474
|
+
tilmeld.config.maxUsernameLength +
|
|
475
|
+
' characters.',
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (difference(groupname.split(''), tilmeld.config.validChars.split(''))
|
|
479
|
+
.length) {
|
|
480
|
+
return {
|
|
481
|
+
result: false,
|
|
482
|
+
message: tilmeld.config.validCharsNotice,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (!tilmeld.config.validRegex.test(groupname)) {
|
|
486
|
+
return {
|
|
487
|
+
result: false,
|
|
488
|
+
message: tilmeld.config.validRegexNotice,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (domain != null) {
|
|
492
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
493
|
+
return {
|
|
494
|
+
result: false,
|
|
495
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (!this.$is(tilmeld.currentUser?.group) &&
|
|
499
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
500
|
+
return {
|
|
501
|
+
result: false,
|
|
502
|
+
message: "You don't have the authority to manage groups on this domain.",
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const selector = {
|
|
507
|
+
type: '&',
|
|
508
|
+
ilike: [
|
|
509
|
+
'groupname',
|
|
510
|
+
this.$data.groupname.replace(/([\\%_])/g, (s) => `\\${s}`),
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
if (this.guid != null) {
|
|
514
|
+
selector['!guid'] = this.guid;
|
|
515
|
+
}
|
|
516
|
+
const test = await this.$nymph.getEntity({ class: tilmeld.Group, skipAc: true }, selector);
|
|
517
|
+
if (test != null) {
|
|
518
|
+
return { result: false, message: 'That groupname is taken.' };
|
|
519
|
+
}
|
|
520
|
+
for (let callback of this.constructor
|
|
521
|
+
.checkGroupnameCallbacks) {
|
|
522
|
+
if (callback) {
|
|
523
|
+
const result = await callback(this, {
|
|
524
|
+
groupname: this.$data.groupname,
|
|
525
|
+
});
|
|
526
|
+
if (!result.result) {
|
|
527
|
+
return { message: 'Groupname is not available.', ...result };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
453
531
|
return {
|
|
454
|
-
result:
|
|
455
|
-
message:
|
|
532
|
+
result: true,
|
|
533
|
+
message: this.guid != null ? 'Groupname is valid.' : 'Groupname is available!',
|
|
456
534
|
};
|
|
457
535
|
}
|
|
458
|
-
|
|
459
|
-
|
|
536
|
+
else {
|
|
537
|
+
this.$data.email = this.$data.groupname;
|
|
538
|
+
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
539
|
+
return { result: false, message: 'Please specify an email.' };
|
|
540
|
+
}
|
|
541
|
+
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
460
542
|
return {
|
|
461
543
|
result: false,
|
|
462
|
-
message:
|
|
544
|
+
message: 'Emails must be at least ' +
|
|
545
|
+
tilmeld.config.minUsernameLength +
|
|
546
|
+
' characters.',
|
|
463
547
|
};
|
|
464
548
|
}
|
|
465
|
-
if (
|
|
466
|
-
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
549
|
+
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
467
550
|
return {
|
|
468
551
|
result: false,
|
|
469
|
-
message:
|
|
552
|
+
message: 'Emails must not exceed ' +
|
|
553
|
+
tilmeld.config.maxUsernameLength +
|
|
554
|
+
' characters.',
|
|
470
555
|
};
|
|
471
556
|
}
|
|
557
|
+
return await this.$checkEmail();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Check that an email is unique.
|
|
562
|
+
*
|
|
563
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
564
|
+
*/
|
|
565
|
+
async $checkEmail() {
|
|
566
|
+
const tilmeld = enforceTilmeld(this);
|
|
567
|
+
if (this.$data.email === '') {
|
|
568
|
+
return { result: true, message: '' };
|
|
569
|
+
}
|
|
570
|
+
if (this.$data.email == null) {
|
|
571
|
+
return { result: false, message: 'Please specify a valid email.' };
|
|
572
|
+
}
|
|
573
|
+
if (!tilmeld.config.validEmailRegex.test(this.$data.email)) {
|
|
574
|
+
return {
|
|
575
|
+
result: false,
|
|
576
|
+
message: tilmeld.config.validEmailRegexNotice,
|
|
577
|
+
};
|
|
472
578
|
}
|
|
473
579
|
const selector = {
|
|
474
580
|
type: '&',
|
|
475
|
-
ilike: [
|
|
476
|
-
'groupname',
|
|
477
|
-
this.$data.groupname.replace(/([\\%_])/g, (s) => `\\${s}`),
|
|
478
|
-
],
|
|
581
|
+
ilike: ['email', this.$data.email.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
479
582
|
};
|
|
480
583
|
if (this.guid != null) {
|
|
481
584
|
selector['!guid'] = this.guid;
|
|
482
585
|
}
|
|
483
586
|
const test = await this.$nymph.getEntity({ class: tilmeld.Group, skipAc: true }, selector);
|
|
484
587
|
if (test != null) {
|
|
485
|
-
return { result: false, message: 'That groupname is taken.' };
|
|
486
|
-
}
|
|
487
|
-
for (let callback of this.constructor
|
|
488
|
-
.checkGroupnameCallbacks) {
|
|
489
|
-
if (callback) {
|
|
490
|
-
const result = await callback(this, {
|
|
491
|
-
groupname: this.$data.groupname,
|
|
492
|
-
});
|
|
493
|
-
if (!result.result) {
|
|
494
|
-
return { message: 'Groupname is not available.', ...result };
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return {
|
|
499
|
-
result: true,
|
|
500
|
-
message: this.guid != null ? 'Groupname is valid.' : 'Groupname is available!',
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
else {
|
|
504
|
-
this.$data.email = this.$data.groupname;
|
|
505
|
-
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
506
|
-
return { result: false, message: 'Please specify an email.' };
|
|
507
|
-
}
|
|
508
|
-
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
509
588
|
return {
|
|
510
589
|
result: false,
|
|
511
|
-
message: '
|
|
512
|
-
tilmeld.config.minUsernameLength +
|
|
513
|
-
' characters.',
|
|
590
|
+
message: 'That email address is already registered.',
|
|
514
591
|
};
|
|
515
592
|
}
|
|
516
|
-
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
517
|
-
return {
|
|
518
|
-
result: false,
|
|
519
|
-
message: 'Emails must not exceed ' +
|
|
520
|
-
tilmeld.config.maxUsernameLength +
|
|
521
|
-
' characters.',
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
return await this.$checkEmail();
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Check that an email is unique.
|
|
529
|
-
*
|
|
530
|
-
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
531
|
-
*/
|
|
532
|
-
async $checkEmail() {
|
|
533
|
-
const tilmeld = enforceTilmeld(this);
|
|
534
|
-
if (this.$data.email === '') {
|
|
535
|
-
return { result: true, message: '' };
|
|
536
|
-
}
|
|
537
|
-
if (this.$data.email == null) {
|
|
538
|
-
return { result: false, message: 'Please specify a valid email.' };
|
|
539
|
-
}
|
|
540
|
-
if (!tilmeld.config.validEmailRegex.test(this.$data.email)) {
|
|
541
593
|
return {
|
|
542
|
-
result:
|
|
543
|
-
message:
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
const selector = {
|
|
547
|
-
type: '&',
|
|
548
|
-
ilike: ['email', this.$data.email.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
549
|
-
};
|
|
550
|
-
if (this.guid != null) {
|
|
551
|
-
selector['!guid'] = this.guid;
|
|
552
|
-
}
|
|
553
|
-
const test = await this.$nymph.getEntity({ class: tilmeld.Group, skipAc: true }, selector);
|
|
554
|
-
if (test != null) {
|
|
555
|
-
return {
|
|
556
|
-
result: false,
|
|
557
|
-
message: 'That email address is already registered.',
|
|
594
|
+
result: true,
|
|
595
|
+
message: this.guid != null ? 'Email is valid.' : 'Email address is valid!',
|
|
558
596
|
};
|
|
559
597
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
async $save() {
|
|
566
|
-
let tilmeld = enforceTilmeld(this);
|
|
567
|
-
if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
|
|
568
|
-
throw new BadUsernameError('Groupname must not be empty.');
|
|
569
|
-
}
|
|
570
|
-
// Lowercase the domain part.
|
|
571
|
-
if (tilmeld.config.domainSupport) {
|
|
572
|
-
const [groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
573
|
-
if (domain) {
|
|
574
|
-
this.$data.groupname = `${groupname}@${domain.toLowerCase()}`;
|
|
598
|
+
async $save() {
|
|
599
|
+
let tilmeld = enforceTilmeld(this);
|
|
600
|
+
if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
|
|
601
|
+
throw new BadUsernameError('Groupname must not be empty.');
|
|
575
602
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
((originalDomain == null && domain != null) ||
|
|
582
|
-
(domain == null && originalDomain != null))) {
|
|
583
|
-
throw new AccessControlError("Groups can't switch to or from having a domain once created.");
|
|
584
|
-
}
|
|
585
|
-
if (!this.$skipAcWhenSaving &&
|
|
586
|
-
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
587
|
-
!tilmeld.gatekeeper('system/admin')) {
|
|
588
|
-
if (domain != null &&
|
|
589
|
-
!this.$is(tilmeld.currentUser) &&
|
|
590
|
-
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
591
|
-
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
592
|
-
}
|
|
593
|
-
if (originalDomain != null &&
|
|
594
|
-
originalDomain !== domain &&
|
|
595
|
-
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
596
|
-
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))) {
|
|
597
|
-
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
603
|
+
// Lowercase the domain part.
|
|
604
|
+
if (tilmeld.config.domainSupport) {
|
|
605
|
+
const [groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
606
|
+
if (domain) {
|
|
607
|
+
this.$data.groupname = `${groupname}@${domain.toLowerCase()}`;
|
|
598
608
|
}
|
|
599
609
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
delete this.$data.phone;
|
|
624
|
-
}
|
|
625
|
-
if (this.$data.parent == null) {
|
|
626
|
-
delete this.$data.parent;
|
|
627
|
-
}
|
|
628
|
-
if (this.$data.user == null) {
|
|
629
|
-
delete this.$data.user;
|
|
630
|
-
}
|
|
631
|
-
if (!this.$data.defaultPrimary) {
|
|
632
|
-
delete this.$data.defaultPrimary;
|
|
633
|
-
}
|
|
634
|
-
if (!this.$data.defaultSecondary) {
|
|
635
|
-
delete this.$data.defaultSecondary;
|
|
636
|
-
}
|
|
637
|
-
if (!this.$data.unverifiedSecondary) {
|
|
638
|
-
delete this.$data.unverifiedSecondary;
|
|
639
|
-
}
|
|
640
|
-
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
641
|
-
// 'tilmeld/switch', or domain admin abilities.
|
|
642
|
-
this.$data.abilities = (this.$data.abilities ?? []).filter((ability) => {
|
|
643
|
-
if (['system/admin', 'tilmeld/admin', 'tilmeld/switch'].indexOf(ability) !==
|
|
644
|
-
-1) {
|
|
645
|
-
return false;
|
|
610
|
+
if (tilmeld.config.domainSupport) {
|
|
611
|
+
const [_groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
612
|
+
const [_originalGroupname, originalDomain] = splitn(this.$originalGroupname ?? '', '@', 2);
|
|
613
|
+
if (this.guid != null &&
|
|
614
|
+
((originalDomain == null && domain != null) ||
|
|
615
|
+
(domain == null && originalDomain != null))) {
|
|
616
|
+
throw new AccessControlError("Groups can't switch to or from having a domain once created.");
|
|
617
|
+
}
|
|
618
|
+
if (!this.$skipAcWhenSaving &&
|
|
619
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
620
|
+
!tilmeld.gatekeeper('system/admin')) {
|
|
621
|
+
if (domain != null &&
|
|
622
|
+
!this.$is(tilmeld.currentUser) &&
|
|
623
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
624
|
+
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
625
|
+
}
|
|
626
|
+
if (originalDomain != null &&
|
|
627
|
+
originalDomain !== domain &&
|
|
628
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
629
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))) {
|
|
630
|
+
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
631
|
+
}
|
|
632
|
+
}
|
|
646
633
|
}
|
|
647
|
-
|
|
648
|
-
|
|
634
|
+
// Formatting.
|
|
635
|
+
this.$data.groupname = this.$data.groupname.trim();
|
|
636
|
+
if (tilmeld.config.emailUsernames) {
|
|
637
|
+
this.$data.email = this.$data.groupname;
|
|
649
638
|
}
|
|
650
|
-
|
|
651
|
-
|
|
639
|
+
this.$data.email = (this.$data.email ?? '').trim();
|
|
640
|
+
this.$data.name = (this.$data.name ?? '').trim();
|
|
641
|
+
this.$data.phone = (this.$data.phone ?? '').trim();
|
|
642
|
+
this.$data.enabled = !!this.$data.enabled;
|
|
643
|
+
// Clear empty values.
|
|
644
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
645
|
+
delete this.$data.name;
|
|
652
646
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const unCheck = await this.$checkGroupname();
|
|
657
|
-
if (!unCheck.result) {
|
|
658
|
-
throw new BadUsernameError(unCheck.message);
|
|
659
|
-
}
|
|
660
|
-
if (!tilmeld.config.emailUsernames &&
|
|
661
|
-
tilmeld.config.userFields.includes('email')) {
|
|
662
|
-
const emCheck = await this.$checkEmail();
|
|
663
|
-
if (!emCheck.result) {
|
|
664
|
-
throw new BadEmailError(emCheck.message);
|
|
647
|
+
if (this.$data.email === '' ||
|
|
648
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
649
|
+
delete this.$data.email;
|
|
665
650
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
651
|
+
if (this.$data.avatar === '') {
|
|
652
|
+
delete this.$data.avatar;
|
|
653
|
+
}
|
|
654
|
+
if (this.$data.phone === '' ||
|
|
655
|
+
!tilmeld.config.userFields.includes('phone')) {
|
|
656
|
+
delete this.$data.phone;
|
|
657
|
+
}
|
|
658
|
+
if (this.$data.parent == null) {
|
|
659
|
+
delete this.$data.parent;
|
|
660
|
+
}
|
|
661
|
+
if (this.$data.user == null) {
|
|
662
|
+
delete this.$data.user;
|
|
663
|
+
}
|
|
664
|
+
if (!this.$data.defaultPrimary) {
|
|
665
|
+
delete this.$data.defaultPrimary;
|
|
666
|
+
}
|
|
667
|
+
if (!this.$data.defaultSecondary) {
|
|
668
|
+
delete this.$data.defaultSecondary;
|
|
669
|
+
}
|
|
670
|
+
if (!this.$data.unverifiedSecondary) {
|
|
671
|
+
delete this.$data.unverifiedSecondary;
|
|
672
|
+
}
|
|
673
|
+
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
674
|
+
// 'tilmeld/switch', or domain admin abilities.
|
|
675
|
+
this.$data.abilities = (this.$data.abilities ?? []).filter((ability) => {
|
|
676
|
+
if (['system/admin', 'tilmeld/admin', 'tilmeld/switch'].indexOf(ability) !==
|
|
677
|
+
-1) {
|
|
678
|
+
return false;
|
|
694
679
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
`${currentPrimary.groupname}.`);
|
|
680
|
+
if (ability == null) {
|
|
681
|
+
return false;
|
|
698
682
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const transaction = 'tilmeld-save-group-' + (this.guid || nanoid());
|
|
702
|
-
const nymph = this.$nymph;
|
|
703
|
-
const tnymph = await nymph.startTransaction(transaction);
|
|
704
|
-
this.$setNymph(tnymph);
|
|
705
|
-
tilmeld = enforceTilmeld(this);
|
|
706
|
-
let preGuid = this.guid;
|
|
707
|
-
let preCdate = this.cdate;
|
|
708
|
-
let preMdate = this.mdate;
|
|
709
|
-
try {
|
|
710
|
-
for (let callback of this.constructor
|
|
711
|
-
.beforeSaveCallbacks) {
|
|
712
|
-
if (callback) {
|
|
713
|
-
await callback(this);
|
|
683
|
+
if (ability.startsWith('tilmeld/domain/') && ability.endsWith('/admin')) {
|
|
684
|
+
return false;
|
|
714
685
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
686
|
+
return true;
|
|
687
|
+
});
|
|
688
|
+
// Verification.
|
|
689
|
+
const unCheck = await this.$checkGroupname();
|
|
690
|
+
if (!unCheck.result) {
|
|
691
|
+
throw new BadUsernameError(unCheck.message);
|
|
692
|
+
}
|
|
693
|
+
if (!tilmeld.config.emailUsernames &&
|
|
694
|
+
tilmeld.config.userFields.includes('email')) {
|
|
695
|
+
const emCheck = await this.$checkEmail();
|
|
696
|
+
if (!emCheck.result) {
|
|
697
|
+
throw new BadEmailError(emCheck.message);
|
|
722
698
|
}
|
|
723
699
|
}
|
|
724
|
-
|
|
725
|
-
|
|
700
|
+
// Validate group parent. Make sure it's not a descendant of this group.
|
|
701
|
+
const parent = this.$data.parent;
|
|
702
|
+
await parent?.$wake();
|
|
703
|
+
if (parent &&
|
|
704
|
+
(parent.cdate == null ||
|
|
705
|
+
this.$is(parent) ||
|
|
706
|
+
(await parent.$isDescendant(this)))) {
|
|
707
|
+
throw new BadDataError("Group parent can't be itself or descendant of itself.");
|
|
708
|
+
}
|
|
726
709
|
try {
|
|
727
|
-
|
|
710
|
+
tilmeld.config.validatorGroup(tilmeld, this);
|
|
728
711
|
}
|
|
729
712
|
catch (e) {
|
|
730
|
-
|
|
713
|
+
throw new BadDataError(e?.message);
|
|
731
714
|
}
|
|
732
|
-
|
|
733
|
-
this.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
715
|
+
// Only one default primary group is allowed.
|
|
716
|
+
if (this.$data.defaultPrimary) {
|
|
717
|
+
const currentPrimary = await this.$nymph.getEntity({ class: tilmeld.Group }, { type: '&', truthy: 'defaultPrimary' });
|
|
718
|
+
if (currentPrimary != null && !this.$is(currentPrimary)) {
|
|
719
|
+
currentPrimary.defaultPrimary = false;
|
|
720
|
+
try {
|
|
721
|
+
if (this.$skipAcWhenSaving) {
|
|
722
|
+
await currentPrimary.$saveSkipAC();
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
await currentPrimary.$save();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
throw new CouldNotChangeDefaultPrimaryGroupError('Could not change new user primary group from ' +
|
|
730
|
+
`${currentPrimary.groupname}.`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
let preGuid = this.guid;
|
|
735
|
+
let preCdate = this.cdate;
|
|
736
|
+
let preMdate = this.mdate;
|
|
737
|
+
return await transaction(this.$nymph, 'tilmeld-save-group-' + (this.guid || nanoid()), async (nymph) => {
|
|
738
|
+
this.$setNymph(nymph);
|
|
739
|
+
for (let callback of this.constructor
|
|
740
|
+
.beforeSaveCallbacks) {
|
|
741
|
+
if (callback) {
|
|
742
|
+
await callback(this);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
await super.$save();
|
|
746
|
+
for (let callback of this.constructor
|
|
747
|
+
.afterSaveCallbacks) {
|
|
748
|
+
if (callback) {
|
|
749
|
+
await callback(this);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}, async (nymph, committed) => {
|
|
753
|
+
this.$setNymph(nymph);
|
|
754
|
+
if (committed) {
|
|
755
|
+
this.$originalGroupname = this.$data.groupname;
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
this.guid = preGuid;
|
|
759
|
+
this.cdate = preCdate;
|
|
760
|
+
this.mdate = preMdate;
|
|
761
|
+
}
|
|
762
|
+
});
|
|
761
763
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
764
|
+
/*
|
|
765
|
+
* This should *never* be accessible on the client.
|
|
766
|
+
*/
|
|
767
|
+
async $saveSkipAC() {
|
|
768
|
+
this.$skipAcWhenSaving = true;
|
|
769
|
+
await this.$save();
|
|
768
770
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
+
$tilmeldSaveSkipAC() {
|
|
772
|
+
if (this.$skipAcWhenSaving) {
|
|
773
|
+
this.$skipAcWhenSaving = false;
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
return false;
|
|
771
777
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
778
|
+
async $delete() {
|
|
779
|
+
let tilmeld = enforceTilmeld(this);
|
|
780
|
+
const User = this.$nymph.getEntityClass(UserClass);
|
|
781
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
782
|
+
throw new AccessControlError("You don't have the authority to delete groups.");
|
|
783
|
+
}
|
|
784
|
+
if (this.$data.groupname !== this.$originalGroupname) {
|
|
785
|
+
throw new AccessControlError("You can't delete a group while switching its groupname.");
|
|
786
|
+
}
|
|
779
787
|
for (let callback of this.constructor
|
|
780
788
|
.beforeDeleteCallbacks) {
|
|
781
789
|
if (callback) {
|
|
782
790
|
await callback(this);
|
|
783
791
|
}
|
|
784
792
|
}
|
|
785
|
-
// Delete
|
|
786
|
-
const
|
|
787
|
-
if (
|
|
788
|
-
for (let curGroup of
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
await curGroup.$deleteSkipAC();
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
await curGroup.$delete();
|
|
795
|
-
}
|
|
793
|
+
// Delete children.
|
|
794
|
+
const children = await this.$nymph.getEntities({ class: tilmeld.Group, skipAc: true }, { type: '&', ref: ['parent', this] });
|
|
795
|
+
if (children.length) {
|
|
796
|
+
for (let curGroup of children) {
|
|
797
|
+
if (this.$skipAcWhenDeleting) {
|
|
798
|
+
await curGroup.$deleteSkipAC();
|
|
796
799
|
}
|
|
797
|
-
|
|
798
|
-
await
|
|
799
|
-
this.$setNymph(nymph);
|
|
800
|
-
throw e;
|
|
800
|
+
else {
|
|
801
|
+
await curGroup.$delete();
|
|
801
802
|
}
|
|
802
803
|
}
|
|
803
804
|
}
|
|
804
805
|
// Remove users from this primary group.
|
|
805
|
-
const primaryUsers = await
|
|
806
|
-
class: User,
|
|
807
|
-
skipAc: true,
|
|
808
|
-
}, {
|
|
809
|
-
type: '&',
|
|
810
|
-
ref: ['group', this],
|
|
811
|
-
});
|
|
806
|
+
const primaryUsers = await this.$nymph.getEntities({ class: User, skipAc: true }, { type: '&', ref: ['group', this] });
|
|
812
807
|
for (let user of primaryUsers) {
|
|
813
808
|
delete user.group;
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
await user.$saveSkipAC();
|
|
817
|
-
}
|
|
818
|
-
else {
|
|
819
|
-
await user.$save();
|
|
820
|
-
}
|
|
809
|
+
if (this.$skipAcWhenDeleting) {
|
|
810
|
+
await user.$saveSkipAC();
|
|
821
811
|
}
|
|
822
|
-
|
|
823
|
-
await
|
|
824
|
-
this.$setNymph(nymph);
|
|
825
|
-
throw e;
|
|
812
|
+
else {
|
|
813
|
+
await user.$save();
|
|
826
814
|
}
|
|
827
815
|
}
|
|
828
816
|
// Remove users from this secondary group.
|
|
829
|
-
const secondaryUsers = await
|
|
830
|
-
class: User,
|
|
831
|
-
skipAc: true,
|
|
832
|
-
}, {
|
|
833
|
-
type: '&',
|
|
834
|
-
ref: ['groups', this],
|
|
835
|
-
});
|
|
817
|
+
const secondaryUsers = await this.$nymph.getEntities({ class: User, skipAc: true }, { type: '&', ref: ['groups', this] });
|
|
836
818
|
for (let user of secondaryUsers) {
|
|
837
819
|
user.$delGroup(this);
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
await user.$saveSkipAC();
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
await user.$save();
|
|
844
|
-
}
|
|
820
|
+
if (this.$skipAcWhenDeleting) {
|
|
821
|
+
await user.$saveSkipAC();
|
|
845
822
|
}
|
|
846
|
-
|
|
847
|
-
await
|
|
848
|
-
this.$setNymph(nymph);
|
|
849
|
-
throw e;
|
|
823
|
+
else {
|
|
824
|
+
await user.$save();
|
|
850
825
|
}
|
|
851
826
|
}
|
|
852
827
|
// Delete the group.
|
|
@@ -858,63 +833,52 @@ export default class Group extends AbleObject {
|
|
|
858
833
|
}
|
|
859
834
|
}
|
|
860
835
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
this.$setNymph(nymph);
|
|
869
|
-
throw e;
|
|
870
|
-
}
|
|
871
|
-
let committed = false;
|
|
872
|
-
try {
|
|
873
|
-
committed = await tnymph.commit(transaction);
|
|
836
|
+
/*
|
|
837
|
+
* This should *never* be accessible on the client.
|
|
838
|
+
*/
|
|
839
|
+
async $deleteSkipAC() {
|
|
840
|
+
this.$skipAcWhenDeleting = true;
|
|
841
|
+
await this.$delete();
|
|
874
842
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
throw new MethodFailedError('Transaction could not be committed.');
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
/*
|
|
884
|
-
* This should *never* be accessible on the client.
|
|
885
|
-
*/
|
|
886
|
-
async $deleteSkipAC() {
|
|
887
|
-
this.$skipAcWhenDeleting = true;
|
|
888
|
-
await this.$delete();
|
|
889
|
-
}
|
|
890
|
-
$tilmeldDeleteSkipAC() {
|
|
891
|
-
if (this.$skipAcWhenDeleting) {
|
|
892
|
-
this.$skipAcWhenDeleting = false;
|
|
893
|
-
return true;
|
|
894
|
-
}
|
|
895
|
-
return false;
|
|
896
|
-
}
|
|
897
|
-
static on(event, callback) {
|
|
898
|
-
const prop = (event + 'Callbacks');
|
|
899
|
-
if (!(prop in this)) {
|
|
900
|
-
throw new Error('Invalid event type.');
|
|
901
|
-
}
|
|
902
|
-
// @ts-ignore: The callback should always be the right type here.
|
|
903
|
-
this[prop].push(callback);
|
|
904
|
-
return () => this.off(event, callback);
|
|
905
|
-
}
|
|
906
|
-
static off(event, callback) {
|
|
907
|
-
const prop = (event + 'Callbacks');
|
|
908
|
-
if (!(prop in this)) {
|
|
843
|
+
$tilmeldDeleteSkipAC() {
|
|
844
|
+
if (this.$skipAcWhenDeleting) {
|
|
845
|
+
this.$skipAcWhenDeleting = false;
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
909
848
|
return false;
|
|
910
849
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
850
|
+
static on(event, callback) {
|
|
851
|
+
const prop = (event + 'Callbacks');
|
|
852
|
+
if (!(prop in this)) {
|
|
853
|
+
throw new Error('Invalid event type.');
|
|
854
|
+
}
|
|
914
855
|
// @ts-ignore: The callback should always be the right type here.
|
|
915
|
-
this[prop].
|
|
856
|
+
this[prop].push(callback);
|
|
857
|
+
return () => this.off(event, callback);
|
|
916
858
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
859
|
+
static off(event, callback) {
|
|
860
|
+
const prop = (event + 'Callbacks');
|
|
861
|
+
if (!(prop in this)) {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
865
|
+
const i = this[prop].indexOf(callback);
|
|
866
|
+
if (i > -1) {
|
|
867
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
868
|
+
this[prop].splice(i, 1);
|
|
869
|
+
}
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
})();
|
|
874
|
+
/**
|
|
875
|
+
* A user group data model.
|
|
876
|
+
*
|
|
877
|
+
* Written by Hunter Perrin for SciActive.
|
|
878
|
+
*
|
|
879
|
+
* @author Hunter Perrin <hperrin@port87.com>
|
|
880
|
+
* @copyright SciActive Inc
|
|
881
|
+
* @see http://nymph.io/
|
|
882
|
+
*/
|
|
883
|
+
export default Group;
|
|
920
884
|
//# sourceMappingURL=Group.js.map
|