@xen-orchestra/rest-api 0.17.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 (47) hide show
  1. package/dist/abstract-classes/base-controller.mjs +20 -0
  2. package/dist/abstract-classes/xapi-xo-controller.mjs +10 -0
  3. package/dist/abstract-classes/xo-controller.mjs +1 -1
  4. package/dist/alarms/alarm.controller.mjs +2 -1
  5. package/dist/backup-archives/backup-archive.controller.mjs +96 -0
  6. package/dist/backup-jobs/backup-job.controller.mjs +3 -1
  7. package/dist/backup-logs/backup-log.controller.mjs +2 -1
  8. package/dist/backup-repositories/backup-repositories.controller.mjs +2 -1
  9. package/dist/groups/group.controller.mjs +26 -1
  10. package/dist/hosts/host.controller.mjs +81 -2
  11. package/dist/index.mjs +3 -2
  12. package/dist/ioc/ioc.mjs +0 -5
  13. package/dist/messages/message.controller.mjs +3 -2
  14. package/dist/middlewares/authentication.middleware.mjs +44 -20
  15. package/dist/networks/network.controller.mjs +81 -2
  16. package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +60 -0
  17. package/dist/open-api/oa-examples/pbd.oa-example.mjs +41 -0
  18. package/dist/open-api/oa-examples/user.oa-example.mjs +12 -0
  19. package/dist/open-api/routes/routes.js +2094 -538
  20. package/dist/pbds/pbd.controller.mjs +60 -0
  21. package/dist/pcis/pci.controller.mjs +2 -1
  22. package/dist/pgpus/pgpu.controller.mjs +2 -1
  23. package/dist/pifs/pif.controller.mjs +50 -1
  24. package/dist/pools/pool.controller.mjs +80 -3
  25. package/dist/proxies/proxy.controller.mjs +2 -1
  26. package/dist/restore-logs/restore-log.controller.mjs +3 -1
  27. package/dist/schedules/schedule.controller.mjs +2 -1
  28. package/dist/servers/server.controller.mjs +26 -2
  29. package/dist/sms/sm.controller.mjs +2 -1
  30. package/dist/srs/sr.controller.mjs +81 -2
  31. package/dist/tasks/task.controller.mjs +2 -1
  32. package/dist/users/user.controller.mjs +80 -31
  33. package/dist/users/user.middleware.mjs +11 -0
  34. package/dist/vbds/vbd.controller.mjs +50 -1
  35. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +81 -2
  36. package/dist/vdis/vdi.controller.mjs +105 -2
  37. package/dist/vifs/vif.controller.mjs +50 -1
  38. package/dist/vm-controller/vm-controller.controller.mjs +81 -2
  39. package/dist/vm-snapshots/vm-snapshot.controller.mjs +59 -8
  40. package/dist/vm-templates/vm-template.controller.mjs +59 -8
  41. package/dist/vms/vm.controller.mjs +38 -19
  42. package/dist/xoa/xoa.controller.mjs +2 -1
  43. package/open-api/spec/swagger.json +6380 -1008
  44. package/package.json +4 -4
  45. package/tsconfig.json +1 -0
  46. package/tsoa.json +19 -0
  47. 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
  */
@@ -1,4 +1,6 @@
1
1
  import { BaseController } from './base-controller.mjs';
2
+ import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
3
+ import { RAW_ALARM_FILTER } from '../alarms/alarm.service.mjs';
2
4
  export class XapiXoController extends BaseController {
3
5
  #type;
4
6
  constructor(type, restApi) {
@@ -14,4 +16,12 @@ export class XapiXoController extends BaseController {
14
16
  getXapiObject(maybeId) {
15
17
  return this.restApi.getXapiObject(maybeId, this.#type);
16
18
  }
19
+ getMessagesForObject(id, { filter, limit } = {}) {
20
+ const object = this.getObject(id);
21
+ const messages = this.restApi.getObjectsByType('message', {
22
+ filter: `${escapeUnsafeComplexMatcher(filter) ?? ''} $object:${object.uuid} !${RAW_ALARM_FILTER}`,
23
+ limit,
24
+ });
25
+ return messages;
26
+ }
17
27
  }
@@ -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,8 +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
+ 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';
22
24
  let HostController = class HostController extends XapiXoController {
23
25
  #alarmService;
24
26
  #hostService;
@@ -117,6 +119,42 @@ let HostController = class HostController extends XapiXoController {
117
119
  const { missingPatches } = await this.#hostService.getMissingPatchesInfo({ filter: host => host.id === id });
118
120
  return missingPatches;
119
121
  }
122
+ /**
123
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
124
+ * @example fields "name,id,$object"
125
+ * @example filter "name:PBD_PLUG_FAILED_ON_SERVER_START"
126
+ * @example limit 42
127
+ */
128
+ getHostMessages(req, id, fields, ndjson, filter, limit) {
129
+ const messages = this.getMessagesForObject(id, { filter, limit });
130
+ return this.sendObjects(Object.values(messages), req, 'messages');
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
+ }
120
158
  };
121
159
  __decorate([
122
160
  Example(hostIds),
@@ -185,9 +223,50 @@ __decorate([
185
223
  Response(featureUnauthorized.status, featureUnauthorized.description),
186
224
  __param(0, Path())
187
225
  ], HostController.prototype, "getMissingPatches", null);
226
+ __decorate([
227
+ Example(messageIds),
228
+ Example(partialMessages),
229
+ Get('{id}/messages'),
230
+ Tags('messages'),
231
+ Response(notFoundResp.status, notFoundResp.description),
232
+ __param(0, Request()),
233
+ __param(1, Path()),
234
+ __param(2, Query()),
235
+ __param(3, Query()),
236
+ __param(4, Query()),
237
+ __param(5, Query())
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);
188
266
  HostController = __decorate([
189
267
  Route('hosts'),
190
268
  Security('*'),
269
+ Response(badRequestResp.status, badRequestResp.description),
191
270
  Response(unauthorizedResp.status, unauthorizedResp.description),
192
271
  Tags('hosts'),
193
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,8 +76,9 @@ __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
- Tags('message'),
81
+ Tags('messages'),
81
82
  provide(MessageController),
82
83
  __param(0, inject(RestApi))
83
84
  ], 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
  }