@xen-orchestra/rest-api 0.29.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +101 -1
  2. package/dist/abstract-classes/base-controller.mjs +20 -3
  3. package/dist/abstract-classes/listener.mjs +116 -12
  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 +22 -9
  7. package/dist/alarms/alarm.service.mjs +8 -0
  8. package/dist/backup-archives/backup-archive.controller.mjs +30 -21
  9. package/dist/backup-archives/backup-archive.service.mjs +21 -0
  10. package/dist/backup-jobs/backup-job.controller.mjs +59 -15
  11. package/dist/backup-jobs/backup-job.service.mjs +7 -0
  12. package/dist/backup-logs/backup-log.controller.mjs +25 -11
  13. package/dist/backup-logs/backup-log.service.mjs +19 -0
  14. package/dist/backup-repositories/backup-repositories.controller.mjs +21 -3
  15. package/dist/events/event.class.mjs +24 -9
  16. package/dist/events/event.controller.mjs +3 -0
  17. package/dist/events/event.service.mjs +2 -1
  18. package/dist/groups/group.controller.mjs +90 -6
  19. package/dist/hosts/host.controller.mjs +78 -7
  20. package/dist/ioc/ioc.mjs +13 -4
  21. package/dist/messages/message.controller.mjs +29 -8
  22. package/dist/middlewares/acl.middleware.mjs +206 -0
  23. package/dist/middlewares/authentication.middleware.mjs +15 -6
  24. package/dist/middlewares/tsoa-to-xo-error.middleware.mjs +19 -1
  25. package/dist/networks/network.controller.mjs +60 -9
  26. package/dist/open-api/oa-examples/acl-privilege.oa-example.mjs +25 -0
  27. package/dist/open-api/oa-examples/acl-role.oa-example.mjs +22 -0
  28. package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +6 -6
  29. package/dist/open-api/oa-examples/common.oa-example.mjs +3 -0
  30. package/dist/open-api/routes/routes.js +676 -132
  31. package/dist/pbds/pbd.controller.mjs +17 -3
  32. package/dist/pcis/pci.controller.mjs +16 -3
  33. package/dist/pgpus/pgpu.controller.mjs +16 -3
  34. package/dist/pifs/pif.controller.mjs +44 -8
  35. package/dist/pools/pool.controller.mjs +154 -9
  36. package/dist/proxies/proxy.controller.mjs +22 -4
  37. package/dist/restore-logs/restore-log.controller.mjs +36 -19
  38. package/dist/schedules/schedule.controller.mjs +33 -3
  39. package/dist/servers/server.controller.mjs +65 -5
  40. package/dist/sms/sm.controller.mjs +14 -2
  41. package/dist/srs/sr.controller.mjs +62 -10
  42. package/dist/tasks/task.controller.mjs +75 -11
  43. package/dist/users/user.controller.mjs +115 -16
  44. package/dist/vbds/vbd.controller.mjs +65 -31
  45. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +36 -6
  46. package/dist/vdis/vdi.controller.mjs +69 -8
  47. package/dist/vifs/vif.controller.mjs +43 -7
  48. package/dist/vm-controller/vm-controller.controller.mjs +62 -9
  49. package/dist/vm-snapshots/vm-snapshot.controller.mjs +70 -8
  50. package/dist/vm-templates/vm-template.controller.mjs +71 -8
  51. package/dist/vms/vm.controller.mjs +164 -12
  52. package/open-api/spec/swagger.json +10907 -3265
  53. package/package.json +4 -3
