@xen-orchestra/rest-api 0.29.0 → 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.
- package/README.md +108 -1
- package/dist/abstract-classes/base-controller.mjs +18 -3
- package/dist/abstract-classes/listener.mjs +116 -12
- package/dist/acl-privileges/acl-privilege.controller.mjs +172 -0
- package/dist/acl-roles/acl-role.controller.mjs +384 -0
- package/dist/alarms/alarm.controller.mjs +22 -9
- package/dist/alarms/alarm.service.mjs +8 -0
- package/dist/backup-archives/backup-archive.controller.mjs +30 -21
- package/dist/backup-archives/backup-archive.service.mjs +21 -0
- package/dist/backup-jobs/backup-job.controller.mjs +59 -15
- package/dist/backup-jobs/backup-job.service.mjs +7 -0
- package/dist/backup-logs/backup-log.controller.mjs +25 -11
- package/dist/backup-logs/backup-log.service.mjs +19 -0
- package/dist/backup-repositories/backup-repositories.controller.mjs +21 -3
- package/dist/events/event.class.mjs +24 -9
- package/dist/events/event.controller.mjs +3 -0
- package/dist/events/event.service.mjs +2 -1
- package/dist/groups/group.controller.mjs +90 -6
- package/dist/hosts/host.controller.mjs +78 -7
- package/dist/ioc/ioc.mjs +13 -4
- package/dist/messages/message.controller.mjs +29 -8
- package/dist/middlewares/acl.middleware.mjs +202 -0
- package/dist/middlewares/authentication.middleware.mjs +15 -6
- package/dist/middlewares/tsoa-to-xo-error.middleware.mjs +19 -1
- package/dist/networks/network.controller.mjs +60 -9
- package/dist/open-api/oa-examples/acl-privilege.oa-example.mjs +25 -0
- package/dist/open-api/oa-examples/acl-role.oa-example.mjs +22 -0
- package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +6 -6
- package/dist/open-api/oa-examples/common.oa-example.mjs +3 -0
- package/dist/open-api/routes/routes.js +676 -132
- package/dist/pbds/pbd.controller.mjs +17 -3
- package/dist/pcis/pci.controller.mjs +16 -3
- package/dist/pgpus/pgpu.controller.mjs +16 -3
- package/dist/pifs/pif.controller.mjs +44 -8
- package/dist/pools/pool.controller.mjs +154 -9
- package/dist/proxies/proxy.controller.mjs +22 -4
- package/dist/restore-logs/restore-log.controller.mjs +36 -19
- package/dist/schedules/schedule.controller.mjs +33 -3
- package/dist/servers/server.controller.mjs +65 -5
- package/dist/sms/sm.controller.mjs +14 -2
- package/dist/srs/sr.controller.mjs +62 -10
- package/dist/tasks/task.controller.mjs +71 -11
- package/dist/users/user.controller.mjs +115 -16
- package/dist/vbds/vbd.controller.mjs +65 -31
- package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +36 -6
- package/dist/vdis/vdi.controller.mjs +69 -8
- package/dist/vifs/vif.controller.mjs +43 -7
- package/dist/vm-controller/vm-controller.controller.mjs +62 -9
- package/dist/vm-snapshots/vm-snapshot.controller.mjs +70 -8
- package/dist/vm-templates/vm-template.controller.mjs +71 -8
- package/dist/vms/vm.controller.mjs +164 -12
- package/open-api/spec/swagger.json +10907 -3265
- package/package.json +4 -3
|
@@ -0,0 +1,202 @@
|
|
|
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
|
+
let userPrivileges;
|
|
184
|
+
try {
|
|
185
|
+
userPrivileges = (await restApi.xoApp.getAclV2UserPrivileges(user.id));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return next(error);
|
|
189
|
+
}
|
|
190
|
+
const missingPrivileges = getMissingPrivileges(missingPrivilegeParams, userPrivileges);
|
|
191
|
+
if (missingPrivileges.length > 0) {
|
|
192
|
+
return next(new ApiError('not enough privileges', 403, {
|
|
193
|
+
data: {
|
|
194
|
+
missingPrivileges,
|
|
195
|
+
},
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
next();
|
|
199
|
+
}
|
|
200
|
+
Object.defineProperty(middleware, 'name', { value: ACL_MIDDLEWARE_NAME });
|
|
201
|
+
return middleware;
|
|
202
|
+
}
|
|
@@ -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 {
|
|
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
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
71
|
-
return this.sendObjects(Object.values(messages), req,
|
|
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
|
|
81
|
-
return this.sendObjects(Object.values(tasks), req,
|
|
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
|
|
3
|
-
'/rest/v0/backup-archives/1af95910-01b4-4e87-9c2f-d895cafe0776
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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',
|