@xen-orchestra/rest-api 0.28.2 → 0.30.0

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 (55) hide show
  1. package/README.md +108 -1
  2. package/dist/abstract-classes/base-controller.mjs +28 -3
  3. package/dist/abstract-classes/listener.mjs +124 -15
  4. package/dist/acl-privileges/acl-privilege.controller.mjs +172 -0
  5. package/dist/acl-roles/acl-role.controller.mjs +384 -0
  6. package/dist/alarms/alarm.controller.mjs +25 -11
  7. package/dist/alarms/alarm.service.mjs +8 -0
  8. package/dist/backup-archives/backup-archive.controller.mjs +33 -23
  9. package/dist/backup-archives/backup-archive.service.mjs +21 -0
  10. package/dist/backup-jobs/backup-job.controller.mjs +74 -25
  11. package/dist/backup-jobs/backup-job.service.mjs +7 -0
  12. package/dist/backup-logs/backup-log.controller.mjs +28 -13
  13. package/dist/backup-logs/backup-log.service.mjs +19 -0
  14. package/dist/backup-repositories/backup-repositories.controller.mjs +24 -5
  15. package/dist/events/event.class.mjs +36 -18
  16. package/dist/events/event.controller.mjs +3 -0
  17. package/dist/events/event.service.mjs +4 -4
  18. package/dist/groups/group.controller.mjs +99 -12
  19. package/dist/helpers/markdown.helper.mjs +20 -0
  20. package/dist/helpers/object-wrapper.helper.mjs +3 -3
  21. package/dist/hosts/host.controller.mjs +90 -15
  22. package/dist/ioc/ioc.mjs +13 -4
  23. package/dist/messages/message.controller.mjs +32 -10
  24. package/dist/middlewares/acl.middleware.mjs +202 -0
  25. package/dist/middlewares/authentication.middleware.mjs +15 -6
  26. package/dist/middlewares/tsoa-to-xo-error.middleware.mjs +19 -1
  27. package/dist/networks/network.controller.mjs +72 -17
  28. package/dist/open-api/oa-examples/acl-privilege.oa-example.mjs +25 -0
  29. package/dist/open-api/oa-examples/acl-role.oa-example.mjs +22 -0
  30. package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +6 -6
  31. package/dist/open-api/oa-examples/common.oa-example.mjs +3 -0
  32. package/dist/open-api/routes/routes.js +856 -172
  33. package/dist/pbds/pbd.controller.mjs +20 -5
  34. package/dist/pcis/pci.controller.mjs +19 -5
  35. package/dist/pgpus/pgpu.controller.mjs +19 -5
  36. package/dist/pifs/pif.controller.mjs +56 -16
  37. package/dist/pools/pool.controller.mjs +166 -17
  38. package/dist/proxies/proxy.controller.mjs +25 -6
  39. package/dist/restore-logs/restore-log.controller.mjs +42 -23
  40. package/dist/schedules/schedule.controller.mjs +36 -5
  41. package/dist/servers/server.controller.mjs +71 -9
  42. package/dist/sms/sm.controller.mjs +17 -4
  43. package/dist/srs/sr.controller.mjs +74 -18
  44. package/dist/tasks/task.controller.mjs +74 -13
  45. package/dist/users/user.controller.mjs +124 -22
  46. package/dist/vbds/vbd.controller.mjs +76 -38
  47. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +48 -14
  48. package/dist/vdis/vdi.controller.mjs +81 -16
  49. package/dist/vifs/vif.controller.mjs +118 -16
  50. package/dist/vm-controller/vm-controller.controller.mjs +77 -19
  51. package/dist/vm-snapshots/vm-snapshot.controller.mjs +85 -18
  52. package/dist/vm-templates/vm-template.controller.mjs +86 -18
  53. package/dist/vms/vm.controller.mjs +182 -24
  54. package/open-api/spec/swagger.json +12112 -3537
  55. package/package.json +12 -11
@@ -12,9 +12,10 @@ import { inject } from 'inversify';
12
12
  import { noSuchObject } from 'xo-common/api-errors.js';