@@ -0,0 +1,206 @@
1
+ import { getMissingPrivileges, } from '@xen-orchestra/acl';
2
+ import { RestApi } from '../rest-api/rest-api.mjs';
3
+ import { iocContainer } from '../ioc/ioc.mjs';
4
+ import { ValidateError } from 'tsoa';
5
+ import { ApiError } from '../helpers/error.helper.mjs';
6
+ export const ACL_MIDDLEWARE_NAME = '_aclMiddleware';
7
+ export function actionsFromBody(actions) {
8
+ return ({ req }) => actions.filter(action => {
9
+ const [, field] = action.split(':');
10
+ return field in req.body;
11
+ });
12
+ }
13
+ export function actionFromBody(action) {
14
+ return opts => actionsFromBody([action])(opts)[0];
15
+ }
16
+ export function actionsIfNotSelfUser(actions) {
17
+ return ({ req, restApi }) => {
18
+ const currentUser = restApi.getCurrentUser();
19
+ const userId = req.params.id;
20
+ if (currentUser.id !== userId) {
21
+ return actions;
22
+ }
23
+ return [];
24
+ };
25
+ }
26
+ export function actionIfNotSelfUser(action) {
27
+ return opts => actionsIfNotSelfUser([action])(opts)[0];
28
+ }
29
+ export function autoBindService(serviceId, method) {
30
+ return ({ restApi }) => {
31
+ const service = restApi.ioc.get(serviceId);
32
+ const fn = service[method];
33
+ if (typeof fn !== 'function') {
34
+ throw new Error(`Invalid method: ${String(method)}`);
35
+ }
36
+ return fn.bind(service);
37
+ };
38
+ }
39
+ export const XAPI_TYPE_BY_ACL_RESOURCE = {
40
+ message: 'message',
41
+ gpuGroup: 'gpuGroup',
42
+ host: 'host',
43
+ network: 'network',
44
+ pbd: 'PBD',
45
+ pci: 'PCI',
46
+ pgpu: 'PGPU',
47
+ pif: 'PIF',
48
+ pool: 'pool',
49
+ sr: 'SR',
50
+ vbd: 'VBD',
51
+ vdi: 'VDI',
52
+ 'vdi-snapshot': 'VDI-snapshot',
53
+ 'vdi-unmanaged': 'VDI-unmanaged',
54
+ vgpu: 'vgpu',
55
+ vgpuType: 'vgpuType',
56
+ vif: 'VIF',
57
+ vm: 'VM',
58
+ 'vm-controller': 'VM-controller',
59
+ 'vm-snapshot': 'VM-snapshot',
60
+ 'vm-template': 'VM-template',
61
+ vtpm: 'VTPM',
62
+ sm: 'SM',
63
+ };
64
+ function normalizeAclEntry(acl) {
65
+ const aclAction = acl.action;
66
+ const aclActions = 'actions' in acl && acl.actions !== undefined
67
+ ? acl.actions
68
+ : typeof aclAction === 'function'
69
+ ? (opts) => [aclAction(opts)]
70
+ : [aclAction];
71
+ const actionsResolver = typeof aclActions === 'function'
72
+ ? (req, restApi) => aclActions({ req, restApi })
73
+ : () => aclActions;
74
+ const base = { resource: acl.resource, actionsResolver, getObject: acl.getObject };
75
+ if ('objects' in acl && acl.objects !== undefined) {
76
+ return { ...base, objects: acl.objects };
77
+ }
78
+ if ('object' in acl && acl.object !== undefined) {
79
+ let objects;
80
+ if (typeof acl.object === 'function') {
81
+ const fn = acl.object;
82
+ objects = async (opts) => [await fn(opts)];
83
+ }
84
+ else {
85
+ objects = [acl.object];
86
+ }
87
+ return { ...base, objects };
88
+ }
89
+ if ('objectIds' in acl && acl.objectIds !== undefined) {
90
+ return { ...base, objectIds: acl.objectIds };
91
+ }
92
+ if ('objectId' in acl && acl.objectId !== undefined) {
93
+ let objectIds;
94
+ if (typeof acl.objectId === 'function') {
95
+ const fn = acl.objectId;
96
+ objectIds = (opts) => [fn(opts)];
97
+ }
98
+ else {
99
+ objectIds = [acl.objectId];
100
+ }
101
+ return { ...base, objectIds };
102
+ }
103
+ throw new Error('invalid ACL entry');
104
+ }
105
+ export function acl(acls) {
106
+ acls = Array.isArray(acls) ? acls : [acls];
107
+ const _acls = acls.map(normalizeAclEntry);
108
+ async function middleware(req, res, next) {
109
+ const restApi = iocContainer.get(RestApi);
110
+ const user = restApi.getCurrentUser();
111
+ const invalidFields = {};
112
+ const missingPrivilegeParams = [];
113
+ for (const acl of _acls) {
114
+ let objects = [];
115
+ if ('objectIds' in acl) {
116
+ let ids = [];
117
+ if (typeof acl.objectIds === 'function') {
118
+ try {
119
+ ids = acl.objectIds({ req, restApi }).map(id => ({ id }));
120
+ }
121
+ catch (error) {
122
+ return next(error);
123
+ }
124
+ }
125
+ else {
126
+ for (const path of acl.objectIds) {
127
+ const id = path.split('.').reduce((obj, part) => obj?.[part], req);
128
+ ids.push({ id, path });
129
+ }
130
+ }
131
+ for (const { id, path } of ids) {
132
+ if (id === undefined) {
133
+ continue;
134
+ }
135
+ if (typeof id !== 'string') {
136
+ invalidFields[path ?? 'unknown'] = {
137
+ message: 'invalid string value',
138
+ value: id,
139
+ };
140
+ continue;
141
+ }
142
+ if (Object.keys(invalidFields).length > 0) {
143
+ // No need to get objects if we already known some params are invalid
144
+ continue;
145
+ }
146
+ try {
147
+ const object = (await acl.getObject?.({ restApi })(id)) ??
148
+ restApi.getObject(id, XAPI_TYPE_BY_ACL_RESOURCE[acl.resource]);
149
+ objects.push(object);
150
+ }
151
+ catch (error) {
152
+ return next(error);
153
+ }
154
+ }
155
+ }
156
+ if ('objects' in acl) {
157
+ if (typeof acl.objects === 'function') {
158
+ const _objects = await acl.objects({ req, restApi });
159
+ if (_objects !== undefined) {
160
+ objects = _objects;
161
+ }
162
+ }
163
+ else {
164
+ objects = acl.objects;
165
+ }
166
+ }
167
+ // We cast here to restore the discriminated union correlation between `resource` and `action`.
168
+ // When rebuilding an object from individual properties of a discriminated union, TypeScript transform it into an union type
169
+ // { resource: SupportedResource, action: SupportedAction<SupportedResource> }
170
+ // This loses the original correlation (e.g. it would allow invalid pairs like { resource: 'vgpu', action: 'snapshot' }).
171
+ // The `as` cast re-asserts the discriminated union member type.
172
+ for (const action of acl.actionsResolver(req, restApi)) {
173
+ if (action === undefined) {
174
+ // action can be undefined, if the action is created from `actionFromBody` or `actionIfNotSelfUser`
175
+ continue;
176
+ }
177
+ missingPrivilegeParams.push({ action, resource: acl.resource, objects, user });
178
+ }
179
+ }
180
+ if (Object.keys(invalidFields).length > 0) {
181
+ return next(new ValidateError(invalidFields, 'invalid parameters'));
182
+ }
183
+ if (user.permission === 'admin') {
184
+ // Administrator users do not need to go further
185
+ return next();
186
+ }
187
+ let userPrivileges;
188
+ try {
189
+ userPrivileges = (await restApi.xoApp.getAclV2UserPrivileges(user.id));
190
+ }
191
+ catch (error) {
192
+ return next(error);
193
+ }
194
+ const missingPrivileges = getMissingPrivileges(missingPrivilegeParams, userPrivileges);
195
+ if (missingPrivileges.length > 0) {
196
+ return next(new ApiError('not enough privileges', 403, {
197
+ data: {
198
+ missingPrivileges,
199
+ },
200
+ }));
201
+ }
202
+ next();
203
+ }
204
+ Object.defineProperty(middleware, 'name', { value: ACL_MIDDLEWARE_NAME });
205
+ return middleware;
206
+ }
@@ -1,13 +1,12 @@
1
1
  import { createLogger } from '@xen-orchestra/log';
