@stamhoofd/backend 2.105.0 → 2.106.1

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.
Files changed (28) hide show
  1. package/package.json +10 -10
  2. package/src/crons.ts +39 -5
  3. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +953 -47
  4. package/src/endpoints/global/members/GetMembersEndpoint.ts +1 -1
  5. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +142 -0
  6. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +1 -1
  7. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +163 -8
  8. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
  9. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.test.ts +108 -0
  10. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +40 -0
  11. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
  12. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -1
  13. package/src/helpers/AdminPermissionChecker.ts +30 -6
  14. package/src/helpers/AuthenticatedStructures.ts +2 -2
  15. package/src/helpers/MemberUserSyncer.test.ts +400 -1
  16. package/src/helpers/MemberUserSyncer.ts +15 -10
  17. package/src/helpers/ServiceFeeHelper.ts +63 -0
  18. package/src/helpers/StripeHelper.ts +7 -4
  19. package/src/helpers/StripePayoutChecker.ts +1 -1
  20. package/src/seeds/0000000001-development-user.ts +2 -2
  21. package/src/seeds/0000000004-single-organization.ts +60 -0
  22. package/src/seeds/1754560914-groups-prices.test.ts +3023 -0
  23. package/src/seeds/1754560914-groups-prices.ts +408 -0
  24. package/src/seeds/{1722344162-sync-member-users.ts → 1761665607-sync-member-users.ts} +1 -1
  25. package/src/sql-filters/members.ts +1 -1
  26. package/tests/init/initAdmin.ts +19 -5
  27. package/tests/init/initPermissionRole.ts +14 -4
  28. package/tests/init/initPlatformRecordCategory.ts +8 -0
@@ -1,9 +1,10 @@
1
1
  import { Endpoint, Request } from '@simonbackx/simple-endpoints';
