@stamhoofd/backend 2.78.2 → 2.78.4
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/.env.ci.json +32 -16
- package/index.ts +7 -0
- package/jest.config.cjs +17 -0
- package/package.json +10 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +0 -10
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +726 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +31 -18
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +1 -1
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +0 -4
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +0 -4
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +288 -8
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +8 -13
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +7 -7
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -217
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +6 -3
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -6
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +2 -20
- package/src/helpers/AdminPermissionChecker.ts +88 -140
- package/src/helpers/GlobalHelper.ts +6 -1
- package/src/services/FileSignService.ts +3 -3
- package/src/services/MemberRecordStore.ts +155 -0
- package/src/services/PlatformMembershipService.ts +17 -8
- package/tests/e2e/register.test.ts +49 -21
- package/tests/helpers/StripeMocker.ts +7 -2
- package/tests/jest.global.setup.ts +6 -1
- package/tests/jest.setup.ts +10 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, PatchableArray } from '@simonbackx/simple-encoding';
|
|
2
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
3
|
+
import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
5
4
|
|
|
6
5
|
import { testServer } from '../../../../../tests/helpers/TestServer';
|
|
7
6
|
import { PatchOrganizationEndpoint } from './PatchOrganizationEndpoint';
|
|
@@ -60,218 +59,4 @@ describe('Endpoint.PatchOrganization', () => {
|
|
|
60
59
|
|
|
61
60
|
await expect(testServer.test(endpoint, r)).rejects.toThrow(/permissions/i);
|
|
62
61
|
});
|
|
63
|
-
|
|
64
|
-
test('Change the name of a group with access', async () => {
|
|
65
|
-
const organization = await new OrganizationFactory({}).create();
|
|
66
|
-
const role = PermissionRoleDetailed.create({
|
|
67
|
-
name: 'Role',
|
|
68
|
-
});
|
|
69
|
-
organization.privateMeta.roles.push(
|
|
70
|
-
role,
|
|
71
|
-
);
|
|
72
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
73
|
-
|
|
74
|
-
role.resources.set(PermissionsResourceType.Groups, new Map());
|
|
75
|
-
role.resources.get(PermissionsResourceType.Groups)!.set(groups[0].id, ResourcePermissions.create({
|
|
76
|
-
level: PermissionLevel.Full,
|
|
77
|
-
}));
|
|
78
|
-
|
|
79
|
-
await organization.save();
|
|
80
|
-
|
|
81
|
-
const validPermissions = [
|
|
82
|
-
Permissions.create({
|
|
83
|
-
level: PermissionLevel.None,
|
|
84
|
-
roles: [PermissionRole.create(role)],
|
|
85
|
-
}),
|
|
86
|
-
Permissions.create({
|
|
87
|
-
level: PermissionLevel.Full,
|
|
88
|
-
}),
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
for (const permission of validPermissions) {
|
|
92
|
-
const user = await new UserFactory({ organization,
|
|
93
|
-
permissions: permission,
|
|
94
|
-
}).create();
|
|
95
|
-
const token = await Token.createToken(user);
|
|
96
|
-
|
|
97
|
-
const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
|
|
98
|
-
changes.addPatch(GroupPatch.create({
|
|
99
|
-
id: groups[0].id,
|
|
100
|
-
settings: GroupSettingsPatch.create({
|
|
101
|
-
name: 'My crazy group name',
|
|
102
|
-
}),
|
|
103
|
-
}));
|
|
104
|
-
|
|
105
|
-
const r = Request.buildJson('PATCH', '/organization', organization.getApiHost(), {
|
|
106
|
-
id: organization.id,
|
|
107
|
-
groups: changes.encode({ version: 2 }),
|
|
108
|
-
});
|
|
109
|
-
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
110
|
-
|
|
111
|
-
const response = await testServer.test(endpoint, r);
|
|
112
|
-
expect(response.body).toBeDefined();
|
|
113
|
-
|
|
114
|
-
if (!(response.body instanceof Organization)) {
|
|
115
|
-
throw new Error('Expected Organization');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
expect(response.body.id).toEqual(organization.id);
|
|
119
|
-
expect(response.body.groups.find(g => g.id == groups[0].id)!.settings.name).toEqual('My crazy group name');
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("Can't change name of group without access", async () => {
|
|
124
|
-
const organization = await new OrganizationFactory({}).create();
|
|
125
|
-
const role = PermissionRoleDetailed.create({
|
|
126
|
-
name: 'Role',
|
|
127
|
-
});
|
|
128
|
-
const role2 = PermissionRoleDetailed.create({
|
|
129
|
-
name: 'Role2',
|
|
130
|
-
});
|
|
131
|
-
organization.privateMeta.roles.push(
|
|
132
|
-
role,
|
|
133
|
-
role2,
|
|
134
|
-
);
|
|
135
|
-
await organization.save();
|
|
136
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
137
|
-
|
|
138
|
-
groups[0].privateSettings.permissions.write.push(PermissionRole.create(role));
|
|
139
|
-
await groups[0].save();
|
|
140
|
-
|
|
141
|
-
groups[0].privateSettings.permissions.read.push(PermissionRole.create(role2));
|
|
142
|
-
await groups[0].save();
|
|
143
|
-
|
|
144
|
-
const invalidPermissions = [
|
|
145
|
-
Permissions.create({
|
|
146
|
-
level: PermissionLevel.Read,
|
|
147
|
-
roles: [PermissionRole.create(role)],
|
|
148
|
-
}),
|
|
149
|
-
Permissions.create({
|
|
150
|
-
level: PermissionLevel.None,
|
|
151
|
-
roles: [PermissionRole.create(role2)],
|
|
152
|
-
}),
|
|
153
|
-
Permissions.create({
|
|
154
|
-
level: PermissionLevel.Write,
|
|
155
|
-
roles: [PermissionRole.create(role2), PermissionRole.create(role)],
|
|
156
|
-
}),
|
|
157
|
-
Permissions.create({
|
|
158
|
-
level: PermissionLevel.Write,
|
|
159
|
-
}),
|
|
160
|
-
Permissions.create({
|
|
161
|
-
level: PermissionLevel.Read,
|
|
162
|
-
}),
|
|
163
|
-
null,
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
for (const permission of invalidPermissions) {
|
|
167
|
-
const user = await new UserFactory({
|
|
168
|
-
organization,
|
|
169
|
-
permissions: permission,
|
|
170
|
-
}).create();
|
|
171
|
-
const token = await Token.createToken(user);
|
|
172
|
-
|
|
173
|
-
const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
|
|
174
|
-
changes.addPatch(GroupPatch.create({
|
|
175
|
-
id: groups[0].id,
|
|
176
|
-
settings: GroupSettingsPatch.create({
|
|
177
|
-
name: 'My crazy group name',
|
|
178
|
-
}),
|
|
179
|
-
}));
|
|
180
|
-
const r = Request.buildJson('PATCH', '/organization', organization.getApiHost(), {
|
|
181
|
-
id: organization.id,
|
|
182
|
-
groups: changes.encode({ version: 2 }),
|
|
183
|
-
});
|
|
184
|
-
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
185
|
-
await expect(testServer.test(endpoint, r)).rejects.toThrow(/permissions/i);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('Create a group with access', async () => {
|
|
190
|
-
const organization = await new OrganizationFactory({}).create();
|
|
191
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
192
|
-
|
|
193
|
-
const validPermissions = [
|
|
194
|
-
Permissions.create({
|
|
195
|
-
level: PermissionLevel.Full,
|
|
196
|
-
}),
|
|
197
|
-
];
|
|
198
|
-
|
|
199
|
-
const invalidPermissions = [
|
|
200
|
-
Permissions.create({
|
|
201
|
-
level: PermissionLevel.Write,
|
|
202
|
-
}),
|
|
203
|
-
];
|
|
204
|
-
|
|
205
|
-
for (const permission of validPermissions) {
|
|
206
|
-
const user = await new UserFactory({
|
|
207
|
-
organization,
|
|
208
|
-
permissions: permission,
|
|
209
|
-
}).create();
|
|
210
|
-
const token = await Token.createToken(user);
|
|
211
|
-
|
|
212
|
-
const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
|
|
213
|
-
const put = Group.create({
|
|
214
|
-
cycle: 0,
|
|
215
|
-
organizationId: organization.id,
|
|
216
|
-
periodId: organization.periodId,
|
|
217
|
-
settings: GroupSettings.create({
|
|
218
|
-
name: 'My crazy group name',
|
|
219
|
-
startDate: new Date(),
|
|
220
|
-
endDate: new Date(),
|
|
221
|
-
registrationStartDate: new Date(),
|
|
222
|
-
registrationEndDate: new Date(),
|
|
223
|
-
genderType: GroupGenderType.Mixed,
|
|
224
|
-
}),
|
|
225
|
-
privateSettings: GroupPrivateSettings.create({}),
|
|
226
|
-
});
|
|
227
|
-
changes.addPut(put);
|
|
228
|
-
|
|
229
|
-
const r = Request.buildJson('PATCH', '/v140/organization', organization.getApiHost(), {
|
|
230
|
-
id: organization.id,
|
|
231
|
-
groups: changes.encode({ version: 140 }),
|
|
232
|
-
});
|
|
233
|
-
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
234
|
-
|
|
235
|
-
const response = await testServer.test(endpoint, r);
|
|
236
|
-
expect(response.body).toBeDefined();
|
|
237
|
-
|
|
238
|
-
if (!(response.body instanceof Organization)) {
|
|
239
|
-
throw new Error('Expected Organization');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
expect(response.body.id).toEqual(organization.id);
|
|
243
|
-
expect(response.body.groups.map(g => g.id)).toContainEqual(put.id);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
for (const permission of invalidPermissions) {
|
|
247
|
-
const user = await new UserFactory({
|
|
248
|
-
organization,
|
|
249
|
-
permissions: permission,
|
|
250
|
-
}).create();
|
|
251
|
-
const token = await Token.createToken(user);
|
|
252
|
-
|
|
253
|
-
const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
|
|
254
|
-
const put = Group.create({
|
|
255
|
-
cycle: 0,
|
|
256
|
-
organizationId: organization.id,
|
|
257
|
-
periodId: organization.periodId,
|
|
258
|
-
settings: GroupSettings.create({
|
|
259
|
-
name: 'My crazy group name',
|
|
260
|
-
startDate: new Date(),
|
|
261
|
-
endDate: new Date(),
|
|
262
|
-
registrationStartDate: new Date(),
|
|
263
|
-
registrationEndDate: new Date(),
|
|
264
|
-
genderType: GroupGenderType.Mixed,
|
|
265
|
-
}),
|
|
266
|
-
});
|
|
267
|
-
changes.addPut(put);
|
|
268
|
-
|
|
269
|
-
const r = Request.buildJson('PATCH', '/v2/organization', organization.getApiHost(), {
|
|
270
|
-
id: organization.id,
|
|
271
|
-
groups: changes.encode({ version: 2 }),
|
|
272
|
-
});
|
|
273
|
-
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
274
|
-
await expect(testServer.test(endpoint, r)).rejects.toThrow(/permissions/i);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
62
|
});
|
|
@@ -250,7 +250,7 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
250
250
|
if (!member || !(await Context.auth.canLinkBalanceItemToMember(member))) {
|
|
251
251
|
throw new SimpleError({
|
|
252
252
|
code: 'permission_denied',
|
|
253
|
-
message: 'No permission to link
|
|
253
|
+
message: 'No permission to link balance items to this member',
|
|
254
254
|
human: 'Je hebt geen toegang om aanrekeningen te maken verbonden met dit lid',
|
|
255
255
|
field: 'memberId',
|
|
256
256
|
});
|
|
@@ -264,7 +264,7 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
264
264
|
if (!user || !await Context.auth.canLinkBalanceItemToUser(balanceItem, user)) {
|
|
265
265
|
throw new SimpleError({
|
|
266
266
|
code: 'permission_denied',
|
|
267
|
-
message: 'No permission to link
|
|
267
|
+
message: 'No permission to link balance items to this user',
|
|
268
268
|
human: 'Je hebt geen toegang om aanrekeningen te maken verbonden met deze gebruiker',
|
|
269
269
|
field: 'userId',
|
|
270
270
|
});
|
|
@@ -137,7 +137,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
137
137
|
await order.updateStock(null, true);
|
|
138
138
|
const totalPrice = order.data.totalPrice;
|
|
139
139
|
|
|
140
|
-
if (totalPrice
|
|
140
|
+
if (totalPrice === 0) {
|
|
141
141
|
// Force unknown payment method
|
|
142
142
|
order.data.paymentMethod = PaymentMethod.Unknown;
|
|
143
143
|
|
|
@@ -161,6 +161,9 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
161
161
|
order.paymentId = payment.id;
|
|
162
162
|
order.setRelation(Order.payment, payment);
|
|
163
163
|
|
|
164
|
+
// Save order because we need the id
|
|
165
|
+
await order.save();
|
|
166
|
+
|
|
164
167
|
// Create balance item
|
|
165
168
|
const balanceItem = new BalanceItem();
|
|
166
169
|
balanceItem.orderId = order.id;
|
|
@@ -189,12 +192,12 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
189
192
|
balanceItemPayment.price = balanceItem.price;
|
|
190
193
|
await balanceItemPayment.save();
|
|
191
194
|
|
|
192
|
-
if (payment.method
|
|
195
|
+
if (payment.method === PaymentMethod.Transfer) {
|
|
193
196
|
await order.markValid(payment, []);
|
|
194
197
|
await payment.save();
|
|
195
198
|
await order.save();
|
|
196
199
|
}
|
|
197
|
-
else if (payment.method
|
|
200
|
+
else if (payment.method === PaymentMethod.PointOfSale) {
|
|
198
201
|
// Not really paid, but needed to create the tickets if needed
|
|
199
202
|
await order.markPaid(payment, organization, webshop);
|
|
200
203
|
await payment.save();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import {
|
|
2
|
+
import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
3
3
|
import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { testServer } from '../../../../../tests/helpers/TestServer';
|
|
@@ -12,7 +12,6 @@ describe('Endpoint.GetOrganization', () => {
|
|
|
12
12
|
test('Get organization as signed in user', async () => {
|
|
13
13
|
const organization = await new OrganizationFactory({}).create();
|
|
14
14
|
const user = await new UserFactory({ organization }).create();
|
|
15
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
16
15
|
const token = await Token.createToken(user);
|
|
17
16
|
|
|
18
17
|
const r = Request.buildJson('GET', '/v3/organization', organization.getApiHost());
|
|
@@ -26,7 +25,6 @@ describe('Endpoint.GetOrganization', () => {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
expect(response.body.id).toEqual(organization.id);
|
|
29
|
-
expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort());
|
|
30
28
|
expect(response.body.privateMeta).toEqual(null);
|
|
31
29
|
});
|
|
32
30
|
|
|
@@ -39,7 +37,6 @@ describe('Endpoint.GetOrganization', () => {
|
|
|
39
37
|
}),
|
|
40
38
|
}).create();
|
|
41
39
|
|
|
42
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
43
40
|
const token = await Token.createToken(user);
|
|
44
41
|
|
|
45
42
|
const r = Request.buildJson('GET', '/v3/organization', organization.getApiHost());
|
|
@@ -53,7 +50,6 @@ describe('Endpoint.GetOrganization', () => {
|
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
expect(response.body.id).toEqual(organization.id);
|
|
56
|
-
expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort());
|
|
57
53
|
expect(response.body.privateMeta).not.toEqual(null);
|
|
58
54
|
});
|
|
59
55
|
|
|
@@ -72,6 +68,8 @@ describe('Endpoint.GetOrganization', () => {
|
|
|
72
68
|
const r = Request.buildJson('GET', '/v3/organization', organization.getApiHost());
|
|
73
69
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
74
70
|
|
|
75
|
-
await
|
|
71
|
+
const response = await testServer.test(endpoint, r);
|
|
72
|
+
expect(response.body).toBeDefined();
|
|
73
|
+
expect(response.body.privateMeta).toEqual(null);
|
|
76
74
|
});
|
|
77
75
|
});
|
|
@@ -105,25 +105,7 @@ describe('Endpoint.GetWebshop', () => {
|
|
|
105
105
|
const r = Request.buildJson('GET', '/v244/webshop/' + webshop.id, organization.getApiHost());
|
|
106
106
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
107
107
|
|
|
108
|
-
await
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
test('If organization scope is missing in v243, access is still checked correctly', async () => {
|
|
112
|
-
const organization = await new OrganizationFactory({}).create();
|
|
113
|
-
const organization2 = await new OrganizationFactory({}).create();
|
|
114
|
-
const user = await new UserFactory({
|
|
115
|
-
organization: organization2,
|
|
116
|
-
permissions: Permissions.create({
|
|
117
|
-
level: PermissionLevel.Full,
|
|
118
|
-
}),
|
|
119
|
-
}).create();
|
|
120
|
-
|
|
121
|
-
const token = await Token.createToken(user);
|
|
122
|
-
const webshop = await new WebshopFactory({ organizationId: organization.id }).create();
|
|
123
|
-
|
|
124
|
-
const r = Request.buildJson('GET', '/v243/webshop/' + webshop.id);
|
|
125
|
-
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
126
|
-
|
|
127
|
-
await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
|
|
108
|
+
const response = await testServer.test(endpoint, r);
|
|
109
|
+
expect((response.body as any).privateMeta).toBeUndefined();
|
|
128
110
|
});
|
|
129
111
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, PatchMap } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { BalanceItem, CachedBalance, Document, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
|
|
4
|
-
import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from '@stamhoofd/structures';
|
|
4
|
+
import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory, RecordSettings } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess';
|
|
7
|
+
import { MemberRecordStore } from '../services/MemberRecordStore';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* One class with all the responsabilities of checking permissions to each resource in the system by a given user, possibly in an organization context.
|
|
@@ -90,6 +91,10 @@ export class AdminPermissionChecker {
|
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
memberNotFoundOrNoAccess(): SimpleError {
|
|
95
|
+
return this.notFoundOrNoAccess($t('Je hebt geen toegang tot dit lid of het bestaat niet'));
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
notFoundOrNoAccess(message?: string): SimpleError {
|
|
94
99
|
return new SimpleError({
|
|
95
100
|
code: 'not_found',
|
|
@@ -853,80 +858,6 @@ export class AdminPermissionChecker {
|
|
|
853
858
|
return !!member.users.find(u => u.id === this.user.id);
|
|
854
859
|
}
|
|
855
860
|
|
|
856
|
-
/**
|
|
857
|
-
* Return a list of RecordSettings the current user can view or edit
|
|
858
|
-
*/
|
|
859
|
-
async getAccessibleRecordCategories(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<RecordCategory[]> {
|
|
860
|
-
// First list all organizations this member is part of
|
|
861
|
-
const organizations: Organization[] = [];
|
|
862
|
-
|
|
863
|
-
if (member.organizationId) {
|
|
864
|
-
if (this.checkScope(member.organizationId)) {
|
|
865
|
-
organizations.push(await this.getOrganization(member.organizationId));
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
for (const registration of member.registrations) {
|
|
870
|
-
if (this.checkScope(registration.organizationId)) {
|
|
871
|
-
if (!organizations.find(o => o.id === registration.organizationId)) {
|
|
872
|
-
organizations.push(await this.getOrganization(registration.organizationId));
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Loop all organizations.
|
|
878
|
-
// Check if we have access to their data
|
|
879
|
-
const recordCategories: RecordCategory[] = [];
|
|
880
|
-
for (const organization of organizations) {
|
|
881
|
-
const permissions = await this.getOrganizationPermissions(organization);
|
|
882
|
-
|
|
883
|
-
if (!permissions) {
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// Now add all records of this organization
|
|
888
|
-
for (const category of organization.meta.recordsConfiguration.recordCategories) {
|
|
889
|
-
if (recordCategories.find(c => c.id === category.id)) {
|
|
890
|
-
// Already added
|
|
891
|
-
continue;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
if (permissions.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
|
|
895
|
-
recordCategories.push(category);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// Platform ones where we have been given permissions for in this organization
|
|
900
|
-
for (const category of this.platform.config.recordsConfiguration.recordCategories) {
|
|
901
|
-
if (recordCategories.find(c => c.id === category.id)) {
|
|
902
|
-
// Already added
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
if (permissions.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
|
|
907
|
-
recordCategories.push(category);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// Platform data
|
|
913
|
-
const platformPermissions = this.platformPermissions;
|
|
914
|
-
if (platformPermissions) {
|
|
915
|
-
for (const category of this.platform.config.recordsConfiguration.recordCategories) {
|
|
916
|
-
if (recordCategories.find(c => c.id === category.id)) {
|
|
917
|
-
// Already added
|
|
918
|
-
continue;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
if (platformPermissions?.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
|
|
922
|
-
recordCategories.push(category);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
return recordCategories;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
861
|
/**
|
|
931
862
|
* Return a list of RecordSettings the current user can view or edit
|
|
932
863
|
*/
|
|
@@ -1053,56 +984,87 @@ export class AdminPermissionChecker {
|
|
|
1053
984
|
return false;
|
|
1054
985
|
}
|
|
1055
986
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
987
|
+
async checkRecordAccess(member: MemberWithRegistrations, recordId: string, level: PermissionLevel = PermissionLevel.Read): Promise<{ canAccess: false; record: RecordSettings | null } | { canAccess: true; record: RecordSettings }> {
|
|
988
|
+
const record = await MemberRecordStore.getRecord(recordId);
|
|
989
|
+
if (!record) {
|
|
990
|
+
return {
|
|
991
|
+
canAccess: false,
|
|
992
|
+
record: null,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
1062
995
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
996
|
+
if (!this.checkScope(record.organizationId)) {
|
|
997
|
+
return {
|
|
998
|
+
canAccess: false,
|
|
999
|
+
record: record.record,
|
|
1000
|
+
};
|
|
1067
1001
|
}
|
|
1068
1002
|
|
|
1069
1003
|
const isUserManager = this.isUserManager(member);
|
|
1070
|
-
|
|
1071
|
-
// Also include those we can access as user manager
|
|
1072
1004
|
if (isUserManager) {
|
|
1073
|
-
|
|
1005
|
+
if (record.record.checkPermissionForUserManager(level)) {
|
|
1006
|
+
return {
|
|
1007
|
+
canAccess: true,
|
|
1008
|
+
record: record.record,
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1074
1012
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1013
|
+
if (!this.user.permissions) {
|
|
1014
|
+
return {
|
|
1015
|
+
canAccess: false,
|
|
1016
|
+
record: record.record,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1077
1019
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1020
|
+
if (record.organizationId) {
|
|
1021
|
+
const organizationPermissions = await this.getOrganizationPermissions(record.organizationId);
|
|
1022
|
+
if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1023
|
+
return {
|
|
1024
|
+
canAccess: true,
|
|
1025
|
+
record: record.record,
|
|
1026
|
+
};
|
|
1082
1027
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
// 1. Check all organizations (they can give permissions)
|
|
1031
|
+
for (const organizationId of this.user.permissions.organizationPermissions.keys()) {
|
|
1032
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId);
|
|
1033
|
+
if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1034
|
+
return {
|
|
1035
|
+
canAccess: true,
|
|
1036
|
+
record: record.record,
|
|
1037
|
+
};
|
|
1089
1038
|
}
|
|
1090
1039
|
}
|
|
1040
|
+
}
|
|
1091
1041
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1042
|
+
// 2. Check platform permissions
|
|
1043
|
+
if (this.platformPermissions?.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1044
|
+
return {
|
|
1045
|
+
canAccess: true,
|
|
1046
|
+
record: record.record,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1095
1049
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1050
|
+
// It is possible that this is a platform admin, and inherits automatic permissions for tags. So'll need to loop all the organizations where this member has an active registration for
|
|
1051
|
+
if (!record.organizationId && this.platformPermissions) {
|
|
1052
|
+
const organizations = Formatter.uniqueArray(member.registrations.map(r => r.organizationId));
|
|
1053
|
+
for (const organizationId of organizations) {
|
|
1054
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId);
|
|
1055
|
+
if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1056
|
+
return {
|
|
1057
|
+
canAccess: true,
|
|
1058
|
+
record: record.record,
|
|
1059
|
+
};
|
|
1101
1060
|
}
|
|
1102
1061
|
}
|
|
1103
1062
|
}
|
|
1104
1063
|
|
|
1105
|
-
return
|
|
1064
|
+
return {
|
|
1065
|
+
canAccess: false,
|
|
1066
|
+
record: record.record,
|
|
1067
|
+
};
|
|
1106
1068
|
}
|
|
1107
1069
|
|
|
1108
1070
|
async getAccessibleGroups(organizationId: string, level: PermissionLevel = PermissionLevel.Read): Promise<string[] | 'all'> {
|
|
@@ -1125,22 +1087,27 @@ export class AdminPermissionChecker {
|
|
|
1125
1087
|
* Changes data inline
|
|
1126
1088
|
*/
|
|
1127
1089
|
async filterMemberData(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob): Promise<MemberWithRegistrationsBlob> {
|
|
1128
|
-
const records = await this.getAccessibleRecordSet(member, PermissionLevel.Read);
|
|
1129
|
-
|
|
1130
1090
|
const cloned = data.clone();
|
|
1131
1091
|
|
|
1132
1092
|
for (const [key, value] of cloned.details.recordAnswers.entries()) {
|
|
1133
|
-
|
|
1093
|
+
const { canAccess, record } = await this.checkRecordAccess(member, key, PermissionLevel.Read);
|
|
1094
|
+
if (!canAccess) {
|
|
1134
1095
|
cloned.details.recordAnswers.delete(key);
|
|
1135
1096
|
}
|
|
1097
|
+
else {
|
|
1098
|
+
if (value) {
|
|
1099
|
+
// Force update
|
|
1100
|
+
value.settings = record;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1136
1103
|
}
|
|
1137
1104
|
|
|
1138
1105
|
const isUserManager = this.isUserManager(member);
|
|
1139
1106
|
if (isUserManager) {
|
|
1140
1107
|
// For a user manager without an organization, we don't delete data, because when registering a new member, it doesn't have any organizations yet...
|
|
1141
1108
|
if (!(await this.canAccessMember(member, PermissionLevel.Full))) {
|
|
1142
|
-
cloned.details.securityCode = null;
|
|
1143
1109
|
cloned.details.notes = null;
|
|
1110
|
+
// a user manager can see the security codes
|
|
1144
1111
|
}
|
|
1145
1112
|
|
|
1146
1113
|
return cloned;
|
|
@@ -1227,39 +1194,20 @@ export class AdminPermissionChecker {
|
|
|
1227
1194
|
});
|
|
1228
1195
|
}
|
|
1229
1196
|
|
|
1230
|
-
const records = await this.getAccessibleRecordSet(member, PermissionLevel.Write);
|
|
1231
|
-
|
|
1232
1197
|
for (const [key, value] of data.details.recordAnswers.entries()) {
|
|
1233
|
-
|
|
1234
|
-
if (
|
|
1235
|
-
if (value.isPatch()) {
|
|
1236
|
-
throw new SimpleError({
|
|
1237
|
-
code: 'invalid_request',
|
|
1238
|
-
message: 'Cannot PATCH a record answer object',
|
|
1239
|
-
statusCode: 400,
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
const id = value.settings.id;
|
|
1244
|
-
|
|
1245
|
-
if (id !== key) {
|
|
1246
|
-
throw new SimpleError({
|
|
1247
|
-
code: 'invalid_request',
|
|
1248
|
-
message: 'Record answer key does not match record id',
|
|
1249
|
-
statusCode: 400,
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
name = value.settings.name;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
if (!records.has(key)) {
|
|
1198
|
+
const { canAccess, record } = await this.checkRecordAccess(member, key, PermissionLevel.Write);
|
|
1199
|
+
if (!canAccess) {
|
|
1257
1200
|
throw new SimpleError({
|
|
1258
1201
|
code: 'permission_denied',
|
|
1259
|
-
message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
|
|
1202
|
+
message: `Je hebt geen toegangsrechten om het antwoord op ${record?.name ?? 'deze vraag'} aan te passen voor dit lid`,
|
|
1260
1203
|
statusCode: 400,
|
|
1261
1204
|
});
|
|
1262
1205
|
}
|
|
1206
|
+
|
|
1207
|
+
// Force set the value settings
|
|
1208
|
+
if (value) {
|
|
1209
|
+
value.settings = record;
|
|
1210
|
+
}
|
|
1263
1211
|
}
|
|
1264
1212
|
}
|
|
1265
1213
|
|