2
2
  import { unauthorized } from 'xo-common/api-errors.js';
3
+ import { ApiError } from '../helpers/error.helper.mjs';
3
4
  import { iocContainer } from '../ioc/ioc.mjs';
4
5
  import { RestApi } from '../rest-api/rest-api.mjs';
5
- import { ApiError } from '../helpers/error.helper.mjs';
6
+ import { ACL_MIDDLEWARE_NAME } from './acl.middleware.mjs';
6
7
  const log = createLogger('xo:rest-api:authentication');
7
- // TODO: correctly handle ACL/Resource set users
8
- // for now only support "xoa-admin"
9
8
  // TSOA spec require this function to be async
10
- export async function expressAuthentication(req, securityName) {
9
+ export async function expressAuthentication(req, securityName, scopes) {
11
10
  if (securityName === 'none') {
12
11
  return undefined;
13
12
  }
@@ -17,8 +16,18 @@ export async function expressAuthentication(req, securityName) {
17
16
  if (securityName !== '*' && authType !== securityName) {
18
17
  throw new ApiError(`invalid authentification. please use ${securityName} authentication`, 401);
19
18
  }
20
- if (user.permission !== 'admin') {
21
- log.error(`The REST API can only be used by 'xoa-admin' users for now. Your permission: ${user.permission}`);
19
+ if (user.permission === 'admin') {
20
+ return user;
21
+ }
22
+ // This means the route requires authentication, but it doesn't need to be associated with a specific ACL.
23
+ // The route's content will depend on the users' ACLs.
24
+ // (For example: GET /vms -> empty array if no VMs ACL; GET /events -> only changes related to the users' ACLs will be sent)
25
+ if (scopes.includes('acl')) {
26
+ return user;
27
+ }
28
+ const aclMiddleware = req.route.stack.find(layer => layer.name === ACL_MIDDLEWARE_NAME);
29
+ if (aclMiddleware === undefined) {
30
+ log.error(`${req.route.path} can only be used by an administrator`);
22
31
  throw unauthorized();
23
32
  }
24
33
  return user;
@@ -2,7 +2,25 @@ import { invalidParameters } from 'xo-common/api-errors.js';
2
2
  import { ValidateError } from 'tsoa';
3
3
  export default function tsoaToXoErrorHandler(error, _req, _res, next) {
4
4
  if (error instanceof ValidateError) {
5
- throw invalidParameters(error.fields);
5
+ const fields = simplifyUnionValidationErrors(error.fields);
6
+ throw invalidParameters(fields);
6
7
  }
7
8
  return next(error);
8
9
  }
10
+ function simplifyUnionValidationErrors(fields) {
11
+ const result = {};
12
+ for (const [key, field] of Object.entries(fields)) {
13
+ if (isUnionMismatchError(field)) {
14
+ result[key] = {
15
+ ...field,
16
+ message: 'Value does not match any allowed schema',
17
+ };
18
+ continue;
19
+ }
20
+ result[key] = field;
21
+ }
22
+ return result;
23
+ }
24
+ function isUnionMismatchError(field) {
25
+ return field.message.startsWith('Could not match the union against any of the items');
26
+ }
@@ -7,18 +7,19 @@ 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, Put } from 'tsoa';
10
+ import { Example, Get, Path, Query, Response, Request, Route, Security, Tags, Delete, SuccessResponse, Put, Middlewares, } 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 { badRequestResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
17
+ import { badRequestResp, forbiddenOperationResp, 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
20
  import { messageIds, partialMessages } from '../open-api/oa-examples/message.oa-example.mjs';
21
21
  import { partialTasks, taskIds } from '../open-api/oa-examples/task.oa-example.mjs';
22
+ import { acl } from '../middlewares/acl.middleware.mjs';
22
23
  let NetworkController = class NetworkController extends XapiXoController {
23
24
  #alarmService;
24
25
  constructor(restApi, alarmService) {
@@ -26,27 +27,42 @@ let NetworkController = class NetworkController extends XapiXoController {
26
27
  this.#alarmService = alarmService;
27
28
  }
28
29
  /**
30
+ * Returns all networks that match the following privilege:
31
+ * - resource: network, action: read
32
+ *
29
33
  * @example fields "nbd,name_label,id"
30
34
  * @example filter "nbd?"
31
35
  * @example limit 42
32
36
  */
33
37
  getNetworks(req, fields, ndjson, markdown, filter, limit) {
34
- return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
38
+ return this.sendObjects(Object.values(this.getObjects({ filter })), req, {
39
+ limit,
40
+ privilege: { action: 'read', resource: 'network' },
41
+ });
35
42
  }
36
43
  /**
44
+ * Required privilege:
45
+ * - resource: network, action: read
46
+ *
37
47
  * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
38
48
  */
39
49
  getNetwork(id) {
40
50
  return this.getObject(id);
41
51
  }
42
52
  /**
43
- * @example id "593c39a5-9c56-28eb-969b-255b2f53791b"
53
+ * Required privilege:
54
+ * - resource: network, action: delete
55
+ *
56
+ * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
44
57
  */
45
58
  async deleteNetwork(id) {
46
59
  const networkId = id;
47
60
  await this.getXapiObject(networkId).$xapi.deleteNetwork(networkId);
48
61
  }
49
62
  /**
63
+ * Returns all alarms that match the following privilege:
64
+ * - resource: alarm, action: read
65
+ *
50
66
  * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
51
67
  * @example fields "id,time"
52
68
  * @example filter "time:>1747053793"
@@ -56,31 +72,51 @@ let NetworkController = class NetworkController extends XapiXoController {
56
72
  const network = this.getObject(id);
57
73
  const alarms = this.#alarmService.getAlarms({
58
74
  filter: `${escapeUnsafeComplexMatcher(filter) ?? ''} object:uuid:${network.uuid}`,
75
+ });
76
+ return this.sendObjects(Object.values(alarms), req, {
77
+ path: 'alarms',
59
78
  limit,
79
+ privilege: { action: 'read', resource: 'alarm' },
60
80
  });
61
- return this.sendObjects(Object.values(alarms), req, 'alarms');
62
81
  }
63
82
  /**
83
+ * Returns all messages that match the following privilege:
84
+ * - resource: message, action: read
85
+ *
64
86
  * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
65
87
  * @example fields "name,id,$object"
66
88
  * @example filter "name:VM_STARTED"
67
89
  * @example limit 42
68
90
  */
69
91
  getNetworkMessages(req, id, fields, ndjson, markdown, filter, limit) {
70
- const messages = this.getMessagesForObject(id, { filter, limit });
71
- return this.sendObjects(Object.values(messages), req, 'messages');
92
+ const messages = this.getMessagesForObject(id, { filter });
93
+ return this.sendObjects(Object.values(messages), req, {
94
+ path: 'messages',
95
+ limit,
96
+ privilege: { action: 'read', resource: 'message' },
97
+ });
72
98
  }
73
99
  /**
100
+ * Returns all tasks that match the following privilege:
101
+ * - resource: task, action: read
102
+ *
74
103
  * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
75
104
  * @example fields "id,status,properties"
76
105
  * @example filter "status:failure"
77
106
  * @example limit 42
78
107
  */
79
108
  async getNetworkTasks(req, id, fields, ndjson, markdown, filter, limit) {
80
- const tasks = await this.getTasksForObject(id, { filter, limit });
81
- return this.sendObjects(Object.values(tasks), req, 'tasks');
109
+ const tasks = await this.getTasksForObject(id, { filter });
110
+ return this.sendObjects(Object.values(tasks), req, {
111
+ path: 'tasks',
112
+ limit,
113
+ privilege: { action: 'read', resource: 'task' },
114
+ });
82
115
  }
83
116
  /**
117
+ * Required privilege:
118
+ * - resource: network, action: update:tags
119
+ *
84
120
  * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
85
121
  * @example tag "from-rest-api"
86
122
  */
@@ -89,6 +125,9 @@ let NetworkController = class NetworkController extends XapiXoController {
89
125
  await network.$call('add_tags', tag);
90
126
  }
91
127
  /**
128
+ * Required privilege:
129
+ * - resource: network, action: update:tags
130
+ *
92
131
  * @example id "9fe12ca3-d75d-cfb0-492e-cfd2bc6c568f"
93
132
  * @example tag "from-rest-api"
94
133
  */
@@ -101,6 +140,7 @@ __decorate([
101
140
  Example(networkIds),
102
141
  Example(partialNetworks),
103
142
  Get(''),
143
+ Security('*', ['acl']),
104
144
  __param(0, Request()),
105
145
  __param(1, Query()),
106
146
  __param(2, Query()),
@@ -111,11 +151,15 @@ __decorate([
111
151
  __decorate([
112
152
  Example(network),
113
153
  Get('{id}'),
154
+ Middlewares(acl({ resource: 'network', action: 'read', objectId: 'params.id' })),
155
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
114
156
  Response(notFoundResp.status, notFoundResp.description),
115
157
  __param(0, Path())
116
158
  ], NetworkController.prototype, "getNetwork", null);
117
159
  __decorate([
118
160
  Delete('{id}'),
161
+ Middlewares(acl({ resource: 'network', action: 'delete', objectId: 'params.id' })),
162
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
119
163
  SuccessResponse(noContentResp.status, noContentResp.description),
120
164
  Response(notFoundResp.status, notFoundResp.description),
121
165
  __param(0, Path())
@@ -123,6 +167,7 @@ __decorate([
123
167
  __decorate([
124
168
  Example(genericAlarmsExample),
125
169
  Get('{id}/alarms'),
170
+ Security('*', ['acl']),
126
171
  Tags('alarms'),
127
172
  Response(notFoundResp.status, notFoundResp.description),
128
173
  __param(0, Request()),
@@ -137,6 +182,7 @@ __decorate([
137
182
  Example(messageIds),
138
183
  Example(partialMessages),
139
184
  Get('{id}/messages'),
185
+ Security('*', ['acl']),
140
186
  Tags('messages'),
141
187
  Response(notFoundResp.status, notFoundResp.description),
142
188
  __param(0, Request()),
@@ -151,6 +197,7 @@ __decorate([
151
197
  Example(taskIds),
152
198
  Example(partialTasks),
153
199
  Get('{id}/tasks'),
200
+ Security('*', ['acl']),
154
201
  Tags('tasks'),
155
202
  Response(notFoundResp.status, notFoundResp.description),
156
203
  __param(0, Request()),
@@ -163,14 +210,18 @@ __decorate([
163
210
  ], NetworkController.prototype, "getNetworkTasks", null);
164
211
  __decorate([
165
212
  Put('{id}/tags/{tag}'),
213
+ Middlewares(acl({ resource: 'network', action: 'update:tags', objectId: 'params.id' })),
166
214
  SuccessResponse(noContentResp.status, noContentResp.description),
215
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
167
216
  Response(notFoundResp.status, notFoundResp.description),
168
217
  __param(0, Path()),
169
218
  __param(1, Path())
170
219
  ], NetworkController.prototype, "putNetworkTag", null);
171
220
  __decorate([
172
221
  Delete('{id}/tags/{tag}'),
222
+ Middlewares(acl({ resource: 'network', action: 'update:tags', objectId: 'params.id' })),
173
223
  SuccessResponse(noContentResp.status, noContentResp.description),
224
+ Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
174
225
  Response(notFoundResp.status, notFoundResp.description),
175
226
  __param(0, Path()),
176
227
  __param(1, Path())
@@ -0,0 +1,25 @@
1
+ export const aclPrivilegeIds = [
2
+ '/rest/v0/acl-privileges/6d16ac11-ad48-483a-a926-3d9655648b28',
3
+ '/rest/v0/acl-privileges/7a81ac36-264e-468e-ad4d-63d9a10aee6a',
4
+ ];
5
+ export const partialAclPrivileges = [
6
+ {
7
+ id: '6d16ac11-ad48-483a-a926-3d9655648b28',
8
+ action: 'create',
9
+ resource: 'vif',
10
+ href: '/rest/v0/acl-privileges/6d16ac11-ad48-483a-a926-3d9655648b28',
11
+ },
12
+ {
13
+ id: '7a81ac36-264e-468e-ad4d-63d9a10aee6a',
14
+ action: 'create',
15
+ resource: 'vdi',
16
+ href: '/rest/v0/acl-privileges/7a81ac36-264e-468e-ad4d-63d9a10aee6a',
17
+ },
18
+ ];
19
+ export const aclPrivilege = {
20
+ action: 'create',
21
+ effect: 'allow',
22
+ resource: 'vif',
23
+ roleId: 'ccaab6e6-bae6-4d9c-9866-911dfc88bdc6',
24
+ id: '6d16ac11-ad48-483a-a926-3d9655648b28',
25
+ };
@@ -0,0 +1,22 @@
1
+ export const aclRoleIds = [
2
+ '/rest/v0/acl-roles/9cac02e2-f612-4b71-851e-d28b9c0cda88',
3
+ '/rest/v0/acl-roles/784bd959-08de-4b26-b575-92ded5aef872',
4
+ ];
5
+ export const partialAclRoles = [
6
+ {
7
+ id: '9cac02e2-f612-4b71-851e-d28b9c0cda88',
8
+ name: 'VMs read only',
9
+ isTemplate: true,
10
+ href: '/rest/v0/acl-roles/9cac02e2-f612-4b71-851e-d28b9c0cda88',
11
+ },
12
+ {
13
+ id: '784bd959-08de-4b26-b575-92ded5aef872',
14
+ name: 'mra-test-read-only',
15
+ href: '/rest/v0/acl-roles/784bd959-08de-4b26-b575-92ded5aef872',
16
+ },
17
+ ];
18
+ export const aclRole = {
19
+ name: 'mra-test-read-only',
20
+ description: 'Access the whole infra in read-only mode',
21
+ id: '784bd959-08de-4b26-b575-92ded5aef872',
22
+ };
@@ -1,10 +1,10 @@
1
1
  export const backupArchiveIds = [
2
- '/rest/v0/backup-archives/231264c3-af43-4ec0-a3be-394c5b1fdbfc//xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json',
3
- '/rest/v0/backup-archives/1af95910-01b4-4e87-9c2f-d895cafe0776//xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
2
+ '/rest/v0/backup-archives/231264c3-af43-4ec0-a3be-394c5b1fdbfc/xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json',
3
+ '/rest/v0/backup-archives/1af95910-01b4-4e87-9c2f-d895cafe0776/xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
4
4
  ];
5
5
  export const partialBackupArchives = [
6
6
  {
7
- id: '231264c3-af43-4ec0-a3be-394c5b1fdbfc//xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json',
7
+ id: '231264c3-af43-4ec0-a3be-394c5b1fdbfc/xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json',
8
8
  backupRepository: '231264c3-af43-4ec0-a3be-394c5b1fdbfc',
9
9
  disks: [
10
10
  {
@@ -21,7 +21,7 @@ export const partialBackupArchives = [
21
21
  href: '/rest/v0/backup-archives/231264c3-af43-4ec0-a3be-394c5b1fdbfc/xo-vm-backups/6ef7c09e-677b-1e6f-0546-7ab30413c61c/20250801T080832Z.json',
22
22
  },
23
23
  {
24
- id: '1af95910-01b4-4e87-9c2f-d895cafe0776//xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
24
+ id: '1af95910-01b4-4e87-9c2f-d895cafe0776/xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
25
25
  backupRepository: '1af95910-01b4-4e87-9c2f-d895cafe0776',
26
26
  disks: [
27
27
  {
@@ -30,7 +30,7 @@ export const partialBackupArchives = [
30
30
  uuid: '73ed06ed-fdc8-43ef-a1c4-253e9005fbe0',
31
31
  },
32
32
  ],
33
- href: '/rest/v0/backup-archives/1af95910-01b4-4e87-9c2f-d895cafe0776//xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
33
+ href: '/rest/v0/backup-archives/1af95910-01b4-4e87-9c2f-d895cafe0776/xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
34
34
  },
35
35
  ];
36
36
  export const backupArchive = {
@@ -43,7 +43,7 @@ export const backupArchive = {
43
43
  uuid: 'c922ef3c-9d76-4482-87f8-a4da5849ee45',
44
44
  },
45
45
  ],
46
- id: '1af95910-01b4-4e87-9c2f-d895cafe0776//xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
46
+ id: '1af95910-01b4-4e87-9c2f-d895cafe0776/xo-vm-backups/7cf6150f-a978-09e6-6b41-0d1d41967bdc/20250918T132942Z.json',
47
47
  jobId: 'f2599aa4-7bb4-434b-bf71-cf0ebe1e06a4',
48
48
  mode: 'delta',
49
49
  scheduleId: '8db1c2da-2635-436f-8f78-62079fea3aa6',
@@ -0,0 +1,3 @@
1
+ export const entityId = {
2
+ id: '9cac02e2-f612-4b71-851e-d28b9c0cda88',
3
+ };