2
- import { EventFactory, GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
- import { AccessRight, EventMeta, GroupType, LimitedFilteredRequest, NamedObject, PermissionLevel, PermissionRoleDetailed, Permissions, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
2
+ import { EventFactory, GroupFactory, MemberFactory, OrganizationFactory, RecordCategoryFactory, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
+ import { AccessRight, EventMeta, GroupType, LimitedFilteredRequest, NamedObject, PermissionLevel, PermissionRoleDetailed, Permissions, PermissionsResourceType, RecordAnswer, RecordTextAnswer, RecordType, ResourcePermissions } from '@stamhoofd/structures';
4
4
  import { STExpect, TestUtils } from '@stamhoofd/test-utils';
5
5
  import { GetMembersEndpoint } from './GetMembersEndpoint';
6
6
  import { testServer } from '../../../../tests/helpers/TestServer';
7
+ import { initPlatformRecordCategory } from '../../../../tests/init/initPlatformRecordCategory';
7
8
 
8
9
  const baseUrl = `/members`;
9
10
  const endpoint = new GetMembersEndpoint();
@@ -884,14 +885,572 @@ describe('Endpoint.GetMembersEndpoint', () => {
884
885
  STExpect.errorWithCode('permission_denied'),
885
886
  );
886
887
  });
888
+
889
+ test('Not allowed: A user cannot filter on record answer if no permission for that category', async () => {
890
+ // Same test, but without giving the user permissions to read the group
891
+ // Setup
892
+ const role = PermissionRoleDetailed.create({
893
+ name: 'Test Role',
894
+ accessRights: [],
895
+ });
896
+
897
+ const resources = new Map();
898
+
899
+ const organization = await new OrganizationFactory({ period, roles: [role] })
900
+ .create();
901
+
902
+ const recordCategory = await new RecordCategoryFactory({
903
+ records: [
904
+ {
905
+ type: RecordType.Text,
906
+ },
907
+ ],
908
+ }).create();
909
+
910
+ await initPlatformRecordCategory({ recordCategory });
911
+ const record = recordCategory.records[0];
912
+
913
+ const user = await new UserFactory({
914
+ organization,
915
+ permissions: Permissions.create({
916
+ level: PermissionLevel.None,
917
+ roles: [
918
+ role,
919
+ ],
920
+ resources,
921
+ }),
922
+ })
923
+ .create();
924
+
925
+ const token = await Token.createToken(user);
926
+ const member1 = await new MemberFactory({ }).create();
927
+ const member2 = await new MemberFactory({ }).create();
928
+
929
+ // Make sure member1 has answered the question
930
+ const answer = RecordTextAnswer.create({ settings: record });
931
+ answer.value = 'This has been answered';
932
+ member1.details.recordAnswers.set(record.id, answer);
933
+ await member1.save();
934
+
935
+ const group = await new GroupFactory({ organization, period }).create();
936
+
937
+ // The user can read members registered for default group, but not events for default group
938
+ resources.set(
939
+ PermissionsResourceType.Groups, new Map([[
940
+ group.id,
941
+ ResourcePermissions.create({
942
+ level: PermissionLevel.Read,
943
+ accessRights: [],
944
+ }),
945
+ ]]),
946
+ );
947
+
948
+ await user.save();
949
+
950
+ await new RegistrationFactory({ member: member1, group }).create();
951
+ await new RegistrationFactory({ member: member2, group }).create();
952
+
953
+ // Try to request members for this group
954
+ const request = Request.get({
955
+ path: baseUrl,
956
+ host: organization.getApiHost(),
957
+ query: new LimitedFilteredRequest({
958
+ filter: {
959
+ registrations: {
960
+ $elemMatch: {
961
+ groupId: group.id,
962
+ },
963
+ },
964
+ details: {
965
+ recordAnswers: {
966
+ [record.id]: {
967
+ value: {
968
+ $contains: 'has been',
969
+ },
970
+ },
971
+ },
972
+ },
973
+ },
974
+ limit: 10,
975
+ }),
976
+ headers: {
977
+ authorization: 'Bearer ' + token.accessToken,
978
+ },
979
+ });
980
+
981
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(
982
+ STExpect.errorWithCode('permission_denied'),
983
+ );
984
+ });
985
+
986
+ test('Allowed: A user can filter on record answer if permission for that category', async () => {
987
+ const resources = new Map();
988
+
989
+ const organization = await new OrganizationFactory({ period, roles: [] })
990
+ .create();
991
+
992
+ const recordCategory = await new RecordCategoryFactory({
993
+ records: [
994
+ {
995
+ type: RecordType.Text,
996
+ },
997
+ ],
998
+ }).create();
999
+
1000
+ await initPlatformRecordCategory({ recordCategory });
1001
+ const record = recordCategory.records[0];
1002
+
1003
+ const user = await new UserFactory({
1004
+ organization,
1005
+ permissions: Permissions.create({
1006
+ level: PermissionLevel.None,
1007
+ roles: [],
1008
+ resources,
1009
+ }),
1010
+ })
1011
+ .create();
1012
+
1013
+ const token = await Token.createToken(user);
1014
+ const member1 = await new MemberFactory({ }).create();
1015
+ const member2 = await new MemberFactory({ }).create();
1016
+
1017
+ // Make sure member1 has answered the question
1018
+ const answer = RecordTextAnswer.create({ settings: record });
1019
+ answer.value = 'This has been answered';
1020
+ member1.details.recordAnswers.set(record.id, answer);
1021
+ await member1.save();
1022
+
1023
+ const group = await new GroupFactory({ organization, period }).create();
1024
+
1025
+ // The user can read members registered for default group, but not events for default group
1026
+ resources.set(
1027
+ PermissionsResourceType.Groups, new Map([[
1028
+ group.id,
1029
+ ResourcePermissions.create({
1030
+ level: PermissionLevel.Read,
1031
+ accessRights: [],
1032
+ }),
1033
+ ]]),
1034
+ );
1035
+
1036
+ resources.set(
1037
+ PermissionsResourceType.RecordCategories, new Map([[
1038
+ recordCategory.id,
1039
+ ResourcePermissions.create({
1040
+ level: PermissionLevel.Read,
1041
+ accessRights: [],
1042
+ }),
1043
+ ]]),
1044
+ );
1045
+
1046
+ await user.save();
1047
+
1048
+ await new RegistrationFactory({ member: member1, group }).create();
1049
+ await new RegistrationFactory({ member: member2, group }).create();
1050
+
1051
+ // Try to request members for this group
1052
+ const request = Request.get({
1053
+ path: baseUrl,
1054
+ host: organization.getApiHost(),
1055
+ query: new LimitedFilteredRequest({
1056
+ filter: {
1057
+ registrations: {
1058
+ $elemMatch: {
1059
+ groupId: group.id,
1060
+ },
1061
+ },
1062
+ details: {
1063
+ recordAnswers: {
1064
+ [record.id]: {
1065
+ value: {
1066
+ $contains: 'has been',
1067
+ },
1068
+ },
1069
+ },
1070
+ },
1071
+ },
1072
+ limit: 10,
1073
+ }),
1074
+ headers: {
1075
+ authorization: 'Bearer ' + token.accessToken,
1076
+ },
1077
+ });
1078
+
1079
+ // Response only includes members registered in a membership group, not the event group
1080
+ const response = await testServer.test(endpoint, request);
1081
+ expect(response.status).toBe(200);
1082
+ expect(response.body.results.members).toHaveLength(1);
1083
+ // Check ids are matching without depending on ordering using jest extended
1084
+ expect(response.body.results.members).toIncludeSameMembers([
1085
+ expect.objectContaining({ id: member1.id }),
1086
+ ]);
1087
+ });
1088
+
1089
+ test('Allowed: A platform admin can filter on record answer if permission for all members', async () => {
1090
+ const resources = new Map();
1091
+
1092
+ const organization = await new OrganizationFactory({ period, roles: [] })
1093
+ .create();
1094
+
1095
+ const recordCategory = await new RecordCategoryFactory({
1096
+ records: [
1097
+ {
1098
+ type: RecordType.Text,
1099
+ },
1100
+ ],
1101
+ }).create();
1102
+
1103
+ await initPlatformRecordCategory({ recordCategory });
1104
+ const record = recordCategory.records[0];
1105
+
1106
+ const user = await new UserFactory({
1107
+ globalPermissions: Permissions.create({
1108
+ level: PermissionLevel.None,
1109
+ roles: [],
1110
+ resources,
1111
+ }),
1112
+ })
1113
+ .create();
1114
+
1115
+ const token = await Token.createToken(user);
1116
+ const member1 = await new MemberFactory({ }).create();
1117
+ const member2 = await new MemberFactory({ }).create();
1118
+
1119
+ // Make sure member1 has answered the question
1120
+ const answer = RecordTextAnswer.create({ settings: record });
1121
+ answer.value = 'This has been answered';
1122
+ member1.details.recordAnswers.set(record.id, answer);
1123
+ await member1.save();
1124
+
1125
+ const group = await new GroupFactory({ organization, period }).create();
1126
+
1127
+ resources.set(
1128
+ PermissionsResourceType.OrganizationTags, new Map([[
1129
+ '',
1130
+ ResourcePermissions.create({
1131
+ level: PermissionLevel.Full,
1132
+ accessRights: [],
1133
+ }),
1134
+ ]]),
1135
+ );
1136
+
1137
+ await user.save();
1138
+
1139
+ await new RegistrationFactory({ member: member1, group }).create();
1140
+ await new RegistrationFactory({ member: member2, group }).create();
1141
+
1142
+ // Try to request members for this group
1143
+ const request = Request.get({
1144
+ path: baseUrl,
1145
+ host: organization.getApiHost(),
1146
+ query: new LimitedFilteredRequest({
1147
+ filter: {
1148
+ registrations: {
1149
+ $elemMatch: {
1150
+ groupId: group.id,
1151
+ },
1152
+ },
1153
+ details: {
1154
+ recordAnswers: {
1155
+ [record.id]: {
1156
+ value: {
1157
+ $contains: 'has been',
1158
+ },
1159
+ },
1160
+ },
1161
+ },
1162
+ },
1163
+ limit: 10,
1164
+ }),
1165
+ headers: {
1166
+ authorization: 'Bearer ' + token.accessToken,
1167
+ },
1168
+ });
1169
+
1170
+ // Response only includes members registered in a membership group, not the event group
1171
+ const response = await testServer.test(endpoint, request);
1172
+ expect(response.status).toBe(200);
1173
+ expect(response.body.results.members).toHaveLength(1);
1174
+ // Check ids are matching without depending on ordering using jest extended
1175
+ expect(response.body.results.members).toIncludeSameMembers([
1176
+ expect.objectContaining({ id: member1.id }),
1177
+ ]);
1178
+ });
1179
+
1180
+ test('Not allowed: A platform admin cannot filter on record answer if no permission for all members', async () => {
1181
+ const resources = new Map();
1182
+
1183
+ const organization = await new OrganizationFactory({ period, roles: [] })
1184
+ .create();
1185
+
1186
+ const recordCategory = await new RecordCategoryFactory({
1187
+ records: [
1188
+ {
1189
+ type: RecordType.Text,
1190
+ },
1191
+ ],
1192
+ }).create();
1193
+
1194
+ await initPlatformRecordCategory({ recordCategory });
1195
+ const record = recordCategory.records[0];
1196
+ const group = await new GroupFactory({ organization, period }).create();
1197
+
1198
+ const user = await new UserFactory({
1199
+ organization,
1200
+ permissions: Permissions.create({
1201
+ level: PermissionLevel.None,
1202
+ resources: new Map([[
1203
+ PermissionsResourceType.Groups, new Map([[
1204
+ '',
1205
+ ResourcePermissions.create({
1206
+ level: PermissionLevel.Full,
1207
+ accessRights: [],
1208
+ }),
1209
+ ]]),
1210
+ ]]),
1211
+ }),
1212
+ globalPermissions: Permissions.create({
1213
+ level: PermissionLevel.None,
1214
+ roles: [],
1215
+ resources,
1216
+ }),
1217
+ })
1218
+ .create();
1219
+
1220
+ const token = await Token.createToken(user);
1221
+ const member1 = await new MemberFactory({ }).create();
1222
+ const member2 = await new MemberFactory({ }).create();
1223
+
1224
+ // Make sure member1 has answered the question
1225
+ const answer = RecordTextAnswer.create({ settings: record });
1226
+ answer.value = 'This has been answered';
1227
+ member1.details.recordAnswers.set(record.id, answer);
1228
+ await member1.save();
1229
+
1230
+ resources.set(
1231
+ PermissionsResourceType.OrganizationTags, new Map([[
1232
+ 'tagtest',
1233
+ ResourcePermissions.create({
1234
+ level: PermissionLevel.Full,
1235
+ accessRights: [],
1236
+ }),
1237
+ ]]),
1238
+ );
1239
+
1240
+ await user.save();
1241
+
1242
+ await new RegistrationFactory({ member: member1, group }).create();
1243
+ await new RegistrationFactory({ member: member2, group }).create();
1244
+
1245
+ // Try to request members for this group
1246
+ const request = Request.get({
1247
+ path: baseUrl,
1248
+ host: organization.getApiHost(),
1249
+ query: new LimitedFilteredRequest({
1250
+ filter: {
1251
+ registrations: {
1252
+ $elemMatch: {
1253
+ groupId: group.id,
1254
+ },
1255
+ },
1256
+ details: {
1257
+ recordAnswers: {
1258
+ [record.id]: {
1259
+ value: {
1260
+ $contains: 'has been',
1261
+ },
1262
+ },
1263
+ },
1264
+ },
1265
+ },
1266
+ limit: 10,
1267
+ }),
1268
+ headers: {
1269
+ authorization: 'Bearer ' + token.accessToken,
1270
+ },
1271
+ });
1272
+
1273
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(
1274
+ STExpect.errorWithCode('permission_denied'),
1275
+ );
1276
+ });
1277
+ });
1278
+
1279
+ describe('Default filtering', () => {
1280
+ test('A user without read permissions for all groups will only see members of membership groups', async () => {
1281
+ // Same test, but without giving the user permissions to read the group
1282
+ // Setup
1283
+ const role = PermissionRoleDetailed.create({
1284
+ name: 'Test Role',
1285
+ accessRights: [],
1286
+ });
1287
+
1288
+ const resources = new Map();
1289
+
1290
+ const organization = await new OrganizationFactory({ period, roles: [role] })
1291
+ .create();
1292
+
1293
+ const user = await new UserFactory({
1294
+ organization,
1295
+ permissions: Permissions.create({
1296
+ level: PermissionLevel.None,
1297
+ roles: [
1298
+ role,
1299
+ ],
1300
+ resources,
1301
+ }),
1302
+ })
1303
+ .create();
1304
+
1305
+ const token = await Token.createToken(user);
1306
+ const member1 = await new MemberFactory({ }).create();
1307
+ const member2 = await new MemberFactory({ }).create();
1308
+ const member3 = await new MemberFactory({ }).create();
1309
+ const member4 = await new MemberFactory({ }).create();
1310
+ const group = await new GroupFactory({ organization, period }).create();
1311
+ const group2 = await new GroupFactory({ organization, period }).create();
1312
+ const group3 = await new GroupFactory({ organization, period }).create();
1313
+
1314
+ const group4 = await new GroupFactory({ organization, period, type: GroupType.EventRegistration }).create();
1315
+
1316
+ // Give permission for 2 / 3 groups + one event group
1317
+ resources.set(
1318
+ PermissionsResourceType.Groups, new Map([[
1319
+ group.id,
1320
+ ResourcePermissions.create({
1321
+ level: PermissionLevel.Read,
1322
+ }),
1323
+ ], [
1324
+ group2.id,
1325
+ ResourcePermissions.create({
1326
+ level: PermissionLevel.Write,
1327
+ }),
1328
+ ], [
1329
+ group4.id,
1330
+ ResourcePermissions.create({
1331
+ level: PermissionLevel.Read,
1332
+ }),
1333
+ ]]),
1334
+ );
1335
+
1336
+ await user.save();
1337
+
1338
+ await new RegistrationFactory({ member: member1, group }).create();
1339
+ await new RegistrationFactory({ member: member2, group: group2 }).create();
1340
+ await new RegistrationFactory({ member: member3, group: group3 }).create();
1341
+ await new RegistrationFactory({ member: member4, group: group4 }).create();
1342
+
1343
+ // Try to request all members
1344
+ const request = Request.get({
1345
+ path: baseUrl,
1346
+ host: organization.getApiHost(),
1347
+ query: new LimitedFilteredRequest({
1348
+ limit: 10,
1349
+ }),
1350
+ headers: {
1351
+ authorization: 'Bearer ' + token.accessToken,
1352
+ },
1353
+ });
1354
+
1355
+ // Response only includes members registered in a membership group, not the event group
1356
+ const response = await testServer.test(endpoint, request);
1357
+ expect(response.status).toBe(200);
1358
+ expect(response.body.results.members).toHaveLength(2);
1359
+ // Check ids are matching without depending on ordering using jest extended
1360
+ expect(response.body.results.members).toIncludeSameMembers([
1361
+ expect.objectContaining({ id: member1.id }),
1362
+ expect.objectContaining({ id: member2.id }),
1363
+ ]);
1364
+ });
1365
+
1366
+ test('A user with read permissions for all groups will also see members of event groups', async () => {
1367
+ // Same test, but without giving the user permissions to read the group
1368
+ // Setup
1369
+ const role = PermissionRoleDetailed.create({
1370
+ name: 'Test Role',
1371
+ accessRights: [],
1372
+ });
1373
+
1374
+ const resources = new Map();
1375
+
1376
+ const organization = await new OrganizationFactory({ period, roles: [role] })
1377
+ .create();
1378
+
1379
+ const user = await new UserFactory({
1380
+ organization,
1381
+ permissions: Permissions.create({
1382
+ level: PermissionLevel.None,
1383
+ roles: [
1384
+ role,
1385
+ ],
1386
+ resources,
1387
+ }),
1388
+ })
1389
+ .create();
1390
+
1391
+ const token = await Token.createToken(user);
1392
+ const member1 = await new MemberFactory({ }).create();
1393
+ const member2 = await new MemberFactory({ }).create();
1394
+ const member3 = await new MemberFactory({ }).create();
1395
+ const member4 = await new MemberFactory({ }).create();
1396
+ const group = await new GroupFactory({ organization, period }).create();
1397
+ const group2 = await new GroupFactory({ organization, period }).create();
1398
+ const group3 = await new GroupFactory({ organization, period }).create();
1399
+
1400
+ const group4 = await new GroupFactory({ organization, period, type: GroupType.EventRegistration }).create();
1401
+
1402
+ // Give permission to all groups
1403
+ resources.set(
1404
+ PermissionsResourceType.Groups, new Map([[
1405
+ '',
1406
+ ResourcePermissions.create({
1407
+ level: PermissionLevel.Read,
1408
+ }),
1409
+ ]]),
1410
+ );
1411
+
1412
+ await user.save();
1413
+
1414
+ await new RegistrationFactory({ member: member1, group }).create();
1415
+ await new RegistrationFactory({ member: member2, group: group2 }).create();
1416
+ await new RegistrationFactory({ member: member3, group: group3 }).create();
1417
+ await new RegistrationFactory({ member: member4, group: group4 }).create();
1418
+
1419
+ // Try to request all members
1420
+ const request = Request.get({
1421
+ path: baseUrl,
1422
+ host: organization.getApiHost(),
1423
+ query: new LimitedFilteredRequest({
1424
+ limit: 10,
1425
+ }),
1426
+ headers: {
1427
+ authorization: 'Bearer ' + token.accessToken,
1428
+ },
1429
+ });
1430
+
1431
+ // Response only includes members registered in a membership group, not the event group
1432
+ const response = await testServer.test(endpoint, request);
1433
+ expect(response.status).toBe(200);
1434
+ expect(response.body.results.members).toHaveLength(4);
1435
+ // Check ids are matching without depending on ordering using jest extended
1436
+ expect(response.body.results.members).toIncludeSameMembers([
1437
+ expect.objectContaining({ id: member1.id }),
1438
+ expect.objectContaining({ id: member2.id }),
1439
+ expect.objectContaining({ id: member3.id }),
1440
+ expect.objectContaining({ id: member4.id }),
1441
+ ]);
1442
+ });
887
1443
  });