13
13
  import { Deprecated, Example, Get, Hidden, Middlewares, Path, Query, Request, Response, Route, Security, Tags, } from 'tsoa';
14
14
  import { provide } from 'inversify-binding-decorators';
15
+ import { acl, autoBindService } from '../middlewares/acl.middleware.mjs';
15
16
  import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
16
17
  import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
17
- import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
18
+ import { badRequestResp, forbiddenOperationResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
18
19
  import { RestApi } from '../rest-api/rest-api.mjs';
19
20
  import { limitAndFilterArray, safeParseComplexMatcher } from '../helpers/utils.helper.mjs';
20
21
  import { XoController } from '../abstract-classes/xo-controller.mjs';
@@ -33,24 +34,30 @@ let BackupJobController = class BackupJobController extends XoController {
33
34
  const backupJobs = allJobs.filter(job => this.#backupJobService.isBackupJob(job));
34
35
  return backupJobs;
35
36
  }
36
- async getCollectionObject(id) {
37
- const job = await this.restApi.xoApp.getJob(id);
38
- if (!this.#backupJobService.isBackupJob(job)) {
39
- throw noSuchObject(id, 'backup-job');
40
- }
41
- return job;
37
+ getCollectionObject(id) {
38
+ return this.#backupJobService.getBackupJob(id);
42
39
  }
43
40
  /**
41
+ * Returns all backup jobs that match the following privilege:
42
+ * - resource: backup-job, action: read
44
43
  *
45
44
  * @example fields "name,mode,type,id"
46
45
  * @example filter "type:backup"
47
46
  * @example limit 42
48
47
  */
49
- async getBackupJobs(req, fields, ndjson, filter, limit) {
50
- const backupJobs = await this.getObjects({ filter, limit });
51
- return this.sendObjects(Object.values(backupJobs), req, 'backup-jobs');
48
+ async getBackupJobs(req, fields, ndjson, markdown, filter, limit) {
49
+ const backupJobs = await this.getObjects({ filter });
50
+ return this.sendObjects(Object.values(backupJobs), req, {
51
+ path: 'backup-jobs',
52
+ limit,
53
+ privilege: { action: 'read', resource: 'backup-job' },
54
+ });
52
55
  }
53
56
  /**
57
+ *
58
+ * Required privilege:
59
+ * - resource: backup-job, action: read
60
+ *
54
61
  * @example id "d33f3dc1-92b4-469c-ad58-4c2a106a4721"
55
62
  */
56
63
  getBackupJob(id) {
@@ -61,16 +68,25 @@ __decorate([
61
68
  Example(vmBackupJobIds),
62
69
  Example(partialVmBackupJobs),
63
70
  Get(''),
71
+ Security('*', ['acl']),
64
72
  __param(0, Request()),
65
73
  __param(1, Query()),
66
74
  __param(2, Query()),
67
75
  __param(3, Query()),
68
- __param(4, Query())
76
+ __param(4, Query()),
77
+ __param(5, Query())
69
78
  ], BackupJobController.prototype, "getBackupJobs", null);
70
79
  __decorate([
71
80
  Example(vmBackupJob),
72
- Response(notFoundResp.status, notFoundResp.description),
73
81
  Get('{id}'),
82
+ Middlewares(acl({
83
+ resource: 'backup-job',
84
+ action: 'read',
85
+ objectId: 'params.id',
86
+ getObject: autoBindService(BackupJobService, 'getBackupJob'),
87
+ })),
88
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
89
+ Response(notFoundResp.status, notFoundResp.description),
74
90
  __param(0, Path())
75
91
  ], BackupJobController.prototype, "getBackupJob", null);
76
92
  BackupJobController = __decorate([
@@ -112,14 +128,20 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
112
128
  return backupJob;
113
129
  }
114
130
  /**
131
+ * Returns all VM backup jobs that match the following privilege:
132
+ * - resource: backup-job, action: read
115
133
  *
116
134
  * @example fields "name,mode,id"
117
135
  * @example filter "mode:delta"
118
136
  * @example limit 42
119
137
  */
120
- async getVmBackupJobs(req, fields, ndjson, filter, limit) {
138
+ async getVmBackupJobs(req, fields, ndjson, markdown, filter, limit) {
121
139
  const vmBackupJobs = await this.restApi.xoApp.getAllJobs('backup');
122
- return this.sendObjects(limitAndFilterArray(vmBackupJobs, { filter, limit }), req, 'backup-jobs');
140
+ return this.sendObjects(limitAndFilterArray(vmBackupJobs, { filter }), req, {
141
+ path: 'backup-jobs',
142
+ limit,
143
+ privilege: { action: 'read', resource: 'backup-job' },
144
+ });
123
145
  }
124
146
  // For compatibility, redirect /backup/jobs/:id to /backup/jobs/vm/:id
125
147
  async redirectToVmBackupJob(req, id) {
@@ -133,14 +155,20 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
133
155
  return this.getObject(id, 'backup');
134
156
  }
135
157
  /**
158
+ * Returns all metadata backup jobs that match the following privilege:
159
+ * - resource: backup-job, action: read
136
160
  *
137
161
  * @example fields "name,xoMetadata,id"
138
162
  * @example filter "xoMetadata?"
139
163
  * @example limit 42
140
164
  */
141
- async getMetadataBackupJobs(req, fields, ndjson, filter, limit) {
165
+ async getMetadataBackupJobs(req, fields, ndjson, markdown, filter, limit) {
142
166
  const metadataBackupJobs = await this.restApi.xoApp.getAllJobs('metadataBackup');
143
- return this.sendObjects(limitAndFilterArray(metadataBackupJobs, { filter, limit }), req, 'backup-jobs');
167
+ return this.sendObjects(limitAndFilterArray(metadataBackupJobs, { filter }), req, {
168
+ path: 'backup-jobs',
169
+ limit,
170
+ privilege: { action: 'read', resource: 'backup-job' },
171
+ });
144
172
  }
145
173
  /**
146
174
  * @example id "b50f95fd-f6b7-4027-87b6-6a02c7dcd5f5"
@@ -149,14 +177,20 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
149
177
  return this.getObject(id, 'metadataBackup');
150
178
  }
151
179
  /**
180
+ * Returns all mirror backup jobs that match the following privilege:
181
+ * - resource: backup-job, action: read
152
182
  *
153
183
  * @example fields "name,mode,id"
154
184
  * @example filter "mode:delta"
155
185
  * @example limit 42
156
186
  */
157
- async getMirrorBackupJobs(req, fields, ndjson, filter, limit) {
187
+ async getMirrorBackupJobs(req, fields, ndjson, markdown, filter, limit) {
158
188
  const mirrorBackupJobs = await this.restApi.xoApp.getAllJobs('mirrorBackup');
159
- return this.sendObjects(limitAndFilterArray(mirrorBackupJobs, { filter, limit }), req, 'backup-jobs');
189
+ return this.sendObjects(limitAndFilterArray(mirrorBackupJobs, { filter }), req, {
190
+ path: 'backup-jobs',
191
+ limit,
192
+ privilege: { action: 'read', resource: 'backup-job' },
193
+ });
160
194
  }
161
195
  /**
162
196
  * @example id "e680c14c-ab52-45c8-bb0e-bd4ca12ea8f9"
@@ -165,11 +199,14 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
165
199
  return this.getObject(id, 'mirrorBackup');
166
200
  }
167
201
  /**
202
+ * Returns all backup logs that match the following privilege:
203
+ * - resource: backup-log, action: read
204
+ *
168
205
  * @example fields "jobName,status,data"
169
206
  * @example filter "status:success"
170
207
  * @example limit 42
171
208
  */
172
- async getDeprecatedBackupLogs(req, fields, ndjson, filter, limit) {
209
+ async getDeprecatedBackupLogs(req, fields, ndjson, markdown, filter, limit) {
173
210
  const userFilter = filter === undefined ? () => true : safeParseComplexMatcher(filter).createPredicate();
174
211
  const predicate = (log) => {
175
212
  if (!this.#backupLogService.isBackupLog(log)) {
@@ -177,8 +214,12 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
177
214
  }
178
215
  return userFilter(log);
179
216
  };
180
- const logs = (await this.restApi.xoApp.getBackupNgLogsSorted({ filter: predicate, limit }));
181
- return this.sendObjects(logs, req, 'backup-logs');
217
+ const logs = (await this.restApi.xoApp.getBackupNgLogsSorted({ filter: predicate }));
218
+ return this.sendObjects(logs, req, {
219
+ path: 'backup-logs',
220
+ limit,
221
+ privilege: { action: 'read', resource: 'backup-log' },
222
+ });
182
223
  }
183
224
  /**
184
225
  * @example id "1753776067468"
@@ -196,12 +237,14 @@ __decorate([
196
237
  Example(partialVmBackupJobs),
197
238
  Deprecated(),
198
239
  Get('jobs/vm'),
240
+ Security('*', ['acl']),
199
241
  Tags('backup-jobs'),
200
242
  __param(0, Request()),
201
243
  __param(1, Query()),
202
244
  __param(2, Query()),
203
245
  __param(3, Query()),
204
- __param(4, Query())
246
+ __param(4, Query()),
247
+ __param(5, Query())
205
248
  ], DeprecatedBackupController.prototype, "getVmBackupJobs", null);
206
249
  __decorate([
207
250
  Hidden(),
@@ -223,12 +266,14 @@ __decorate([
223
266
  Example(partialMetadataBackupJobs),
224
267
  Deprecated(),
225
268
  Get('jobs/metadata'),
269
+ Security('*', ['acl']),
226
270
  Tags('backup-jobs'),
227
271
  __param(0, Request()),
228
272
  __param(1, Query()),
229
273
  __param(2, Query()),
230
274
  __param(3, Query()),
231
- __param(4, Query())
275
+ __param(4, Query()),
276
+ __param(5, Query())
232
277
  ], DeprecatedBackupController.prototype, "getMetadataBackupJobs", null);
233
278
  __decorate([
234
279
  Example(metadataBackupJob),
@@ -243,12 +288,14 @@ __decorate([
243
288
  Example(partialMirrorBackupJobs),
244
289
  Deprecated(),
245
290
  Get('jobs/mirror'),
291
+ Security('*', ['acl']),
246
292
  Tags('backup-jobs'),
247
293
  __param(0, Request()),
248
294
  __param(1, Query()),
249
295
  __param(2, Query()),
250
296
  __param(3, Query()),
251
- __param(4, Query())
297
+ __param(4, Query()),
298
+ __param(5, Query())
252
299
  ], DeprecatedBackupController.prototype, "getMirrorBackupJobs", null);
253
300
  __decorate([
254
301
  Example(mirrorBackupJob),
@@ -263,12 +310,14 @@ __decorate([
263
310
  Example(partialBackupLogs),
264
311
  Deprecated(),
265
312
  Get('logs'),
313
+ Security('*', ['acl']),
266
314
  Tags('backup-logs'),
267
315
  __param(0, Request()),
268
316
  __param(1, Query()),
269
317
  __param(2, Query()),
270
318
  __param(3, Query()),
271
- __param(4, Query())
319
+ __param(4, Query()),
320
+ __param(5, Query())
272
321
  ], DeprecatedBackupController.prototype, "getDeprecatedBackupLogs", null);
273
322
  __decorate([
274
323
  Example(backupLog),
@@ -8,6 +8,13 @@ export class BackupJobService {
8
8
  constructor(restApi) {
9
9
  this.#restApi = restApi;
10
10
  }
11
+ async getBackupJob(id) {
12
+ const job = await this.#restApi.xoApp.getJob(id);
13
+ if (!this.isBackupJob(job)) {
14
+ throw noSuchObject(id, 'backup-job');
15
+ }
16
+ return job;
17
+ }
11
18
  isBackupJob(anyJob) {
12
19
  return this.#backupJobTypes.includes(anyJob.type);
13
20
  }
@@ -7,15 +7,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
10
+ import { Example, Get, Middlewares, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
- import { noSuchObject } from 'xo-common/api-errors.js';
13
12
  import { provide } from 'inversify-binding-decorators';
14
13
  import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
15
14
  import { BackupLogService } from './backup-log.service.mjs';
16
15
  import { RestApi } from '../rest-api/rest-api.mjs';
17
- import { badRequestResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
16
+ import { badRequestResp, forbiddenOperationResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
18
17
  import { XoController } from '../abstract-classes/xo-controller.mjs';
18
+ import { acl, autoBindService } from '../middlewares/acl.middleware.mjs';
19
19
  let BackupLogController = class BackupLogController extends XoController {
20
20
  #backupLogService;
21
21
  constructor(restApi, backupLogService) {
@@ -25,23 +25,28 @@ let BackupLogController = class BackupLogController extends XoController {
25
25
  getAllCollectionObjects() {
26
26
  return this.restApi.xoApp.getBackupNgLogsSorted({ filter: this.#backupLogService.isBackupLog });
27
27
  }
28
- async getCollectionObject(id) {
29
- const log = await this.restApi.xoApp.getBackupNgLogs(id);
30
- if (!this.#backupLogService.isBackupLog(log)) {
31
- throw noSuchObject('backup-log');
32
- }
33
- return log;
28
+ getCollectionObject(id) {
29
+ return this.#backupLogService.getBackupLog(id);
34
30
  }
35
31
  /**
32
+ * Returns all backup logs that match the following privilege:
33
+ * - resource: backup-log, action: read
34
+ *
36
35
  * @example fields "jobName,status,data"
37
36
  * @example filter "status:success"
38
37
  * @example limit 42
39
38
  */
40
- async getBackupLogs(req, fields, ndjson, filter, limit) {
41
- const backupLogs = await this.getObjects({ filter, limit });
42
- return this.sendObjects(Object.values(backupLogs), req);
39
+ async getBackupLogs(req, fields, ndjson, markdown, filter, limit) {
40
+ const backupLogs = await this.getObjects({ filter });
41
+ return this.sendObjects(Object.values(backupLogs), req, {
42
+ limit,
43
+ privilege: { action: 'read', resource: 'backup-log' },
44
+ });
43
45
  }
44
46
  /**
47
+ * Required privilege:
48
+ * - resource: backup-log, action: read
49
+ *
45
50
  * @example id "1753776067468"
46
51
  */
47
52
  getBackupLog(id) {
@@ -52,15 +57,25 @@ __decorate([
52
57
  Example(backupLogIds),
53
58
  Example(partialBackupLogs),
54
59
  Get(''),
60
+ Security('*', ['acl']),
55
61
  __param(0, Request()),
56
62
  __param(1, Query()),
57
63
  __param(2, Query()),
58
64
  __param(3, Query()),
59
- __param(4, Query())
65
+ __param(4, Query()),
66
+ __param(5, Query())
60
67
  ], BackupLogController.prototype, "getBackupLogs", null);
61
68
  __decorate([
62
69
  Example(backupLog),
63
70
  Get('{id}'),
71
+ Middlewares(acl({
72
+ resource: 'backup-log',
73
+ action: 'read',
74
+ objectId: 'params.id',
75
+ getObject: autoBindService(BackupLogService, 'getBackupLog'),
76
+ })),
77
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
78
+ Response(notFoundResp.status, notFoundResp.description),
64
79
  __param(0, Path())
65
80
  ], BackupLogController.prototype, "getBackupLog", null);
66
81
  BackupLogController = __decorate([
@@ -1,7 +1,26 @@
1
+ import { noSuchObject } from 'xo-common/api-errors.js';
1
2
  export class BackupLogService {
3
+ #restApi;
4
+ constructor(restApi) {
5
+ this.#restApi = restApi;
6
+ }
2
7
  isBackupLog(log) {
3
8
  return log.message === 'backup';
4
9
  }
10
+ async getBackupLog(id) {
11
+ const log = await this.#restApi.xoApp.getBackupNgLogs(id);
12
+ if (!this.isBackupLog(log)) {
13
+ throw noSuchObject('backup-log');
14
+ }
15
+ return log;
16
+ }
17
+ async getRestoreLog(id) {
18
+ const log = await this.#restApi.xoApp.getBackupNgLogs(id);
19
+ if (this.isBackupLog(log)) {
20
+ throw noSuchObject('restore-log');
21
+ }
22
+ return log;
23
+ }
5
24
  getVmBackupTaskLog(log, vmId) {
6
25
  return log.tasks?.find(task => task.data?.id === vmId);
7
26
  }
@@ -7,10 +7,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
10
+ import { Example, Get, Middlewares, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { provide } from 'inversify-binding-decorators';
13
- import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
13
+ import { acl } from '../middlewares/acl.middleware.mjs';
14
+ import { badRequestResp, forbiddenOperationResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
14
15
  import { backupRepositoryIds, partialBackupRepositories, backupRepository, } from '../open-api/oa-examples/backup-repository.oa-example.mjs';
15
16
  import { XoController } from '../abstract-classes/xo-controller.mjs';
16
17
  import { RestApi } from '../rest-api/rest-api.mjs';
@@ -26,14 +27,23 @@ let BackupRepositoryController = class BackupRepositoryController extends XoCont
26
27
  return this.restApi.xoApp.getRemote(id);
27
28
  }
28
29
  /**
30
+ * Returns all backup repositories that match the following privilege:
31
+ * - resource: backup-repository, action: read
32
+ *
29
33
  * @example fields "id,name,enabled"
30
34
  * @example filter "enabled?"
31
35
  * @example limit 42
32
36
  */
33
- async getRepositories(req, fields, ndjson, filter, limit) {
34
- return this.sendObjects(Object.values(await this.getObjects({ filter, limit })), req);
37
+ async getRepositories(req, fields, ndjson, markdown, filter, limit) {
38
+ return this.sendObjects(Object.values(await this.getObjects({ filter })), req, {
39
+ limit,
40
+ privilege: { action: 'read', resource: 'backup-repository' },
41
+ });
35
42
  }
36
43
  /**
44
+ * Required privilege:
45
+ * - resource: backup-repository, action: read
46
+ *
37
47
  * @example id "c4284e12-37c9-7967-b9e8-83ef229c3e03"
38
48
  */
39
49
  getRepository(id) {
@@ -44,15 +54,24 @@ __decorate([
44
54
  Example(backupRepositoryIds),
45
55
  Example(partialBackupRepositories),
46
56
  Get(''),
57
+ Security('*', ['acl']),
47
58
  __param(0, Request()),
48
59
  __param(1, Query()),
49
60
  __param(2, Query()),
50
61
  __param(3, Query()),
51
- __param(4, Query())
62
+ __param(4, Query()),
63
+ __param(5, Query())
52
64
  ], BackupRepositoryController.prototype, "getRepositories", null);
53
65
  __decorate([
54
66
  Example(backupRepository),
55
67
  Get('{id}'),
68
+ Middlewares(acl({
69
+ resource: 'backup-repository',
70
+ action: 'read',
71
+ objectId: 'params.id',
72
+ getObject: ({ restApi }) => restApi.xoApp.getRemote,
73
+ })),
74
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
56
75
  Response(notFoundResp.status, notFoundResp.description),
57
76
  __param(0, Path())
58
77
  ], BackupRepositoryController.prototype, "getRepository", null);
@@ -10,6 +10,8 @@ export class Subscriber {
10
10
  #manager;
11
11
  #connection;
12
12
  #isAlive;
13
+ #cleanupCallbacks = new Set();
14
+ #userId;
13
15
  get id() {
14
16
  return this.#id;
15
17
  }
@@ -19,13 +21,17 @@ export class Subscriber {
19
21
  get connection() {
20
22
  return this.#connection;
21
23
  }
22
- constructor(connection, manager) {
24
+ get userId() {
25
+ return this.#userId;
26
+ }
27
+ constructor(connection, manager, userId) {
23
28
  this.#id = crypto.randomUUID();
24
29
  connection.on('close', () => this.clear());
25
30
  manager.addSubscriber(this);
26
31
  this.#connection = connection;
27
32
  this.#manager = manager;
28
33
  this.#isAlive = true;
34
+ this.#userId = userId;
29
35
  }
30
36
  #safeWrite(payload) {
31
37
  const ok = this.#connection.write(payload);
@@ -34,6 +40,10 @@ export class Subscriber {
34
40
  this.clear();
35
41
  }
36
42
  }
43
+ onClear(callback) {
44
+ this.#cleanupCallbacks.add(callback);
45
+ return () => this.#cleanupCallbacks.delete(callback);
46
+ }
37
47
  broadcast(event, data) {
38
48
  if (!this.#isAlive) {
39
49
  log.warn('broadcast called on a subscriber that is not alive, but still in memory! Force clear and do nothing');
@@ -44,32 +54,34 @@ export class Subscriber {
44
54
  this.#safeWrite(payload);
45
55
  }
46
56
  clear() {
47
- this.#isAlive = false;
48
- if (!this.#connection.closed || !this.#connection.destroyed) {
49
- this.#connection.destroy();
57
+ if (!this.#isAlive) {
58
+ return;
50
59
  }
60
+ this.#isAlive = false;
61
+ this.#connection.destroy();
62
+ this.#cleanupCallbacks.forEach(cb => cb());
63
+ this.#cleanupCallbacks.clear();
51
64
  this.#manager.removeSubscriber(this.id);
52
65
  }
53
66
  }
54
67
  export class XoListener extends Listener {
55
- #type;
56
68
  #alarmService;
57
69
  constructor(type, eventEmitter, alarmService) {
58
- super(eventEmitter, ['add', 'update', 'remove']);
59
- this.#type = type;
70
+ super(eventEmitter, ['add', 'update', 'remove'], type);
60
71
  this.#alarmService = alarmService;
61
72
  }
62
- handleData({ fields, event }, object, previousObj) {
73
+ async handleData({ fields, event, subscriber }, object, previousObj) {
63
74
  let _object = object;
64
75
  let _prevObject = previousObj;
65
- if (this.#type === 'alarm' || this.#type === 'message') {
76
+ ///
77
+ if (this.type === 'alarm' || this.type === 'message') {
66
78
  const isAlarm = (object) => object !== undefined && 'type' in object && object.type === 'message' && this.#alarmService.isAlarm(object);
67
79
  const objectIsAlarm = isAlarm(object);
68
80
  const prevObjectIsAlarm = isAlarm(previousObj);
69
81
  // If we are in an alarm listener and the objects are messages
70
82
  // we clean them to ensure they are not sent via the SSE
71
83
  // Same if we are in a message listener and the objects are alarms
72
- if (this.#type === 'alarm') {
84
+ if (this.type === 'alarm') {
73
85
  _object = objectIsAlarm ? this.#alarmService.parseAlarm(object) : undefined;
74
86
  _prevObject = prevObjectIsAlarm ? this.#alarmService.parseAlarm(previousObj) : undefined;
75
87
  }
@@ -81,6 +93,17 @@ export class XoListener extends Listener {
81
93
  if (_object === undefined && _prevObject === undefined) {
82
94
  return;
83
95
  }
96
+ const aclEvent = await this.getAclEvent({
97
+ event,
98
+ object: _object,
99
+ previousObject: _prevObject,
100
+ userId: subscriber.userId,
101
+ });
102
+ // If the user has no 'read' privileges for the changes, don't send the update
103
+ if (aclEvent === undefined) {
104
+ return;
105
+ }
106
+ event = aclEvent;
84
107
  if (fields !== '*') {
85
108
  if (_object !== undefined) {
86
109
  _object = pick(_object, fields);
@@ -94,7 +117,7 @@ export class XoListener extends Listener {
94
117
  return;
95
118
  }
96
119
  // if _object === undefined, this means we are on a remove event, so _prevObject will not be undefined
97
- return { $subscription: this.#type, ...(_object ?? _prevObject) };
120
+ return { $subscription: this.type, event, ...(_object ?? _prevObject) };
98
121
  }
99
122
  }
100
123
  export class PingListener extends Listener {
@@ -105,7 +128,7 @@ export class PingListener extends Listener {
105
128
  this.eventEmitter.emit('ping');
106
129
  }, 1000 * 30);
107
130
  }
108
- handleData() {
131
+ async handleData() {
109
132
  return { ping: Date.now() };
110
133
  }
111
134
  clear() {
@@ -132,11 +155,6 @@ export class SubscriberManager {
132
155
  this.#subscribers.delete(id);
133
156
  }
134
157
  clear() {
135
- this.#subscribers.forEach(subscriber => {
136
- if (!subscriber.connection.closed) {
137
- subscriber.connection.destroy();
138
- }
139
- this.#subscribers.delete(subscriber.id);
140
- });
158
+ this.#subscribers.forEach(subscriber => subscriber.clear());
141
159
  }
142
160
  }
@@ -70,12 +70,14 @@ let EventController = class EventController extends Controller {
70
70
  };
71
71
  __decorate([
72
72
  Get(''),
73
+ Security('*', ['acl']),
73
74
  SuccessResponse(200, 'OK'),
74
75
  __param(0, Request())
75
76
  ], EventController.prototype, "openSseConnection", null);
76
77
  __decorate([
77
78
  Example(addSubscription),
78
79
  Post('{id}/subscriptions'),
80
+ Security('*', ['acl']),
79
81
  Middlewares(json()),
80
82
  SuccessResponse(createdResp.status, createdResp.description),
81
83
  Response(notFoundResp.status, notFoundResp.description),
@@ -84,6 +86,7 @@ __decorate([
84
86
  ], EventController.prototype, "addSubscription", null);
85
87
  __decorate([
86
88
  Delete('{id}/subscriptions/{subscriptionId}'),
89
+ Security('*', ['acl']),
87
90
  SuccessResponse(noContentResp.status, noContentResp.description),
88
91
  Response(notFoundResp.status, notFoundResp.description),
89
92
  __param(0, Path()),
@@ -1,4 +1,3 @@
1
- import os from 'node:os';
2
1
  import { createLogger } from '@xen-orchestra/log';
3
2
  import { PassThrough, pipeline } from 'node:stream';
4
3
  import { PingListener, Subscriber, SubscriberManager, XoListener } from './event.class.mjs';
@@ -50,16 +49,17 @@ export class EventService {
50
49
  'Cache-Control': 'no-cache, no-transform',
51
50
  });
52
51
  res.setHeaders(headers);
53
- const maxRam = this.#restApi.xoApp.config.get('rest-api.percentOfRamAllocatedPerSseClient');
52
+ const maxRam = this.#restApi.xoApp.config.get('rest-api.maxRamAllocatedPerSseClient');
54
53
  const connection = new PassThrough({
55
- highWaterMark: Math.round(os.totalmem() * (maxRam / 100)),
54
+ highWaterMark: Math.round(1024 * 1024 * maxRam),
56
55
  });
57
56
  pipeline(connection, res, error => {
58
57
  if (error?.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
59
58
  log.error(error);
60
59
  }
61
60
  });
62
- const subscriber = new Subscriber(connection, this.#subscriberManager);
61
+ const user = this.#restApi.getCurrentUser();
62
+ const subscriber = new Subscriber(connection, this.#subscriberManager, user.id);
63
63
  subscriber.broadcast('init', { id: subscriber.id });
64
64
  this.addListenerFor(subscriber.id, { type: 'ping' });
65
65
  log.debug(`new SSE subscriber added: ${subscriber.id}`);