@xen-orchestra/rest-api 0.18.0 → 0.19.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 (45) hide show
  1. package/dist/abstract-classes/base-controller.mjs +20 -0
  2. package/dist/abstract-classes/xo-controller.mjs +1 -1
  3. package/dist/alarms/alarm.controller.mjs +2 -1
  4. package/dist/backup-archives/backup-archive.controller.mjs +96 -0
  5. package/dist/backup-jobs/backup-job.controller.mjs +3 -1
  6. package/dist/backup-logs/backup-log.controller.mjs +2 -1
  7. package/dist/backup-repositories/backup-repositories.controller.mjs +2 -1
  8. package/dist/groups/group.controller.mjs +26 -1
  9. package/dist/hosts/host.controller.mjs +57 -2
  10. package/dist/index.mjs +3 -2
  11. package/dist/ioc/ioc.mjs +0 -5
  12. package/dist/messages/message.controller.mjs +2 -1
  13. package/dist/middlewares/authentication.middleware.mjs +44 -20
  14. package/dist/networks/network.controller.mjs +81 -2
  15. package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +60 -0
  16. package/dist/open-api/oa-examples/pbd.oa-example.mjs +41 -0
  17. package/dist/open-api/oa-examples/user.oa-example.mjs +12 -0
  18. package/dist/open-api/routes/routes.js +2025 -565
  19. package/dist/pbds/pbd.controller.mjs +60 -0
  20. package/dist/pcis/pci.controller.mjs +2 -1
  21. package/dist/pgpus/pgpu.controller.mjs +2 -1
  22. package/dist/pifs/pif.controller.mjs +50 -1
  23. package/dist/pools/pool.controller.mjs +56 -3
  24. package/dist/proxies/proxy.controller.mjs +2 -1
  25. package/dist/restore-logs/restore-log.controller.mjs +3 -1
  26. package/dist/schedules/schedule.controller.mjs +2 -1
  27. package/dist/servers/server.controller.mjs +26 -2
  28. package/dist/sms/sm.controller.mjs +2 -1
  29. package/dist/srs/sr.controller.mjs +57 -2
  30. package/dist/tasks/task.controller.mjs +2 -1
  31. package/dist/users/user.controller.mjs +80 -31
  32. package/dist/users/user.middleware.mjs +11 -0
  33. package/dist/vbds/vbd.controller.mjs +50 -1
  34. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +81 -2
  35. package/dist/vdis/vdi.controller.mjs +105 -2
  36. package/dist/vifs/vif.controller.mjs +50 -1
  37. package/dist/vm-controller/vm-controller.controller.mjs +81 -2
  38. package/dist/vm-snapshots/vm-snapshot.controller.mjs +57 -2
  39. package/dist/vm-templates/vm-template.controller.mjs +57 -2
  40. package/dist/vms/vm.controller.mjs +36 -13
  41. package/dist/xoa/xoa.controller.mjs +2 -1
  42. package/open-api/spec/swagger.json +7565 -2508
  43. package/package.json +3 -3
  44. package/tsoa.json +19 -0
  45. package/dist/tasks/task.service.mjs +0 -24
@@ -1,3 +1,4 @@
1
+ import * as CM from 'complex-matcher';
1
2
  import { Controller } from 'tsoa';
2
3
  import { createGzip } from 'node:zlib';
3
4
  import { pipeline } from 'node:stream/promises';
@@ -27,6 +28,25 @@ export class BaseController extends Controller {
27
28
  return mappedObjects;
28
29
  }
29
30
  }
31
+ async getTasksForObject(id, { filter, limit = Infinity } = {}) {
32
+ const tasks = {};
33
+ const object = await Promise.resolve(this.getObject(id));
34
+ const objectFilter = (task) => task.properties.objectId === object.id || task.properties.params?.id === object.id;
35
+ let userFilter = () => true;
36
+ if (filter !== undefined) {
37
+ userFilter = typeof filter === 'string' ? CM.parse(filter).createPredicate() : filter;
38
+ }
39
+ for await (const task of this.restApi.tasks.list({ filter: objectFilter })) {
40
+ if (limit === 0) {
41
+ break;
42
+ }
43
+ if (userFilter(task)) {
44
+ tasks[task.id] = task;
45
+ limit--;
46
+ }
47
+ }
48
+ return tasks;
49
+ }
30
50
  /**
31
51
  * statusCode must represent the status code in case of a synchronous request. Default 200
32
52
  */