888
1444
 
889
- describe('Default filtering', () => {
890
- test('A user without read permissions for all groups will only see members of membership groups', async () => {
891
- // Same test, but without giving the user permissions to read the group
892
- // Setup
1445
+ describe('Record answer filtering', () => {
1446
+ test('[REGRESSION] A user with minimal access can also view platform record answers in platform scope', async () => {
1447
+ /**
1448
+ * When fetching members via the admin api, without organization scope, we need to calculate which records to return and which not.
1449
+ * This test makes sure we check all registrations of the member to know whether we can return a platform record cateogry answer.
1450
+ */
893
1451
  const role = PermissionRoleDetailed.create({
894
- name: 'Test Role',
1452
+ name: 'Stamhoofd verantwoordelijke',
1453
+ level: PermissionLevel.None,
895
1454
  accessRights: [],
896
1455
  });
897
1456
 
@@ -900,6 +1459,28 @@ describe('Endpoint.GetMembersEndpoint', () => {
900
1459
  const organization = await new OrganizationFactory({ period, roles: [role] })
901
1460
  .create();
902
1461
 
1462
+ const recordCategory = await new RecordCategoryFactory({
1463
+ records: [
1464
+ {
1465
+ type: RecordType.Text,
1466
+ },
1467
+ ],
1468
+ }).create();
1469
+
1470
+ // Add a record category the admin does not have access to
1471
+ const controlRecordCategory = await new RecordCategoryFactory({
1472
+ records: [
1473
+ {
1474
+ type: RecordType.Text,
1475
+ },
1476
+ ],
1477
+ }).create();
1478
+
1479
+ await initPlatformRecordCategory({ recordCategory });
1480
+ await initPlatformRecordCategory({ recordCategory: controlRecordCategory });
1481
+ const record = recordCategory.records[0];
1482
+ const controlRecord = controlRecordCategory.records[0];
1483
+
903
1484
  const user = await new UserFactory({
904
1485
  organization,
905
1486
  permissions: Permissions.create({
@@ -914,29 +1495,32 @@ describe('Endpoint.GetMembersEndpoint', () => {
914
1495
 
915
1496
  const token = await Token.createToken(user);
916
1497
  const member1 = await new MemberFactory({ }).create();
917
- const member2 = await new MemberFactory({ }).create();
918
- const member3 = await new MemberFactory({ }).create();
919
- const member4 = await new MemberFactory({ }).create();
920
1498
  const group = await new GroupFactory({ organization, period }).create();
921
- const group2 = await new GroupFactory({ organization, period }).create();
922
- const group3 = await new GroupFactory({ organization, period }).create();
923
1499
 
924
- const group4 = await new GroupFactory({ organization, period, type: GroupType.EventRegistration }).create();
1500
+ // Make sure member1 has answered the question
1501
+ const answer = RecordTextAnswer.create({ settings: record });
1502
+ answer.value = 'This has been answered';
1503
+ member1.details.recordAnswers.set(record.id, answer);
925
1504
 
926
- // Give permission for 2 / 3 groups + one event group
1505
+ const controlAnswer = RecordTextAnswer.create({ settings: controlRecord });
1506
+ controlAnswer.value = 'This should be invisible';
1507
+ member1.details.recordAnswers.set(controlRecord.id, controlAnswer);
1508
+ await member1.save();
1509
+
1510
+ // Give read permission to the group
927
1511
  resources.set(
928
1512
  PermissionsResourceType.Groups, new Map([[
929
1513
  group.id,
930
1514
  ResourcePermissions.create({
931
1515
  level: PermissionLevel.Read,
932
1516
  }),
933
- ], [
934
- group2.id,
935
- ResourcePermissions.create({
936
- level: PermissionLevel.Write,
937
- }),
938
- ], [
939
- group4.id,
1517
+ ]]),
1518
+ );
1519
+
1520
+ // Give permission to read the record category
1521
+ resources.set(
1522
+ PermissionsResourceType.RecordCategories, new Map([[
1523
+ recordCategory.id,
940
1524
  ResourcePermissions.create({
941
1525
  level: PermissionLevel.Read,
942
1526
  }),
@@ -945,17 +1529,21 @@ describe('Endpoint.GetMembersEndpoint', () => {
945
1529
 
946
1530
  await user.save();
947
1531
 
1532
+ // Register the memebr for group
948
1533
  await new RegistrationFactory({ member: member1, group }).create();
949
- await new RegistrationFactory({ member: member2, group: group2 }).create();
950
- await new RegistrationFactory({ member: member3, group: group3 }).create();
951
- await new RegistrationFactory({ member: member4, group: group4 }).create();
952
1534
 
953
1535
  // Try to request all members
954
1536
  const request = Request.get({
955
1537
  path: baseUrl,
956
- host: organization.getApiHost(),
957
1538
  query: new LimitedFilteredRequest({
958
1539
  limit: 10,
1540
+ filter: {
1541
+ registrations: {
1542
+ $elemMatch: {
1543
+ groupId: group.id,
1544
+ },
1545
+ },
1546
+ },
959
1547
  }),
960
1548
  headers: {
961
1549
  authorization: 'Bearer ' + token.accessToken,
@@ -965,19 +1553,33 @@ describe('Endpoint.GetMembersEndpoint', () => {
965
1553
  // Response only includes members registered in a membership group, not the event group
966
1554
  const response = await testServer.test(endpoint, request);
967
1555
  expect(response.status).toBe(200);
968
- expect(response.body.results.members).toHaveLength(2);
1556
+ expect(response.body.results.members).toHaveLength(1);
969
1557
  // Check ids are matching without depending on ordering using jest extended
970
1558
  expect(response.body.results.members).toIncludeSameMembers([
971
1559
  expect.objectContaining({ id: member1.id }),
972
- expect.objectContaining({ id: member2.id }),
973
1560
  ]);
1561
+
1562
+ const returnedMember = response.body.results.members[0];
1563
+
1564
+ // Check only one record answer returned
1565
+ expect(returnedMember.details.recordAnswers.size).toEqual(1);
1566
+
1567
+ expect(returnedMember.details.recordAnswers.get(record.id)).toMatchObject({
1568
+ value: 'This has been answered',
1569
+ settings: expect.objectContaining({
1570
+ id: record.id,
1571
+ }),
1572
+ });
974
1573
  });
975
1574
 
976
- test('A user with read permissions for all groups will also see members of event groups', async () => {
977
- // Same test, but without giving the user permissions to read the group
978
- // Setup
1575
+ test('[REGRESSION] A user with full access to a single organization can also view platform record answers in platform scope', async () => {
1576
+ /**
1577
+ * When fetching members via the admin api, without organization scope, we need to calculate which records to return and which not.
1578
+ * This test makes sure we check all registrations of the member to know whether we can return a platform record cateogry answer.
1579
+ */
979
1580
  const role = PermissionRoleDetailed.create({
980
- name: 'Test Role',
1581
+ name: 'Stamhoofd verantwoordelijke',
1582
+ level: PermissionLevel.Full,
981
1583
  accessRights: [],
982
1584
  });
983
1585
 
@@ -986,6 +1588,28 @@ describe('Endpoint.GetMembersEndpoint', () => {
986
1588
  const organization = await new OrganizationFactory({ period, roles: [role] })
987
1589
  .create();
988
1590
 
1591
+ const recordCategory = await new RecordCategoryFactory({
1592
+ records: [
1593
+ {
1594
+ type: RecordType.Text,
1595
+ },
1596
+ ],
1597
+ }).create();
1598
+
1599
+ // Add a record category the admin does not have access to
1600
+ const controlRecordCategory = await new RecordCategoryFactory({
1601
+ records: [
1602
+ {
1603
+ type: RecordType.Text,
1604
+ },
1605
+ ],
1606
+ }).create();
1607
+
1608
+ await initPlatformRecordCategory({ recordCategory });
1609
+ await initPlatformRecordCategory({ recordCategory: controlRecordCategory });
1610
+ const record = recordCategory.records[0];
1611
+ const controlRecord = controlRecordCategory.records[0];
1612
+
989
1613
  const user = await new UserFactory({
990
1614
  organization,
991
1615
  permissions: Permissions.create({
@@ -1000,38 +1624,188 @@ describe('Endpoint.GetMembersEndpoint', () => {
1000
1624
 
1001
1625
  const token = await Token.createToken(user);
1002
1626
  const member1 = await new MemberFactory({ }).create();
1003
- const member2 = await new MemberFactory({ }).create();
1004
- const member3 = await new MemberFactory({ }).create();
1005
- const member4 = await new MemberFactory({ }).create();
1006
1627
  const group = await new GroupFactory({ organization, period }).create();
1007
- const group2 = await new GroupFactory({ organization, period }).create();
1008
- const group3 = await new GroupFactory({ organization, period }).create();
1009
1628
 
1010
- const group4 = await new GroupFactory({ organization, period, type: GroupType.EventRegistration }).create();
1629
+ // Make sure member1 has answered the question
1630
+ const answer = RecordTextAnswer.create({ settings: record });
1631
+ answer.value = 'This has been answered';
1632
+ member1.details.recordAnswers.set(record.id, answer);
1011
1633
 
1012
- // Give permission to all groups
1634
+ const controlAnswer = RecordTextAnswer.create({ settings: controlRecord });
1635
+ controlAnswer.value = 'This should be invisible';
1636
+ member1.details.recordAnswers.set(controlRecord.id, controlAnswer);
1637
+ await member1.save();
1638
+
1639
+ // Register the memebr for group
1640
+ await new RegistrationFactory({ member: member1, group }).create();
1641
+
1642
+ // Try to request all members
1643
+ const request = Request.get({
1644
+ path: baseUrl,
1645
+ query: new LimitedFilteredRequest({
1646
+ limit: 10,
1647
+ filter: {
1648
+ registrations: {
1649
+ $elemMatch: {
1650
+ groupId: group.id,
1651
+ },
1652
+ },
1653
+ },
1654
+ }),
1655
+ headers: {
1656
+ authorization: 'Bearer ' + token.accessToken,
1657
+ },
1658
+ });
1659
+
1660
+ // Response only includes members registered in a membership group, not the event group
1661
+ const response = await testServer.test(endpoint, request);
1662
+ expect(response.status).toBe(200);
1663
+ expect(response.body.results.members).toHaveLength(1);
1664
+ // Check ids are matching without depending on ordering using jest extended
1665
+ expect(response.body.results.members).toIncludeSameMembers([
1666
+ expect.objectContaining({ id: member1.id }),
1667
+ ]);
1668
+
1669
+ const returnedMember = response.body.results.members[0];
1670
+
1671
+ // Check only one record answer returned
1672
+ expect(returnedMember.details.recordAnswers.size).toEqual(2);
1673
+ });
1674
+
1675
+ test('[REGRESSION] A user with full access to a single organization cannot view platform record answers in platform scope of member of different organization', async () => {
1676
+ /**
1677
+ * Case:
1678
+ * - organization1 gives read access to the member, not the record category
1679
+ * - organization2 gives read access to the member and record category
1680
+ *
1681
+ * member is registered at organization1, and at organization2 (but in a group the user does not have access to)
1682
+ * -> cannot see answer
1683
+ */
1684
+ const resources = new Map();
1685
+ const resources2 = new Map();
1686
+
1687
+ const organization = await new OrganizationFactory({ period, roles: [] }).create();
1688
+ const organization2 = await new OrganizationFactory({ period, roles: [] }).create();
1689
+
1690
+ const recordCategory = await new RecordCategoryFactory({
1691
+ records: [
1692
+ {
1693
+ type: RecordType.Text,
1694
+ },
1695
+ ],
1696
+ }).create();
1697
+
1698
+ // Add a record category the admin does not have access to
1699
+ const controlRecordCategory = await new RecordCategoryFactory({
1700
+ records: [
1701
+ {
1702
+ type: RecordType.Text,
1703
+ },
1704
+ ],
1705
+ }).create();
1706
+
1707
+ await initPlatformRecordCategory({ recordCategory });
1708
+ await initPlatformRecordCategory({ recordCategory: controlRecordCategory });
1709
+ const record = recordCategory.records[0];
1710
+ const controlRecord = controlRecordCategory.records[0];
1711
+
1712
+ const user = await new UserFactory({
1713
+ organization,
1714
+ permissions: Permissions.create({
1715
+ level: PermissionLevel.None,
1716
+ roles: [],
1717
+ resources,
1718
+ }),
1719
+ })
1720
+ .create();
1721
+
1722
+ const token = await Token.createToken(user);
1723
+ const member1 = await new MemberFactory({ }).create();
1724
+ const controlMember = await new MemberFactory({ }).create();
1725
+ const group = await new GroupFactory({ organization, period }).create();
1726
+ const unauthorizedGroup = await new GroupFactory({ organization, period }).create();
1727
+ const controlGroup = await new GroupFactory({ organization: organization2, period }).create();
1728
+
1729
+ // Make sure member1 has answered the question
1730
+ const answer = RecordTextAnswer.create({ settings: record });
1731
+ answer.value = 'This has been answered';
1732
+ member1.details.recordAnswers.set(record.id, answer);
1733
+
1734
+ const controlAnswer = RecordTextAnswer.create({ settings: controlRecord });
1735
+ controlAnswer.value = 'This should be invisible';
1736
+ member1.details.recordAnswers.set(controlRecord.id, controlAnswer);
1737
+ await member1.save();
1738
+
1739
+ // Make sure member1 has answered the question
1740
+ const controlMemberAnswer = RecordTextAnswer.create({ settings: record });
1741
+ controlMemberAnswer.value = 'This has been answered control';
1742
+ controlMember.details.recordAnswers.set(record.id, controlMemberAnswer);
1743
+
1744
+ const controlMemberControlAnswer = RecordTextAnswer.create({ settings: controlRecord });
1745
+ controlMemberControlAnswer.value = 'This should be invisible';
1746
+ controlMember.details.recordAnswers.set(controlRecord.id, controlMemberControlAnswer);
1747
+ await controlMember.save();
1748
+
1749
+ // Give read permission to the group
1013
1750
  resources.set(
1014
1751
  PermissionsResourceType.Groups, new Map([[
1015
- '',
1752
+ group.id,
1753
+ ResourcePermissions.create({
1754
+ level: PermissionLevel.Read,
1755
+ }),
1756
+ ]]),
1757
+ );
1758
+
1759
+ // Do not give permission to read the record category
1760
+
1761
+ // Give read permission to the control group
1762
+ resources2.set(
1763
+ PermissionsResourceType.Groups, new Map([[
1764
+ controlGroup.id,
1765
+ ResourcePermissions.create({
1766
+ level: PermissionLevel.Read,
1767
+ }),
1768
+ ]]),
1769
+ );
1770
+
1771
+ // Give permission to read the record category
1772
+ resources2.set(
1773
+ PermissionsResourceType.RecordCategories, new Map([[
1774
+ recordCategory.id,
1016
1775
  ResourcePermissions.create({
1017
1776
  level: PermissionLevel.Read,
1018
1777
  }),
1019
1778
  ]]),
1020
1779
  );
1021
1780
 
1781
+ // Add permission for organization2
1782
+ user.permissions!.organizationPermissions.set(organization2.id, Permissions.create({
1783
+ level: PermissionLevel.None,
1784
+ resources: resources2,
1785
+ }));
1022
1786
  await user.save();
1023
1787
 
1788
+ // Register the memebr for group
1024
1789
  await new RegistrationFactory({ member: member1, group }).create();
1025
- await new RegistrationFactory({ member: member2, group: group2 }).create();
1026
- await new RegistrationFactory({ member: member3, group: group3 }).create();
1027
- await new RegistrationFactory({ member: member4, group: group4 }).create();
1790
+ await new RegistrationFactory({ member: member1, group: unauthorizedGroup }).create();
1791
+ await new RegistrationFactory({ member: controlMember, group: controlGroup }).create();
1028
1792
 
1029
1793
  // Try to request all members
1030
1794
  const request = Request.get({
1031
1795
  path: baseUrl,
1032
- host: organization.getApiHost(),
1033
1796
  query: new LimitedFilteredRequest({
1034
1797
  limit: 10,
1798
+ filter: {
1799
+ registrations: {
1800
+ $elemMatch: {
1801
+ groupId: {
1802
+ $in: [
1803
+ group.id, controlGroup.id,
1804
+ ],
1805
+ },
1806
+ },
1807
+ },
1808
+ },
1035
1809
  }),
1036
1810
  headers: {
1037
1811
  authorization: 'Bearer ' + token.accessToken,
@@ -1041,13 +1815,145 @@ describe('Endpoint.GetMembersEndpoint', () => {
1041
1815
  // Response only includes members registered in a membership group, not the event group
1042
1816
  const response = await testServer.test(endpoint, request);
1043
1817
  expect(response.status).toBe(200);
1044
- expect(response.body.results.members).toHaveLength(4);
1818
+ expect(response.body.results.members).toHaveLength(2);
1045
1819
  // Check ids are matching without depending on ordering using jest extended
1046
1820
  expect(response.body.results.members).toIncludeSameMembers([
1047
1821
  expect.objectContaining({ id: member1.id }),
1048
- expect.objectContaining({ id: member2.id }),
1049
- expect.objectContaining({ id: member3.id }),
1050
- expect.objectContaining({ id: member4.id }),
1822
+ expect.objectContaining({ id: controlMember.id }),
1823
+ ]);
1824
+
1825
+ const returnedMember = response.body.results.members.find(m => m.id === member1.id)!;
1826
+ const returnedControlMember = response.body.results.members.find(m => m.id === controlMember.id)!;
1827
+
1828
+ expect(returnedMember).toBeDefined();
1829
+ expect(returnedControlMember).toBeDefined();
1830
+
1831
+ // Check only one record answer returned
1832
+ expect(returnedMember.details.recordAnswers.size).toEqual(0);
1833
+ expect(returnedControlMember.details.recordAnswers.size).toEqual(1);
1834
+
1835
+ expect(returnedControlMember.details.recordAnswers.get(record.id)).toMatchObject({
1836
+ value: 'This has been answered control',
1837
+ settings: expect.objectContaining({
1838
+ id: record.id,
1839
+ }),
1840
+ });
1841
+ });
1842
+ });
1843
+
1844
+ // Returned registrations in the members
1845
+ describe('Filtering registrations', () => {
1846
+ test('[REGRESSION] Deactivated registrations are returned when having access to that group', async () => {
1847
+ /**
1848
+ * Note: a deactivated registration doesn't give an admin access to a member, so we need an
1849
+ * extra registration so the admin does have acess to the member and can fetch it.
1850
+ * Next, we add two deactivated registrations: one for a group we have access to, one without. We should only see the one with access of course.
1851
+ */
1852
+
1853
+ const role = PermissionRoleDetailed.create({
1854
+ name: 'Stamhoofd verantwoordelijke',
1855
+ level: PermissionLevel.None,
1856
+ accessRights: [],
1857
+ });
1858
+
1859
+ const resources = new Map();
1860
+
1861
+ const organization = await new OrganizationFactory({ period, roles: [role] })
1862
+ .create();
1863
+ const member = await new MemberFactory({ }).create();
1864
+
1865
+ // Group we have access for, but with an active registration
1866
+ const accessGroup = await new GroupFactory({
1867
+ organization,
1868
+ period,
1869
+ }).create();
1870
+
1871
+ // Deactivated, with access
1872
+ const deactivatedGroup = await new GroupFactory({
1873
+ organization,
1874
+ period,
1875
+ }).create();
1876
+
1877
+ // Deactivated, without access
1878
+ const deactivatedControlGroup = await new GroupFactory({
1879
+ organization,
1880
+ period,
1881
+ }).create();
1882
+
1883
+ // Create 3 registrations with each group
1884
+ const accessRegistration = await new RegistrationFactory({
1885
+ group: accessGroup,
1886
+ member,
1887
+ }).create();
1888
+
1889
+ const deactivatedRegistration = await new RegistrationFactory({
1890
+ group: deactivatedGroup,
1891
+ member,
1892
+ deactivatedAt: new Date(),
1893
+ }).create();
1894
+
1895
+ // deactivatedControlRegistration
1896
+ await new RegistrationFactory({
1897
+ group: deactivatedControlGroup,
1898
+ member,
1899
+ deactivatedAt: new Date(),
1900
+ }).create();
1901
+
1902
+ // Give read permission to the group
1903
+ resources.set(
1904
+ PermissionsResourceType.Groups, new Map([[
1905
+ accessGroup.id,
1906
+ ResourcePermissions.create({
1907
+ level: PermissionLevel.Read,
1908
+ }),
1909
+ ], [
1910
+ deactivatedGroup.id, // not for the control group
1911
+ ResourcePermissions.create({
1912
+ level: PermissionLevel.Read,
1913
+ }),
1914
+ ]]),
1915
+ );
1916
+ const user = await new UserFactory({
1917
+ organization,
1918
+ permissions: Permissions.create({
1919
+ level: PermissionLevel.None,
1920
+ roles: [
1921
+ role,
1922
+ ],
1923
+ resources,
1924
+ }),
1925
+ })
1926
+ .create();
1927
+
1928
+ const token = await Token.createToken(user);
1929
+
1930
+ // Try to request all members at organization
1931
+ const request = Request.get({
1932
+ path: baseUrl,
1933
+ host: organization.getApiHost(),
1934
+ query: new LimitedFilteredRequest({
1935
+ limit: 10,
1936
+ }),
1937
+ headers: {
1938
+ authorization: 'Bearer ' + token.accessToken,
1939
+ },
1940
+ });
1941
+
1942
+ const response = await testServer.test(endpoint, request);
1943
+ expect(response.status).toBe(200);
1944
+ expect(response.body.results.members).toHaveLength(1);
1945
+ expect(response.body.results.members).toIncludeSameMembers([
1946
+ expect.objectContaining({ id: member.id }),
1947
+ ]);
1948
+
1949
+ const returnedMember = response.body.results.members[0];
1950
+
1951
+ // Check only one record answer returned
1952
+ expect(returnedMember.registrations.length).toEqual(2);
1953
+
1954
+ expect(returnedMember.registrations).toIncludeSameMembers([
1955
+ expect.objectContaining({ id: accessRegistration.id, deactivatedAt: null }),
1956
+ expect.objectContaining({ id: deactivatedRegistration.id, deactivatedAt: deactivatedRegistration.deactivatedAt }),
1051
1957
  ]);
1052
1958
  });
1053
1959
  });