@@ -16,7 +16,7 @@ let XoController = class XoController extends BaseController {
16
16
  super(restApi);
17
17
  }
18
18
  async getObjects(opts = {}) {
19
- let objects = await this.getAllCollectionObjects();
19
+ let objects = await this.getAllCollectionObjects(opts);
20
20
  objects = limitAndFilterArray(objects, opts);
21
21
  const objectById = {};
22
22
  objects.forEach(obj => {
@@ -12,7 +12,7 @@ import { inject } from 'inversify';
12
12
  import { noSuchObject } from 'xo-common/api-errors.js';
13
13
  import { provide } from 'inversify-binding-decorators';
14
14
  import { alarm, alarmIds, partialAlarms } from '../open-api/oa-examples/alarm.oa-example.mjs';
15
- import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
15
+ import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
16
16
  import { RestApi } from '../rest-api/rest-api.mjs';
17
17
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
18
18
  import { AlarmService } from './alarm.service.mjs';
@@ -72,6 +72,7 @@ __decorate([
72
72
  AlarmController = __decorate([
73
73
  Route('alarms'),
74
74
  Security('*'),
75
+ Response(badRequestResp.status, badRequestResp.description),
75
76
  Response(unauthorizedResp.status, unauthorizedResp.description),
76
77
  Tags('alarms'),
77
78
  provide(AlarmController),
@@ -0,0 +1,96 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
11
+ import { noSuchObject } from 'xo-common/api-errors.js';
12
+ import { provide } from 'inversify-binding-decorators';
13
+ import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
14
+ import { XoController } from '../abstract-classes/xo-controller.mjs';
15
+ import { backupArchive, backupArchiveIds, partialBackupArchives, } from '../open-api/oa-examples/backup-archive.oa-example.mjs';
16
+ // BR uuid/xo-vm-backups/VM uuid/(ISO 8601 compact).json
17
+ const BACKUP_ARCHIVE_ID_REGEX = /^([0-9a-fA-F-]{36})\/+xo-vm-backups\/+([0-9a-fA-F-]{36})\/+(\d{8}T\d{6}Z)\.json$/;
18
+ let BackupArchiveController = class BackupArchiveController extends XoController {
19
+ async getAllCollectionObjects({ backupRepositories = [], } = {}) {
20
+ const backupRepositoryIds = [];
21
+ if (backupRepositories.includes('*')) {
22
+ const allBackupRepositories = await this.restApi.xoApp.getAllRemotes();
23
+ allBackupRepositories.forEach(br => backupRepositoryIds.push(br.id));
24
+ }
25
+ else {
26
+ for (const brId of backupRepositories) {
27
+ const br = await this.restApi.xoApp.getRemote(brId);
28
+ backupRepositoryIds.push(br.id);
29
+ }
30
+ }
31
+ const backupArchivesByRemote = await this.restApi.xoApp.listVmBackupsNg(backupRepositoryIds);
32
+ const vmBackupArchives = Object.values(backupArchivesByRemote)
33
+ .map(backupsByVm => Object.values(backupsByVm))
34
+ .flat(2);
35
+ return vmBackupArchives;
36
+ }
37
+ async getCollectionObject(id) {
38
+ const match = id.match(BACKUP_ARCHIVE_ID_REGEX);
39
+ if (match === null) {
40
+ throw noSuchObject(id, 'backup-archive');
41
+ }
42
+ const [, brId, vmId] = match;
43
+ const backupArchive = (await this.restApi.xoApp.listVmBackupsNg([brId]))[brId]?.[vmId]?.find(ba => ba.id === id);
44
+ if (backupArchive === undefined) {
45
+ throw noSuchObject(id, 'backup-archive');
46
+ }
47
+ return backupArchive;
48
+ }
49
+ /**
50
+ *
51
+ * You can use the alias "*" in "backup-repository" to select all backup repositories.
52
+ *
53
+ * @example backupRepositories ["c4284e12-37c9-7967-b9e8-83ef229c3e03", "1af95910-01b4-4e87-9c2f-d895cafe0776"]
54
+ * @example fields "id,backupRepository,disks"
55
+ * @example filter "disks:length:>0"
56
+ * @example limit 42
57
+ */
58
+ async getBackupArchives(req, backupRepositories, fields, ndjson, filter, limit) {
59
+ const backupArchives = await this.getObjects({ backupRepositories, filter, limit });
60
+ return this.sendObjects(Object.values(backupArchives), req);
61
+ }
62
+ /**
63
+ * @example id "231264c3-af43-4ec0-a3be-394c5b1fdbfc//xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json"
64
+ */
65
+ async getBackupArchive(id) {
66
+ const backupArchive = await this.getObject(id);
67
+ return backupArchive;
68
+ }
69
+ };
70
+ __decorate([
71
+ Example(backupArchiveIds),
72
+ Example(partialBackupArchives),
73
+ Get(''),
74
+ Response(notFoundResp.status, notFoundResp.description),
75
+ __param(0, Request()),
76
+ __param(1, Query('backup-repository')),
77
+ __param(2, Query()),
78
+ __param(3, Query()),
79
+ __param(4, Query()),
80
+ __param(5, Query())
81
+ ], BackupArchiveController.prototype, "getBackupArchives", null);
82
+ __decorate([
83
+ Example(backupArchive),
84
+ Get('{id}'),
85
+ Response(notFoundResp.status, notFoundResp.description),
86
+ __param(0, Path())
87
+ ], BackupArchiveController.prototype, "getBackupArchive", null);
88
+ BackupArchiveController = __decorate([
89
+ Route('backup-archives'),
90
+ Security('*'),
91
+ Response(badRequestResp.status, badRequestResp.description),
92
+ Response(unauthorizedResp.status, unauthorizedResp.description),
93
+ Tags('backup-archives'),
94
+ provide(BackupArchiveController)
95
+ ], BackupArchiveController);
96
+ export { BackupArchiveController };
@@ -15,7 +15,7 @@ import { Deprecated, Example, Get, Hidden, Middlewares, Path, Query, Request, Re
15
15
  import { provide } from 'inversify-binding-decorators';
16
16
  import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
17
17
  import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
18
- import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
18
+ import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
19
19
  import { RestApi } from '../rest-api/rest-api.mjs';
20
20
  import { limitAndFilterArray } from '../helpers/utils.helper.mjs';
21
21
  import { XoController } from '../abstract-classes/xo-controller.mjs';
@@ -72,6 +72,7 @@ __decorate([
72
72
  BackupJobController = __decorate([
73
73
  Route('backup-jobs'),
74
74
  Security('*'),
75
+ Response(badRequestResp.status, badRequestResp.description),
75
76
  Response(unauthorizedResp.status, unauthorizedResp.description),
76
77
  Tags('backup-jobs'),
77
78
  provide(BackupJobController)
@@ -271,6 +272,7 @@ __decorate([
271
272
  DeprecatedBackupController = __decorate([
272
273
  Route('backup'),
273
274
  Security('*'),
275
+ Response(badRequestResp.status, badRequestResp.description),
274
276
  Response(unauthorizedResp.status, unauthorizedResp.description),
275
277
  Middlewares((_req, _res, next) => {
276
278
  log.warn('You are calling a deprecated route. It will be removed in the futur. Please use `/rest/v0/backup-jobs` or `/rest/v0/backup-logs` instead');
@@ -14,7 +14,7 @@ import { provide } from 'inversify-binding-decorators';
14
14
  import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
15
15
  import { BackupLogService } from './backup-log.service.mjs';
16
16
  import { RestApi } from '../rest-api/rest-api.mjs';
17
- import { unauthorizedResp } from '../open-api/common/response.common.mjs';
17
+ import { badRequestResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
18
18
  import { XoController } from '../abstract-classes/xo-controller.mjs';
19
19
  let BackupLogController = class BackupLogController extends XoController {
20
20
  #backupLogService;
@@ -66,6 +66,7 @@ __decorate([
66
66
  BackupLogController = __decorate([
67
67
  Route('backup-logs'),
68
68
  Security('*'),
69
+ Response(badRequestResp.status, badRequestResp.description),
69
70
  Response(unauthorizedResp.status, unauthorizedResp.description),
70
71
  Tags('backup-logs'),
71
72
  provide(BackupLogController),
@@ -9,7 +9,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
9
9
  };
10
10
  import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
11
11
  import { provide } from 'inversify-binding-decorators';
12
- import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
12
+ import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
13
13
  import { backupRepositoryIds, partialBackupRepositories, backupRepository, } from '../open-api/oa-examples/backup-repository.oa-example.mjs';
14
14
  import { XoController } from '../abstract-classes/xo-controller.mjs';
15
15
  let BackupRepositoryController = class BackupRepositoryController extends XoController {
@@ -54,6 +54,7 @@ __decorate([
54
54
  BackupRepositoryController = __decorate([
55
55
  Route('backup-repositories'),
56
56
  Security('*'),
57
+ Response(badRequestResp.status, badRequestResp.description),
57
58
  Response(unauthorizedResp.status, unauthorizedResp.description),
58
59
  Tags('backup-repositories'),
59
60
  provide(BackupRepositoryController)
@@ -12,13 +12,14 @@ import { inject } from 'inversify';
12
12
  import { json } from 'express';
13
13
  import { provide } from 'inversify-binding-decorators';
14
14
  import { forbiddenOperation } from 'xo-common/api-errors.js';
15
- import { createdResp, forbiddenOperationResp, invalidParameters, noContentResp, notFoundResp, resourceAlreadyExists, unauthorizedResp, } from '../open-api/common/response.common.mjs';
15
+ import { badRequestResp, createdResp, forbiddenOperationResp, invalidParameters, noContentResp, notFoundResp, resourceAlreadyExists, unauthorizedResp, } from '../open-api/common/response.common.mjs';
16
16
  import { group, groupId, groupIds, partialGroups } from '../open-api/oa-examples/group.oa-example.mjs';
17
17
  import { XoController } from '../abstract-classes/xo-controller.mjs';
18
18
  import { UserService } from '../users/user.service.mjs';
19
19
  import { RestApi } from '../rest-api/rest-api.mjs';
20
20
  import { limitAndFilterArray } from '../helpers/utils.helper.mjs';
21
21
  import { partialUsers, userIds } from '../open-api/oa-examples/user.oa-example.mjs';
22
+ import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
22
23
  let GroupController = class GroupController extends XoController {
23
24
  #userService;
24
25
  constructor(restApi, userService) {
@@ -106,6 +107,16 @@ let GroupController = class GroupController extends XoController {
106
107
  const users = await Promise.all(group.users.map(id => this.#userService.getUser(id)));
107
108
  return this.sendObjects(limitAndFilterArray(users, { filter, limit }), req, 'users');
108
109
  }
110
+ /**
111
+ * @example id "6c81b5e1-afc1-43ea-8f8d-939ceb5f3f90"
112
+ * @example fields "id,status,properties"
113
+ * @example filter "status:failure"
114
+ * @example limit 42
115
+ */
116
+ async getGroupTasks(req, id, fields, ndjson, filter, limit) {
117
+ const tasks = await this.getTasksForObject(id, { filter, limit });
118
+ return this.sendObjects(Object.values(tasks), req, 'tasks');
119
+ }
109
120
  };
110
121
  __decorate([
111
122
  Example(groupIds),
@@ -177,9 +188,23 @@ __decorate([
177
188
  __param(4, Query()),
178
189
  __param(5, Query())
179
190
  ], GroupController.prototype, "getGroupUsers", null);
191
+ __decorate([
192
+ Example(taskIds),
193
+ Example(partialTasks),
194
+ Get('{id}/tasks'),
195
+ Tags('tasks'),
196
+ Response(notFoundResp.status, notFoundResp.description),
197
+ __param(0, Request()),
198
+ __param(1, Path()),
199
+ __param(2, Query()),
200
+ __param(3, Query()),
201
+ __param(4, Query()),
202
+ __param(5, Query())
203
+ ], GroupController.prototype, "getGroupTasks", null);
180
204
  GroupController = __decorate([
181
205
  Route('groups'),
182
206
  Security('*'),
207
+ Response(badRequestResp.status, badRequestResp.description),
183
208
  Response(unauthorizedResp.status, unauthorizedResp.description),
184
209
  Tags('groups'),
185
210
  provide(GroupController),
@@ -7,7 +7,7 @@ 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, SuccessResponse, Tags } from 'tsoa';
10
+ import { Delete, Example, Get, Path, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { pipeline } from 'node:stream/promises';
13
13
  import { provide } from 'inversify-binding-decorators';
@@ -17,9 +17,10 @@ import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.m
17
17
  import { host, hostIds, hostSmt, hostMissingPatches, hostStats, partialHosts, } from '../open-api/oa-examples/host.oa-example.mjs';
18
18
  import { RestApi } from '../rest-api/rest-api.mjs';
19
19
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
20
- import { featureUnauthorized, internalServerErrorResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
20
+ import { badRequestResp, featureUnauthorized, internalServerErrorResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
21
21
  import { HostService } from './host.service.mjs';
22
22
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
23
+ import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
23
24
  let HostController = class HostController extends XapiXoController {
24
25
  #alarmService;
25
26
  #hostService;
@@ -128,6 +129,32 @@ let HostController = class HostController extends XapiXoController {
128
129
  const messages = this.getMessagesForObject(id, { filter, limit });
129
130
  return this.sendObjects(Object.values(messages), req, 'messages');
130
131
  }
132
+ /**
133
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
134
+ * @example fields "id,status,properties"
135
+ * @example filter "status:failure"
136
+ * @example limit 42
137
+ */
138
+ async getHostTasks(req, id, fields, ndjson, filter, limit) {
139
+ const tasks = await this.getTasksForObject(id, { filter, limit });
140
+ return this.sendObjects(Object.values(tasks), req, 'tasks');
141
+ }
142
+ /**
143
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
144
+ * @example tag "from-rest-api"
145
+ */
146
+ async putHostTag(id, tag) {
147
+ const host = this.getXapiObject(id);
148
+ await host.$call('add_tags', tag);
149
+ }
150
+ /**
151
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
152
+ * @example tag "from-rest-api"
153
+ */
154
+ async deleteHostTag(id, tag) {
155
+ const host = this.getXapiObject(id);
156
+ await host.$call('remove_tags', tag);
157
+ }
131
158
  };
132
159
  __decorate([
133
160
  Example(hostIds),
@@ -209,9 +236,37 @@ __decorate([
209
236
  __param(4, Query()),
210
237
  __param(5, Query())
211
238
  ], HostController.prototype, "getHostMessages", null);
239
+ __decorate([
240
+ Example(taskIds),
241
+ Example(partialTasks),
242
+ Get('{id}/tasks'),
243
+ Tags('tasks'),
244
+ Response(notFoundResp.status, notFoundResp.description),
245
+ __param(0, Request()),
246
+ __param(1, Path()),
247
+ __param(2, Query()),
248
+ __param(3, Query()),
249
+ __param(4, Query()),
250
+ __param(5, Query())
251
+ ], HostController.prototype, "getHostTasks", null);
252
+ __decorate([
253
+ Put('{id}/tags/{tag}'),
254
+ SuccessResponse(noContentResp.status, noContentResp.description),
255
+ Response(notFoundResp.status, notFoundResp.description),
256
+ __param(0, Path()),
257
+ __param(1, Path())
258
+ ], HostController.prototype, "putHostTag", null);
259
+ __decorate([
260
+ Delete('{id}/tags/{tag}'),
261
+ SuccessResponse(noContentResp.status, noContentResp.description),
262
+ Response(notFoundResp.status, notFoundResp.description),
263
+ __param(0, Path()),
264
+ __param(1, Path())
265
+ ], HostController.prototype, "deleteHostTag", null);
212
266
  HostController = __decorate([
213
267
  Route('hosts'),
214
268
  Security('*'),
269
+ Response(badRequestResp.status, badRequestResp.description),
215
270
  Response(unauthorizedResp.status, unauthorizedResp.description),
216
271
  Tags('hosts'),
217
272
  provide(HostController),
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import genericErrorHandler from './middlewares/generic-error-handler.middleware.
4
4
  import tsoaToXoErrorHandler from './middlewares/tsoa-to-xo-error.middleware.mjs';
5
5
  import { RegisterRoutes } from './open-api/routes/routes.js';
6
6
  import { setupContainer } from './ioc/ioc.mjs';
7
+ import { setupApiContext } from './middlewares/authentication.middleware.mjs';
7
8
  // Avoid using "import from" to import a json file as this requires assert/with and will break compatibility with recent node versions
8
9
  // https://github.com/nodejs/node/issues/51622
9
10
  const require = createRequire(import.meta.url);
@@ -31,14 +32,14 @@ const SWAGGER_UI_OPTIONS = {
31
32
  };
32
33
  export default function setupRestApi(express, xoApp) {
33
34
  setupContainer(xoApp);
35
+ express.use(BASE_URL, setupApiContext(xoApp));
34
36
  RegisterRoutes(express);
35
37
  express.get(`${BASE_URL}/docs/swagger.json`, (_req, res) => {
36
38
  res.setHeader('Content-Type', 'application/json');
37
39
  res.json(swaggerOpenApiSpec);
38
40
  });
39
41
  // do not register the doc at the root level, or it may lead to unwanted behaviour
40
- // uncomment when all endpoints are migrated to this API
41
- // express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'))
42
+ express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'));
42
43
  express.use(`${BASE_URL}/docs`, swaggerUi.serve, swaggerUi.setup(swaggerOpenApiSpec, SWAGGER_UI_OPTIONS));
43
44
  express.use(BASE_URL, tsoaToXoErrorHandler);
44
45
  express.use(BASE_URL, genericErrorHandler);
package/dist/ioc/ioc.mjs CHANGED
@@ -8,7 +8,6 @@ import { HostService } from '../hosts/host.service.mjs';
8
8
  import { PoolService } from '../pools/pool.service.mjs';
9
9
  import { AlarmService } from '../alarms/alarm.service.mjs';
10
10
  import { VdiService } from '../vdis/vdi.service.mjs';
11
- import { TaskService } from '../tasks/task.service.mjs';
12
11
  import { UserService } from '../users/user.service.mjs';
13
12
  import { BackupJobService } from '../backup-jobs/backup-job.service.mjs';
14
13
  import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
@@ -58,10 +57,6 @@ export function setupContainer(xoApp) {
58
57
  return new AlarmService(restApi);
59
58
  })
60
59
  .inSingletonScope();
61
- iocContainer.bind(TaskService).toDynamicValue(ctx => {
62
- const restApi = ctx.container.get(RestApi);
63
- return new TaskService(restApi);
64
- });
65
60
  iocContainer
66
61
  .bind(VdiService)
67
62
  .toDynamicValue(ctx => {
@@ -15,7 +15,7 @@ import { provide } from 'inversify-binding-decorators';
15
15
  import { alarmPredicate } from '../alarms/alarm.service.mjs';
16
16
  import { message, messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
17
17
  import { RestApi } from '../rest-api/rest-api.mjs';
18
- import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
18
+ import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
19
19
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
20
20
  let MessageController = class MessageController extends XapiXoController {
21
21
  constructor(restapi) {
@@ -76,6 +76,7 @@ __decorate([
76
76
  MessageController = __decorate([
77
77
  Route('messages'),
78
78
  Security('*'),
79
+ Response(badRequestResp.status, badRequestResp.description),
79
80
  Response(unauthorizedResp.status, unauthorizedResp.description),
80
81
  Tags('messages'),
81
82
  provide(MessageController),
@@ -1,34 +1,58 @@
1
1
  import { createLogger } from '@xen-orchestra/log';
2
- import { invalidCredentials, notImplemented, unauthorized } from 'xo-common/api-errors.js';
2
+ import { unauthorized } from 'xo-common/api-errors.js';
3
3
  import { iocContainer } from '../ioc/ioc.mjs';
4
4
  import { RestApi } from '../rest-api/rest-api.mjs';
5
- const noop = () => { };
5
+ import { ApiError } from '../helpers/error.helper.mjs';
6
6
  const log = createLogger('xo:rest-api:authentication');
7
- function getCredentials(securityName, req) {
8
- const token = req.cookies.token ?? req.cookies.authenticationToken;
9
- if (securityName === '*' || securityName === 'token') {
10
- if (token !== undefined) {
11
- return { token };
12
- }
13
- return;
14
- }
15
- log.error(`${securityName} not implemented.`);
16
- throw notImplemented();
17
- }
18
7
  // TODO: correctly handle ACL/Resource set users
19
8
  // for now only support "xoa-admin"
9
+ // TSOA spec require this function to be async
20
10
  export async function expressAuthentication(req, securityName) {
21
11
  const restApi = iocContainer.get(RestApi);
22
- const ip = req.ip;
23
- const credentials = getCredentials(securityName, req);
24
- if (credentials === undefined) {
25
- throw invalidCredentials();
12
+ const user = restApi.getCurrentUser();
13
+ const authType = req.res.locals.authType;
14
+ if (securityName !== '*' && authType !== securityName) {
15
+ throw new ApiError(`invalid authentification. please use ${securityName} authentication`, 401);
26
16
  }
27
- const { user } = await restApi.authenticateUser(credentials, { ip });
28
17
  if (user.permission !== 'admin') {
29
18
  log.error(`The REST API can only be used by 'xoa-admin' users for now. Your permission: ${user.permission}`);
30
19
  throw unauthorized();
31
20
  }
32
- await restApi.runWithApiContext(user, noop);
33
- Promise.resolve(user);
21
+ return user;
22
+ }
23
+ export function setupApiContext(xoApp) {
24
+ return async (req, res, next) => {
25
+ const { cookies, headers, ip, query } = req;
26
+ const { authorization } = headers;
27
+ const token = cookies.authenticationToken ?? cookies.token;
28
+ const hasToken = Boolean(token);
29
+ const hasBasic = Boolean(authorization);
30
+ if (hasToken && hasBasic) {
31
+ return next(new ApiError('Having multiple authentication methods is not supported, please choose one', 400));
32
+ }
33
+ if (!hasToken && !hasBasic) {
34
+ return xoApp.runWithApiContext(undefined, next);
35
+ }
36
+ const credentials = {};
37
+ if (hasToken) {
38
+ Object.assign(credentials, { token });
39
+ res.locals.authType = 'token';
40
+ }
41
+ else {
42
+ const [, encoded] = authorization.split(' ');
43
+ if (encoded === undefined) {
44
+ return next(new ApiError('Malformed Authorization header', 400));
45
+ }
46
+ const [username, password] = Buffer.from(encoded, 'base64').toString().split(':');
47
+ Object.assign(credentials, { username, password, otp: query.otp });
48
+ res.locals.authType = 'basic';
49
+ }
50
+ try {
51
+ const { user } = await xoApp.authenticateUser(credentials, { ip });
52
+ return xoApp.runWithApiContext(user, next);
53
+ }
54
+ catch (error) {
55
+ return next(error);
56
+ }
57
+ };
34
58
  }
@@ -7,16 +7,18 @@ 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, Response, Request, Route, Security, Tags, Delete, SuccessResponse } from 'tsoa';
10
+ import { Example, Get, Path, Query, Response, Request, Route, Security, Tags, Delete, SuccessResponse, Put } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
12
  import { provide } from 'inversify-binding-decorators';
13
13
  import { AlarmService } from '../alarms/alarm.service.mjs';
14
14
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
15
15
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
16
16
  import { network, networkIds, partialNetworks } from '../open-api/oa-examples/network.oa-example.mjs';
17
- import { noContentResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
17
+ import { badRequestResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
18
18
  import { RestApi } from '../rest-api/rest-api.mjs';
19
19
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
20
+ import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
21
+ import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
20
22
  let NetworkController = class NetworkController extends XapiXoController {
21
23
  #alarmService;
22
24
  constructor(restApi, alarmService) {
@@ -58,6 +60,42 @@ let NetworkController = class NetworkController extends XapiXoController {
58
60
  });
59
61
  return this.sendObjects(Object.values(alarms), req, 'alarms');
60
62
  }
63
+ /**
64
+ * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
65
+ * @example fields "name,id,$object"
66
+ * @example filter "name:VM_STARTED"
67
+ * @example limit 42
68
+ */
69
+ getNetworkMessages(req, id, fields, ndjson, filter, limit) {
70
+ const messages = this.getMessagesForObject(id, { filter, limit });
71
+ return this.sendObjects(Object.values(messages), req, 'messages');
72
+ }
73
+ /**
74
+ * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
75
+ * @example fields "id,status,properties"
76
+ * @example filter "status:failure"
77
+ * @example limit 42
78
+ */
79
+ async getNetworkTasks(req, id, fields, ndjson, filter, limit) {
80
+ const tasks = await this.getTasksForObject(id, { filter, limit });
81
+ return this.sendObjects(Object.values(tasks), req, 'tasks');
82
+ }
83
+ /**
84
+ * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
85
+ * @example tag "from-rest-api"
86
+ */
87
+ async putNetworkTag(id, tag) {
88
+ const network = this.getXapiObject(id);
89
+ await network.$call('add_tags', tag);
90
+ }
91
+ /**
92
+ * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
93
+ * @example tag "from-rest-api"
94
+ */
95
+ async deleteNetworkTag(id, tag) {
96
+ const network = this.getXapiObject(id);
97
+ await network.$call('remove_tags', tag);
98
+ }
61
99
  };
62
100
  __decorate([
63
101
  Example(networkIds),
@@ -93,9 +131,50 @@ __decorate([
93
131
  __param(4, Query()),
94
132
  __param(5, Query())
95
133
  ], NetworkController.prototype, "getNetworkAlarms", null);
134
+ __decorate([
135
+ Example(messageIds),
136
+ Example(partialMessages),
137
+ Get('{id}/messages'),
138
+ Tags('messages'),
139
+ Response(notFoundResp.status, notFoundResp.description),
140
+ __param(0, Request()),
141
+ __param(1, Path()),
142
+ __param(2, Query()),
143
+ __param(3, Query()),
144
+ __param(4, Query()),
145
+ __param(5, Query())
146
+ ], NetworkController.prototype, "getNetworkMessages", null);
147
+ __decorate([
148
+ Example(taskIds),
149
+ Example(partialTasks),
150
+ Get('{id}/tasks'),
151
+ Tags('tasks'),
152
+ Response(notFoundResp.status, notFoundResp.description),
153
+ __param(0, Request()),
154
+ __param(1, Path()),
155
+ __param(2, Query()),
156
+ __param(3, Query()),
157
+ __param(4, Query()),
158
+ __param(5, Query())
159
+ ], NetworkController.prototype, "getNetworkTasks", null);
160
+ __decorate([
161
+ Put('{id}/tags/{tag}'),
162
+ SuccessResponse(noContentResp.status, noContentResp.description),
163
+ Response(notFoundResp.status, notFoundResp.description),
164
+ __param(0, Path()),
165
+ __param(1, Path())
166
+ ], NetworkController.prototype, "putNetworkTag", null);
167
+ __decorate([
168
+ Delete('{id}/tags/{tag}'),
169
+ SuccessResponse(noContentResp.status, noContentResp.description),
170
+ Response(notFoundResp.status, notFoundResp.description),
171
+ __param(0, Path()),
172
+ __param(1, Path())
173
+ ], NetworkController.prototype, "deleteNetworkTag", null);
96
174
  NetworkController = __decorate([
97
175
  Route('networks'),
98
176
  Security('*'),
177
+ Response(badRequestResp.status, badRequestResp.description),
99
178
  Response(unauthorizedResp.status, unauthorizedResp.description),
100
179
  Tags('networks'),
101
180
  provide(NetworkController),